-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmod.rs
More file actions
174 lines (160 loc) · 6.69 KB
/
Copy pathmod.rs
File metadata and controls
174 lines (160 loc) · 6.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! Audio codec framework.
//!
//! Squad-24 (2026-04-17 PM5): adds the decoder/encoder traits + the wire
//! types Squad-23 (audio mux pipeline) consumes. Decoders cover MP3 and
//! Vorbis (mux already handles AAC/Opus/AC-3 passthrough — no decode
//! needed for those). The encoder side currently exposes Opus only;
//! the user decision on the audio expansion (recorded in TODO.md at the
//! time; today's TODO.md keeps only the open items) picked Opus over AAC
//! because the libopus binding is BSD/Apache, modern browsers all play
//! Opus-in-MP4, and the iOS-13-and-older floor is acceptable. Since then
//! the Opus encoder grew to 1–8 channels (family 1 multistream for 3–8)
//! and `filter::channelmap` remaps the PCM; the decoders are still MP3
//! and Vorbis — the AC-3 / AAC decoders are the open items in TODO.md's
//! "Audio — multichannel decode" section.
//!
//! Wire model
//! ----------
//! - [`AudioFrame`] is the canonical PCM exchange type: f32 in
//! [-1.0, 1.0], interleaved planar layout (LRLRLR for stereo), with
//! the source sample rate and channel count carried alongside the
//! samples and a microsecond-domain PTS.
//! - [`EncodedAudioPacket`] carries one encoder output packet plus
//! PTS/duration in encoder timescale (Opus = 48000 ticks per second
//! per RFC 7845 §4.1).
//! - [`AudioDecoder`] / [`AudioEncoder`] traits are object-safe so
//! pipeline code can hand out `Box<dyn AudioEncoder>`.
//!
//! Pre-skip + extra_data contract (Opus-specific)
//! ----------------------------------------------
//! [`AudioEncoder::pre_skip`] returns the number of *48 kHz* samples of
//! lookahead the libopus encoder injects (queried via
//! `OPUS_GET_LOOKAHEAD` and reported in 48 kHz ticks no matter the
//! configured rate). Squad-23's mux side writes this into the `dOps`
//! body so a conformant decoder discards the lookahead at the start of
//! the file.
//!
//! [`AudioEncoder::extra_data`] returns the `dOps` body bytes per RFC
//! 7845 §4.5: 11 bytes minimum, channel-mapping family 0 (mono/stereo).
//! Multistream (>2 channels) is out of scope for this sprint and
//! returns [`AudioError::Unsupported`].
pub mod decode;
pub mod encode;
pub mod filter;
pub mod resample;
#[derive(thiserror::Error, Debug)]
pub enum AudioError {
#[error("decode failed: {0}")]
Decode(String),
#[error("encode failed: {0}")]
Encode(String),
#[error("resample failed: {0}")]
Resample(String),
#[error("unsupported: {0}")]
Unsupported(String),
}
/// One decoded audio frame.
///
/// `samples` is interleaved planar — for stereo the layout is
/// `[L0, R0, L1, R1, ...]`, length `frames * channels`. Values are
/// f32 in `[-1.0, 1.0]`. The encoder side accepts the same layout.
#[derive(Clone, Debug)]
pub struct AudioFrame {
/// Interleaved planar samples (LRLRLR for stereo) in `[-1.0, 1.0]`.
pub samples: Vec<f32>,
pub sample_rate: u32,
pub channels: u8,
/// Presentation timestamp, microseconds, signed (allows negative
/// pre-roll positions for codecs that emit lookahead frames before
/// PTS=0 — Opus uses pre_skip rather than negative PTS, but this
/// keeps the type general).
pub pts: i64,
}
/// One encoded audio packet leaving the encoder.
#[derive(Clone, Debug)]
pub struct EncodedAudioPacket {
pub data: Vec<u8>,
/// PTS in microseconds (matches `AudioFrame::pts` domain).
pub pts: i64,
/// Duration in encoder timescale ticks. For Opus this is 48000
/// ticks/sec (one 20 ms frame = 960 ticks).
pub duration: i64,
}
#[derive(Clone, Debug)]
pub struct AudioEncoderConfig {
pub codec: AudioCodec,
/// Input sample rate the caller will feed [`AudioEncoder::encode`].
/// The encoder transparently resamples to its native rate (48 kHz
/// for Opus) when this differs.
pub sample_rate: u32,
pub channels: u8,
/// Target bitrate in bits per second.
pub bitrate: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AudioCodec {
Opus,
}
pub trait AudioDecoder: Send {
/// Decode one input packet at the given PTS (microseconds). May
/// return zero or more output frames (zero is normal — some
/// decoders need to see two frames before emitting one).
fn decode(&mut self, packet: &[u8], pts: i64) -> Result<Vec<AudioFrame>, AudioError>;
/// Drain any frames buffered inside the decoder. Call once at EOS.
fn flush(&mut self) -> Result<Vec<AudioFrame>, AudioError>;
}
pub trait AudioEncoder: Send {
/// Encode one input frame. The encoder buffers up to one output
/// frame's worth of samples internally — Opus's smallest frame is
/// 2.5 ms, default 20 ms — so this returns 0..N packets.
fn encode(&mut self, frame: &AudioFrame) -> Result<Vec<EncodedAudioPacket>, AudioError>;
/// Drain any buffered samples. May produce a final partial packet.
fn flush(&mut self) -> Result<Vec<EncodedAudioPacket>, AudioError>;
/// Lookahead samples at 48 kHz (Opus convention). For Opus,
/// queried via `OPUS_GET_LOOKAHEAD` and scaled to 48 kHz when the
/// encoder is internally running at a non-48k rate.
fn pre_skip(&self) -> u16;
/// The codec-specific extra_data the muxer puts in the sample
/// entry's config box. For Opus this is the `dOps` body per RFC
/// 7845 §4.5 (11 bytes for channel-mapping family 0).
fn extra_data(&self) -> Vec<u8>;
}
/// Construct an audio decoder for the given codec name.
///
/// `codec` is matched case-insensitively. Supported tokens:
/// - `mp3` / `mpeg`
/// - `vorbis` (raw audio packet form — caller is responsible for
/// feeding the three Xiph setup packets first via the `extra_data`
/// parameter on first construction, then the audio packets via
/// `decode`)
///
/// `extra_data`, `sample_rate`, and `channels` come from the demux
/// side's container metadata. For codecs that carry full setup in the
/// stream (MP3) `extra_data` may be `None`.
pub fn create_decoder(
codec: &str,
extra_data: Option<&[u8]>,
sample_rate: u32,
channels: u8,
) -> Result<Box<dyn AudioDecoder>, AudioError> {
match codec.to_ascii_lowercase().as_str() {
"mp3" | "mpeg" | "mp3a" => Ok(Box::new(decode::mp3::Mp3Decoder::new(
sample_rate,
channels,
)?)),
"vorbis" => Ok(Box::new(decode::vorbis::VorbisDecoder::new(
extra_data,
sample_rate,
channels,
)?)),
other => Err(AudioError::Unsupported(format!(
"audio decoder for codec {other}"
))),
}
}
/// Construct an audio encoder.
pub fn create_encoder(config: AudioEncoderConfig) -> Result<Box<dyn AudioEncoder>, AudioError> {
match config.codec {
AudioCodec::Opus => Ok(Box::new(encode::opus::OpusEncoder::new(config)?)),
}
}