1 // Copyright (C) 2020 Matthew Waters <matthew@centricular.com>
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Library General Public
5 // License as published by the Free Software Foundation; either
6 // version 2 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 // Library General Public License for more details.
12 //
13 // You should have received a copy of the GNU Library General Public
14 // License along with this library; if not, write to the
15 // Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
16 // Boston, MA 02110-1335, USA.
17
18 use gst::prelude::*;
19 use gst::ClockTime;
20
21 use std::sync::{Arc, Mutex};
22
23 use pretty_assertions::assert_eq;
24
init()25 fn init() {
26 use std::sync::Once;
27 static INIT: Once = Once::new();
28
29 INIT.call_once(|| {
30 gst::init().unwrap();
31 gstrsclosedcaption::plugin_register_static().unwrap();
32 });
33 }
34
35 struct NotifyState {
36 cc608_count: u32,
37 cc708_count: u32,
38 }
39
40 impl Default for NotifyState {
default() -> Self41 fn default() -> Self {
42 NotifyState {
43 cc608_count: 0,
44 cc708_count: 0,
45 }
46 }
47 }
48
49 macro_rules! assert_push_data {
50 ($h:expr, $state:expr, $data:expr, $ts:expr, $cc608_count:expr, $cc708_count:expr) => {
51 let mut buf = gst::Buffer::from_mut_slice($data);
52 buf.get_mut().unwrap().set_pts($ts);
53
54 assert_eq!($h.push(buf), Ok(gst::FlowSuccess::Ok));
55 {
56 let state_guard = $state.lock().unwrap();
57 assert_eq!(state_guard.cc608_count, $cc608_count);
58 assert_eq!(state_guard.cc708_count, $cc708_count);
59 }
60 };
61 }
62
63 #[test]
test_have_cc_data_notify()64 fn test_have_cc_data_notify() {
65 init();
66 let valid_cc608_data = vec![0xfc, 0x80, 0x81];
67 let invalid_cc608_data = vec![0xf8, 0x80, 0x81];
68 let valid_cc708_data = vec![0xfe, 0x80, 0x81];
69 let invalid_cc708_data = vec![0xfa, 0x80, 0x81];
70
71 let mut h = gst_check::Harness::new("ccdetect");
72 h.set_src_caps_str("closedcaption/x-cea-708,format=cc_data");
73 h.set_sink_caps_str("closedcaption/x-cea-708,format=cc_data");
74 h.element()
75 .unwrap()
76 .set_property("window", &(500_000_000u64))
77 .unwrap();
78
79 let state = Arc::new(Mutex::new(NotifyState::default()));
80 let state_c = state.clone();
81 h.element()
82 .unwrap()
83 .connect_notify(Some("cc608"), move |o, _pspec| {
84 let mut state_guard = state_c.lock().unwrap();
85 state_guard.cc608_count += 1;
86 o.property("cc608").unwrap();
87 });
88 let state_c = state.clone();
89 h.element()
90 .unwrap()
91 .connect_notify(Some("cc708"), move |o, _pspec| {
92 let mut state_guard = state_c.lock().unwrap();
93 state_guard.cc708_count += 1;
94 o.property("cc708").unwrap();
95 });
96
97 /* valid cc608 data moves cc608 property to true */
98 assert_push_data!(h, state, valid_cc608_data, ClockTime::ZERO, 1, 0);
99
100 /* invalid cc608 data moves cc608 property to false */
101 assert_push_data!(
102 h,
103 state,
104 invalid_cc608_data,
105 ClockTime::from_nseconds(1_000_000_000),
106 2,
107 0
108 );
109
110 /* valid cc708 data moves cc708 property to true */
111 assert_push_data!(
112 h,
113 state,
114 valid_cc708_data,
115 ClockTime::from_nseconds(2_000_000_000),
116 2,
117 1
118 );
119
120 /* invalid cc708 data moves cc708 property to false */
121 assert_push_data!(
122 h,
123 state,
124 invalid_cc708_data,
125 ClockTime::from_nseconds(3_000_000_000),
126 2,
127 2
128 );
129 }
130
131 #[test]
test_cc_data_window()132 fn test_cc_data_window() {
133 init();
134 let valid_cc608_data = vec![0xfc, 0x80, 0x81];
135 let invalid_cc608_data = vec![0xf8, 0x80, 0x81];
136
137 let mut h = gst_check::Harness::new("ccdetect");
138 h.set_src_caps_str("closedcaption/x-cea-708,format=cc_data");
139 h.set_sink_caps_str("closedcaption/x-cea-708,format=cc_data");
140 h.element()
141 .unwrap()
142 .set_property("window", &500_000_000u64)
143 .unwrap();
144
145 let state = Arc::new(Mutex::new(NotifyState::default()));
146 let state_c = state.clone();
147 h.element()
148 .unwrap()
149 .connect_notify(Some("cc608"), move |_o, _pspec| {
150 let mut state_guard = state_c.lock().unwrap();
151 state_guard.cc608_count += 1;
152 });
153 let state_c = state.clone();
154 h.element()
155 .unwrap()
156 .connect_notify(Some("cc708"), move |_o, _pspec| {
157 let mut state_guard = state_c.lock().unwrap();
158 state_guard.cc708_count += 1;
159 });
160
161 /* valid cc608 data moves cc608 property to true */
162 assert_push_data!(h, state, valid_cc608_data.clone(), ClockTime::ZERO, 1, 0);
163
164 /* valid cc608 data moves within window */
165 assert_push_data!(
166 h,
167 state,
168 valid_cc608_data.clone(),
169 ClockTime::from_nseconds(300_000_000),
170 1,
171 0
172 );
173
174 /* invalid cc608 data before window expires, no change */
175 assert_push_data!(
176 h,
177 state,
178 invalid_cc608_data.clone(),
179 ClockTime::from_nseconds(600_000_000),
180 1,
181 0
182 );
183
184 /* invalid cc608 data after window expires, cc608 changes to false */
185 assert_push_data!(
186 h,
187 state,
188 invalid_cc608_data,
189 ClockTime::from_nseconds(1_000_000_000),
190 2,
191 0
192 );
193
194 /* valid cc608 data before window expires, no change */
195 assert_push_data!(
196 h,
197 state,
198 valid_cc608_data.clone(),
199 ClockTime::from_nseconds(1_300_000_000),
200 2,
201 0
202 );
203
204 /* valid cc608 data after window expires, property changes */
205 assert_push_data!(
206 h,
207 state,
208 valid_cc608_data,
209 ClockTime::from_nseconds(1_600_000_000),
210 3,
211 0
212 );
213 }
214
215 #[test]
test_have_cdp_notify()216 fn test_have_cdp_notify() {
217 init();
218 let valid_cc608_data = vec![
219 0x96, 0x69, /* cdp magic bytes */
220 0x10, /* length of cdp packet */
221 0x8f, /* framerate */
222 0x43, /* flags */
223 0x00, 0x00, /* sequence counter */
224 0x72, /* cc_data byte header */
225 0xe1, /* n cc_data triples with 0xe0 as reserved bits */
226 0xfc, 0x80, 0x81, /* cc_data triple */
227 0x74, /* cdp end of frame byte header */
228 0x00, 0x00, /* sequence counter */
229 0x60, /* checksum */
230 ];
231 let invalid_cc608_data = vec![
232 0x96, 0x69, 0x10, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xe1, 0xf8, 0x81, 0x82, 0x74, 0x00, 0x00,
233 0x60,
234 ];
235
236 let mut h = gst_check::Harness::new("ccdetect");
237 h.set_src_caps_str("closedcaption/x-cea-708,format=cdp");
238 h.set_sink_caps_str("closedcaption/x-cea-708,format=cdp");
239 h.element()
240 .unwrap()
241 .set_property("window", &500_000_000u64)
242 .unwrap();
243
244 let state = Arc::new(Mutex::new(NotifyState::default()));
245 let state_c = state.clone();
246 h.element()
247 .unwrap()
248 .connect_notify(Some("cc608"), move |_o, _pspec| {
249 let mut state_guard = state_c.lock().unwrap();
250 state_guard.cc608_count += 1;
251 });
252 let state_c = state.clone();
253 h.element()
254 .unwrap()
255 .connect_notify(Some("cc708"), move |_o, _pspec| {
256 let mut state_guard = state_c.lock().unwrap();
257 state_guard.cc708_count += 1;
258 });
259
260 /* valid cc608 data moves cc608 property to true */
261 assert_push_data!(h, state, valid_cc608_data, ClockTime::ZERO, 1, 0);
262
263 /* invalid cc608 data moves cc608 property to false */
264 assert_push_data!(
265 h,
266 state,
267 invalid_cc608_data,
268 ClockTime::from_nseconds(1_000_000_000),
269 2,
270 0
271 );
272 }
273
274 #[test]
test_malformed_cdp_notify()275 fn test_malformed_cdp_notify() {
276 init();
277 let too_short = vec![0x96, 0x69];
278 let wrong_magic = vec![
279 0x00, 0x00, 0x10, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xe1, 0xfc, 0x81, 0x82, 0x74, 0x00, 0x00,
280 0x60,
281 ];
282 let length_too_long = vec![
283 0x96, 0x69, 0x20, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xe1, 0xfc, 0x81, 0x82, 0x74, 0x00, 0x00,
284 0x60,
285 ];
286 let length_too_short = vec![
287 0x96, 0x69, 0x00, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xe1, 0xfc, 0x81, 0x82, 0x74, 0x00, 0x00,
288 0x60,
289 ];
290 let wrong_cc_data_header_byte = vec![
291 0x96, 0x69, 0x10, 0x8f, 0x43, 0x00, 0x00, 0xff, 0xe1, 0xfc, 0x81, 0x82, 0x74, 0x00, 0x00,
292 0x60,
293 ];
294 let big_cc_count = vec![
295 0x96, 0x69, 0x10, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xef, 0xfc, 0x81, 0x82, 0x74, 0x00, 0x00,
296 0x60,
297 ];
298 let wrong_cc_count_reserved_bits = vec![
299 0x96, 0x69, 0x10, 0x8f, 0x43, 0x00, 0x00, 0x72, 0x01, 0xfc, 0x81, 0x82, 0x74, 0x00, 0x00,
300 0x60,
301 ];
302 let cc608_after_cc708 = vec![
303 0x96, 0x69, 0x13, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xe2, 0xfe, 0x81, 0x82, 0xfc, 0x83, 0x84,
304 0x74, 0x00, 0x00, 0x60,
305 ];
306
307 let mut h = gst_check::Harness::new("ccdetect");
308 h.set_src_caps_str("closedcaption/x-cea-708,format=cdp");
309 h.set_sink_caps_str("closedcaption/x-cea-708,format=cdp");
310 h.element().unwrap().set_property("window", &0u64).unwrap();
311
312 let state = Arc::new(Mutex::new(NotifyState::default()));
313 let state_c = state.clone();
314 h.element()
315 .unwrap()
316 .connect_notify(Some("cc608"), move |_o, _pspec| {
317 let mut state_guard = state_c.lock().unwrap();
318 state_guard.cc608_count += 1;
319 });
320 let state_c = state.clone();
321 h.element()
322 .unwrap()
323 .connect_notify(Some("cc708"), move |_o, _pspec| {
324 let mut state_guard = state_c.lock().unwrap();
325 state_guard.cc708_count += 1;
326 });
327
328 /* all invalid data does not change properties */
329 assert_push_data!(h, state, too_short, ClockTime::from_nseconds(0), 0, 0);
330 assert_push_data!(h, state, wrong_magic, ClockTime::from_nseconds(1_000), 0, 0);
331 assert_push_data!(
332 h,
333 state,
334 length_too_long,
335 ClockTime::from_nseconds(2_000),
336 0,
337 0
338 );
339 assert_push_data!(
340 h,
341 state,
342 length_too_short,
343 ClockTime::from_nseconds(3_000),
344 0,
345 0
346 );
347 assert_push_data!(
348 h,
349 state,
350 wrong_cc_data_header_byte,
351 ClockTime::from_nseconds(4_000),
352 0,
353 0
354 );
355 assert_push_data!(
356 h,
357 state,
358 big_cc_count,
359 ClockTime::from_nseconds(5_000),
360 0,
361 0
362 );
363 assert_push_data!(
364 h,
365 state,
366 wrong_cc_count_reserved_bits,
367 ClockTime::from_nseconds(6_000),
368 0,
369 0
370 );
371 assert_push_data!(
372 h,
373 state,
374 cc608_after_cc708,
375 ClockTime::from_nseconds(7_000),
376 0,
377 0
378 );
379 }
380
381 #[test]
test_gap_events()382 fn test_gap_events() {
383 init();
384 let valid_cc608_data = vec![0xfc, 0x80, 0x81];
385
386 let mut h = gst_check::Harness::new("ccdetect");
387 h.set_src_caps_str("closedcaption/x-cea-708,format=cc_data");
388 h.set_sink_caps_str("closedcaption/x-cea-708,format=cc_data");
389 h.element()
390 .unwrap()
391 .set_property("window", &500_000_000u64)
392 .unwrap();
393
394 let state = Arc::new(Mutex::new(NotifyState::default()));
395 let state_c = state.clone();
396 h.element()
397 .unwrap()
398 .connect_notify(Some("cc608"), move |_o, _pspec| {
399 let mut state_guard = state_c.lock().unwrap();
400 state_guard.cc608_count += 1;
401 });
402 let state_c = state.clone();
403 h.element()
404 .unwrap()
405 .connect_notify(Some("cc708"), move |_o, _pspec| {
406 let mut state_guard = state_c.lock().unwrap();
407 state_guard.cc708_count += 1;
408 });
409
410 /* valid cc608 data moves cc608 property to true */
411 assert_push_data!(h, state, valid_cc608_data, ClockTime::ZERO, 1, 0);
412
413 /* pushing gap event within the window changes nothing */
414 assert_eq!(
415 h.push_event(gst::event::Gap::new(
416 ClockTime::from_nseconds(100_000_000),
417 ClockTime::from_nseconds(1)
418 )),
419 true
420 );
421
422 {
423 let state_guard = state.lock().unwrap();
424 assert_eq!(state_guard.cc608_count, 1);
425 assert_eq!(state_guard.cc708_count, 0);
426 }
427
428 /* pushing gap event outside the window moves cc608 property to false */
429 assert_eq!(
430 h.push_event(gst::event::Gap::new(
431 ClockTime::from_nseconds(1_000_000_000),
432 ClockTime::from_nseconds(1)
433 )),
434 true
435 );
436
437 {
438 let state_guard = state.lock().unwrap();
439 assert_eq!(state_guard.cc608_count, 2);
440 assert_eq!(state_guard.cc708_count, 0);
441 }
442 }
443