-
Notifications
You must be signed in to change notification settings - Fork 149
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an ADPCM decoder supporting Microsoft and IMA ADPCM codecs.
- Loading branch information
Showing
14 changed files
with
713 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.