Skip to content

Commit

Permalink
adpcm: Add ADPCM Decoder (#160)
Browse files Browse the repository at this point in the history
Add an ADPCM decoder supporting Microsoft and IMA
ADPCM codecs.
  • Loading branch information
geckoxx authored Nov 8, 2022
1 parent 2323cbc commit 4c508b2
Show file tree
Hide file tree
Showing 14 changed files with 713 additions and 37 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ BlackHoleFox [https://github.com/blackholefox]
darksv [https://github.com/darksv]
djugei [https://github.com/djugei]
FelixMcFelix [https://github.com/FelixMcFelix]
geckoxx [https://github.com/geckoxx]
Herohtar [https://github.com/herohtar]
nicholaswyoung [https://github.com/nicholaswyoung]
richardmitic [https://github.com/richardmitic]
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"symphonia-bundle-flac",
"symphonia-bundle-mp3",
"symphonia-codec-aac",
"symphonia-codec-adpcm",
"symphonia-codec-alac",
"symphonia-codec-opus",
"symphonia-codec-pcm",
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ A status of *great* indicates that major development is complete and that the fe
| Codec | Status | Gapless | Feature Flag | Default | Crate |
|------------------------------|-----------|---------|--------------|---------|----------------------------|
| AAC-LC | Great | No | `aac` | No | [`symphonia-codec-aac`] |
| ADPCM | Good | Yes | `adpcm` | Yes | [`symphonia-codec-adpcm`] |
| ALAC | Great | Yes | `alac` | No | [`symphonia-codec-alac`] |
| HE-AAC (AAC+, aacPlus) | - | - | `aac` | No | [`symphonia-codec-aac`] |
| HE-AACv2 (eAAC+, aacPlus v2) | - | - | `aac` | No | [`symphonia-codec-aac`] |
Expand All @@ -113,6 +114,7 @@ A status of *great* indicates that major development is complete and that the fe
A `symphonia-bundle-*` package is a combination of a decoder and a native demuxer.

[`symphonia-codec-aac`]: https://docs.rs/symphonia-codec-aac
[`symphonia-codec-adpcm`]: https://docs.rs/symphonia-codec-adpcm
[`symphonia-codec-alac`]: https://docs.rs/symphonia-codec-alac
[`symphonia-bundle-flac`]: https://docs.rs/symphonia-bundle-flac
[`symphonia-bundle-mp3`]: https://docs.rs/symphonia-bundle-mp3
Expand Down
17 changes: 17 additions & 0 deletions symphonia-codec-adpcm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "symphonia-codec-adpcm"
version = "0.5.1"
description = "Pure Rust ADPCM audio decoder (a part of project Symphonia)."
homepage = "https://github.com/pdeljanov/Symphonia"
repository = "https://github.com/pdeljanov/Symphonia"
authors = ["Philip Deljanov <[email protected]>", "Johannes Hackel <[email protected]>"]
license = "MPL-2.0"
readme = "README.md"
categories = ["multimedia", "multimedia::audio", "multimedia::encoding"]
keywords = ["audio", "codec", "decoder", "adpcm"]
edition = "2018"
rust-version = "1.53"

[dependencies]
log = "0.4"
symphonia-core = { version = "0.5", path = "../symphonia-core" }
26 changes: 26 additions & 0 deletions symphonia-codec-adpcm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Symphonia ADPCM Codec

[![Docs](https://docs.rs/symphonia-codec-adpcm/badge.svg)](https://docs.rs/symphonia-codec-adpcm)

ADPCM audio decoders for Project Symphonia.

**Note:** This crate is part of Symphonia. Please use the [`symphonia`](https://crates.io/crates/symphonia) crate instead of this one directly.

## Support

The following ADPCM encodings are supported:

* Microsoft ADPCM
* ADPCM IMA WAV

Only 4 bits per sample and only mono and stereo channels are supported.

## License

Symphonia is provided under the MPL v2.0 license. Please refer to the LICENSE file for more details.

## Contributing

Symphonia is an open-source project and contributions are very welcome! If you would like to make a large contribution, please raise an issue ahead of time to make sure your efforts fit into the project goals, and that no duplication of efforts occurs.

All contributors will be credited within the CONTRIBUTORS file.
99 changes: 99 additions & 0 deletions symphonia-codec-adpcm/src/codec_ima.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Symphonia
// Copyright (c) 2019-2022 The Project Symphonia Developers.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use symphonia_core::errors::Result;
use symphonia_core::io::ReadBytes;
use symphonia_core::util::clamp::clamp_i16;

use crate::common::{from_i16_shift, u16_to_i32, Nibble};

#[rustfmt::skip]
const IMA_INDEX_TABLE: [i32; 16] = [
-1, -1, -1, -1, 2, 4, 6, 8,
-1, -1, -1, -1, 2, 4, 6, 8,
];

#[rustfmt::skip]
const IMA_STEP_TABLE: [i32; 89] = [
7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767,
];

/// `AdpcmImaBlockStatus` contains values to decode a block
struct AdpcmImaBlockStatus {
predictor: i32,
step_index: i32,
}

impl AdpcmImaBlockStatus {
fn read_preamble<B: ReadBytes>(stream: &mut B) -> Result<Self> {
let predictor = u16_to_i32!(stream.read_u16()?);
let step_index = stream.read_byte()? as i32;
//reserved byte
let _ = stream.read_byte()?;
let status = Self { predictor, step_index };
Ok(status)
}

fn expand_nibble(&mut self, byte: u8, nibble: Nibble) -> i32 {
let nibble = nibble.get_nibble(byte);
let step = IMA_STEP_TABLE[self.step_index as usize];
let sign = (nibble & 0x08) != 0;
let delta = (nibble & 0x07) as i32;
let diff = ((2 * delta + 1) * step) >> 3;
let predictor = if sign { self.predictor - diff } else { self.predictor + diff };
self.predictor = clamp_i16(predictor) as i32;
self.step_index = (self.step_index + IMA_INDEX_TABLE[nibble as usize]).min(88).max(0);
from_i16_shift!(self.predictor)
}
}

pub(crate) fn decode_mono<B: ReadBytes>(
stream: &mut B,
buffer: &mut [i32],
frames_per_block: usize,
) -> Result<()> {
let data_bytes_per_channel = (frames_per_block - 1) / 2;
let mut status = AdpcmImaBlockStatus::read_preamble(stream)?;
buffer[0] = from_i16_shift!(status.predictor);
for byte in 0..data_bytes_per_channel {
let nibbles = stream.read_u8()?;
buffer[1 + byte * 2] = status.expand_nibble(nibbles, Nibble::Lower);
buffer[1 + byte * 2 + 1] = status.expand_nibble(nibbles, Nibble::Upper);
}
Ok(())
}

pub(crate) fn decode_stereo<B: ReadBytes>(
stream: &mut B,
buffers: [&mut [i32]; 2],
frames_per_block: usize,
) -> Result<()> {
let data_bytes_per_channel = frames_per_block - 1;
let mut status =
[AdpcmImaBlockStatus::read_preamble(stream)?, AdpcmImaBlockStatus::read_preamble(stream)?];
buffers[0][0] = from_i16_shift!(status[0].predictor);
buffers[1][0] = from_i16_shift!(status[1].predictor);
for index in 0..data_bytes_per_channel {
let channel = (index / 4) & 1;
let offset = (index / 8) * 8;
let byte = index % 4;
let nibbles = stream.read_u8()?;
buffers[channel][1 + offset + byte * 2] =
status[channel].expand_nibble(nibbles, Nibble::Lower);
buffers[channel][1 + offset + byte * 2 + 1] =
status[channel].expand_nibble(nibbles, Nibble::Upper);
}
Ok(())
}
139 changes: 139 additions & 0 deletions symphonia-codec-adpcm/src/codec_ms.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Symphonia
// Copyright (c) 2019-2022 The Project Symphonia Developers.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use symphonia_core::errors::{unsupported_error, Result};
use symphonia_core::io::ReadBytes;
use symphonia_core::util::clamp::clamp_i16;

use crate::common::{from_i16_shift, u16_to_i32, Nibble};

#[rustfmt::skip]
const MS_ADAPTATION_TABLE: [i32; 16] = [
230, 230, 230, 230, 307, 409, 512, 614,
768, 614, 512, 409, 307, 230, 230, 230,
];

const MS_ADAPT_COEFFS1: [i32; 7] = [256, 512, 0, 192, 240, 460, 392];
const MS_ADAPT_COEFFS2: [i32; 7] = [0, -256, 0, 64, 0, -208, -232];

const DELTA_MIN: i32 = 16;

macro_rules! check_block_predictor {
($block_predictor:ident, $max:expr) => {
if $block_predictor > $max {
return unsupported_error("adpcm: block predictor exceeds range.");
}
};
}

pub fn signed_nibble(nibble: u8) -> i8 {
if (nibble & 0x08) != 0 {
nibble as i8 - 0x10
}
else {
nibble as i8
}
}

/// `AdpcmMsBlockStatus` contains values to decode a block
struct AdpcmMsBlockStatus {
coeff1: i32,
coeff2: i32,
delta: i32,
sample1: i32,
sample2: i32,
}

impl AdpcmMsBlockStatus {
fn read_mono_preamble<B: ReadBytes>(stream: &mut B) -> Result<Self> {
let block_predictor = stream.read_byte()? as usize;
check_block_predictor!(block_predictor, 6);
let status = Self {
coeff1: MS_ADAPT_COEFFS1[block_predictor],
coeff2: MS_ADAPT_COEFFS2[block_predictor],
delta: u16_to_i32!(stream.read_u16()?),
sample1: u16_to_i32!(stream.read_u16()?),
sample2: u16_to_i32!(stream.read_u16()?),
};
Ok(status)
}

fn read_stereo_preamble<B: ReadBytes>(stream: &mut B) -> Result<(Self, Self)> {
let left_block_predictor = stream.read_byte()? as usize;
check_block_predictor!(left_block_predictor, 6);
let right_block_predictor = stream.read_byte()? as usize;
check_block_predictor!(right_block_predictor, 6);
let left_delta = u16_to_i32!(stream.read_u16()?);
let right_delta = u16_to_i32!(stream.read_u16()?);
let left_sample1 = u16_to_i32!(stream.read_u16()?);
let right_sample1 = u16_to_i32!(stream.read_u16()?);
let left_sample2 = u16_to_i32!(stream.read_u16()?);
let right_sample2 = u16_to_i32!(stream.read_u16()?);
Ok((
Self {
coeff1: MS_ADAPT_COEFFS1[left_block_predictor],
coeff2: MS_ADAPT_COEFFS2[left_block_predictor],
delta: left_delta,
sample1: left_sample1,
sample2: left_sample2,
},
Self {
coeff1: MS_ADAPT_COEFFS1[right_block_predictor],
coeff2: MS_ADAPT_COEFFS2[right_block_predictor],
delta: right_delta,
sample1: right_sample1,
sample2: right_sample2,
},
))
}

fn expand_nibble(&mut self, byte: u8, nibble: Nibble) -> i32 {
let nibble = nibble.get_nibble(byte);
let signed_nibble = signed_nibble(nibble) as i32;
let predictor = ((self.sample1 * self.coeff1) + (self.sample2 * self.coeff2)) / 256
+ signed_nibble * self.delta;
self.sample2 = self.sample1;
self.sample1 = clamp_i16(predictor) as i32;
self.delta = (MS_ADAPTATION_TABLE[nibble as usize] * self.delta) / 256;
self.delta = self.delta.max(DELTA_MIN);
from_i16_shift!(self.sample1)
}
}

pub(crate) fn decode_mono<B: ReadBytes>(
stream: &mut B,
buffer: &mut [i32],
frames_per_block: usize,
) -> Result<()> {
let mut status = AdpcmMsBlockStatus::read_mono_preamble(stream)?;
buffer[0] = from_i16_shift!(status.sample2);
buffer[1] = from_i16_shift!(status.sample1);
for byte in 1..(frames_per_block / 2) {
let nibbles = stream.read_u8()?;
buffer[byte * 2] = status.expand_nibble(nibbles, Nibble::Upper);
buffer[byte * 2 + 1] = status.expand_nibble(nibbles, Nibble::Lower);
}
Ok(())
}

pub(crate) fn decode_stereo<B: ReadBytes>(
stream: &mut B,
buffers: [&mut [i32]; 2],
frames_per_block: usize,
) -> Result<()> {
let (mut left_status, mut right_status) = AdpcmMsBlockStatus::read_stereo_preamble(stream)?;
buffers[0][0] = from_i16_shift!(left_status.sample2);
buffers[0][1] = from_i16_shift!(left_status.sample1);
buffers[1][0] = from_i16_shift!(right_status.sample2);
buffers[1][1] = from_i16_shift!(right_status.sample1);
for frame in 2..frames_per_block {
let nibbles = stream.read_u8()?;
buffers[0][frame] = left_status.expand_nibble(nibbles, Nibble::Upper);
buffers[1][frame] = right_status.expand_nibble(nibbles, Nibble::Lower);
}
Ok(())
}
36 changes: 36 additions & 0 deletions symphonia-codec-adpcm/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Symphonia
// Copyright (c) 2019-2022 The Project Symphonia Developers.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

/// `Nibble` represents the lower or upper 4 bits of a byte
pub(crate) enum Nibble {
Upper,
Lower,
}

impl Nibble {
pub fn get_nibble(&self, byte: u8) -> u8 {
match self {
Nibble::Upper => byte >> 4,
Nibble::Lower => byte & 0x0F,
}
}
}

macro_rules! u16_to_i32 {
($input:expr) => {
$input as i16 as i32
};
}

macro_rules! from_i16_shift {
($input:expr) => {
($input as i32) << 16
};
}

pub(crate) use from_i16_shift;
pub(crate) use u16_to_i32;
Loading

0 comments on commit 4c508b2

Please sign in to comment.