Skip to main content

gstreamer_base/subclass/
base_parse.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::mem;
4
5use glib::translate::*;
6use gst::subclass::prelude::*;
7
8use crate::{BaseParse, BaseParseFrame, BaseParseFrameFlags, ffi, prelude::*};
9
10pub trait BaseParseImpl: ElementImpl + ObjectSubclass<Type: IsA<BaseParse>> {
11    // rustdoc-stripper-ignore-next
12    /// Whether `Self::detect` will be called or not.
13    const USE_DETECT: bool = false;
14
15    fn start(&self) -> Result<(), gst::ErrorMessage> {
16        self.parent_start()
17    }
18
19    fn stop(&self) -> Result<(), gst::ErrorMessage> {
20        self.parent_stop()
21    }
22
23    fn sink_caps(&self, filter: Option<&gst::Caps>) -> gst::Caps {
24        self.parent_sink_caps(filter)
25    }
26
27    fn set_sink_caps(&self, caps: &gst::Caps) -> Result<(), gst::LoggableError> {
28        self.parent_set_sink_caps(caps)
29    }
30
31    fn handle_frame(
32        &self,
33        frame: BaseParseFrame,
34    ) -> Result<(gst::FlowSuccess, u32), gst::FlowError> {
35        self.parent_handle_frame(frame)
36    }
37
38    fn convert(
39        &self,
40        src_val: impl gst::format::FormattedValue,
41        dest_format: gst::Format,
42    ) -> Option<gst::GenericFormattedValue> {
43        self.parent_convert(src_val, dest_format)
44    }
45
46    fn sink_event(&self, event: gst::Event) -> bool {
47        self.parent_sink_event(event)
48    }
49
50    fn src_event(&self, event: gst::Event) -> bool {
51        self.parent_src_event(event)
52    }
53
54    fn sink_query(&self, query: &mut gst::QueryRef) -> bool {
55        self.parent_sink_query(query)
56    }
57
58    fn src_query(&self, query: &mut gst::QueryRef) -> bool {
59        self.parent_src_query(query)
60    }
61
62    fn pre_push_frame(
63        &self,
64        frame: &mut BaseParseFrame,
65    ) -> Result<gst::FlowSuccess, gst::FlowError> {
66        self.parent_pre_push_frame(frame)
67    }
68
69    // rustdoc-stripper-ignore-next
70    /// Detect the stream format.
71    ///
72    /// Note that this is only called if `Self::USE_DETECT` is set to `true`.
73    fn detect(&self, buffer: &gst::BufferRef) -> Result<gst::FlowSuccess, gst::FlowError> {
74        self.parent_detect(buffer)
75    }
76}
77
78pub trait BaseParseImplExt: BaseParseImpl {
79    fn parent_start(&self) -> Result<(), gst::ErrorMessage> {
80        unsafe {
81            let data = Self::type_data();
82            let parent_class = data.as_ref().parent_class() as *mut ffi::GstBaseParseClass;
83            (*parent_class)
84                .start
85                .map(|f| {
86                    if from_glib(f(self
87                        .obj()
88                        .unsafe_cast_ref::<BaseParse>()
89                        .to_glib_none()
90                        .0))
91                    {
92                        Ok(())
93                    } else {
94                        Err(gst::error_msg!(
95                            gst::CoreError::StateChange,
96                            ["Parent function `start` failed"]
97                        ))
98                    }
99                })
100                .unwrap_or(Ok(()))
101        }
102    }
103
104    fn parent_stop(&self) -> Result<(), gst::ErrorMessage> {
105        unsafe {
106            let data = Self::type_data();
107            let parent_class = data.as_ref().parent_class() as *mut ffi::GstBaseParseClass;
108            (*parent_class)
109                .stop
110                .map(|f| {
111                    if from_glib(f(self
112                        .obj()
113                        .unsafe_cast_ref::<BaseParse>()
114                        .to_glib_none()
115                        .0))
116                    {
117                        Ok(())
118                    } else {
119                        Err(gst::error_msg!(
120                            gst::CoreError::StateChange,
121                            ["Parent function `stop` failed"]
122                        ))
123                    }
124                })
125                .unwrap_or(Ok(()))
126        }
127    }
128
129    fn parent_sink_caps(&self, filter: Option<&gst::Caps>) -> gst::Caps {
130        unsafe {
131            let data = Self::type_data();
132            let parent_class = data.as_ref().parent_class() as *mut ffi::GstBaseParseClass;
133            if let Some(f) = (*parent_class).get_sink_caps {
134                from_glib_full(f(
135                    self.obj().unsafe_cast_ref::<BaseParse>().to_glib_none().0,
136                    filter.to_glib_none().0,
137                ))
138            } else {
139                let templ_caps = self.obj().sink_pad().pad_template_caps();
140                if let Some(filter) = filter {
141                    filter.intersect_with_mode(&templ_caps, gst::CapsIntersectMode::First)
142                } else {
143                    templ_caps
144                }
145            }
146        }
147    }
148
149    fn parent_set_sink_caps(&self, caps: &gst::Caps) -> Result<(), gst::LoggableError> {
150        unsafe {
151            let data = Self::type_data();
152            let parent_class = data.as_ref().parent_class() as *mut ffi::GstBaseParseClass;
153            (*parent_class)
154                .set_sink_caps
155                .map(|f| {
156                    gst::result_from_gboolean!(
157                        f(
158                            self.obj().unsafe_cast_ref::<BaseParse>().to_glib_none().0,
159                            caps.to_glib_none().0,
160                        ),
161                        gst::CAT_RUST,
162                        "Parent function `set_sink_caps` failed",
163                    )
164                })
165                .unwrap_or(Ok(()))
166        }
167    }
168
169    fn parent_handle_frame(
170        &self,
171        frame: BaseParseFrame,
172    ) -> Result<(gst::FlowSuccess, u32), gst::FlowError> {
173        unsafe {
174            let data = Self::type_data();
175            let parent_class = data.as_ref().parent_class() as *mut ffi::GstBaseParseClass;
176            let mut skipsize = 0;
177            (*parent_class)
178                .handle_frame
179                .map(|f| {
180                    let res = try_from_glib(f(
181                        self.obj().unsafe_cast_ref::<BaseParse>().to_glib_none().0,
182                        frame.to_glib_none().0,
183                        &mut skipsize,
184                    ));
185                    (res.unwrap(), skipsize as u32)
186                })
187                .ok_or(gst::FlowError::Error)
188        }
189    }
190
191    fn parent_convert(
192        &self,
193        src_val: impl gst::format::FormattedValue,
194        dest_format: gst::Format,
195    ) -> Option<gst::GenericFormattedValue> {
196        unsafe {
197            let data = Self::type_data();
198            let parent_class = data.as_ref().parent_class() as *mut ffi::GstBaseParseClass;
199            let res = (*parent_class).convert.map(|f| {
200                let mut dest_val = mem::MaybeUninit::uninit();
201
202                let res = from_glib(f(
203                    self.obj().unsafe_cast_ref::<BaseParse>().to_glib_none().0,
204                    src_val.format().into_glib(),
205                    src_val.into_raw_value(),
206                    dest_format.into_glib(),
207                    dest_val.as_mut_ptr(),
208                ));
209                (res, dest_val)
210            });
211
212            match res {
213                Some((true, dest_val)) => Some(gst::GenericFormattedValue::new(
214                    dest_format,
215                    dest_val.assume_init(),
216                )),
217                _ => None,
218            }
219        }
220    }
221
222    fn parent_sink_event(&self, event: gst::Event) -> bool {
223        unsafe {
224            let data = Self::type_data();
225            let parent_class = data.as_ref().parent_class() as *mut ffi::GstBaseParseClass;
226            (*parent_class)
227                .sink_event
228                .map(|f| {
229                    from_glib(f(
230                        self.obj().unsafe_cast_ref::<BaseParse>().to_glib_none().0,
231                        event.into_glib_ptr(),
232                    ))
233                })
234                .unwrap_or(true)
235        }
236    }
237
238    fn parent_src_event(&self, event: gst::Event) -> bool {
239        unsafe {
240            let data = Self::type_data();
241            let parent_class = data.as_ref().parent_class() as *mut ffi::GstBaseParseClass;
242            (*parent_class)
243                .src_event
244                .map(|f| {
245                    from_glib(f(
246                        self.obj().unsafe_cast_ref::<BaseParse>().to_glib_none().0,
247                        event.into_glib_ptr(),
248                    ))
249                })
250                .unwrap_or(true)
251        }
252    }
253
254    fn parent_sink_query(&self, query: &mut gst::QueryRef) -> bool {
255        unsafe {
256            let data = Self::type_data();
257            let parent_class = data.as_ref().parent_class() as *mut ffi::GstBaseParseClass;
258            (*parent_class)
259                .sink_query
260                .map(|f| {
261                    from_glib(f(
262                        self.obj().unsafe_cast_ref::<BaseParse>().to_glib_none().0,
263                        query.as_mut_ptr(),
264                    ))
265                })
266                .unwrap_or(false)
267        }
268    }
269
270    fn parent_src_query(&self, query: &mut gst::QueryRef) -> bool {
271        unsafe {
272            let data = Self::type_data();
273            let parent_class = data.as_ref().parent_class() as *mut ffi::GstBaseParseClass;
274            (*parent_class)
275                .src_query
276                .map(|f| {
277                    from_glib(f(
278                        self.obj().unsafe_cast_ref::<BaseParse>().to_glib_none().0,
279                        query.as_mut_ptr(),
280                    ))
281                })
282                .unwrap_or(false)
283        }
284    }
285
286    fn parent_pre_push_frame(
287        &self,
288        frame: &mut BaseParseFrame,
289    ) -> Result<gst::FlowSuccess, gst::FlowError> {
290        unsafe {
291            let data = Self::type_data();
292            let parent_class = data.as_ref().parent_class() as *mut ffi::GstBaseParseClass;
293            match (*parent_class).pre_push_frame {
294                Some(f) => gst::FlowSuccess::try_from_glib(f(
295                    self.obj().unsafe_cast_ref::<BaseParse>().to_glib_none().0,
296                    frame.to_glib_none().0,
297                )),
298                // If there is no parent implementation, replicate the base
299                // class behavior: clip the frame.
300                None => {
301                    frame.set_flags(BaseParseFrameFlags::CLIP);
302                    Ok(gst::FlowSuccess::Ok)
303                }
304            }
305        }
306    }
307
308    fn parent_detect(&self, buffer: &gst::BufferRef) -> Result<gst::FlowSuccess, gst::FlowError> {
309        unsafe {
310            let data = Self::type_data();
311            let parent_class = data.as_ref().parent_class() as *mut ffi::GstBaseParseClass;
312            (*parent_class)
313                .detect
314                .map(|f| {
315                    gst::FlowSuccess::try_from_glib(f(
316                        self.obj().unsafe_cast_ref::<BaseParse>().to_glib_none().0,
317                        buffer.as_ptr() as *mut _,
318                    ))
319                })
320                .unwrap_or(Ok(gst::FlowSuccess::Ok))
321        }
322    }
323}
324
325impl<T: BaseParseImpl> BaseParseImplExt for T {}
326
327unsafe impl<T: BaseParseImpl> IsSubclassable<T> for BaseParse {
328    fn class_init(klass: &mut glib::Class<Self>) {
329        Self::parent_class_init::<T>(klass);
330        let klass = klass.as_mut();
331        klass.start = Some(base_parse_start::<T>);
332        klass.stop = Some(base_parse_stop::<T>);
333        klass.set_sink_caps = Some(base_parse_set_sink_caps::<T>);
334        klass.get_sink_caps = Some(base_parse_get_sink_caps::<T>);
335        klass.handle_frame = Some(base_parse_handle_frame::<T>);
336        klass.convert = Some(base_parse_convert::<T>);
337        klass.sink_event = Some(base_parse_sink_event::<T>);
338        klass.src_event = Some(base_parse_src_event::<T>);
339        klass.sink_query = Some(base_parse_sink_query::<T>);
340        klass.src_query = Some(base_parse_src_query::<T>);
341        klass.pre_push_frame = Some(base_parse_pre_push_frame::<T>);
342        if T::USE_DETECT {
343            klass.detect = Some(base_parse_detect::<T>);
344        }
345    }
346}
347
348unsafe extern "C" fn base_parse_start<T: BaseParseImpl>(
349    ptr: *mut ffi::GstBaseParse,
350) -> glib::ffi::gboolean {
351    unsafe {
352        let instance = &*(ptr as *mut T::Instance);
353        let imp = instance.imp();
354
355        gst::panic_to_error!(imp, false, {
356            match imp.start() {
357                Ok(()) => true,
358                Err(err) => {
359                    imp.post_error_message(err);
360                    false
361                }
362            }
363        })
364        .into_glib()
365    }
366}
367
368unsafe extern "C" fn base_parse_stop<T: BaseParseImpl>(
369    ptr: *mut ffi::GstBaseParse,
370) -> glib::ffi::gboolean {
371    unsafe {
372        let instance = &*(ptr as *mut T::Instance);
373        let imp = instance.imp();
374
375        gst::panic_to_error!(imp, false, {
376            match imp.stop() {
377                Ok(()) => true,
378                Err(err) => {
379                    imp.post_error_message(err);
380                    false
381                }
382            }
383        })
384        .into_glib()
385    }
386}
387
388unsafe extern "C" fn base_parse_set_sink_caps<T: BaseParseImpl>(
389    ptr: *mut ffi::GstBaseParse,
390    caps: *mut gst::ffi::GstCaps,
391) -> glib::ffi::gboolean {
392    unsafe {
393        let instance = &*(ptr as *mut T::Instance);
394        let imp = instance.imp();
395        let caps: Borrowed<gst::Caps> = from_glib_borrow(caps);
396
397        gst::panic_to_error!(imp, false, {
398            match imp.set_sink_caps(&caps) {
399                Ok(()) => true,
400                Err(err) => {
401                    err.log_with_imp(imp);
402                    false
403                }
404            }
405        })
406        .into_glib()
407    }
408}
409
410unsafe extern "C" fn base_parse_get_sink_caps<T: BaseParseImpl>(
411    ptr: *mut ffi::GstBaseParse,
412    filter: *mut gst::ffi::GstCaps,
413) -> *mut gst::ffi::GstCaps {
414    unsafe {
415        let instance = &*(ptr as *mut T::Instance);
416        let imp = instance.imp();
417        let filter: Borrowed<Option<gst::Caps>> = from_glib_borrow(filter);
418
419        gst::panic_to_error!(imp, gst::Caps::new_empty(), {
420            imp.sink_caps(filter.as_ref().as_ref())
421        })
422        .into_glib_ptr()
423    }
424}
425
426unsafe extern "C" fn base_parse_handle_frame<T: BaseParseImpl>(
427    ptr: *mut ffi::GstBaseParse,
428    frame: *mut ffi::GstBaseParseFrame,
429    skipsize: *mut i32,
430) -> gst::ffi::GstFlowReturn {
431    unsafe {
432        let instance = &*(ptr as *mut T::Instance);
433        let imp = instance.imp();
434        let instance = imp.obj();
435        let instance = instance.unsafe_cast_ref::<BaseParse>();
436        let wrap_frame = BaseParseFrame::new(frame, instance);
437
438        let res = gst::panic_to_error!(imp, Err(gst::FlowError::Error), {
439            imp.handle_frame(wrap_frame)
440        });
441
442        match res {
443            Ok((flow, skip)) => {
444                *skipsize = i32::try_from(skip).expect("skip is higher than i32::MAX");
445                gst::FlowReturn::from_ok(flow)
446            }
447            Err(flow) => gst::FlowReturn::from_error(flow),
448        }
449        .into_glib()
450    }
451}
452
453unsafe extern "C" fn base_parse_convert<T: BaseParseImpl>(
454    ptr: *mut ffi::GstBaseParse,
455    source_format: gst::ffi::GstFormat,
456    source_value: i64,
457    dest_format: gst::ffi::GstFormat,
458    dest_value: *mut i64,
459) -> glib::ffi::gboolean {
460    unsafe {
461        let instance = &*(ptr as *mut T::Instance);
462        let imp = instance.imp();
463        let source = gst::GenericFormattedValue::new(from_glib(source_format), source_value);
464
465        let res = gst::panic_to_error!(imp, None, { imp.convert(source, from_glib(dest_format)) });
466
467        match res {
468            Some(dest) => {
469                *dest_value = dest.into_raw_value();
470                true
471            }
472            _ => false,
473        }
474        .into_glib()
475    }
476}
477
478unsafe extern "C" fn base_parse_sink_event<T: BaseParseImpl>(
479    ptr: *mut ffi::GstBaseParse,
480    event: *mut gst::ffi::GstEvent,
481) -> glib::ffi::gboolean {
482    unsafe {
483        let instance = &*(ptr as *mut T::Instance);
484        let imp = instance.imp();
485
486        gst::panic_to_error!(imp, false, { imp.sink_event(from_glib_full(event)) }).into_glib()
487    }
488}
489
490unsafe extern "C" fn base_parse_src_event<T: BaseParseImpl>(
491    ptr: *mut ffi::GstBaseParse,
492    event: *mut gst::ffi::GstEvent,
493) -> glib::ffi::gboolean {
494    unsafe {
495        let instance = &*(ptr as *mut T::Instance);
496        let imp = instance.imp();
497
498        gst::panic_to_error!(imp, false, { imp.src_event(from_glib_full(event)) }).into_glib()
499    }
500}
501
502unsafe extern "C" fn base_parse_sink_query<T: BaseParseImpl>(
503    ptr: *mut ffi::GstBaseParse,
504    query_ptr: *mut gst::ffi::GstQuery,
505) -> glib::ffi::gboolean {
506    unsafe {
507        let instance = &*(ptr as *mut T::Instance);
508        let imp = instance.imp();
509        let query = gst::QueryRef::from_mut_ptr(query_ptr);
510
511        gst::panic_to_error!(imp, false, { imp.sink_query(query) }).into_glib()
512    }
513}
514
515unsafe extern "C" fn base_parse_src_query<T: BaseParseImpl>(
516    ptr: *mut ffi::GstBaseParse,
517    query_ptr: *mut gst::ffi::GstQuery,
518) -> glib::ffi::gboolean {
519    unsafe {
520        let instance = &*(ptr as *mut T::Instance);
521        let imp = instance.imp();
522        let query = gst::QueryRef::from_mut_ptr(query_ptr);
523
524        gst::panic_to_error!(imp, false, { imp.src_query(query) }).into_glib()
525    }
526}
527
528unsafe extern "C" fn base_parse_pre_push_frame<T: BaseParseImpl>(
529    ptr: *mut ffi::GstBaseParse,
530    frame_ptr: *mut ffi::GstBaseParseFrame,
531) -> gst::ffi::GstFlowReturn {
532    unsafe {
533        let instance = &*(ptr as *mut T::Instance);
534        let imp = instance.imp();
535        let instance = imp.obj();
536        let instance = instance.unsafe_cast_ref::<BaseParse>();
537        let mut frame = BaseParseFrame::new(frame_ptr, instance);
538
539        let res = gst::panic_to_error!(imp, Err(gst::FlowError::Error), {
540            imp.pre_push_frame(&mut frame)
541        });
542
543        match res {
544            Ok(flow) => gst::FlowReturn::from_ok(flow),
545            Err(flow) => gst::FlowReturn::from_error(flow),
546        }
547        .into_glib()
548    }
549}
550
551unsafe extern "C" fn base_parse_detect<T: BaseParseImpl>(
552    ptr: *mut ffi::GstBaseParse,
553    buffer: *mut gst::ffi::GstBuffer,
554) -> gst::ffi::GstFlowReturn {
555    unsafe {
556        let instance = &*(ptr as *mut T::Instance);
557        let imp = instance.imp();
558        let buffer = gst::BufferRef::from_ptr(buffer);
559
560        let res = gst::panic_to_error!(imp, Err(gst::FlowError::Error), { imp.detect(buffer) });
561
562        match res {
563            Ok(flow) => gst::FlowReturn::from_ok(flow),
564            Err(flow) => gst::FlowReturn::from_error(flow),
565        }
566        .into_glib()
567    }
568}
569
570#[cfg(test)]
571mod tests {
572    use super::*;
573    use std::sync::atomic::{self, AtomicBool, AtomicU32};
574
575    const MIN_FRAME_SIZE: u32 = 8;
576
577    mod imp {
578        use super::*;
579
580        fn test_pad_templates() -> &'static [gst::PadTemplate] {
581            static PAD_TEMPLATES: std::sync::OnceLock<Vec<gst::PadTemplate>> =
582                std::sync::OnceLock::new();
583
584            PAD_TEMPLATES.get_or_init(|| {
585                let caps = gst::Caps::new_any();
586                vec![
587                    gst::PadTemplate::new(
588                        "src",
589                        gst::PadDirection::Src,
590                        gst::PadPresence::Always,
591                        &caps,
592                    )
593                    .unwrap(),
594                    gst::PadTemplate::new(
595                        "sink",
596                        gst::PadDirection::Sink,
597                        gst::PadPresence::Always,
598                        &caps,
599                    )
600                    .unwrap(),
601                ]
602            })
603        }
604
605        #[derive(Default)]
606        pub struct TestParse {
607            sink_caps_set: AtomicBool,
608            pub handle_frame_count: AtomicU32,
609            pub pre_push_frame_count: AtomicU32,
610        }
611
612        #[glib::object_subclass]
613        impl ObjectSubclass for TestParse {
614            const NAME: &'static str = "TestParse";
615            type Type = super::TestParse;
616            type ParentType = crate::BaseParse;
617        }
618
619        impl ObjectImpl for TestParse {}
620
621        impl GstObjectImpl for TestParse {}
622
623        impl ElementImpl for TestParse {
624            fn pad_templates() -> &'static [gst::PadTemplate] {
625                test_pad_templates()
626            }
627        }
628
629        impl BaseParseImpl for TestParse {
630            fn start(&self) -> Result<(), gst::ErrorMessage> {
631                self.obj().set_min_frame_size(MIN_FRAME_SIZE);
632                self.parent_start()
633            }
634
635            fn handle_frame(
636                &self,
637                frame: BaseParseFrame,
638            ) -> Result<(gst::FlowSuccess, u32), gst::FlowError> {
639                self.handle_frame_count
640                    .fetch_add(1, atomic::Ordering::SeqCst);
641                let instance = self.obj();
642                if !self.sink_caps_set.swap(true, atomic::Ordering::SeqCst) {
643                    let caps = instance
644                        .sink_pad()
645                        .current_caps()
646                        .expect("sink pad has no caps");
647                    instance.src_pad().push_event(gst::event::Caps::new(&caps));
648                }
649
650                let flow = instance.finish_frame(frame, MIN_FRAME_SIZE)?;
651                Ok((flow, 0))
652            }
653
654            fn pre_push_frame(
655                &self,
656                frame: &mut BaseParseFrame,
657            ) -> Result<gst::FlowSuccess, gst::FlowError> {
658                self.pre_push_frame_count
659                    .fetch_add(1, atomic::Ordering::SeqCst);
660                self.parent_pre_push_frame(frame)
661            }
662        }
663
664        #[derive(Default)]
665        pub struct TestParseDetect {
666            sink_caps_set: AtomicBool,
667            detected: AtomicBool,
668            pub detect_count: AtomicU32,
669        }
670
671        #[glib::object_subclass]
672        impl ObjectSubclass for TestParseDetect {
673            const NAME: &'static str = "TestParseDetect";
674            type Type = super::TestParseDetect;
675            type ParentType = crate::BaseParse;
676        }
677
678        impl ObjectImpl for TestParseDetect {}
679
680        impl GstObjectImpl for TestParseDetect {}
681
682        impl ElementImpl for TestParseDetect {
683            fn pad_templates() -> &'static [gst::PadTemplate] {
684                test_pad_templates()
685            }
686        }
687
688        impl BaseParseImpl for TestParseDetect {
689            const USE_DETECT: bool = true;
690
691            fn start(&self) -> Result<(), gst::ErrorMessage> {
692                self.obj().set_min_frame_size(MIN_FRAME_SIZE);
693                self.parent_start()
694            }
695
696            fn handle_frame(
697                &self,
698                frame: BaseParseFrame,
699            ) -> Result<(gst::FlowSuccess, u32), gst::FlowError> {
700                let instance = self.obj();
701                if !self.sink_caps_set.swap(true, atomic::Ordering::SeqCst) {
702                    let caps = instance
703                        .sink_pad()
704                        .current_caps()
705                        .expect("sink pad has no caps");
706                    instance.src_pad().push_event(gst::event::Caps::new(&caps));
707                }
708
709                let flow = instance.finish_frame(frame, MIN_FRAME_SIZE)?;
710                Ok((flow, 0))
711            }
712
713            fn detect(&self, _buffer: &gst::BufferRef) -> Result<gst::FlowSuccess, gst::FlowError> {
714                self.detect_count.fetch_add(1, atomic::Ordering::SeqCst);
715
716                // Require two buffers for detection
717                if self.detected.swap(true, atomic::Ordering::SeqCst) {
718                    Ok(gst::FlowSuccess::Ok)
719                } else {
720                    Err(gst::FlowError::NotNegotiated)
721                }
722            }
723        }
724    }
725
726    glib::wrapper! {
727        pub struct TestParse(ObjectSubclass<imp::TestParse>) @extends crate::BaseParse, gst::Element, gst::Object;
728    }
729
730    glib::wrapper! {
731        pub struct TestParseDetect(ObjectSubclass<imp::TestParseDetect>) @extends crate::BaseParse, gst::Element, gst::Object;
732    }
733
734    fn run_pipeline(element: &glib::Object, num_buffers: i32) -> u64 {
735        let element = element.downcast_ref::<gst::Element>().unwrap();
736        let pipeline = gst::Pipeline::new();
737        let src = gst::ElementFactory::make("audiotestsrc")
738            .property("num-buffers", num_buffers)
739            .build()
740            .unwrap();
741        let sink = gst::ElementFactory::make("fakesink").build().unwrap();
742
743        pipeline.add_many([&src, element, &sink]).unwrap();
744        gst::Element::link_many([&src, element, &sink]).unwrap();
745
746        pipeline.set_state(gst::State::Playing).unwrap();
747        let bus = pipeline.bus().unwrap();
748
749        let msg = bus
750            .timed_pop_filtered(
751                gst::ClockTime::NONE,
752                &[gst::MessageType::Eos, gst::MessageType::Error],
753            )
754            .expect("no EOS or Error message received");
755        assert_eq!(
756            msg.type_(),
757            gst::MessageType::Eos,
758            "expected EOS, got {:?}",
759            msg
760        );
761
762        let rendered = sink
763            .property::<gst::Structure>("stats")
764            .get::<u64>("rendered")
765            .unwrap();
766        pipeline.set_state(gst::State::Null).unwrap();
767
768        rendered
769    }
770
771    #[test]
772    fn test_parse_subclass() {
773        gst::init().unwrap();
774
775        let element = glib::Object::new::<TestParse>();
776        let rendered = run_pipeline(element.upcast_ref(), 10);
777
778        assert_eq!(rendered, 2560);
779        assert!(
780            element
781                .imp()
782                .handle_frame_count
783                .load(atomic::Ordering::SeqCst)
784                > 0,
785            "handle_frame must be invoked"
786        );
787        assert_eq!(
788            element
789                .imp()
790                .pre_push_frame_count
791                .load(atomic::Ordering::SeqCst) as u64,
792            rendered,
793            "every finished frame must be pushed exactly once"
794        );
795    }
796
797    // Ignored until this is merged:
798    // https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/12459
799    #[test]
800    #[ignore]
801    fn test_parse_subclass_detect() {
802        gst::init().unwrap();
803
804        let element = glib::Object::new::<TestParseDetect>();
805        let rendered = run_pipeline(element.upcast_ref(), 3);
806
807        assert_eq!(rendered, 768, "expected output frames, got {rendered}");
808        assert_eq!(
809            element.imp().detect_count.load(atomic::Ordering::SeqCst),
810            2,
811            "detect is invoked until it reports the stream format"
812        );
813    }
814}