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#include "vbi608extractor.h"
20
21#include <algorithm>
22#include <cfloat>
23#include <cstdint>
24
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::ExtractCC12(const unsigned char *buf, uint width)
258{
259 m_code[0] = UINT16_MAX;
260 if (FindClocks(buf, width))
261 {
262 uint maxv = 0;
263 for (uint j = 0; j < m_start + (8 * m_rate); j++)
264 maxv = std::max(uint(buf[j]), maxv);
265 uint avgv = maxv / 2;
266
267 if (buf[uint(m_start + ((0+7) * m_rate))] > avgv ||
268 buf[uint(m_start + ((1+7) * m_rate))] > avgv ||
269 buf[uint(m_start + ((2+7) * m_rate))] < avgv)
270 {
271 LOG(VB_VBI, LOG_DEBUG, LOC + "did not find VBI 608 header");
272 return false;
273 }
274
275 m_code[0] = 0;
276 for (uint j = 0; j < 8+8; j++)
277 {
278 bool bit = buf[uint(m_start + ((j+7+3) * m_rate))] > avgv;
279 m_code[0] = (m_code[0]>>1) | (bit?(1<<15):0);
280 }
281
282 return true;
283 }
284 return false;
285}
286
287bool VBI608Extractor::ExtractCC34(const unsigned char *buf, uint width)
288{
289 m_code[1] = UINT16_MAX;
290 if (FindClocks(buf, width))
291 {
292 uint maxv = 0;
293 for (uint j = 0; j < m_start + (8 * m_rate); j++)
294 maxv = std::max(uint(buf[j]), maxv);
295 uint avgv = maxv / 2;
296
297 if (buf[uint(m_start + ((0+7) * m_rate))] > avgv ||
298 buf[uint(m_start + ((1+7) * m_rate))] > avgv ||
299 buf[uint(m_start + ((2+7) * m_rate))] < avgv)
300 {
301 return false;
302 }
303
304 m_code[1] = 0;
305 for (uint j = 0; j < 8+8; j++)
306 {
307 bool bit = buf[uint(m_start + ((j+7+3) * m_rate))] > avgv;
308 m_code[1] = (m_code[1]>>1) | (bit?(1<<15):0);
309 }
310 return true;
311 }
312 return false;
313}
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
QList< uint > m_rawMaximas
QList< float > m_maximas
QList< float > m_minimas
bool ExtractCC12(const unsigned char *buf, uint width)
unsigned int uint
Definition: compat.h:60
#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)