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