Skip to content

Commit

Permalink
feat: introduce mania-codec
Browse files Browse the repository at this point in the history
  • Loading branch information
pk5ls20 committed Feb 21, 2025
1 parent fd47672 commit 3f07c6a
Show file tree
Hide file tree
Showing 14 changed files with 989 additions and 10 deletions.
428 changes: 422 additions & 6 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["mania", "mania-macros", "examples"]
members = ["mania", "mania-macros", "examples", "mania-codec"]

[workspace.package]
version = "0.0.1"
Expand All @@ -22,6 +22,9 @@ tokio = { version = "1.43.0", features = [
"tracing",
] }
uuid = { version = "1.12.1", features = ["serde", "v4"] }
thiserror = "2.0.11"
num_enum = "0.7.3"
bytes = { version = "1.10.0", features = ["serde"] }

[profile.release]
opt-level = 2
Expand Down
14 changes: 14 additions & 0 deletions mania-codec/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "mania-codec"
version.workspace = true
edition.workspace = true
license-file.workspace = true

[dependencies]
num-traits = "0.2.19"
rubato = "0.16.1"
silk-sys = "0.1.0"
symphonia = { version = "0.5.4", features = ["all"] }
thiserror.workspace = true
num_enum.workspace = true
bytes.workspace = true
7 changes: 7 additions & 0 deletions mania-codec/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## mania-codec

Audio codec library for Mania

### Special Thanks

- [Konata.Codec](https://github.com/KonataDev/Konata.Codec)
Binary file added mania-codec/resource/test.mp3
Binary file not shown.
160 changes: 160 additions & 0 deletions mania-codec/src/audio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use crate::audio::decoder::{AudioCodecDecoderError, AudioDecoder};
use crate::audio::encoder::{AudioCodecEncoderError, AudioEncoder};
use crate::audio::resampler::{AudioCodecResamplerError, AudioResampler};
use num_traits::FromPrimitive;
use std::io::{Read, Seek};
use std::marker::PhantomData;
use symphonia::core::conv::{FromSample, IntoSample};
use symphonia::core::sample::{i24, u24};
use thiserror::Error;

pub mod decoder;
pub mod encoder;
pub mod resampler;

#[derive(Debug, Error)]
pub enum AudioCodecError {
#[error("Decode error {0}")]
DecodeError(#[from] AudioCodecDecoderError),
#[error("Resample error {0}")]
ResampleError(#[from] AudioCodecResamplerError),
#[error("Encode error {0}")]
EncodeError(#[from] AudioCodecEncoderError),
}

pub trait Sample: Copy + Send + Sync {}
impl Sample for u8 {}
impl Sample for i16 {}
impl Sample for i32 {}
impl Sample for f32 {}

pub trait ResampleSample: Sample + IntoSample<f32> {}
impl ResampleSample for i16 {}
impl ResampleSample for f32 {}

pub trait DecodeSample:
FromSample<u8>
+ FromSample<u16>
+ FromSample<u24>
+ FromSample<u32>
+ FromSample<i8>
+ FromSample<i16>
+ FromSample<i24>
+ FromSample<i32>
+ FromSample<f32>
+ FromSample<f64>
+ FromPrimitive
+ ResampleSample
{
}
impl DecodeSample for f32 {}

pub trait EncodeSample: Sample {}
impl EncodeSample for i16 {}
impl EncodeSample for u8 {}

pub trait RSStream: Read + Seek + Send + Sync {
fn is_seekable(&self) -> bool;
fn byte_len(&self) -> Option<u64>;
}

impl RSStream for std::fs::File {
fn is_seekable(&self) -> bool {
true
}

fn byte_len(&self) -> Option<u64> {
self.metadata().ok().map(|m| m.len())
}
}

#[derive(Debug, Clone)]
pub struct AudioInfo<T: Sample> {
pub sample_rate: u32,
pub channels: u16,
_phantom: PhantomData<T>,
}

impl<T: Sample> AudioInfo<T> {
pub fn new(sample_rate: u32, channels: u16) -> Self {
Self {
sample_rate,
channels,
_phantom: PhantomData,
}
}
}

pub struct AudioRwStream<T: ResampleSample> {
pub stream: Box<dyn RSStream>,
pub info: Option<AudioInfo<T>>,
}

impl<T: ResampleSample> AudioRwStream<T> {
pub fn new(stream: Box<dyn RSStream>) -> Self {
Self {
stream,
info: None, // cannot get info now
}
}

pub fn decode<D: AudioDecoder<T>>(
self,
decoder: D,
) -> Result<AudioResampleStream<T>, AudioCodecDecoderError>
where
T: DecodeSample,
{
decoder.decode(self)
}
}

pub struct AudioResampleStream<T: ResampleSample> {
pub stream: Vec<T>,
pub info: AudioInfo<T>,
}

impl<T: ResampleSample> AudioResampleStream<T> {
pub fn resample<U: ResampleSample, R: AudioResampler<T, U>>(
self,
resampler: R,
) -> Result<AudioResampleStream<U>, AudioCodecResamplerError> {
resampler.resample(&self)
}

pub fn encode<E: AudioEncoder<T>>(
self,
encoder: E,
) -> Result<AudioEncodeStream<T>, AudioCodecEncoderError>
where
T: EncodeSample,
{
encoder.encode(&self)
}
}

pub struct AudioEncodeStream<T: EncodeSample> {
pub stream: Vec<u8>,
pub info: AudioInfo<T>,
}

#[cfg(test)]
mod test {
use super::*;
use crate::audio::decoder::symphonia_decoder::SymphoniaDecoder;
use crate::audio::encoder::silk_encoder::SilkEncoder;
use crate::audio::resampler::rubato_resampler::RubatoResampler;
use std::fs::File;
use std::io::Write;

#[test]
fn test_pipeline() -> Result<(), AudioCodecError> {
let pipeline = AudioRwStream::new(Box::new(File::open("resource/test.mp3").unwrap()))
.decode(SymphoniaDecoder::<f32>::new())?
.resample(RubatoResampler::<i16>::new(24000))?
.encode(SilkEncoder::new(30000))?;
let mut file = File::create("resource/test.silk").unwrap();
file.write_all(&pipeline.stream).unwrap();
Ok(())
}
}
14 changes: 14 additions & 0 deletions mania-codec/src/audio/decoder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pub mod symphonia_decoder;

use crate::audio::{AudioResampleStream, AudioRwStream, DecodeSample};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum AudioCodecDecoderError {}

pub trait AudioDecoder<T: DecodeSample> {
fn decode(
&self,
input: AudioRwStream<T>,
) -> Result<AudioResampleStream<T>, AudioCodecDecoderError>;
}
135 changes: 135 additions & 0 deletions mania-codec/src/audio/decoder/symphonia_decoder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use crate::audio::decoder::{AudioCodecDecoderError, AudioDecoder};
use crate::audio::{AudioInfo, AudioResampleStream, AudioRwStream, DecodeSample, RSStream};
use std::io::{Read, Seek, SeekFrom};
use std::marker::PhantomData;
use symphonia::core::audio::{AudioBufferRef, Signal};
use symphonia::core::codecs::CODEC_TYPE_NULL;
use symphonia::core::formats::FormatOptions;
use symphonia::core::io::{MediaSource, MediaSourceStream};
use symphonia::core::meta::MetadataOptions;
use symphonia::core::probe::Hint;

struct RSStreamAdapter {
inner: Box<dyn RSStream>,
is_seekable: bool,
byte_len: Option<u64>,
}

impl Read for RSStreamAdapter {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.inner.read(buf)
}
}

impl Seek for RSStreamAdapter {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
self.inner.seek(pos)
}
}

impl RSStream for RSStreamAdapter {
fn is_seekable(&self) -> bool {
self.is_seekable
}

fn byte_len(&self) -> Option<u64> {
self.byte_len
}
}

impl MediaSource for RSStreamAdapter {
fn is_seekable(&self) -> bool {
self.is_seekable
}

fn byte_len(&self) -> Option<u64> {
self.byte_len
}
}

pub struct SymphoniaDecoder<T: DecodeSample> {
_phantom: PhantomData<T>,
}

impl<T: DecodeSample> SymphoniaDecoder<T> {
pub fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
}

impl<T: DecodeSample> Default for SymphoniaDecoder<T> {
fn default() -> Self {
Self::new()
}
}

// FIXME: It is now just a rigid conversion to mono (in the form of [0])!
fn conv<S, T>(samples: &mut Vec<S>, data: std::borrow::Cow<symphonia::core::audio::AudioBuffer<T>>)
where
T: symphonia::core::sample::Sample,
S: symphonia::core::conv::FromSample<T>,
{
samples.extend(data.chan(0).iter().map(|v| S::from_sample(*v)));
}

// ref: https://github.com/kyutai-labs/hibiki/blob/main/hibiki-rs/src/audio_io.rs
impl<T: DecodeSample> AudioDecoder<T> for SymphoniaDecoder<T> {
fn decode(
&self,
input: AudioRwStream<T>,
) -> Result<AudioResampleStream<T>, AudioCodecDecoderError> {
let (stream, _) = (input.stream, input.info);
let is_seekable = stream.is_seekable();
let byte_len = stream.byte_len();
let adapter = RSStreamAdapter {
inner: stream,
is_seekable,
byte_len,
};
let mss = MediaSourceStream::new(Box::new(adapter), Default::default());
let hint = Hint::new();
let meta_opts: MetadataOptions = Default::default();
let fmt_opts: FormatOptions = Default::default();
let probed = symphonia::default::get_probe()
.format(&hint, mss, &fmt_opts, &meta_opts)
.unwrap();
let mut format = probed.format;
let track = format
.tracks()
.iter()
.find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
.expect("no supported audio tracks");
let mut decoder = symphonia::default::get_codecs()
.make(&track.codec_params, &Default::default())
.expect("unsupported codec");
let track_id = track.id;
let sample_rate = track.codec_params.sample_rate.unwrap_or(0);
let mut pcm_data = Vec::new();
while let Ok(packet) = format.next_packet() {
while !format.metadata().is_latest() {
format.metadata().pop();
}
if packet.track_id() != track_id {
continue;
}
match decoder.decode(&packet).unwrap() {
AudioBufferRef::F32(data) => conv(&mut pcm_data, data),
AudioBufferRef::U8(data) => conv(&mut pcm_data, data),
AudioBufferRef::U16(data) => conv(&mut pcm_data, data),
AudioBufferRef::U24(data) => conv(&mut pcm_data, data),
AudioBufferRef::U32(data) => conv(&mut pcm_data, data),
AudioBufferRef::S8(data) => conv(&mut pcm_data, data),
AudioBufferRef::S16(data) => conv(&mut pcm_data, data),
AudioBufferRef::S24(data) => conv(&mut pcm_data, data),
AudioBufferRef::S32(data) => conv(&mut pcm_data, data),
AudioBufferRef::F64(data) => conv(&mut pcm_data, data),
}
}
Ok(AudioResampleStream {
stream: pcm_data,
info: AudioInfo::new(sample_rate, 1), // TODO: channel
})
}
}
19 changes: 19 additions & 0 deletions mania-codec/src/audio/encoder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
pub mod silk_encoder;

use crate::audio::{AudioEncodeStream, AudioResampleStream, EncodeSample, ResampleSample};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum AudioCodecEncoderError {
#[error("Silk encoder error: {0}")]
SilkEncoderKnownError(#[from] silk_encoder::SilkError),
#[error("Silk encoder error: {0}")]
SilkEncoderUnknownError(i32),
}

pub trait AudioEncoder<T: EncodeSample + ResampleSample> {
fn encode(
&self,
input: &AudioResampleStream<T>,
) -> Result<AudioEncodeStream<T>, AudioCodecEncoderError>;
}
Loading

0 comments on commit 3f07c6a

Please sign in to comment.