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