Add test for typefind element

This commit is contained in:
Rafael Caricio 2023-10-26 16:23:11 -07:00
parent 670123373b
commit f0be8c32a3
Signed by: rafaelcaricio
GPG key ID: 3C86DBCE8E93C947
8 changed files with 72 additions and 232 deletions

View file

@ -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"

View file

@ -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)
}

View file

@ -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<Vec<gst::PadTemplate>> = 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,

View file

@ -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

View file

@ -1,186 +0,0 @@
// Copyright (C) 2023 Rafael Caricio <rafael@caricio.com>
//
// 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
// <https://mozilla.org/MPL/2.0/>.
//
// 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<gst::DebugCategory> = 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<gst::subclass::ElementMetadata> = Lazy::new(|| {
gst::subclass::ElementMetadata::new(
"QOA parser",
"Codec/Parser/Audio",
"Quite OK Audio parser",
"Rafael Caricio <rafael@caricio.com>",
)
});
Some(&*ELEMENT_METADATA)
}
fn pad_templates() -> &'static [gst::PadTemplate] {
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = 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))
}
}

View file

@ -1,37 +0,0 @@
// Copyright (C) 2023 Rafael Caricio <rafael@caricio.com>
//
// 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
// <https://mozilla.org/MPL/2.0/>.
//
// 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<imp::QoaParse>) @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(),
)
}

View file

@ -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(),

View file

@ -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();
//
// }