core: Add symphonia as optional MP3 decoder

Add symphonia_mp3 feature to enable symphonia as an MP3 decoder.
This is a pure Rust MP3 decoder that could be used on web.
This commit is contained in:
Mike Welsh 2021-05-08 01:04:59 -07:00
parent 94d02fa653
commit 483995823f
5 changed files with 331 additions and 99 deletions

77
Cargo.lock generated
View File

@ -106,6 +106,12 @@ dependencies = [
"serde",
]
[[package]]
name = "arrayvec"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "269d0f5e68353a7cab87f81e7c736adc008d279a36ebc6a05dfe01193a89f0c9"
[[package]]
name = "ash"
version = "0.32.1"
@ -1482,7 +1488,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f9e453baf3aaef2b0c354ce0b3d63d76402e406a59b64b7182d123cfa6635ae"
dependencies = [
"arrayvec",
"arrayvec 0.5.2",
"bitflags",
"gfx-auxil",
"gfx-hal",
@ -1505,7 +1511,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21506399f64a3c4d389182a89a30073856ae33eb712315456b4fd8f39ee7682a"
dependencies = [
"arrayvec",
"arrayvec 0.5.2",
"bit-set",
"bitflags",
"d3d12",
@ -1539,7 +1545,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bae057fc3a0ab23ecf97ae51d4017d27d5ddf0aab16ee6dcb58981af88c3152"
dependencies = [
"arrayvec",
"arrayvec 0.5.2",
"bitflags",
"fxhash",
"gfx-hal",
@ -1561,7 +1567,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de85808e2a98994c6af925253f8a9593bc57180ef1ea137deab6d35cc949517"
dependencies = [
"arrayvec",
"arrayvec 0.5.2",
"bitflags",
"block",
"cocoa-foundation",
@ -1587,7 +1593,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9861ec855acbbc65c0e4f966d761224886e811dc2c6d413a4776e9293d0e5c0"
dependencies = [
"arrayvec",
"arrayvec 0.5.2",
"ash",
"byteorder",
"core-graphics-types",
@ -1963,7 +1969,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
dependencies = [
"arrayvec",
"arrayvec 0.5.2",
"bitflags",
"cfg-if 1.0.0",
"ryu",
@ -2083,7 +2089,7 @@ version = "0.17.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1e79ed83352715656b6e73bd9c1b54736f22d0c6ad9b1809ad20d585b943f4a"
dependencies = [
"arrayvec",
"arrayvec 0.5.2",
"euclid",
"num-traits",
]
@ -2103,7 +2109,7 @@ version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c76c657e6eadab8fc258b0570226d88f186d2a462105a62dc015471f56fe761"
dependencies = [
"arrayvec",
"arrayvec 0.5.2",
"float_next_after",
"lyon_path",
"sid",
@ -3065,6 +3071,7 @@ dependencies = [
"serde",
"smallvec",
"swf",
"symphonia",
"thiserror",
"url",
"weak-table",
@ -3494,6 +3501,56 @@ dependencies = [
"smallvec",
]
[[package]]
name = "symphonia"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fae959d5ea7b4cd0cd8db3b899ec4f549b0d8a298694826a36ae7e5f19d4aa6"
dependencies = [
"lazy_static",
"symphonia-bundle-mp3",
"symphonia-core",
"symphonia-metadata",
]
[[package]]
name = "symphonia-bundle-mp3"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f596fe16d2ae06e9404558644b61e27e2dcfee6c511f559be4699c403283fa"
dependencies = [
"bitflags",
"lazy_static",
"log",
"symphonia-core",
"symphonia-metadata",
]
[[package]]
name = "symphonia-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1742e06f50b4a7ed7abee53433231e050a248b498cd0ae2c639c8a70b115001"
dependencies = [
"arrayvec 0.6.1",
"bitflags",
"byteorder",
"lazy_static",
"log",
]
[[package]]
name = "symphonia-metadata"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db5e36e38a7400f968569135e7ac0f8647de42e93905ad41c79d583aaeae565c"
dependencies = [
"encoding_rs",
"lazy_static",
"log",
"symphonia-core",
]
[[package]]
name = "syn"
version = "1.0.76"
@ -4001,7 +4058,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd247f8b26fd3d42ef2f320d378025cd6e84d782ef749fab45cc3b981fbe3275"
dependencies = [
"arrayvec",
"arrayvec 0.5.2",
"js-sys",
"log",
"naga",
@ -4022,7 +4079,7 @@ version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "958a8a5e418492723ab4e7933bf6dbdf06f5dc87274ba2ae0e4f9c891aac579c"
dependencies = [
"arrayvec",
"arrayvec 0.5.2",
"bitflags",
"cfg_aliases",
"copyless",

View File

@ -43,6 +43,7 @@ flash-lso = { git = "https://github.com/ruffle-rs/rust-flash-lso", rev = "19fecd
json = "0.12.4"
lzma-rs = {version = "0.2.0", optional = true }
dasp = { git = "https://github.com/RustAudio/dasp", rev = "f05a703", features = ["interpolate", "interpolate-linear", "signal"] }
symphonia = { version = "0.3.0", default-features = false, features = ["mp3"], optional = true }
[dependencies.jpeg-decoder]
version = "0.1.22"

View File

@ -1,14 +1,16 @@
//! Audio decoders.
mod adpcm;
#[cfg(feature = "minimp3")]
#[cfg(any(feature = "minimp3", feature = "symphonia"))]
mod mp3;
mod nellymoser;
mod pcm;
pub use adpcm::AdpcmDecoder;
#[cfg(feature = "minimp3")]
pub use mp3::Mp3Decoder;
pub use mp3::minimp3::Mp3Decoder;
#[cfg(all(feature = "symphonia", not(feature = "minimp3")))]
pub use mp3::symphonia::Mp3Decoder;
pub use nellymoser::NellymoserDecoder;
pub use pcm::PcmDecoder;
@ -29,11 +31,11 @@ pub trait Decoder: Iterator<Item = [i16; 2]> {
}
/// Instantiate a decoder for the compression that the sound data uses.
pub fn make_decoder<'a, R: 'a + Send + Read>(
pub fn make_decoder<R: 'static + Send + Read>(
format: &SoundFormat,
data: R,
) -> Result<Box<dyn 'a + Send + Decoder>, Error> {
let decoder: Box<dyn 'a + Send + Decoder> = match format.compression {
) -> Result<Box<dyn Send + Decoder>, Error> {
let decoder: Box<dyn Send + Decoder> = match format.compression {
AudioCompression::UncompressedUnknownEndian => {
// Cross fingers that it's little endian.
log::warn!("make_decoder: PCM sound is unknown endian; assuming little endian");
@ -55,7 +57,7 @@ pub fn make_decoder<'a, R: 'a + Send + Read>(
format.is_stereo,
format.sample_rate,
)),
#[cfg(feature = "minimp3")]
#[cfg(any(feature = "minimp3", feature = "symphonia"))]
AudioCompression::Mp3 => Box::new(Mp3Decoder::new(
if format.is_stereo { 2 } else { 1 },
format.sample_rate.into(),

View File

@ -1,95 +1,267 @@
use super::{Decoder, SeekableDecoder};
use std::io::{Cursor, Read};
#[cfg(feature = "minimp3")]
pub struct Mp3Decoder<R: Read> {
decoder: minimp3::Decoder<R>,
sample_rate: u32,
num_channels: u16,
cur_frame: minimp3::Frame,
cur_sample: usize,
num_samples: usize,
}
pub mod minimp3 {
use crate::backend::audio::decoders::{Decoder, SeekableDecoder};
use std::io::{Cursor, Read};
#[cfg(feature = "minimp3")]
impl<R: Read> Mp3Decoder<R> {
pub fn new(num_channels: u16, sample_rate: u32, reader: R) -> Self {
Mp3Decoder {
decoder: minimp3::Decoder::new(reader),
num_channels,
sample_rate,
cur_frame: minimp3::Frame {
data: vec![],
sample_rate: sample_rate as i32,
channels: num_channels.into(),
layer: 3,
bitrate: 128,
},
cur_sample: 0,
num_samples: 0,
}
pub struct Mp3Decoder<R: Read> {
decoder: minimp3::Decoder<R>,
sample_rate: u32,
num_channels: u16,
cur_frame: minimp3::Frame,
cur_sample: usize,
num_samples: usize,
}
fn next_frame(&mut self) {
if let Ok(frame) = self.decoder.next_frame() {
self.num_samples = frame.data.len();
self.cur_frame = frame;
} else {
self.num_samples = 0;
}
self.cur_sample = 0;
}
}
#[cfg(feature = "minimp3")]
impl<R: Read> Iterator for Mp3Decoder<R> {
type Item = [i16; 2];
#[inline]
#[allow(unknown_lints, clippy::branches_sharing_code)]
fn next(&mut self) -> Option<Self::Item> {
if self.cur_sample >= self.num_samples {
self.next_frame();
impl<R: Read> Mp3Decoder<R> {
pub fn new(num_channels: u16, sample_rate: u32, reader: R) -> Self {
Mp3Decoder {
decoder: minimp3::Decoder::new(reader),
num_channels,
sample_rate,
cur_frame: minimp3::Frame {
data: vec![],
sample_rate: sample_rate as i32,
channels: num_channels.into(),
layer: 3,
bitrate: 128,
},
cur_sample: 0,
num_samples: 0,
}
}
if self.num_samples > 0 {
if self.num_channels == 2 {
let left = self.cur_frame.data[self.cur_sample];
let right = self.cur_frame.data[self.cur_sample + 1];
self.cur_sample += 2;
Some([left, right])
fn next_frame(&mut self) {
if let Ok(frame) = self.decoder.next_frame() {
self.num_samples = frame.data.len();
self.cur_frame = frame;
} else {
let sample = self.cur_frame.data[self.cur_sample];
self.num_samples = 0;
}
self.cur_sample = 0;
}
}
impl<R: Read> Iterator for Mp3Decoder<R> {
type Item = [i16; 2];
#[inline]
#[allow(unknown_lints, clippy::branches_sharing_code)]
fn next(&mut self) -> Option<Self::Item> {
if self.cur_sample >= self.num_samples {
self.next_frame();
}
if self.num_samples > 0 {
if self.num_channels == 2 {
let left = self.cur_frame.data[self.cur_sample];
let right = self.cur_frame.data[self.cur_sample + 1];
self.cur_sample += 2;
Some([left, right])
} else {
let sample = self.cur_frame.data[self.cur_sample];
self.cur_sample += 1;
Some([sample, sample])
}
} else {
None
}
}
}
impl<R: AsRef<[u8]> + Default> SeekableDecoder for Mp3Decoder<Cursor<R>> {
#[inline]
fn reset(&mut self) {
// TODO: This is funky.
// I want to reset the `BitStream` and `Cursor` to their initial positions,
// but have to work around the borrowing rules of Rust.
let mut cursor = std::mem::take(self.decoder.reader_mut());
cursor.set_position(0);
*self = Mp3Decoder::new(self.num_channels, self.sample_rate, cursor);
}
}
impl<R: Read> Decoder for Mp3Decoder<R> {
#[inline]
fn num_channels(&self) -> u8 {
self.num_channels as u8
}
#[inline]
fn sample_rate(&self) -> u16 {
self.sample_rate as u16
}
}
}
#[cfg(feature = "symphonia")]
#[allow(dead_code)]
pub mod symphonia {
use crate::backend::audio::decoders::{Decoder, SeekableDecoder};
use std::io::{Cursor, Read};
use symphonia::{
core::{
self, audio, codecs, errors,
formats::{self, FormatReader},
io,
},
default::formats::Mp3Reader as SymphoniaMp3Reader,
};
pub struct Mp3Decoder {
reader: SymphoniaMp3Reader,
decoder: Box<dyn codecs::Decoder>,
codec_params: codecs::CodecParameters,
sample_buf: audio::SampleBuffer<i16>,
cur_sample: usize,
sample_rate: u16,
num_channels: u8,
stream_ended: bool,
}
impl Mp3Decoder {
pub fn new<R: 'static + Read + Send>(
num_channels: u16,
sample_rate: u32,
reader: R,
) -> Self {
let source = Box::new(io::ReadOnlySource::new(reader)) as Box<dyn io::MediaSource>;
let source = io::MediaSourceStream::new(source, Default::default());
let reader = SymphoniaMp3Reader::try_new(source, &Default::default()).unwrap();
let track = reader.default_track().unwrap();
let codec_params = track.codec_params.clone();
let decoder = symphonia::default::get_codecs()
.make(&codec_params, &Default::default())
.unwrap();
let sample_rate = codec_params
.sample_rate
.map_or(sample_rate as u16, |n| n as u16);
let num_channels = codec_params
.channels
.map_or(num_channels as u8, |n| n.count() as u8);
Mp3Decoder {
reader,
decoder,
codec_params,
sample_buf: audio::SampleBuffer::new(
0,
audio::SignalSpec::new(0, Default::default()),
),
cur_sample: 0,
num_channels,
sample_rate,
stream_ended: false,
}
}
pub fn new_seekable<R: 'static + AsRef<[u8]> + Send>(
num_channels: u16,
sample_rate: u32,
reader: Cursor<R>,
) -> Self {
let source = Box::new(reader) as Box<dyn io::MediaSource>;
let source = io::MediaSourceStream::new(source, Default::default());
let reader = SymphoniaMp3Reader::try_new(source, &Default::default()).unwrap();
let track = reader.default_track().unwrap();
let codec_params = track.codec_params.clone();
let decoder = symphonia::default::get_codecs()
.make(&codec_params, &Default::default())
.unwrap();
let sample_rate = codec_params
.sample_rate
.map_or(sample_rate as u16, |n| n as u16);
let num_channels = codec_params
.channels
.map_or(num_channels as u8, |n| n.count() as u8);
Mp3Decoder {
reader,
decoder,
codec_params,
sample_buf: audio::SampleBuffer::new(
0,
audio::SignalSpec::new(0, Default::default()),
),
cur_sample: 0,
num_channels,
sample_rate,
stream_ended: false,
}
}
fn next_frame(&mut self) {
if self.stream_ended {
return;
}
self.cur_sample = 0;
while let Ok(packet) = self.reader.next_packet() {
match self.decoder.decode(&packet) {
Ok(decoded) => {
if self.sample_buf.len() == 0 {
self.sample_buf = audio::SampleBuffer::new(
decoded.capacity() as core::units::Duration,
*decoded.spec(),
);
}
self.sample_buf.copy_interleaved_ref(decoded);
return;
}
// Decode errors are not fatal.
Err(errors::Error::DecodeError(_)) => (),
Err(_) => break,
}
}
// EOF reached.
self.decoder.close();
self.stream_ended = true;
}
}
impl Iterator for Mp3Decoder {
type Item = [i16; 2];
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.cur_sample >= self.sample_buf.len() {
self.next_frame();
if self.stream_ended {
return None;
}
}
let sample_buf = self.sample_buf.samples();
if self.num_channels == 2 {
let samples: [i16; 2] =
[sample_buf[self.cur_sample], sample_buf[self.cur_sample + 1]];
self.cur_sample += 2;
Some(samples)
} else {
let sample = sample_buf[self.cur_sample];
self.cur_sample += 1;
Some([sample, sample])
}
} else {
None
}
}
impl SeekableDecoder for Mp3Decoder {
#[inline]
fn reset(&mut self) {
let _ = self.reader.seek(
formats::SeekMode::Accurate,
formats::SeekTo::TimeStamp { track_id: 0, ts: 0 },
);
self.cur_sample = self.sample_buf.len();
}
}
impl Decoder for Mp3Decoder {
#[inline]
fn num_channels(&self) -> u8 {
self.num_channels
}
#[inline]
fn sample_rate(&self) -> u16 {
self.sample_rate
}
}
}
#[cfg(feature = "minimp3")]
impl<R: AsRef<[u8]> + Default> SeekableDecoder for Mp3Decoder<Cursor<R>> {
#[inline]
fn reset(&mut self) {
// TODO: This is funky.
// I want to reset the `BitStream` and `Cursor` to their initial positions,
// but have to work around the borrowing rules of Rust.
let mut cursor = std::mem::take(self.decoder.reader_mut());
cursor.set_position(0);
*self = Mp3Decoder::new(self.num_channels, self.sample_rate, cursor);
}
}
impl<R: Read> Decoder for Mp3Decoder<R> {
#[inline]
fn num_channels(&self) -> u8 {
self.num_channels as u8
}
#[inline]
fn sample_rate(&self) -> u16 {
self.sample_rate as u16
}
}

View File

@ -146,7 +146,7 @@ impl AudioMixer {
format.sample_rate.into(),
data,
)),
#[cfg(all(feature = "symphonia_mp3", not(feature = "minimp3")))]
#[cfg(all(feature = "symphonia", not(feature = "minimp3")))]
AudioCompression::Mp3 => Box::new(decoders::Mp3Decoder::new_seekable(
if format.is_stereo { 2 } else { 1 },
format.sample_rate.into(),