MythTV  master
vbi608extractor.cpp
Go to the documentation of this file.
1 /*
2  VBI 608 Extractor, extracts CEA-608 VBI from a line of raw data.
3  Copyright (C) 2010 Digital Nirvana, Inc.
4 
5  This program is free software; you can redistribute it and/or
6  modify it under the terms of the GNU General Public License
7  as published by the Free Software Foundation; either version 2
8  of the License, or (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program; if not, write to the Free Software
17  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19 
20 #include <algorithm>
21 #include <cfloat>
22 #include <cstdint>
23 using namespace std;
24 
26 #include "mythlogging.h"
27 
28 #define LOC QString("VBI608Extractor: ")
29 
30 static void print(
31  const QList<uint> &raw_minimas, const QList<uint> &raw_maximas,
32  const QList<float> &minimas, const QList<float> &maximas)
33 {
34  QString raw_mins;
35  QString raw_maxs;
36  for (uint minima : qAsConst(raw_minimas))
37  raw_mins += QString("%1,").arg(minima);
38  for (uint maxima : qAsConst(raw_maximas))
39  raw_maxs += QString("%1,").arg(maxima);
40  LOG(VB_VBI, LOG_DEBUG, QString("raw mins: %1").arg(raw_mins));
41  LOG(VB_VBI, LOG_DEBUG, QString("raw maxs: %1").arg(raw_maxs));
42 
43  QString mins;
44  QString maxs;
45  for (float minima : qAsConst(minimas))
46  mins += QString("%1,").arg(minima);
47  for (float maxima : qAsConst(maximas))
48  maxs += QString("%1,").arg(maxima);
49  LOG(VB_VBI, LOG_DEBUG, QString("mins: %1 maxs: %2")
50  .arg(mins).arg(maxs));
51 }
52 
53 static float find_clock_diff(const QList<float> &list)
54 {
55  float min_diff = FLT_MAX;
56  float max_diff = 0.0F;
57  float avg_diff = 0.0F;
58  for (uint i = 1; i < uint(list.size()); i++)
59  {
60  float diff = list[i] - list[i-1];
61  min_diff = min(diff, min_diff);
62  max_diff = max(diff, max_diff);
63  avg_diff += diff;
64  }
65  if (list.size() >= 2)
66  avg_diff /= (list.size() - 1);
67  if (avg_diff * 1.15F < max_diff)
68  {
69  LOG(VB_VBI, LOG_DEBUG, "max_diff too big");
70  return 0.0F;
71  }
72  if (avg_diff * 0.85F > max_diff)
73  {
74  LOG(VB_VBI, LOG_DEBUG, "min_diff too small");
75  return 0.0F;
76  }
77 
78  return avg_diff;
79 }
80 
81 bool VBI608Extractor::FindClocks(const unsigned char *buf, uint width)
82 {
83  m_rawMinimas.clear();
84  m_rawMaximas.clear();
85  m_maximas.clear();
86  m_minimas.clear();
87 
88  // find our threshold
89  uint minv = 255;
90  for (uint j = width / 8; j < width / 4; j++)
91  minv = min(uint(buf[j]), minv);
92  uint maxv = 0;
93  for (uint j = width / 8; j < width / 4; j++)
94  maxv = max(uint(buf[j]), maxv);
95  uint avgv = (maxv<minv) ? 0 : minv + ((maxv-minv) / 2);
96  if (avgv <= 11)
97  {
98  LOG(VB_VBI, LOG_DEBUG, QString("FindClocks: avgv(%1) <= 11").arg(avgv));
99  return false;
100  }
101 
102  // get the raw minima and maxima list
103  uint noise_flr_sm = max(uint(0.003 * width), 2U);
104  uint noise_flr_lg = max(uint(0.007 * width), noise_flr_sm+1);
105  int last_max = -1;
106  int last_min = -1;
107  for (uint i = 0; i < (width/3); i++)
108  {
109  if (buf[i] > avgv+10)
110  m_rawMaximas.push_back(last_max=i);
111  else if (last_max>=0 && (i - last_max) <= noise_flr_sm)
112  m_rawMaximas.push_back(i);
113  else if (buf[i] < avgv-10)
114  m_rawMinimas.push_back(last_min=i);
115  else if (last_min>=0 && (i - last_min) <= noise_flr_lg)
116  m_rawMinimas.push_back(i);
117  }
118 
119  for (uint i = 0; i < uint(m_rawMaximas.size()); i++)
120  {
121  uint start_idx = m_rawMaximas[i];
122  while ((i+1) < uint(m_rawMaximas.size()) &&
123  (m_rawMaximas[i+1] == m_rawMaximas[i] + 1)) i++;
124  uint end_idx = m_rawMaximas[i];
125  if (end_idx - start_idx > noise_flr_lg)
126  m_maximas.push_back((start_idx + end_idx) * 0.5F);
127  }
128 
129  if (m_maximas.size() < 7)
130  {
131  LOG(VB_VBI, LOG_DEBUG, LOC +
132  QString("FindClocks: maximas %1 < 7").arg(m_maximas.size()));
133  print(m_rawMinimas, m_rawMaximas, m_minimas, m_maximas);
134  return false;
135  }
136 
137  // drop outliers on edges
138  bool dropped = true;
139  while (m_maximas.size() > 7 && dropped)
140  {
141  float min_diff = width * 8;
142  float max_diff = 0.0F;
143  float avg_diff = 0.0F;
144  for (uint i = 1; i < uint(m_maximas.size()); i++)
145  {
146  float diff = m_maximas[i] - m_maximas[i-1];
147  min_diff = min(diff, min_diff);
148  max_diff = max(diff, max_diff);
149  avg_diff += diff;
150  }
151  avg_diff -= min_diff;
152  avg_diff -= max_diff;
153  avg_diff /= (m_maximas.size() - 3);
154 
155  dropped = false;
156  if (avg_diff * 1.1F < max_diff)
157  {
158  float last_diff = m_maximas.back() -
159  m_maximas[(uint)(m_maximas.size())-2];
160  if (last_diff*1.01F >= max_diff || last_diff > avg_diff * 1.2F)
161  {
162  m_maximas.pop_back();
163  dropped = true;
164  }
165  float first_diff = m_maximas[1] - m_maximas[0];
166  if ((m_maximas.size() > 7) && (first_diff*1.01F >= max_diff))
167  {
168  m_maximas.pop_front();
169  dropped = true;
170  }
171  }
172 
173  if (avg_diff * 0.9F > min_diff)
174  {
175  float last_diff = m_maximas.back() -
176  m_maximas[(uint)(m_maximas.size())-2];
177  if ((m_maximas.size() > 7) &&
178  (last_diff*0.99F <= min_diff || last_diff < avg_diff * 0.80F))
179  {
180  m_maximas.pop_back();
181  dropped = true;
182  }
183  float first_diff = m_maximas[1] - m_maximas[0];
184  if ((m_maximas.size() > 7) && (first_diff*0.99F <= min_diff))
185  {
186  m_maximas.pop_front();
187  dropped = true;
188  }
189  }
190  }
191 
192  if (m_maximas.size() != 7)
193  {
194  LOG(VB_VBI, LOG_DEBUG, LOC + QString("FindClocks: maximas: %1 != 7")
195  .arg(m_maximas.size()));
196  print(m_rawMinimas, m_rawMaximas, m_minimas, m_maximas);
197  return false;
198  }
199 
200  // find the minimas
201  for (uint i = 0; i < uint(m_rawMinimas.size()); i++)
202  {
203  uint start_idx = m_rawMinimas[i];
204  while ((i+1) < uint(m_rawMinimas.size()) &&
205  (m_rawMinimas[i+1] == m_rawMinimas[i] + 1)) i++;
206  uint end_idx = m_rawMinimas[i];
207  float center = (start_idx + end_idx) * 0.5F;
208  if (end_idx - start_idx > noise_flr_lg &&
209  center > m_maximas[0] && center < m_maximas.back())
210  {
211  m_minimas.push_back(center);
212  }
213  }
214 
215  if (m_minimas.size() != 6)
216  {
217  LOG(VB_VBI, LOG_DEBUG, LOC + QString("FindClocks: minimas: %1 != 6")
218  .arg(m_minimas.size()));
219  print(m_rawMinimas, m_rawMaximas, m_minimas, m_maximas);
220  return false;
221  }
222 
223  // get the average clock rate in samples
224  float maxima_avg_diff = find_clock_diff(m_maximas);
225  float minima_avg_diff = find_clock_diff(m_minimas);
226  m_rate = (maxima_avg_diff * 7 + minima_avg_diff * 6) / 13.0F;
227  if (maxima_avg_diff == 0.0F || minima_avg_diff == 0.0F)
228  return false;
229 
230  // get the estimated location of the first maxima
231  // based on the rate and location of all maximas
232  m_start = m_maximas[0];
233  for (uint i = 1; i < uint(m_maximas.size()); i++)
234  m_start += m_maximas[i] - i * m_rate;
235  m_start /= m_maximas.size();
236  // then move it back by a third to make each sample
237  // more or less in the center of each encoded byte.
238  m_start -= m_rate * 0.33F;
239 
240  // if the last bit is after the last sample...
241  // 7 clocks + 3 bits run in + 16 bits data
242  if (m_start+((7+3+8+8-1) * m_rate) > width)
243  {
244  LOG(VB_VBI, LOG_DEBUG, LOC + QString("FindClocks: end %1 > width %2")
245  .arg(m_start+((7+3+8+8-1) * m_rate)).arg(width));
246 
247  return false;
248  }
249 
250 #if 0
251  LOG(VB_VBI, LOG_DEBUG, LOC + QString("FindClocks: Clock start %1, rate %2")
252  .arg(m_start).arg(m_rate));
253 #endif
254 
255  return true;
256 }
257 
258 bool VBI608Extractor::ExtractCC(const VideoFrame *picframe, uint max_lines)
259 {
260  int ypitch = picframe->pitches[0];
261  int ywidth = picframe->width;
262 
263  m_code[0] = UINT16_MAX;
264  m_code[1] = UINT16_MAX;
265 
266  // find CC
267  uint found_cnt = 0;
268  for (uint i = 0; i < max_lines; i++)
269  {
270  const unsigned char *y = picframe->buf +
271  picframe->offsets[0] + (i * ypitch);
272  if (FindClocks(y, ywidth))
273  {
274  uint maxv = 0;
275  for (uint j = 0; j < m_start + 8 * m_rate; j++)
276  maxv = max(uint((y+(i * ypitch))[j]), maxv);
277  uint avgv = maxv / 2;
278 
279  if (y[uint(m_start + (0+7) * m_rate)] > avgv ||
280  y[uint(m_start + (1+7) * m_rate)] > avgv ||
281  y[uint(m_start + (2+7) * m_rate)] < avgv)
282  {
283  continue; // need 001 at run in..
284  }
285 
286  m_code[found_cnt] = 0;
287  for (uint j = 0; j < 8+8; j++)
288  {
289  bool bit = y[uint(m_start + (j+7+3) * m_rate)] > avgv;
290  m_code[found_cnt] =
291  (m_code[found_cnt]>>1) | (bit?(1<<15):0);
292  }
293 
294  found_cnt++;
295  if (found_cnt>=2)
296  break;
297 #if 0
298  unsigned char *Y = const_cast<unsigned char*>(y);
299  unsigned char *u = const_cast<unsigned char*>
300  (picframe->buf + picframe->offsets[1] +
301  (i*picframe->pitches[1]));
302  unsigned char *v = const_cast<unsigned char*>
303  (picframe->buf + picframe->offsets[2] +
304  (i*picframe->pitches[2]));
305  unsigned uwidth = picframe->pitches[1];
306  v[uwidth / 3] = 0x40;
307  for (uint j = 0; j < 7+3+8+8; j++)
308  {
309  uint yloc = uint (m_start + j * m_rate + 0.5);
310  Y[yloc] = 0xFF;
311  uint uloc = uint (uwidth * (m_start + j * m_rate + 0.5) / ywidth);
312  u[uloc] = 0x40;
313  }
314 #endif
315  }
316  }
317 
318  return found_cnt > 0;
319 }
320 
321 bool VBI608Extractor::ExtractCC12(const unsigned char *buf, uint width)
322 {
323  m_code[0] = UINT16_MAX;
324  if (FindClocks(buf, width))
325  {
326  uint maxv = 0;
327  for (uint j = 0; j < m_start + 8 * m_rate; j++)
328  maxv = max(uint(buf[j]), maxv);
329  uint avgv = maxv / 2;
330 
331  if (buf[uint(m_start + (0+7) * m_rate)] > avgv ||
332  buf[uint(m_start + (1+7) * m_rate)] > avgv ||
333  buf[uint(m_start + (2+7) * m_rate)] < avgv)
334  {
335  LOG(VB_VBI, LOG_DEBUG, LOC + "did not find VBI 608 header");
336  return false;
337  }
338 
339  m_code[0] = 0;
340  for (uint j = 0; j < 8+8; j++)
341  {
342  bool bit = buf[uint(m_start + (j+7+3) * m_rate)] > avgv;
343  m_code[0] = (m_code[0]>>1) | (bit?(1<<15):0);
344  }
345 
346  return true;
347  }
348  return false;
349 }
350 
351 bool VBI608Extractor::ExtractCC34(const unsigned char *buf, uint width)
352 {
353  m_code[1] = UINT16_MAX;
354  if (FindClocks(buf, width))
355  {
356  uint maxv = 0;
357  for (uint j = 0; j < m_start + 8 * m_rate; j++)
358  maxv = max(uint(buf[j]), maxv);
359  uint avgv = maxv / 2;
360 
361  if (buf[uint(m_start + (0+7) * m_rate)] > avgv ||
362  buf[uint(m_start + (1+7) * m_rate)] > avgv ||
363  buf[uint(m_start + (2+7) * m_rate)] < avgv)
364  {
365  return false;
366  }
367 
368  m_code[1] = 0;
369  for (uint j = 0; j < 8+8; j++)
370  {
371  bool bit = buf[uint(m_start + (j+7+3) * m_rate)] > avgv;
372  m_code[1] = (m_code[1]>>1) | (bit?(1<<15):0);
373  }
374  return true;
375  }
376  return false;
377 }
378 
379 uint VBI608Extractor::FillCCData(uint8_t cc_data[8]) const
380 {
381  uint cc_count = 0;
382  if (m_code[0] != UINT16_MAX)
383  {
384  cc_data[2] = 0x04;
385  cc_data[3] = (m_code[0]) & 0xff;
386  cc_data[4] = (m_code[0]>>8) & 0xff;
387  cc_count++;
388  }
389 
390  if (m_code[1] != UINT16_MAX)
391  {
392  cc_data[2+3*cc_count] = 0x04|0x01;
393  cc_data[3+3*cc_count] = (m_code[1]) & 0xff;
394  cc_data[4+3*cc_count] = (m_code[1]>>8) & 0xff;
395  cc_count++;
396  }
397 
398  if (cc_count)
399  {
400  cc_data[0] = 0x40 | cc_count;
401  cc_data[1] = 0x00;
402  return 2+(3*cc_count);
403  }
404  return 0;
405 }
VideoFrame::pitches
int pitches[3]
Y, U, & V pitches.
Definition: mythframe.h:161
VBI608Extractor::ExtractCC12
bool ExtractCC12(const unsigned char *buf, uint width)
Definition: vbi608extractor.cpp:321
arg
arg(title).arg(filename).arg(doDelete))
VBI608Extractor::ExtractCC
bool ExtractCC(const VideoFrame *picframe, uint max_lines=4)
Definition: vbi608extractor.cpp:258
VideoFrame::buf
unsigned char * buf
Definition: mythframe.h:140
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
VideoFrame
Definition: mythframe.h:137
mythlogging.h
find_clock_diff
static float find_clock_diff(const QList< float > &list)
Definition: vbi608extractor.cpp:53
VBI608Extractor::FindClocks
bool FindClocks(const unsigned char *buf, uint width)
Definition: vbi608extractor.cpp:81
uint
unsigned int uint
Definition: compat.h:140
print
static void print(const QList< uint > &raw_minimas, const QList< uint > &raw_maximas, const QList< float > &minimas, const QList< float > &maximas)
Definition: vbi608extractor.cpp:30
LOC
#define LOC
Definition: vbi608extractor.cpp:28
VideoFrame::offsets
int offsets[3]
Y, U, & V offsets.
Definition: mythframe.h:162
VBI608Extractor::FillCCData
uint FillCCData(uint8_t cc_data[8]) const
Definition: vbi608extractor.cpp:379
vbi608extractor.h
VBI608Extractor::ExtractCC34
bool ExtractCC34(const unsigned char *buf, uint width)
Definition: vbi608extractor.cpp:351
VideoFrame::width
int width
Definition: mythframe.h:142