#![deny(missing_docs)]
#![cfg_attr(feature = "nightly-docs", feature(doc_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]
#[doc(hidden)]
pub mod ffi;
use core::{marker::PhantomData, mem::{MaybeUninit}, num::NonZeroUsize, ptr};
use libc::c_int;
#[cfg(feature = "std")]
use std::{rc::Rc, sync::Arc};
#[inline(always)]
fn data_len_safe(len: usize) -> c_int {
len.min(c_int::max_value() as usize) as c_int
}
#[inline(always)]
unsafe fn source_slice<'src, 'frame>(
data: &'src [u8],
frame_recv: &'frame ffi::mp3dec_frame_info_t,
) -> &'src [u8] {
data.get_unchecked(frame_recv.frame_offset as usize..frame_recv.frame_bytes as usize)
}
pub const MAX_SAMPLES_PER_FRAME: usize = 0x900;
pub struct Audio<'src, 'pcm> {
info: ffi::mp3dec_frame_info_t,
pcm: Option<ptr::NonNull<Sample>>, sample_count: usize,
source: &'src [u8],
phantom: PhantomData<&'pcm [Sample]>,
}
unsafe impl<'src, 'pcm> Send for Audio<'src, 'pcm> {}
unsafe impl<'src, 'pcm> Sync for Audio<'src, 'pcm> {}
pub enum Frame<'src, 'pcm> {
Audio(Audio<'src, 'pcm>),
Other(&'src [u8]),
}
pub struct Decoder<'src> {
cached_peek_len: Option<NonZeroUsize>,
pcm: MaybeUninit<[Sample; MAX_SAMPLES_PER_FRAME]>,
raw: RawDecoder,
source: &'src [u8],
source_copy: &'src [u8],
}
#[cfg_attr(feature = "nightly-docs", doc(cfg(feature = "std")))]
#[cfg_attr(not(feature = "nightly-docs"), cfg(feature = "std"))]
pub struct DecoderOwned<T> {
decoder: Decoder<'static>,
owned: T,
}
pub struct RawDecoder(MaybeUninit<ffi::mp3dec_t>);
#[cfg(not(feature = "float"))]
pub type Sample = i16;
#[cfg(feature = "float")]
pub type Sample = f32;
impl<'src> Decoder<'src> {
pub fn new(source: &'src [u8]) -> Self {
Self {
cached_peek_len: None,
pcm: MaybeUninit::uninit(),
raw: RawDecoder::new(),
source,
source_copy: source,
}
}
pub fn next<'pcm>(&'pcm mut self) -> Option<Frame<'src, 'pcm>> {
self.cached_peek_len = None; unsafe {
let (frame, len) = self.raw.next(self.source, &mut *self.pcm.as_mut_ptr())?;
self.offset_trusted(len);
Some(frame)
}
}
pub fn peek(&mut self) -> Option<Frame<'src, 'static>> {
let (frame, len) = self.raw.peek(self.source)?;
self.cached_peek_len = NonZeroUsize::new(len);
Some(frame)
}
#[inline]
pub fn position(&self) -> usize {
unsafe { self.source.as_ptr().sub(self.source_copy.as_ptr() as usize) as usize }
}
#[inline]
pub fn set_position(&mut self, position: usize) {
let position = self.source_copy.len().min(position);
self.source = unsafe { self.source_copy.get_unchecked(position..) };
self.cached_peek_len = None;
}
pub fn skip(&mut self) -> Option<()> {
unsafe {
let offset = match self.cached_peek_len.take() {
Some(offset) => offset.get(),
None => self.raw.peek(self.source)?.1,
};
self.offset_trusted(offset);
}
Some(())
}
#[inline]
unsafe fn offset_trusted(&mut self, offset: usize) {
self.source = self.source.get_unchecked(offset..);
}
}
#[cfg(feature = "std")]
impl DecoderOwned<Vec<u8>> {
pub fn new<T>(source: T) -> Self
where
T: Into<Vec<u8>>,
{
let source = source.into();
let self_reference = unsafe {
std::mem::transmute::<_, &'static [u8]>(source.as_slice())
};
Self {
decoder: Decoder::new(self_reference),
owned: source,
}
}
}
#[cfg(feature = "std")]
impl<T: Into<Vec<u8>>> From<T> for DecoderOwned<Vec<u8>> {
fn from(x: T) -> Self {
Self::new(x)
}
}
#[cfg(feature = "std")]
impl<T: AsRef<[u8]>> From<Rc<T>> for DecoderOwned<Rc<T>> {
fn from(x: Rc<T>) -> Self {
let self_ref: &'static [u8] = unsafe { std::mem::transmute(x.as_ref().as_ref()) };
Self {
decoder: Decoder::new(self_ref),
owned: x,
}
}
}
#[cfg(feature = "std")]
impl<T: AsRef<[u8]>> From<Arc<T>> for DecoderOwned<Arc<T>> {
fn from(x: Arc<T>) -> Self {
let self_ref: &'static [u8] = unsafe { std::mem::transmute(x.as_ref().as_ref()) };
Self {
decoder: Decoder::new(self_ref),
owned: x,
}
}
}
#[cfg(feature = "std")]
impl<T> DecoderOwned<T> {
#[inline]
pub fn into_inner(self) -> T {
self.owned
}
#[inline]
pub fn next<'a>(&'a mut self) -> Option<Frame<'a, 'a>> {
self.decoder.next()
}
#[inline]
pub fn peek<'a>(&'a mut self) -> Option<Frame<'a, 'static>> {
self.decoder.peek()
}
#[inline]
pub fn position(&self) -> usize {
self.decoder.position()
}
#[inline]
pub fn set_position(&mut self, position: usize) {
self.decoder.set_position(position)
}
#[inline]
pub fn skip(&mut self) -> Option<()> {
self.decoder.skip()
}
}
impl RawDecoder {
pub fn new() -> Self {
let mut decoder = MaybeUninit::uninit();
unsafe {
ffi::mp3dec_init(decoder.as_mut_ptr());
}
Self(decoder)
}
#[inline]
pub fn next<'src, 'pcm>(
&mut self,
src: &'src [u8],
dest: &'pcm mut [Sample; MAX_SAMPLES_PER_FRAME],
) -> Option<(Frame<'src, 'pcm>, usize)> {
self.call(src, Some(dest))
}
#[inline]
pub fn peek<'src>(&mut self, src: &'src [u8]) -> Option<(Frame<'src, 'static>, usize)> {
self.call(src, None)
}
fn call<'src, 'pcm>(
&mut self,
src: &'src [u8],
dest: Option<&'pcm mut [Sample; MAX_SAMPLES_PER_FRAME]>,
) -> Option<(Frame<'src, 'pcm>, usize)> {
let src_length = data_len_safe(src.len());
let dest_ptr: *mut Sample = dest.map_or(ptr::null_mut(), |x| x).cast();
unsafe {
let mut info = MaybeUninit::uninit().assume_init();
let result = ffi::mp3dec_decode_frame(
self.0.as_mut_ptr(),
src.as_ptr(),
src_length,
dest_ptr,
&mut info,
);
let skip = info.frame_bytes as usize;
if result != 0 {
Some((
Frame::Audio(Audio {
info,
pcm: ptr::NonNull::new(dest_ptr),
sample_count: result as usize,
source: source_slice(src, &info),
phantom: PhantomData,
}),
skip,
))
} else if info.frame_bytes != 0 {
Some((Frame::Other(source_slice(src, &info)), skip))
} else {
None
}
}
}
}
impl<'src, 'pcm> Audio<'src, 'pcm> {
#[inline]
pub fn bitrate(&self) -> u32 {
self.info.bitrate_kbps as u32
}
#[inline]
pub fn channels(&self) -> u16 {
self.info.channels as u16
}
#[inline]
pub fn mpeg_layer(&self) -> u8 {
self.info.layer as u8
}
#[inline]
pub fn sample_rate(&self) -> u32 {
self.info.hz as u32
}
#[inline]
pub fn samples(&self) -> &'pcm [Sample] {
match self.pcm {
Some(ptr) => unsafe {
(&*ptr.cast::<[Sample; MAX_SAMPLES_PER_FRAME]>().as_ptr())
.get_unchecked(..self.sample_count * self.info.channels as usize)
},
None => &[],
}
}
#[inline]
pub fn sample_count(&self) -> usize {
self.sample_count
}
#[inline]
pub fn source(&self) -> &'src [u8] {
self.source
}
}
#[cfg(test)]
mod tests {
#[test]
fn sanity() {
assert_eq!(
crate::MAX_SAMPLES_PER_FRAME,
crate::ffi::MINIMP3_MAX_SAMPLES_PER_FRAME as usize,
);
}
}