// Copyright (C) 2020 Matthew Waters // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public // License along with this library; if not, write to the // Free Software Foundation, Inc., 51 Franklin Street, Suite 500, // Boston, MA 02110-1335, USA. use gst::prelude::*; use gst::ClockTime; use std::sync::{Arc, Mutex}; use pretty_assertions::assert_eq; fn init() { use std::sync::Once; static INIT: Once = Once::new(); INIT.call_once(|| { gst::init().unwrap(); gstrsclosedcaption::plugin_register_static().unwrap(); }); } #[derive(Default)] struct NotifyState { cc608_count: u32, cc708_count: u32, } macro_rules! assert_push_data { ($h:expr, $state:expr, $data:expr, $ts:expr, $cc608_count:expr, $cc708_count:expr) => { let mut buf = gst::Buffer::from_mut_slice($data); buf.get_mut().unwrap().set_pts($ts); assert_eq!($h.push(buf), Ok(gst::FlowSuccess::Ok)); { let state_guard = $state.lock().unwrap(); assert_eq!(state_guard.cc608_count, $cc608_count); assert_eq!(state_guard.cc708_count, $cc708_count); } }; } #[test] fn test_have_cc_data_notify() { init(); let valid_cc608_data = vec![0xfc, 0x80, 0x81]; let invalid_cc608_data = vec![0xf8, 0x80, 0x81]; let valid_cc708_data = vec![0xfe, 0x80, 0x81]; let invalid_cc708_data = vec![0xfa, 0x80, 0x81]; let mut h = gst_check::Harness::new("ccdetect"); h.set_src_caps_str("closedcaption/x-cea-708,format=cc_data"); h.set_sink_caps_str("closedcaption/x-cea-708,format=cc_data"); h.element().unwrap().set_property("window", 500_000_000u64); let state = Arc::new(Mutex::new(NotifyState::default())); let state_c = state.clone(); h.element() .unwrap() .connect_notify(Some("cc608"), move |o, _pspec| { let mut state_guard = state_c.lock().unwrap(); state_guard.cc608_count += 1; o.property_value("cc608"); }); let state_c = state.clone(); h.element() .unwrap() .connect_notify(Some("cc708"), move |o, _pspec| { let mut state_guard = state_c.lock().unwrap(); state_guard.cc708_count += 1; o.property_value("cc708"); }); /* valid cc608 data moves cc608 property to true */ assert_push_data!(h, state, valid_cc608_data, ClockTime::ZERO, 1, 0); /* invalid cc608 data moves cc608 property to false */ assert_push_data!( h, state, invalid_cc608_data, ClockTime::from_nseconds(1_000_000_000), 2, 0 ); /* valid cc708 data moves cc708 property to true */ assert_push_data!( h, state, valid_cc708_data, ClockTime::from_nseconds(2_000_000_000), 2, 1 ); /* invalid cc708 data moves cc708 property to false */ assert_push_data!( h, state, invalid_cc708_data, ClockTime::from_nseconds(3_000_000_000), 2, 2 ); } #[test] fn test_cc_data_window() { init(); let valid_cc608_data = vec![0xfc, 0x80, 0x81]; let invalid_cc608_data = vec![0xf8, 0x80, 0x81]; let mut h = gst_check::Harness::new("ccdetect"); h.set_src_caps_str("closedcaption/x-cea-708,format=cc_data"); h.set_sink_caps_str("closedcaption/x-cea-708,format=cc_data"); h.element().unwrap().set_property("window", 500_000_000u64); let state = Arc::new(Mutex::new(NotifyState::default())); let state_c = state.clone(); h.element() .unwrap() .connect_notify(Some("cc608"), move |_o, _pspec| { let mut state_guard = state_c.lock().unwrap(); state_guard.cc608_count += 1; }); let state_c = state.clone(); h.element() .unwrap() .connect_notify(Some("cc708"), move |_o, _pspec| { let mut state_guard = state_c.lock().unwrap(); state_guard.cc708_count += 1; }); /* valid cc608 data moves cc608 property to true */ assert_push_data!(h, state, valid_cc608_data.clone(), ClockTime::ZERO, 1, 0); /* valid cc608 data moves within window */ assert_push_data!( h, state, valid_cc608_data.clone(), ClockTime::from_nseconds(300_000_000), 1, 0 ); /* invalid cc608 data before window expires, no change */ assert_push_data!( h, state, invalid_cc608_data.clone(), ClockTime::from_nseconds(600_000_000), 1, 0 ); /* invalid cc608 data after window expires, cc608 changes to false */ assert_push_data!( h, state, invalid_cc608_data, ClockTime::from_nseconds(1_000_000_000), 2, 0 ); /* valid cc608 data before window expires, no change */ assert_push_data!( h, state, valid_cc608_data.clone(), ClockTime::from_nseconds(1_300_000_000), 2, 0 ); /* valid cc608 data after window expires, property changes */ assert_push_data!( h, state, valid_cc608_data, ClockTime::from_nseconds(1_600_000_000), 3, 0 ); } #[test] fn test_have_cdp_notify() { init(); let valid_cc608_data = vec![ 0x96, 0x69, /* cdp magic bytes */ 0x10, /* length of cdp packet */ 0x8f, /* framerate */ 0x43, /* flags */ 0x00, 0x00, /* sequence counter */ 0x72, /* cc_data byte header */ 0xe1, /* n cc_data triples with 0xe0 as reserved bits */ 0xfc, 0x80, 0x81, /* cc_data triple */ 0x74, /* cdp end of frame byte header */ 0x00, 0x00, /* sequence counter */ 0x60, /* checksum */ ]; let invalid_cc608_data = vec![ 0x96, 0x69, 0x10, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xe1, 0xf8, 0x81, 0x82, 0x74, 0x00, 0x00, 0x60, ]; let mut h = gst_check::Harness::new("ccdetect"); h.set_src_caps_str("closedcaption/x-cea-708,format=cdp"); h.set_sink_caps_str("closedcaption/x-cea-708,format=cdp"); h.element().unwrap().set_property("window", 500_000_000u64); let state = Arc::new(Mutex::new(NotifyState::default())); let state_c = state.clone(); h.element() .unwrap() .connect_notify(Some("cc608"), move |_o, _pspec| { let mut state_guard = state_c.lock().unwrap(); state_guard.cc608_count += 1; }); let state_c = state.clone(); h.element() .unwrap() .connect_notify(Some("cc708"), move |_o, _pspec| { let mut state_guard = state_c.lock().unwrap(); state_guard.cc708_count += 1; }); /* valid cc608 data moves cc608 property to true */ assert_push_data!(h, state, valid_cc608_data, ClockTime::ZERO, 1, 0); /* invalid cc608 data moves cc608 property to false */ assert_push_data!( h, state, invalid_cc608_data, ClockTime::from_nseconds(1_000_000_000), 2, 0 ); } #[test] fn test_malformed_cdp_notify() { init(); let too_short = vec![0x96, 0x69]; let wrong_magic = vec![ 0x00, 0x00, 0x10, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xe1, 0xfc, 0x81, 0x82, 0x74, 0x00, 0x00, 0x60, ]; let length_too_long = vec![ 0x96, 0x69, 0x20, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xe1, 0xfc, 0x81, 0x82, 0x74, 0x00, 0x00, 0x60, ]; let length_too_short = vec![ 0x96, 0x69, 0x00, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xe1, 0xfc, 0x81, 0x82, 0x74, 0x00, 0x00, 0x60, ]; let wrong_cc_data_header_byte = vec![ 0x96, 0x69, 0x10, 0x8f, 0x43, 0x00, 0x00, 0xff, 0xe1, 0xfc, 0x81, 0x82, 0x74, 0x00, 0x00, 0x60, ]; let big_cc_count = vec![ 0x96, 0x69, 0x10, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xef, 0xfc, 0x81, 0x82, 0x74, 0x00, 0x00, 0x60, ]; let wrong_cc_count_reserved_bits = vec![ 0x96, 0x69, 0x10, 0x8f, 0x43, 0x00, 0x00, 0x72, 0x01, 0xfc, 0x81, 0x82, 0x74, 0x00, 0x00, 0x60, ]; let cc608_after_cc708 = vec![ 0x96, 0x69, 0x13, 0x8f, 0x43, 0x00, 0x00, 0x72, 0xe2, 0xfe, 0x81, 0x82, 0xfc, 0x83, 0x84, 0x74, 0x00, 0x00, 0x60, ]; let mut h = gst_check::Harness::new("ccdetect"); h.set_src_caps_str("closedcaption/x-cea-708,format=cdp"); h.set_sink_caps_str("closedcaption/x-cea-708,format=cdp"); h.element().unwrap().set_property("window", 0u64); let state = Arc::new(Mutex::new(NotifyState::default())); let state_c = state.clone(); h.element() .unwrap() .connect_notify(Some("cc608"), move |_o, _pspec| { let mut state_guard = state_c.lock().unwrap(); state_guard.cc608_count += 1; }); let state_c = state.clone(); h.element() .unwrap() .connect_notify(Some("cc708"), move |_o, _pspec| { let mut state_guard = state_c.lock().unwrap(); state_guard.cc708_count += 1; }); /* all invalid data does not change properties */ assert_push_data!(h, state, too_short, ClockTime::from_nseconds(0), 0, 0); assert_push_data!(h, state, wrong_magic, ClockTime::from_nseconds(1_000), 0, 0); assert_push_data!( h, state, length_too_long, ClockTime::from_nseconds(2_000), 0, 0 ); assert_push_data!( h, state, length_too_short, ClockTime::from_nseconds(3_000), 0, 0 ); assert_push_data!( h, state, wrong_cc_data_header_byte, ClockTime::from_nseconds(4_000), 0, 0 ); assert_push_data!( h, state, big_cc_count, ClockTime::from_nseconds(5_000), 0, 0 ); assert_push_data!( h, state, wrong_cc_count_reserved_bits, ClockTime::from_nseconds(6_000), 0, 0 ); assert_push_data!( h, state, cc608_after_cc708, ClockTime::from_nseconds(7_000), 0, 0 ); } #[test] fn test_gap_events() { init(); let valid_cc608_data = vec![0xfc, 0x80, 0x81]; let mut h = gst_check::Harness::new("ccdetect"); h.set_src_caps_str("closedcaption/x-cea-708,format=cc_data"); h.set_sink_caps_str("closedcaption/x-cea-708,format=cc_data"); h.element().unwrap().set_property("window", 500_000_000u64); let state = Arc::new(Mutex::new(NotifyState::default())); let state_c = state.clone(); h.element() .unwrap() .connect_notify(Some("cc608"), move |_o, _pspec| { let mut state_guard = state_c.lock().unwrap(); state_guard.cc608_count += 1; }); let state_c = state.clone(); h.element() .unwrap() .connect_notify(Some("cc708"), move |_o, _pspec| { let mut state_guard = state_c.lock().unwrap(); state_guard.cc708_count += 1; }); /* valid cc608 data moves cc608 property to true */ assert_push_data!(h, state, valid_cc608_data, ClockTime::ZERO, 1, 0); /* pushing gap event within the window changes nothing */ assert!(h.push_event(gst::event::Gap::new( ClockTime::from_nseconds(100_000_000), ClockTime::from_nseconds(1) )),); { let state_guard = state.lock().unwrap(); assert_eq!(state_guard.cc608_count, 1); assert_eq!(state_guard.cc708_count, 0); } /* pushing gap event outside the window moves cc608 property to false */ assert!(h.push_event(gst::event::Gap::new( ClockTime::from_nseconds(1_000_000_000), ClockTime::from_nseconds(1) )),); { let state_guard = state.lock().unwrap(); assert_eq!(state_guard.cc608_count, 2); assert_eq!(state_guard.cc708_count, 0); } }