mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-26 11:36:58 -06:00
- crates/media: probe (duration, rate/channels, size/fps; WMA/WMV recognised), audio decode to f32 PCM (MP3, AAC M4A/ADTS, ALAC, WAV, AIFF, CAF, FLAC, Vorbis via symphonia; Opus with our decoder in Ogg/WebM/MKV), video decode (MP4/MOV/WebM/MKV) with YUV->RGBA, poster frames, trim/fade/volume, Player mixer + per-voice clock (device-clocked or wall-clocked), VideoFeed. - Codec crates ported from FilmCraft: bitstream, h264, hevc, vp9, av1, isobmff, matroska, ogg, opus. - insert.audio/insert.video probe and store duration/size and a poster frame; unplayable media is embedded with a warning. New media.play/pause/toggle/stop/seek/info/posterFrame. - UI: MediaHost (cpal output injected by the desktop app), control bar under media, video frames on canvas and in the show; autoplay, click to play, loop, rewind, across slides, hide, full screen. - Tests with in-code WAV/AIFF and CC0 self-generated fixtures (make.sh, ATTRIBUTION.md rows). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
417 lines
16 KiB
Rust
417 lines
16 KiB
Rust
//! Single-stream Opus decoder: mode switching, hybrid summation, redundancy and transitions
|
|
//! (RFC 6716 §4, §4.5).
|
|
|
|
use crate::celt::CeltDecoder;
|
|
use crate::celt::tables::OVERLAP;
|
|
use crate::packet::{Bandwidth, Mode, Packet};
|
|
use crate::range::RangeDecoder;
|
|
use crate::silk::{LostFlag, SilkDecoder};
|
|
use crate::{Error, Result};
|
|
|
|
/// Output sample rates supported by the decoder.
|
|
pub const SAMPLE_RATES: [u32; 5] = [8000, 12000, 16000, 24000, 48000];
|
|
|
|
/// Decoder for one elementary Opus stream (mono or stereo), like `OpusDecoder`.
|
|
pub struct StreamDecoder {
|
|
rate: u32,
|
|
channels: usize,
|
|
celt: CeltDecoder,
|
|
silk: SilkDecoder,
|
|
stream_channels: usize,
|
|
bandwidth: Bandwidth,
|
|
mode: Option<Mode>,
|
|
prev_mode: Option<Mode>,
|
|
frame_size: usize,
|
|
prev_redundancy: bool,
|
|
last_packet_duration: usize,
|
|
range_final: u32,
|
|
/// Output gain in Q8 dB (OpusHead output gain).
|
|
gain_q8: i16,
|
|
}
|
|
|
|
impl StreamDecoder {
|
|
/// Creates a decoder producing `channels` (1 or 2) at `rate` (8/12/16/24/48 kHz).
|
|
pub fn new(rate: u32, channels: usize) -> Result<StreamDecoder> {
|
|
if !SAMPLE_RATES.contains(&rate) {
|
|
return Err(Error::InvalidArgument("sample rate must be 8000, 12000, 16000, 24000 or 48000"));
|
|
}
|
|
if !(1..=2).contains(&channels) {
|
|
return Err(Error::InvalidArgument("channels must be 1 or 2"));
|
|
}
|
|
Ok(StreamDecoder {
|
|
rate,
|
|
channels,
|
|
celt: CeltDecoder::new(rate, channels),
|
|
silk: SilkDecoder::new(rate, channels),
|
|
stream_channels: channels,
|
|
bandwidth: Bandwidth::Fullband,
|
|
mode: None,
|
|
prev_mode: None,
|
|
frame_size: rate as usize / 400,
|
|
prev_redundancy: false,
|
|
last_packet_duration: 0,
|
|
range_final: 0,
|
|
gain_q8: 0,
|
|
})
|
|
}
|
|
|
|
pub fn sample_rate(&self) -> u32 {
|
|
self.rate
|
|
}
|
|
|
|
pub fn channels(&self) -> usize {
|
|
self.channels
|
|
}
|
|
|
|
/// Final range coder state of the last decoded frame (for conformance checks).
|
|
pub fn final_range(&self) -> u32 {
|
|
self.range_final
|
|
}
|
|
|
|
/// Duration (samples per channel) of the last decoded packet.
|
|
pub fn last_packet_duration(&self) -> usize {
|
|
self.last_packet_duration
|
|
}
|
|
|
|
/// Sets the output gain in Q8 dB (as in the OpusHead header).
|
|
pub fn set_gain_q8(&mut self, gain: i16) {
|
|
self.gain_q8 = gain;
|
|
}
|
|
|
|
/// Disables the 180-degree intensity-stereo phase inversion (RFC 8251 §10).
|
|
pub fn set_phase_inversion_disabled(&mut self, disabled: bool) {
|
|
self.celt.disable_inv = disabled;
|
|
}
|
|
|
|
/// `OPUS_RESET_STATE`.
|
|
pub fn reset(&mut self) {
|
|
self.celt.reset();
|
|
self.silk.reset();
|
|
self.stream_channels = self.channels;
|
|
self.bandwidth = Bandwidth::Fullband;
|
|
self.mode = None;
|
|
self.prev_mode = None;
|
|
self.frame_size = self.rate as usize / 400;
|
|
self.prev_redundancy = false;
|
|
self.last_packet_duration = 0;
|
|
self.range_final = 0;
|
|
}
|
|
|
|
/// Decodes a packet into interleaved samples appended to `out`. `None` conceals one lost
|
|
/// packet of the last packet's duration. Returns samples per channel.
|
|
pub fn decode_interleaved(&mut self, packet: Option<&[u8]>, out: &mut Vec<f32>) -> Result<usize> {
|
|
match packet {
|
|
Some(p) if !p.is_empty() => self.decode_packet(p, out),
|
|
_ => {
|
|
let n = if self.last_packet_duration > 0 { self.last_packet_duration } else { self.rate as usize / 50 };
|
|
self.conceal(n, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Conceals `n` samples (a multiple of 2.5 ms).
|
|
pub fn conceal(&mut self, n: usize, out: &mut Vec<f32>) -> Result<usize> {
|
|
let f2_5 = self.rate as usize / 400;
|
|
if n == 0 || !n.is_multiple_of(f2_5) {
|
|
return Err(Error::InvalidArgument("concealment length must be a multiple of 2.5 ms"));
|
|
}
|
|
let start = out.len();
|
|
out.resize(start + n * self.channels, 0.0);
|
|
let mut done = 0;
|
|
while done < n {
|
|
let r = self.decode_frame(None, &mut out[start + done * self.channels..], n - done, false)?;
|
|
done += r;
|
|
}
|
|
self.last_packet_duration = n;
|
|
Ok(n)
|
|
}
|
|
|
|
/// Decodes the in-band FEC (LBRR) of `packet` for the previous (lost) packet of `n` samples.
|
|
pub fn decode_fec(&mut self, packet: &[u8], n: usize, out: &mut Vec<f32>) -> Result<usize> {
|
|
let p = Packet::parse(packet)?;
|
|
let pfs = p.toc.frame_samples(self.rate);
|
|
if n < pfs || p.toc.mode == Mode::CeltOnly || self.mode == Some(Mode::CeltOnly) {
|
|
return self.conceal(n, out);
|
|
}
|
|
let start = out.len();
|
|
if n > pfs {
|
|
self.conceal(n - pfs, out)?;
|
|
}
|
|
self.mode = Some(p.toc.mode);
|
|
self.bandwidth = p.toc.bandwidth;
|
|
self.frame_size = pfs;
|
|
self.stream_channels = p.toc.channels();
|
|
let off = out.len();
|
|
out.resize(off + pfs * self.channels, 0.0);
|
|
self.decode_frame(Some(p.frames[0]), &mut out[off..], pfs, true)?;
|
|
self.last_packet_duration = n;
|
|
debug_assert_eq!(out.len() - start, n * self.channels);
|
|
Ok(n)
|
|
}
|
|
|
|
fn decode_packet(&mut self, data: &[u8], out: &mut Vec<f32>) -> Result<usize> {
|
|
let p = Packet::parse(data)?;
|
|
let fs = p.toc.frame_samples(self.rate);
|
|
self.mode = Some(p.toc.mode);
|
|
self.bandwidth = p.toc.bandwidth;
|
|
self.frame_size = fs;
|
|
self.stream_channels = p.toc.channels();
|
|
let start = out.len();
|
|
out.resize(start + fs * p.frames.len() * self.channels, 0.0);
|
|
let mut nb = 0;
|
|
for f in &p.frames {
|
|
let r = self.decode_frame(Some(f), &mut out[start + nb * self.channels..], fs, false)?;
|
|
nb += r;
|
|
}
|
|
out.truncate(start + nb * self.channels);
|
|
self.last_packet_duration = nb;
|
|
Ok(nb)
|
|
}
|
|
|
|
/// `opus_decode_frame`: decodes one frame (or conceals when `data` is `None`/≤1 byte).
|
|
fn decode_frame(&mut self, data: Option<&[u8]>, pcm: &mut [f32], frame_size: usize, fec: bool) -> Result<usize> {
|
|
let ch = self.channels;
|
|
let fs = self.rate as usize;
|
|
let f20 = fs / 50;
|
|
let f10 = f20 >> 1;
|
|
let f5 = f10 >> 1;
|
|
let f2_5 = f5 >> 1;
|
|
let mut frame_size = frame_size.min(fs / 25 * 3);
|
|
let data = match data {
|
|
Some(d) if d.len() > 1 => Some(d),
|
|
_ => {
|
|
frame_size = frame_size.min(self.frame_size);
|
|
None
|
|
}
|
|
};
|
|
let (mut audiosize, mode, bandwidth);
|
|
match data {
|
|
Some(_) => {
|
|
audiosize = self.frame_size;
|
|
mode = self.mode.unwrap_or(Mode::CeltOnly);
|
|
bandwidth = Some(self.bandwidth);
|
|
}
|
|
None => {
|
|
audiosize = frame_size;
|
|
bandwidth = None;
|
|
mode = match self.prev_mode {
|
|
None => {
|
|
pcm[..audiosize * ch].fill(0.0);
|
|
return Ok(audiosize);
|
|
}
|
|
Some(m) => m,
|
|
};
|
|
if audiosize > f20 {
|
|
let mut done = 0;
|
|
while done < audiosize {
|
|
let r = self.decode_frame(None, &mut pcm[done * ch..], (audiosize - done).min(f20), false)?;
|
|
done += r;
|
|
}
|
|
return Ok(frame_size);
|
|
} else if audiosize < f20 {
|
|
if audiosize > f10 {
|
|
audiosize = f10;
|
|
} else if mode != Mode::SilkOnly && audiosize > f5 && audiosize < f10 {
|
|
audiosize = f5;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let data_len = data.map_or(0, |d| d.len());
|
|
let mut dec_storage;
|
|
let mut dec: Option<&mut RangeDecoder> = match data {
|
|
Some(d) => {
|
|
dec_storage = RangeDecoder::new(d);
|
|
Some(&mut dec_storage)
|
|
}
|
|
None => None,
|
|
};
|
|
let mut transition = false;
|
|
if data.is_some()
|
|
&& self.prev_mode.is_some()
|
|
&& ((mode == Mode::CeltOnly && self.prev_mode != Some(Mode::CeltOnly) && !self.prev_redundancy)
|
|
|| (mode != Mode::CeltOnly && self.prev_mode == Some(Mode::CeltOnly)))
|
|
{
|
|
transition = true;
|
|
}
|
|
let mut pcm_transition = Vec::new();
|
|
if transition && mode == Mode::CeltOnly {
|
|
pcm_transition.resize(f5 * ch, 0.0);
|
|
let n = f5.min(audiosize);
|
|
self.decode_frame(None, &mut pcm_transition, n, false)?;
|
|
}
|
|
if audiosize > frame_size {
|
|
return Err(Error::InvalidArgument("frame larger than output buffer"));
|
|
}
|
|
frame_size = audiosize;
|
|
|
|
// SILK.
|
|
let mut pcm_silk: Vec<f32> = Vec::new();
|
|
if mode != Mode::CeltOnly {
|
|
pcm_silk.resize(frame_size.max(f10) * ch, 0.0);
|
|
if self.prev_mode == Some(Mode::CeltOnly) {
|
|
self.silk.reset();
|
|
}
|
|
let payload_ms = (1000 * audiosize / fs).max(10);
|
|
let internal_rate = if mode == Mode::SilkOnly {
|
|
match bandwidth {
|
|
Some(Bandwidth::Narrowband) => 8000,
|
|
Some(Bandwidth::Mediumband) => 12000,
|
|
_ => 16000,
|
|
}
|
|
} else {
|
|
16000
|
|
};
|
|
if data.is_some() {
|
|
self.silk.set_stream(self.stream_channels, internal_rate);
|
|
}
|
|
let lost = if data.is_none() {
|
|
LostFlag::Lost
|
|
} else if fec {
|
|
LostFlag::Lbrr
|
|
} else {
|
|
LostFlag::Normal
|
|
};
|
|
let mut decoded = 0;
|
|
while decoded < frame_size {
|
|
let first = decoded == 0;
|
|
let n = match self.silk.decode(dec.as_deref_mut(), lost, first, payload_ms, &mut pcm_silk[decoded * ch..]) {
|
|
Ok(n) => n,
|
|
Err(e) => {
|
|
if lost != LostFlag::Normal {
|
|
let n = frame_size - decoded;
|
|
pcm_silk[decoded * ch..(decoded + n) * ch].fill(0.0);
|
|
n
|
|
} else {
|
|
return Err(e);
|
|
}
|
|
}
|
|
};
|
|
if n == 0 {
|
|
break;
|
|
}
|
|
decoded += n;
|
|
}
|
|
}
|
|
|
|
let mut start_band = 0;
|
|
let mut redundancy = false;
|
|
let mut celt_to_silk = false;
|
|
let mut redundancy_bytes = 0usize;
|
|
let mut len = data_len;
|
|
if !fec
|
|
&& mode != Mode::CeltOnly
|
|
&& let Some(d) = dec.as_deref_mut()
|
|
&& d.tell() + 17 + 20 * (self.mode == Some(Mode::Hybrid)) as i32 <= 8 * len as i32
|
|
{
|
|
redundancy = if mode == Mode::Hybrid { d.bit_logp(12) } else { true };
|
|
if redundancy {
|
|
celt_to_silk = d.bit_logp(1);
|
|
redundancy_bytes = if mode == Mode::Hybrid { d.uint(256) as usize + 2 } else { len - ((d.tell() as usize + 7) >> 3) };
|
|
if redundancy_bytes > len || (len - redundancy_bytes) * 8 < d.tell() as usize {
|
|
len = 0;
|
|
redundancy_bytes = 0;
|
|
redundancy = false;
|
|
} else {
|
|
len -= redundancy_bytes;
|
|
}
|
|
d.shrink_storage(redundancy_bytes);
|
|
}
|
|
}
|
|
if mode != Mode::CeltOnly {
|
|
start_band = 17;
|
|
}
|
|
if redundancy {
|
|
transition = false;
|
|
}
|
|
if transition && mode != Mode::CeltOnly {
|
|
pcm_transition.resize(f5 * ch, 0.0);
|
|
let n = f5.min(audiosize);
|
|
self.decode_frame(None, &mut pcm_transition, n, false)?;
|
|
}
|
|
if let Some(bw) = bandwidth {
|
|
self.celt.end = bw.celt_end_band();
|
|
}
|
|
self.celt.stream_channels = self.stream_channels;
|
|
let mut redundant_audio = vec![0f32; if redundancy { f5 * ch } else { 0 }];
|
|
let mut redundant_rng = 0u32;
|
|
let red_data = data.map(|d| &d[len..len + redundancy_bytes]);
|
|
if redundancy && celt_to_silk {
|
|
self.celt.start = 0;
|
|
self.celt.decode(red_data, f5, &mut redundant_audio, false, None)?;
|
|
redundant_rng = self.celt.rng;
|
|
}
|
|
self.celt.start = start_band;
|
|
|
|
if mode != Mode::SilkOnly {
|
|
let celt_frame = f20.min(frame_size);
|
|
if self.prev_mode.is_some() && Some(mode) != self.prev_mode && !self.prev_redundancy {
|
|
self.celt.reset();
|
|
}
|
|
let cdata = if fec { None } else { data.map(|d| &d[..len]) };
|
|
let cdec = if fec { None } else { dec.as_deref_mut() };
|
|
self.celt.decode(cdata, celt_frame, pcm, false, cdec)?;
|
|
} else {
|
|
pcm[..frame_size * ch].fill(0.0);
|
|
if self.prev_mode == Some(Mode::Hybrid) && !(redundancy && celt_to_silk && self.prev_redundancy) {
|
|
self.celt.start = 0;
|
|
let silence = [0xFFu8, 0xFF];
|
|
self.celt.decode(Some(&silence), f2_5, pcm, true, None)?;
|
|
}
|
|
}
|
|
if mode != Mode::CeltOnly {
|
|
for (o, s) in pcm[..frame_size * ch].iter_mut().zip(&pcm_silk) {
|
|
*o += *s;
|
|
}
|
|
}
|
|
let window = &crate::celt::mode().window;
|
|
if redundancy && !celt_to_silk {
|
|
self.celt.reset();
|
|
self.celt.start = 0;
|
|
self.celt.decode(red_data, f5, &mut redundant_audio, false, None)?;
|
|
redundant_rng = self.celt.rng;
|
|
let o = ch * (frame_size - f2_5);
|
|
let tail: Vec<f32> = pcm[o..o + f2_5 * ch].to_vec();
|
|
smooth_fade(&tail, &redundant_audio[ch * f2_5..], &mut pcm[o..], f2_5, ch, window, self.rate);
|
|
}
|
|
if redundancy && celt_to_silk {
|
|
pcm[..f2_5 * ch].copy_from_slice(&redundant_audio[..f2_5 * ch]);
|
|
let cur: Vec<f32> = pcm[ch * f2_5..ch * 2 * f2_5].to_vec();
|
|
smooth_fade(&redundant_audio[ch * f2_5..], &cur, &mut pcm[ch * f2_5..], f2_5, ch, window, self.rate);
|
|
}
|
|
if transition {
|
|
if audiosize >= f5 {
|
|
pcm[..f2_5 * ch].copy_from_slice(&pcm_transition[..f2_5 * ch]);
|
|
let cur: Vec<f32> = pcm[ch * f2_5..ch * 2 * f2_5].to_vec();
|
|
smooth_fade(&pcm_transition[ch * f2_5..], &cur, &mut pcm[ch * f2_5..], f2_5, ch, window, self.rate);
|
|
} else {
|
|
let cur: Vec<f32> = pcm[..ch * f2_5].to_vec();
|
|
smooth_fade(&pcm_transition, &cur, pcm, f2_5, ch, window, self.rate);
|
|
}
|
|
}
|
|
if self.gain_q8 != 0 {
|
|
let g = (6.488_141e-4f32 * self.gain_q8 as f32).exp2();
|
|
for v in pcm[..frame_size * ch].iter_mut() {
|
|
*v *= g;
|
|
}
|
|
}
|
|
self.range_final = match dec.as_deref() {
|
|
Some(d) if data_len > 1 => d.rng() ^ redundant_rng,
|
|
_ => 0,
|
|
};
|
|
self.prev_mode = Some(mode);
|
|
self.prev_redundancy = redundancy && !celt_to_silk;
|
|
Ok(audiosize)
|
|
}
|
|
}
|
|
|
|
fn smooth_fade(in1: &[f32], in2: &[f32], out: &mut [f32], overlap: usize, ch: usize, window: &[f32; OVERLAP], rate: u32) {
|
|
let inc = (48000 / rate) as usize;
|
|
for c in 0..ch {
|
|
for i in 0..overlap {
|
|
let w = window[i * inc] * window[i * inc];
|
|
out[i * ch + c] = w * in2[i * ch + c] + (1.0 - w) * in1[i * ch + c];
|
|
}
|
|
}
|
|
}
|