diff --git a/Cargo.lock b/Cargo.lock index 14cdda2c8..67ab4b14e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,6 +112,7 @@ dependencies = [ "glib 0.1.3 (git+https://github.com/gtk-rs/glib)", "gstreamer 0.1.0", "gstreamer-app 0.1.0", + "gstreamer-audio 0.1.0", "gstreamer-player 0.1.0", "gtk 0.1.3 (git+https://github.com/gtk-rs/gtk)", "send-cell 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 7af393fd8..4a4074a86 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -7,6 +7,7 @@ authors = ["Sebastian Dröge "] glib = { version = "0.1.3", git = "https://github.com/gtk-rs/glib" } gstreamer = { path = "../gstreamer" } gstreamer-app = { path = "../gstreamer-app" } +gstreamer-audio = { path = "../gstreamer-audio" } gstreamer-player = { path = "../gstreamer-player", optional = true } gtk = { version = "0.1.3", git = "https://github.com/gtk-rs/gtk", features = ["v3_6"] } gio = { version = "0.1.3", git = "https://github.com/gtk-rs/gio" } diff --git a/examples/src/bin/appsink.rs b/examples/src/bin/appsink.rs index 2a4b1b3a2..95e43907d 100644 --- a/examples/src/bin/appsink.rs +++ b/examples/src/bin/appsink.rs @@ -2,6 +2,7 @@ extern crate gstreamer as gst; use gst::*; extern crate gstreamer_app as gst_app; use gst_app::*; +extern crate gstreamer_audio as gst_audio; extern crate glib; @@ -30,7 +31,7 @@ fn create_pipeline() -> Result { appsink.set_caps(&Caps::new_simple( "audio/x-raw", &[ - ("format", &"S16BE"), + ("format", &gst_audio::AUDIO_FORMAT_S16.to_string()), ("layout", &"interleaved"), ("channels", &(1i32)), ("rate", &IntRange::::new(1, i32::MAX)), @@ -52,19 +53,27 @@ fn create_pipeline() -> Result { let buffer = sample .get_buffer() .expect("Unable to extract buffer from the sample"); - assert_eq!(buffer.get_size() % 2, 0); + let map = buffer .map_readable() .expect("Unable to map buffer for reading"); - let data = map.as_slice(); - let sum: f64 = data.chunks(2) + + let data = + gst_audio::AudioData::new(map.as_slice(), gst_audio::AUDIO_FORMAT_S16).unwrap(); + let samples = if let gst_audio::AudioData::S16(samples) = data { + samples + } else { + return FlowReturn::NotNegotiated; + }; + + let sum: f64 = samples + .iter() .map(|sample| { - let u: u16 = ((sample[0] as u16) << 8) | (sample[1] as u16); - let f = (u as i16 as f64) / (i16::MAX as f64); + let f = (*sample as f64) / (i16::MAX as f64); f * f }) .sum(); - let rms = (sum / ((data.len() / 2) as f64)).sqrt(); + let rms = (sum / (samples.len() as f64)).sqrt(); println!("rms: {}", rms); FlowReturn::Ok diff --git a/examples/src/bin/pad_probes.rs b/examples/src/bin/pad_probes.rs index 41367371b..b14e50210 100644 --- a/examples/src/bin/pad_probes.rs +++ b/examples/src/bin/pad_probes.rs @@ -1,15 +1,18 @@ extern crate gstreamer as gst; use gst::*; +extern crate gstreamer_audio as gst_audio; + use std::u64; use std::i16; fn main() { gst::init().unwrap(); - let pipeline = gst::parse_launch( - "audiotestsrc name=src ! audio/x-raw,format=S16BE,channels=1 ! fakesink", - ).unwrap(); + let pipeline = gst::parse_launch(&format!( + "audiotestsrc name=src ! audio/x-raw,format={},channels=1 ! fakesink", + gst_audio::AUDIO_FORMAT_S16.to_string() + )).unwrap(); let bus = pipeline.get_bus().unwrap(); let src = pipeline @@ -22,15 +25,23 @@ fn main() { src_pad.add_probe(PAD_PROBE_TYPE_BUFFER, |_, probe_info| { if let Some(PadProbeData::Buffer(ref buffer)) = probe_info.data { let map = buffer.map_readable().unwrap(); - let data = map.as_slice(); - let sum: f64 = data.chunks(2) + + let data = + gst_audio::AudioData::new(map.as_slice(), gst_audio::AUDIO_FORMAT_S16).unwrap(); + let samples = if let gst_audio::AudioData::S16(samples) = data { + samples + } else { + return PadProbeReturn::Ok; + }; + + let sum: f64 = samples + .iter() .map(|sample| { - let u: u16 = ((sample[0] as u16) << 8) | (sample[1] as u16); - let f = (u as i16 as f64) / (i16::MAX as f64); + let f = (*sample as f64) / (i16::MAX as f64); f * f }) .sum(); - let rms = (sum / ((data.len() / 2) as f64)).sqrt(); + let rms = (sum / (samples.len() as f64)).sqrt(); println!("rms: {}", rms); } diff --git a/gstreamer-audio/src/audio_data.rs b/gstreamer-audio/src/audio_data.rs new file mode 100644 index 000000000..4c5192947 --- /dev/null +++ b/gstreamer-audio/src/audio_data.rs @@ -0,0 +1,184 @@ +// Copyright (C) 2017 Sebastian Dröge +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::slice; +use std::fmt; +use libc::uintptr_t; + +use std::error::Error as StdError; + +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + WrongAlignment, + WrongEndianness, + IncompleteSamples, + UnsupportedFormat, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + f.write_str(self.description()) + } +} + +impl StdError for Error { + fn description(&self) -> &str { + use self::Error::*; + + match *self { + WrongAlignment => "Wrong Alignment", + WrongEndianness => "Wrong Endianness", + IncompleteSamples => "Incomplete Samples", + UnsupportedFormat => "Unsupported Format", + } + } +} + +pub enum AudioData<'a> { + S8(&'a [i8]), + U8(&'a [u8]), + S16(&'a [i16]), + U16(&'a [u16]), + S32(&'a [i32]), + U32(&'a [u32]), + F32(&'a [f32]), + F64(&'a [f64]), +} + +impl<'a> AudioData<'a> { + pub fn new(data: &'a [u8], format: ::AudioFormat) -> Result, Error> { + use AudioFormat::*; + + let alignment = if (data.as_ptr() as uintptr_t) % 8 == 0 { + 8 + } else if (data.as_ptr() as uintptr_t) % 4 == 0 { + 4 + } else if (data.as_ptr() as uintptr_t) % 2 == 0 { + 2 + } else { + 1 + }; + + let format_info = ::AudioFormatInfo::from_format(format); + let width = (format_info.width() / 8) as usize; + + if width != 1 && cfg!(target_endian = "big") && format_info.is_little_endian() { + return Err(Error::WrongEndianness); + } else if width != 1 && cfg!(target_endian = "little") && format_info.is_big_endian() { + return Err(Error::WrongEndianness); + } + + if alignment < width { + return Err(Error::WrongAlignment); + } + + if data.len() % width != 0 { + return Err(Error::IncompleteSamples); + } + + match format { + S8 => Ok(AudioData::S8(unsafe { + slice::from_raw_parts(data.as_ptr() as *const _, data.len()) + })), + U8 => Ok(AudioData::U8(unsafe { + slice::from_raw_parts(data.as_ptr() as *const _, data.len()) + })), + S16le | S16be => Ok(AudioData::S16(unsafe { + slice::from_raw_parts(data.as_ptr() as *const _, data.len() / 2) + })), + U16le | U16be => Ok(AudioData::U16(unsafe { + slice::from_raw_parts(data.as_ptr() as *const _, data.len() / 2) + })), + S32le | S2432le | S32be | S2432be => Ok(AudioData::S32(unsafe { + slice::from_raw_parts(data.as_ptr() as *const _, data.len() / 4) + })), + U32le | U2432le | U32be | U2432be => Ok(AudioData::U32(unsafe { + slice::from_raw_parts(data.as_ptr() as *const _, data.len() / 4) + })), + F32le | F32be => Ok(AudioData::F32(unsafe { + slice::from_raw_parts(data.as_ptr() as *const _, data.len() / 4) + })), + F64le | F64be => Ok(AudioData::F64(unsafe { + slice::from_raw_parts(data.as_ptr() as *const _, data.len() / 8) + })), + _ => Err(Error::UnsupportedFormat), + } + } +} + +pub enum AudioDataMut<'a> { + S8(&'a mut [i8]), + U8(&'a mut [u8]), + S16(&'a mut [i16]), + U16(&'a mut [u16]), + S32(&'a mut [i32]), + U32(&'a mut [u32]), + F32(&'a mut [f32]), + F64(&'a mut [f64]), +} + +impl<'a> AudioDataMut<'a> { + pub fn new(data: &'a mut [u8], format: ::AudioFormat) -> Result, Error> { + use AudioFormat::*; + + let alignment = if (data.as_ptr() as uintptr_t) % 8 == 0 { + 8 + } else if (data.as_ptr() as uintptr_t) % 4 == 0 { + 4 + } else if (data.as_ptr() as uintptr_t) % 2 == 0 { + 2 + } else { + 1 + }; + + let format_info = ::AudioFormatInfo::from_format(format); + let width = (format_info.width() / 8) as usize; + + if width != 1 && cfg!(target_endian = "big") && format_info.is_little_endian() { + return Err(Error::WrongEndianness); + } else if width != 1 && cfg!(target_endian = "little") && format_info.is_big_endian() { + return Err(Error::WrongEndianness); + } + + if alignment < width { + return Err(Error::WrongAlignment); + } + + if data.len() % width != 0 { + return Err(Error::IncompleteSamples); + } + + match format { + S8 => Ok(AudioDataMut::S8(unsafe { + slice::from_raw_parts_mut(data.as_ptr() as *mut _, data.len()) + })), + U8 => Ok(AudioDataMut::U8(unsafe { + slice::from_raw_parts_mut(data.as_ptr() as *mut _, data.len()) + })), + S16le | S16be => Ok(AudioDataMut::S16(unsafe { + slice::from_raw_parts_mut(data.as_ptr() as *mut _, data.len() / 2) + })), + U16le | U16be => Ok(AudioDataMut::U16(unsafe { + slice::from_raw_parts_mut(data.as_ptr() as *mut _, data.len() / 2) + })), + S32le | S2432le | S32be | S2432be => Ok(AudioDataMut::S32(unsafe { + slice::from_raw_parts_mut(data.as_ptr() as *mut _, data.len() / 4) + })), + U32le | U2432le | U32be | U2432be => Ok(AudioDataMut::U32(unsafe { + slice::from_raw_parts_mut(data.as_ptr() as *mut _, data.len() / 4) + })), + F32le | F32be => Ok(AudioDataMut::F32(unsafe { + slice::from_raw_parts_mut(data.as_ptr() as *mut _, data.len() / 4) + })), + F64le | F64be => Ok(AudioDataMut::F64(unsafe { + slice::from_raw_parts_mut(data.as_ptr() as *mut _, data.len() / 8) + })), + _ => Err(Error::UnsupportedFormat), + } + } +} diff --git a/gstreamer-audio/src/lib.rs b/gstreamer-audio/src/lib.rs index 86c6788a1..47e373e8f 100644 --- a/gstreamer-audio/src/lib.rs +++ b/gstreamer-audio/src/lib.rs @@ -49,6 +49,8 @@ mod audio_info; pub use audio_info::*; mod audio_channel_position; pub use audio_channel_position::*; +mod audio_data; +pub use audio_data::{AudioData, AudioDataMut}; use glib::translate::{from_glib_full, ToGlibPtr}; pub fn audio_buffer_clip(