From f0be8c32a3505cffddf8f8cada7b688ac92aa06c Mon Sep 17 00:00:00 2001 From: Rafael Caricio Date: Thu, 26 Oct 2023 16:23:11 -0700 Subject: [PATCH] Add test for typefind element --- audio/qoa/Cargo.toml | 2 +- audio/qoa/src/lib.rs | 2 - audio/qoa/src/qoadec/imp.rs | 6 +- audio/qoa/src/qoadec/mod.rs | 2 +- audio/qoa/src/qoaparse/imp.rs | 186 ---------------------------------- audio/qoa/src/qoaparse/mod.rs | 37 ------- audio/qoa/src/typefind.rs | 3 +- audio/qoa/tests/typefind.rs | 66 ++++++++++++ 8 files changed, 72 insertions(+), 232 deletions(-) delete mode 100644 audio/qoa/src/qoaparse/imp.rs delete mode 100644 audio/qoa/src/qoaparse/mod.rs create mode 100644 audio/qoa/tests/typefind.rs diff --git a/audio/qoa/Cargo.toml b/audio/qoa/Cargo.toml index 683458f5..6df10494 100644 --- a/audio/qoa/Cargo.toml +++ b/audio/qoa/Cargo.toml @@ -13,10 +13,10 @@ gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/g gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" } qoaudio = "0.5.0" byte-slice-cast = "1.0" -once_cell = "1.0" [dev-dependencies] gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" } +gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" } [lib] name = "gstqoa" diff --git a/audio/qoa/src/lib.rs b/audio/qoa/src/lib.rs index ded771e8..023d265d 100644 --- a/audio/qoa/src/lib.rs +++ b/audio/qoa/src/lib.rs @@ -9,12 +9,10 @@ use gst::glib; mod qoadec; -mod qoaparse; mod typefind; fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { qoadec::register(plugin)?; - qoaparse::register(plugin)?; typefind::register(plugin) } diff --git a/audio/qoa/src/qoadec/imp.rs b/audio/qoa/src/qoadec/imp.rs index 5fabd5bf..b638d881 100644 --- a/audio/qoa/src/qoadec/imp.rs +++ b/audio/qoa/src/qoadec/imp.rs @@ -11,7 +11,7 @@ use gst::glib; use gst::subclass::prelude::*; use gst_audio::prelude::*; use gst_audio::subclass::prelude::*; -use once_cell::sync::Lazy; +use gst::glib::once_cell::sync::Lazy; use qoaudio::{DecodedAudio, QoaDecoder}; use std::sync::{Arc, Mutex}; @@ -71,9 +71,7 @@ impl ElementImpl for QoaDec { fn pad_templates() -> &'static [gst::PadTemplate] { static PAD_TEMPLATES: Lazy> = Lazy::new(|| { - let sink_caps = gst::Caps::builder("audio/x-qoa") - .field("parsed", true) - .build(); + let sink_caps = gst::Caps::builder("audio/x-qoa").build(); let sink_pad_template = gst::PadTemplate::new( "sink", gst::PadDirection::Sink, diff --git a/audio/qoa/src/qoadec/mod.rs b/audio/qoa/src/qoadec/mod.rs index abe58774..75c7dbfc 100644 --- a/audio/qoa/src/qoadec/mod.rs +++ b/audio/qoa/src/qoadec/mod.rs @@ -13,7 +13,7 @@ * * ## Example pipeline * ```bash - * gst-launch-1.0 filesrc location=audio.qoa ! qoaparse ! qoadec ! autoaudiosink + * gst-launch-1.0 filesrc location=audio.qoa ! qoadec ! autoaudiosink * ``` * * Since: plugins-rs-0.11.0-alpha.1 diff --git a/audio/qoa/src/qoaparse/imp.rs b/audio/qoa/src/qoaparse/imp.rs deleted file mode 100644 index 3e12224e..00000000 --- a/audio/qoa/src/qoaparse/imp.rs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (C) 2023 Rafael Caricio -// -// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. -// If a copy of the MPL was not distributed with this file, You can obtain one at -// . -// -// SPDX-License-Identifier: MPL-2.0 - -use gst::glib; -use gst::subclass::prelude::*; -use gst_base::prelude::*; -use gst_base::subclass::prelude::*; -use once_cell::sync::Lazy; -use qoaudio::{QOA_HEADER_SIZE, QOA_MAGIC, QOA_MIN_FILESIZE}; - -#[derive(Default)] -pub struct QoaParse; - -static CAT: Lazy = Lazy::new(|| { - gst::DebugCategory::new( - "qoaparse", - gst::DebugColorFlags::empty(), - Some("Quite OK Audio parser"), - ) -}); - -#[glib::object_subclass] -impl ObjectSubclass for QoaParse { - const NAME: &'static str = "GstQoaParse"; - type Type = super::QoaParse; - type ParentType = gst_base::BaseParse; -} - -impl ObjectImpl for QoaParse {} - -impl GstObjectImpl for QoaParse {} - -impl ElementImpl for QoaParse { - fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { - static ELEMENT_METADATA: Lazy = Lazy::new(|| { - gst::subclass::ElementMetadata::new( - "QOA parser", - "Codec/Parser/Audio", - "Quite OK Audio parser", - "Rafael Caricio ", - ) - }); - - Some(&*ELEMENT_METADATA) - } - - fn pad_templates() -> &'static [gst::PadTemplate] { - static PAD_TEMPLATES: Lazy> = Lazy::new(|| { - let sink_caps = gst::Caps::builder("audio/x-qoa").build(); - let sink_pad_template = gst::PadTemplate::new( - "sink", - gst::PadDirection::Sink, - gst::PadPresence::Always, - &sink_caps, - ) - .unwrap(); - - let src_caps = gst::Caps::builder("audio/x-qoa") - .field("parsed", true) - .build(); - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &src_caps, - ) - .unwrap(); - - vec![src_pad_template, sink_pad_template] - }); - - PAD_TEMPLATES.as_ref() - } -} - -impl BaseParseImpl for QoaParse { - fn start(&self) -> Result<(), gst::ErrorMessage> { - gst::debug!(CAT, imp: self, "Starting..."); - - self.obj().set_min_frame_size(QOA_MIN_FILESIZE as u32); - Ok(()) - } - - fn handle_frame( - &self, - mut frame: gst_base::BaseParseFrame, - ) -> Result<(gst::FlowSuccess, u32), gst::FlowError> { - gst::trace!(CAT, imp: self, "Handling frame..."); - - if self.obj().src_pad().current_caps().is_none() { - // Set src pad caps - let src_caps = gst::Caps::builder("audio/x-qoa") - .field("parsed", true) - .build(); - - gst::debug!(CAT, imp: self, "Setting src pad caps {:?}", src_caps); - - self.obj() - .src_pad() - .push_event(gst::event::Caps::new(&src_caps)); - } - - let input = frame.buffer().unwrap(); - let map = input.map_readable().map_err(|_| { - gst::element_imp_error!( - self, - gst::CoreError::Failed, - ["Failed to map input buffer readable"] - ); - gst::FlowError::Error - })?; - let data = map.as_slice(); - - let file_header_size = { - if data.len() >= QOA_MIN_FILESIZE { - let magic = u32::from_be_bytes(data[0..4].try_into().unwrap()); - if magic == QOA_MAGIC { - QOA_HEADER_SIZE - } else { - 0 - } - } else { - 0 - } - }; - - if data.len() < (file_header_size + QOA_HEADER_SIZE) { - // Error with not enough bytes to read the frame header - gst::element_imp_error!( - self, - gst::CoreError::Failed, - ["Not enough bytes to read the frame header"] - ); - return Err(gst::FlowError::Error); - } - - let frame_header = u64::from_be_bytes( - data[file_header_size..(file_header_size + QOA_HEADER_SIZE)] - .try_into() - .unwrap(), - ); - let channels = ((frame_header >> 56) & 0x0000ff) as u64; - let sample_rate = ((frame_header >> 32) & 0xffffff) as u64; - let total_samples = ((frame_header >> 16) & 0x00ffff) as u64; - let frame_size = (frame_header & 0x00ffff) as usize; - - if data.len() < (file_header_size + frame_size) { - gst::trace!( - CAT, - imp: self, - "Not enough bytes to read the frame, need {} bytes, have {}. Waiting for more data...", - file_header_size + frame_size, - data.len() - ); - return Ok((gst::FlowSuccess::Ok, 0)); - } - - drop(map); - - let duration = total_samples - .mul_div_floor(*gst::ClockTime::SECOND, sample_rate) - .map(gst::ClockTime::from_nseconds); - - let buffer = frame.buffer_mut().unwrap(); - buffer.set_duration(duration); - if file_header_size > 0 { - buffer.set_flags(gst::BufferFlags::HEADER); - } - - gst::trace!( - CAT, - imp: self, - "Found frame channels={channels}, sample_rate={sample_rate}, total_samples={total_samples}, size={frame_size}, duration={duration:?}", - ); - - self.obj() - .finish_frame(frame, (file_header_size + frame_size) as u32)?; - - Ok((gst::FlowSuccess::Ok, 0)) - } -} diff --git a/audio/qoa/src/qoaparse/mod.rs b/audio/qoa/src/qoaparse/mod.rs deleted file mode 100644 index c17b34e1..00000000 --- a/audio/qoa/src/qoaparse/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (C) 2023 Rafael Caricio -// -// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. -// If a copy of the MPL was not distributed with this file, You can obtain one at -// . -// -// SPDX-License-Identifier: MPL-2.0 -/** - * element-qoaparse: - * @short_description: Parser for audio encoded in QOA format. - * - * Parser for the Quite OK Audio format. Supports file and streaming modes. - * - * ## Example pipeline - * ```bash - * gst-launch-1.0 filesrc location=audio.qoa ! qoaparse ! qoadec ! autoaudiosink - * ``` - * - * Since: plugins-rs-0.11.0-alpha.1 - */ -use gst::glib; -use gst::prelude::*; - -mod imp; - -glib::wrapper! { - pub struct QoaParse(ObjectSubclass) @extends gst_base::BaseParse, gst::Element, gst::Object; -} - -pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register( - Some(plugin), - "qoaparse", - gst::Rank::Primary, - QoaParse::static_type(), - ) -} diff --git a/audio/qoa/src/typefind.rs b/audio/qoa/src/typefind.rs index 4ca55f3f..14058812 100644 --- a/audio/qoa/src/typefind.rs +++ b/audio/qoa/src/typefind.rs @@ -10,7 +10,8 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { Some(&gst::Caps::builder("audio/x-qoa").build()), |typefind| { if let Some(data) = typefind.peek(0, qoaudio::QOA_MIN_FILESIZE as u32) { - if qoaudio::QoaDecoder::decode_header(data).is_ok() { + let magic = u32::from_be_bytes(data[0..4].try_into().unwrap()); + if magic == qoaudio::QOA_MAGIC { typefind.suggest( TypeFindProbability::Maximum, &gst::Caps::builder("audio/x-qoa").build(), diff --git a/audio/qoa/tests/typefind.rs b/audio/qoa/tests/typefind.rs new file mode 100644 index 00000000..09577c3c --- /dev/null +++ b/audio/qoa/tests/typefind.rs @@ -0,0 +1,66 @@ +use gst::prelude::*; + +fn init() { + use std::sync::Once; + static INIT: Once = Once::new(); + + INIT.call_once(|| { + gst::init().unwrap(); + gstqoa::plugin_register_static().expect("Failed to register csound plugin"); + }); +} + +#[test] +fn find_the_type_of_qoa_data() { + init(); + + let src = gst_app::AppSrc::builder() + .is_live(true) + .build(); + + let typefind = gst::ElementFactory::make("typefind").build().unwrap(); + let fakesink = gst::ElementFactory::make("fakesink").build().unwrap(); + + let pipeline = gst::Pipeline::new(); + pipeline.add_many(&[src.upcast_ref(), &typefind, &fakesink]).unwrap(); + gst::Element::link_many(&[src.upcast_ref(), &typefind, &fakesink]).unwrap(); + + pipeline.set_state(gst::State::Playing).unwrap(); + + // Create some fake QOA data + let mut qoa_data = Vec::new(); + qoa_data.extend(qoaudio::QOA_MAGIC.to_be_bytes()); + qoa_data.extend(&[0u8; 20]); + src.push_buffer(gst::Buffer::from_slice(qoa_data)).unwrap(); + + src.end_of_stream().unwrap(); + + let bus = pipeline.bus().unwrap(); + for msg in bus.iter_timed(gst::ClockTime::NONE) { + use gst::MessageView; + match msg.view() { + MessageView::Error(err) => { + eprintln!( + "Error received from element {:?}: {}", + err.src().map(|s| s.path_string()), + err.error() + ); + eprintln!("Debugging information: {:?}", err.debug()); + pipeline.set_state(gst::State::Null).unwrap(); + unreachable!(); + } + MessageView::Eos(..) => break, + _ => (), + } + } + + let pad = typefind.static_pad("src").unwrap(); + let caps = pad.current_caps().unwrap(); + assert_eq!(caps.to_string(), "audio/x-qoa"); +} + +// #[test] +// fn when_data_is_not_qoa() { +// init(); +// +// }