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