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
29static 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
52static 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
80bool 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
257bool 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
320bool 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
350bool 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}
FramePitches m_pitches
Definition: mythframe.h:141
uint8_t * m_buffer
Definition: mythframe.h:119
FrameOffsets m_offsets
Definition: mythframe.h:142
bool ExtractCC34(const unsigned char *buf, uint width)
bool FindClocks(const unsigned char *buf, uint width)
std::array< uint16_t, 2 > m_code
QList< uint > m_rawMinimas
bool ExtractCC(const MythVideoFrame *picframe, uint max_lines=4)
QList< uint > m_rawMaximas
uint FillCCData(cc608_data &cc_data) const
QList< float > m_maximas
QList< float > m_minimas
bool ExtractCC12(const unsigned char *buf, uint width)
unsigned int uint
Definition: freesurround.h:24
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
#define LOC
static void print(const QList< uint > &raw_minimas, const QList< uint > &raw_maximas, const QList< float > &minimas, const QList< float > &maximas)
static float find_clock_diff(const QList< float > &list)
std::array< uint8_t, 8 > cc608_data