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