Skip to main content

banished_wma/
lib.rs

1#![doc = include_str!("../README.md")]
2#![doc(
3    html_logo_url = "https://tanepiper.github.io/unbanished/assets/un-banished-icon.svg",
4    html_favicon_url = "https://tanepiper.github.io/unbanished/favicon.svg"
5)]
6
7mod bits;
8mod decoder;
9mod mdct;
10mod tables;
11mod vlc;
12
13pub use decoder::Decoder;
14
15/// `WAVE_FORMAT_WMAUDIO2`.
16pub const WMAV2: u16 = 0x161;
17
18/// Encoder flags implied by xWMA, which carries no codec extradata: exponent VLCs, bit
19/// reservoir and variable block lengths, with the widest block-size range.
20pub const XWMA_FLAGS: u16 = 0x1f;
21
22/// Stream parameters, mostly from the `WAVEFORMATEX` header.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct Params {
25    /// Format tag; must be [`WMAV2`].
26    pub tag: u16,
27    /// The number of channels: 1 or 2.
28    pub channels: u16,
29    /// The sample rate, in samples per second.
30    pub sample_rate: u32,
31    /// The average data rate, in bytes per second.
32    pub avg_bytes_per_sec: u32,
33    /// Packet size in bytes.
34    pub block_align: u16,
35    /// The codec's `flags2` word (extradata bytes 4..6 in ASF); [`XWMA_FLAGS`] for xWMA.
36    pub flags: u16,
37}
38
39impl Params {
40    /// Parameters for an xWMA stream.
41    pub fn xwma(channels: u16, sample_rate: u32, avg_bytes_per_sec: u32, block_align: u16) -> Self {
42        Params {
43            tag: WMAV2,
44            channels,
45            sample_rate,
46            avg_bytes_per_sec,
47            block_align,
48            flags: XWMA_FLAGS,
49        }
50    }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54/// Why a stream could not be decoded.
55pub enum Error {
56    /// A valid stream using a feature this decoder does not implement.
57    Unsupported(&'static str),
58    /// Corrupt or inconsistent data.
59    Invalid(&'static str),
60}
61
62impl std::fmt::Display for Error {
63    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
64        match self {
65            Error::Unsupported(m) => write!(f, "unsupported WMA stream: {m}"),
66            Error::Invalid(m) => write!(f, "invalid WMA data: {m}"),
67        }
68    }
69}
70
71impl std::error::Error for Error {}
72
73/// A result with this crate's [`Error`].
74pub type Result<T> = std::result::Result<T, Error>;
75
76/// Decode a whole stream of `block_align`-sized packets to interleaved 16-bit PCM.
77///
78/// Like FFmpeg, packets that fail to decode are skipped, and the output ends with one extra
79/// frame holding the final overlap tail.
80pub fn decode(params: &Params, packets: &[u8]) -> Result<Vec<i16>> {
81    let mut dec = Decoder::new(params)?;
82    let mut pcm = Vec::new();
83    for packet in packets.chunks(usize::from(params.block_align)) {
84        let _ = dec.decode_packet(packet, &mut pcm);
85    }
86    dec.flush(&mut pcm);
87    Ok(to_i16(&pcm))
88}
89
90/// Convert `[-1, 1)` float samples to 16-bit with rounding and clipping.
91pub fn to_i16(samples: &[f32]) -> Vec<i16> {
92    samples
93        .iter()
94        .map(|&s| (s * 32768.0).round_ties_even().clamp(-32768.0, 32767.0) as i16)
95        .collect()
96}