mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-09-26 14:00:12 +00:00
Parser align with frame headers
This commit is contained in:
parent
069c290ae1
commit
eb73b59bf8
6 changed files with 116 additions and 76 deletions
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
|
|
||||||
|
mod qoa;
|
||||||
mod qoadec;
|
mod qoadec;
|
||||||
mod qoaparse;
|
mod qoaparse;
|
||||||
mod typefind;
|
mod typefind;
|
||||||
|
|
96
audio/qoa/src/qoa.rs
Normal file
96
audio/qoa/src/qoa.rs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
use qoaudio::{QOA_HEADER_SIZE, QOA_LMS_LEN};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub const MAX_SLICES_PER_CHANNEL_PER_FRAME: usize = 256;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct InvalidFrameHeader;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
|
pub struct FrameHeader {
|
||||||
|
/// Number of channels in this frame
|
||||||
|
pub channels: u8,
|
||||||
|
/// Sample rate in HZ for this frame
|
||||||
|
pub sample_rate: u32,
|
||||||
|
/// Samples per channel in this frame
|
||||||
|
pub num_samples_per_channel: u16,
|
||||||
|
/// Total size of the frame (includes header size itself)
|
||||||
|
pub frame_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameHeader {
|
||||||
|
/// Parse and validate various traits of a valid frame header.
|
||||||
|
pub fn parse(frame_header: u64) -> Result<Self, InvalidFrameHeader> {
|
||||||
|
let channels = ((frame_header >> 56) & 0x0000ff) as u8;
|
||||||
|
let sample_rate = ((frame_header >> 32) & 0xffffff) as u32;
|
||||||
|
let num_samples_per_channel = ((frame_header >> 16) & 0x00ffff) as u16;
|
||||||
|
let frame_size = (frame_header & 0x00ffff) as usize;
|
||||||
|
|
||||||
|
if channels == 0 || sample_rate == 0 {
|
||||||
|
return Err(InvalidFrameHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LMS_SIZE: usize = 4;
|
||||||
|
let non_sample_data_size = QOA_HEADER_SIZE + QOA_LMS_LEN * LMS_SIZE * channels as usize;
|
||||||
|
if frame_size <= non_sample_data_size {
|
||||||
|
return Err(InvalidFrameHeader);
|
||||||
|
}
|
||||||
|
let data_size = frame_size - non_sample_data_size;
|
||||||
|
let num_slices = data_size / 8;
|
||||||
|
|
||||||
|
if num_slices % channels as usize != 0 {
|
||||||
|
return Err(InvalidFrameHeader);
|
||||||
|
}
|
||||||
|
if num_slices / channels as usize > MAX_SLICES_PER_CHANNEL_PER_FRAME {
|
||||||
|
return Err(InvalidFrameHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(FrameHeader {
|
||||||
|
channels,
|
||||||
|
sample_rate,
|
||||||
|
num_samples_per_channel,
|
||||||
|
frame_size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for FrameHeader {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.channels == other.channels && self.sample_rate == other.sample_rate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FrameHeader {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{{channels={}, sample_rate={}, num_samples_per_channel={}, frame_size={}}}",
|
||||||
|
self.channels, self.sample_rate, self.num_samples_per_channel, self.frame_size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_can_parse_valid_frame_header() {
|
||||||
|
let frame_header = FrameHeader::parse(0x0200a028000003e8).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
frame_header,
|
||||||
|
FrameHeader {
|
||||||
|
channels: 2,
|
||||||
|
sample_rate: 41000,
|
||||||
|
num_samples_per_channel: 100,
|
||||||
|
frame_size: 1000
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_frame_header() {
|
||||||
|
assert!(FrameHeader::parse(0x0000000000000000).is_err());
|
||||||
|
assert!(FrameHeader::parse(0x0420420420420420).is_err());
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,10 +8,10 @@
|
||||||
|
|
||||||
use byte_slice_cast::*;
|
use byte_slice_cast::*;
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
|
use gst::glib::once_cell::sync::Lazy;
|
||||||
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 gst::glib::once_cell::sync::Lazy;
|
|
||||||
use qoaudio::{DecodedAudio, QoaDecoder};
|
use qoaudio::{DecodedAudio, QoaDecoder};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,13 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use crate::qoa::FrameHeader;
|
||||||
use glib::once_cell::sync::Lazy;
|
use glib::once_cell::sync::Lazy;
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
use gst_base::prelude::*;
|
use gst_base::prelude::*;
|
||||||
use gst_base::subclass::prelude::*;
|
use gst_base::subclass::prelude::*;
|
||||||
use qoaudio::{QOA_HEADER_SIZE, QOA_LMS_LEN, QOA_MAGIC, QOA_MIN_FILESIZE};
|
use qoaudio::{QOA_HEADER_SIZE, QOA_MAGIC, QOA_MIN_FILESIZE};
|
||||||
use std::fmt;
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
#[derive(Default, Debug, PartialEq)]
|
#[derive(Default, Debug, PartialEq)]
|
||||||
|
@ -72,7 +72,7 @@ impl ElementImpl for QoaParse {
|
||||||
let src_caps = gst::Caps::builder("audio/x-qoa")
|
let src_caps = gst::Caps::builder("audio/x-qoa")
|
||||||
.field("parsed", true)
|
.field("parsed", true)
|
||||||
.field("rate", gst::IntRange::<i32>::new(1, 16777215))
|
.field("rate", gst::IntRange::<i32>::new(1, 16777215))
|
||||||
.field("channels", &gst::IntRange::<i32>::new(1, 255))
|
.field("channels", gst::IntRange::<i32>::new(1, 255))
|
||||||
.build();
|
.build();
|
||||||
let src_pad_template = gst::PadTemplate::new(
|
let src_pad_template = gst::PadTemplate::new(
|
||||||
"src",
|
"src",
|
||||||
|
@ -211,71 +211,3 @@ impl BaseParseImpl for QoaParse {
|
||||||
Ok((gst::FlowSuccess::Ok, 0))
|
Ok((gst::FlowSuccess::Ok, 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const MAX_SLICES_PER_CHANNEL_PER_FRAME: usize = 256;
|
|
||||||
|
|
||||||
pub struct InvalidFrameHeader;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Default)]
|
|
||||||
pub struct FrameHeader {
|
|
||||||
/// Number of channels in this frame
|
|
||||||
pub channels: u8,
|
|
||||||
/// Sample rate in HZ for this frame
|
|
||||||
pub sample_rate: u32,
|
|
||||||
/// Samples per channel in this frame
|
|
||||||
pub num_samples_per_channel: u16,
|
|
||||||
/// Total size of the frame (includes header size itself)
|
|
||||||
pub frame_size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FrameHeader {
|
|
||||||
/// Parse and validate various traits of a valid frame header.
|
|
||||||
fn parse(frame_header: u64) -> Result<Self, InvalidFrameHeader> {
|
|
||||||
let channels = ((frame_header >> 56) & 0x0000ff) as u8;
|
|
||||||
let sample_rate = ((frame_header >> 32) & 0xffffff) as u32;
|
|
||||||
let num_samples_per_channel = ((frame_header >> 16) & 0x00ffff) as u16;
|
|
||||||
let frame_size = (frame_header & 0x00ffff) as usize;
|
|
||||||
|
|
||||||
if channels == 0 || sample_rate == 0 {
|
|
||||||
return Err(InvalidFrameHeader);
|
|
||||||
}
|
|
||||||
|
|
||||||
const LMS_SIZE: usize = 4;
|
|
||||||
let non_sample_data_size = QOA_HEADER_SIZE + QOA_LMS_LEN * LMS_SIZE * channels as usize;
|
|
||||||
if frame_size <= non_sample_data_size {
|
|
||||||
return Err(InvalidFrameHeader);
|
|
||||||
}
|
|
||||||
let data_size = frame_size - non_sample_data_size;
|
|
||||||
let num_slices = data_size / 8;
|
|
||||||
|
|
||||||
if num_slices % channels as usize != 0 {
|
|
||||||
return Err(InvalidFrameHeader);
|
|
||||||
}
|
|
||||||
if num_slices / channels as usize > MAX_SLICES_PER_CHANNEL_PER_FRAME {
|
|
||||||
return Err(InvalidFrameHeader);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(FrameHeader {
|
|
||||||
channels,
|
|
||||||
sample_rate,
|
|
||||||
num_samples_per_channel,
|
|
||||||
frame_size,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for FrameHeader {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.channels == other.channels && self.sample_rate == other.sample_rate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for FrameHeader {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{{channels={}, sample_rate={}, num_samples_per_channel={}, frame_size={}}}",
|
|
||||||
self.channels, self.sample_rate, self.num_samples_per_channel, self.frame_size
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
use crate::qoa::FrameHeader;
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::{TypeFind, TypeFindProbability};
|
use gst::{TypeFind, TypeFindProbability};
|
||||||
|
use qoaudio::QOA_HEADER_SIZE;
|
||||||
|
|
||||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
TypeFind::register(
|
TypeFind::register(
|
||||||
|
@ -16,6 +18,15 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
TypeFindProbability::Maximum,
|
TypeFindProbability::Maximum,
|
||||||
&gst::Caps::builder("audio/x-qoa").build(),
|
&gst::Caps::builder("audio/x-qoa").build(),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
let raw_header =
|
||||||
|
u64::from_be_bytes(data[0..QOA_HEADER_SIZE].try_into().unwrap());
|
||||||
|
if let Ok(_header) = FrameHeader::parse(raw_header) {
|
||||||
|
typefind.suggest(
|
||||||
|
TypeFindProbability::Likely,
|
||||||
|
&gst::Caps::builder("audio/x-qoa").build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,15 +14,15 @@ fn init() {
|
||||||
fn find_the_type_of_qoa_data() {
|
fn find_the_type_of_qoa_data() {
|
||||||
init();
|
init();
|
||||||
|
|
||||||
let src = gst_app::AppSrc::builder()
|
let src = gst_app::AppSrc::builder().is_live(true).build();
|
||||||
.is_live(true)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let typefind = gst::ElementFactory::make("typefind").build().unwrap();
|
let typefind = gst::ElementFactory::make("typefind").build().unwrap();
|
||||||
let fakesink = gst::ElementFactory::make("fakesink").build().unwrap();
|
let fakesink = gst::ElementFactory::make("fakesink").build().unwrap();
|
||||||
|
|
||||||
let pipeline = gst::Pipeline::new();
|
let pipeline = gst::Pipeline::new();
|
||||||
pipeline.add_many(&[src.upcast_ref(), &typefind, &fakesink]).unwrap();
|
pipeline
|
||||||
|
.add_many(&[src.upcast_ref(), &typefind, &fakesink])
|
||||||
|
.unwrap();
|
||||||
gst::Element::link_many(&[src.upcast_ref(), &typefind, &fakesink]).unwrap();
|
gst::Element::link_many(&[src.upcast_ref(), &typefind, &fakesink]).unwrap();
|
||||||
|
|
||||||
pipeline.set_state(gst::State::Playing).unwrap();
|
pipeline.set_state(gst::State::Playing).unwrap();
|
||||||
|
|
Loading…
Reference in a new issue