mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-09-28 06:42:07 +00:00
Add test for typefind element
This commit is contained in:
parent
670123373b
commit
f0be8c32a3
8 changed files with 72 additions and 232 deletions
|
@ -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" }
|
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
||||||
qoaudio = "0.5.0"
|
qoaudio = "0.5.0"
|
||||||
byte-slice-cast = "1.0"
|
byte-slice-cast = "1.0"
|
||||||
once_cell = "1.0"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
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]
|
[lib]
|
||||||
name = "gstqoa"
|
name = "gstqoa"
|
||||||
|
|
|
@ -9,12 +9,10 @@
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
|
|
||||||
mod qoadec;
|
mod qoadec;
|
||||||
mod qoaparse;
|
|
||||||
mod typefind;
|
mod typefind;
|
||||||
|
|
||||||
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
qoadec::register(plugin)?;
|
qoadec::register(plugin)?;
|
||||||
qoaparse::register(plugin)?;
|
|
||||||
typefind::register(plugin)
|
typefind::register(plugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ use gst::glib;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
use gst_audio::prelude::*;
|
use gst_audio::prelude::*;
|
||||||
use gst_audio::subclass::prelude::*;
|
use gst_audio::subclass::prelude::*;
|
||||||
use once_cell::sync::Lazy;
|
use gst::glib::once_cell::sync::Lazy;
|
||||||
use qoaudio::{DecodedAudio, QoaDecoder};
|
use qoaudio::{DecodedAudio, QoaDecoder};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
@ -71,9 +71,7 @@ impl ElementImpl for QoaDec {
|
||||||
|
|
||||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||||
let sink_caps = gst::Caps::builder("audio/x-qoa")
|
let sink_caps = gst::Caps::builder("audio/x-qoa").build();
|
||||||
.field("parsed", true)
|
|
||||||
.build();
|
|
||||||
let sink_pad_template = gst::PadTemplate::new(
|
let sink_pad_template = gst::PadTemplate::new(
|
||||||
"sink",
|
"sink",
|
||||||
gst::PadDirection::Sink,
|
gst::PadDirection::Sink,
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
*
|
*
|
||||||
* ## Example pipeline
|
* ## Example pipeline
|
||||||
* ```bash
|
* ```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
|
* Since: plugins-rs-0.11.0-alpha.1
|
||||||
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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(),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -10,7 +10,8 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
Some(&gst::Caps::builder("audio/x-qoa").build()),
|
Some(&gst::Caps::builder("audio/x-qoa").build()),
|
||||||
|typefind| {
|
|typefind| {
|
||||||
if let Some(data) = typefind.peek(0, qoaudio::QOA_MIN_FILESIZE as u32) {
|
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(
|
typefind.suggest(
|
||||||
TypeFindProbability::Maximum,
|
TypeFindProbability::Maximum,
|
||||||
&gst::Caps::builder("audio/x-qoa").build(),
|
&gst::Caps::builder("audio/x-qoa").build(),
|
||||||
|
|
66
audio/qoa/tests/typefind.rs
Normal file
66
audio/qoa/tests/typefind.rs
Normal 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();
|
||||||
|
//
|
||||||
|
// }
|
Loading…
Reference in a new issue