Skip to content

Commit d35eeb2

Browse files
adpcm: Support IMA QuickTime (#384)
Refactor and consolidate common IMA implementation details to better support different variations of ADPCM IMA. Add IMA QuickTime support. Co-authored-by: hikari_no_yume <hikari@noyu.me>
1 parent d74f29c commit d35eeb2

File tree

6 files changed

+174
-107
lines changed

6 files changed

+174
-107
lines changed

CONTRIBUTORS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Thom Chiovoloni <chiovolonit@gmail.com>
1717
#
1818
# Please keep this section sorted in ascending order.
1919

20+
acieslewicz [https://github.com/acieslewicz]
2021
aschey [https://github.com/aschey]
2122
BlackHoleFox [https://github.com/blackholefox]
2223
darksv [https://github.com/darksv]
@@ -26,6 +27,7 @@ erikas-taroza [https://github.com/erikas-taroza]
2627
FelixMcFelix [https://github.com/FelixMcFelix]
2728
geckoxx [https://github.com/geckoxx]
2829
Herohtar [https://github.com/herohtar]
30+
hikari_no_yume [https://github.com/hikari-no-yume]
2931
nicholaswyoung [https://github.com/nicholaswyoung]
3032
richardmitic [https://github.com/richardmitic]
3133
sscobici [https://github.com/sscobici]

symphonia-codec-adpcm/src/codec_ima.rs

Lines changed: 0 additions & 102 deletions
This file was deleted.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Symphonia
2+
// Copyright (c) 2019-2025 The Project Symphonia Developers.
3+
//
4+
// This Source Code Form is subject to the terms of the Mozilla Public
5+
// License, v. 2.0. If a copy of the MPL was not distributed with this
6+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
8+
use symphonia_core::errors::Result;
9+
use symphonia_core::io::ReadBytes;
10+
11+
use crate::common::{u16_to_i32, Nibble};
12+
use crate::common_ima::AdpcmImaBlockStatus;
13+
14+
fn read_preamble<B: ReadBytes>(stream: &mut B) -> Result<AdpcmImaBlockStatus> {
15+
let header = stream.read_be_u16()?;
16+
let predictor = u16_to_i32!(header & 0xFF80);
17+
let step_index = ((header & 0x7F) as usize).min(88) as i32;
18+
19+
let status = AdpcmImaBlockStatus { predictor, step_index };
20+
Ok(status)
21+
}
22+
23+
pub(crate) fn decode_mono<B: ReadBytes>(
24+
stream: &mut B,
25+
buffer: &mut [i32],
26+
_: usize,
27+
) -> Result<()> {
28+
// IMA4 apparently always uses 34 bytes packets
29+
// https://wiki.multimedia.cx/index.php/Apple_QuickTime_IMA_ADPCM
30+
let mut status = read_preamble(stream)?;
31+
for byte in 0..32 {
32+
let nibbles = stream.read_u8()?;
33+
buffer[byte * 2] = status.expand_nibble(nibbles, Nibble::Lower);
34+
buffer[byte * 2 + 1] = status.expand_nibble(nibbles, Nibble::Upper);
35+
}
36+
Ok(())
37+
}
38+
39+
pub(crate) fn decode_stereo<B: ReadBytes>(
40+
stream: &mut B,
41+
buffers: [&mut [i32]; 2],
42+
_: usize,
43+
) -> Result<()> {
44+
decode_mono(stream, buffers[0], 0)?;
45+
decode_mono(stream, buffers[1], 0)?;
46+
Ok(())
47+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Symphonia
2+
// Copyright (c) 2019-2022 The Project Symphonia Developers.
3+
//
4+
// This Source Code Form is subject to the terms of the Mozilla Public
5+
// License, v. 2.0. If a copy of the MPL was not distributed with this
6+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
8+
use symphonia_core::errors::{decode_error, Result};
9+
use symphonia_core::io::ReadBytes;
10+
11+
use crate::common::{from_i16_shift, u16_to_i32, Nibble};
12+
use crate::common_ima::AdpcmImaBlockStatus;
13+
14+
fn read_preamble<B: ReadBytes>(stream: &mut B) -> Result<AdpcmImaBlockStatus> {
15+
let predictor = u16_to_i32!(stream.read_u16()?);
16+
let step_index = stream.read_byte()? as i32;
17+
if step_index > 88 {
18+
return decode_error("adpcm (ima): invalid step index");
19+
}
20+
//reserved byte
21+
let _ = stream.read_byte()?;
22+
let status = AdpcmImaBlockStatus { predictor, step_index };
23+
Ok(status)
24+
}
25+
26+
pub(crate) fn decode_mono<B: ReadBytes>(
27+
stream: &mut B,
28+
buffer: &mut [i32],
29+
frames_per_block: usize,
30+
) -> Result<()> {
31+
let data_bytes_per_channel = (frames_per_block - 1) / 2;
32+
let mut status = read_preamble(stream)?;
33+
buffer[0] = from_i16_shift!(status.predictor);
34+
for byte in 0..data_bytes_per_channel {
35+
let nibbles = stream.read_u8()?;
36+
buffer[1 + byte * 2] = status.expand_nibble(nibbles, Nibble::Lower);
37+
buffer[1 + byte * 2 + 1] = status.expand_nibble(nibbles, Nibble::Upper);
38+
}
39+
Ok(())
40+
}
41+
42+
pub(crate) fn decode_stereo<B: ReadBytes>(
43+
stream: &mut B,
44+
buffers: [&mut [i32]; 2],
45+
frames_per_block: usize,
46+
) -> Result<()> {
47+
let data_bytes_per_channel = frames_per_block - 1;
48+
let mut status = [read_preamble(stream)?, read_preamble(stream)?];
49+
buffers[0][0] = from_i16_shift!(status[0].predictor);
50+
buffers[1][0] = from_i16_shift!(status[1].predictor);
51+
for index in 0..data_bytes_per_channel {
52+
let channel = (index / 4) & 1;
53+
let offset = (index / 8) * 8;
54+
let byte = index % 4;
55+
let nibbles = stream.read_u8()?;
56+
buffers[channel][1 + offset + byte * 2] =
57+
status[channel].expand_nibble(nibbles, Nibble::Lower);
58+
buffers[channel][1 + offset + byte * 2 + 1] =
59+
status[channel].expand_nibble(nibbles, Nibble::Upper);
60+
}
61+
Ok(())
62+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Symphonia
2+
// Copyright (c) 2019-2022 The Project Symphonia Developers.
3+
//
4+
// This Source Code Form is subject to the terms of the Mozilla Public
5+
// License, v. 2.0. If a copy of the MPL was not distributed with this
6+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
8+
use symphonia_core::util::clamp::clamp_i16;
9+
10+
use crate::common::{from_i16_shift, Nibble};
11+
12+
#[rustfmt::skip]
13+
const IMA_INDEX_TABLE: [i32; 16] = [
14+
-1, -1, -1, -1, 2, 4, 6, 8,
15+
-1, -1, -1, -1, 2, 4, 6, 8,
16+
];
17+
18+
#[rustfmt::skip]
19+
pub(crate) const IMA_STEP_TABLE: [i32; 89] = [
20+
7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
21+
19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
22+
50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
23+
130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
24+
337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
25+
876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
26+
2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
27+
5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
28+
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767,
29+
];
30+
31+
/// `AdpcmImaBlockStatus` contains values to decode a block
32+
pub(crate) struct AdpcmImaBlockStatus {
33+
pub(crate) predictor: i32,
34+
pub(crate) step_index: i32,
35+
}
36+
37+
impl AdpcmImaBlockStatus {
38+
pub(crate) fn expand_nibble(&mut self, byte: u8, nibble: Nibble) -> i32 {
39+
let nibble = nibble.get_nibble(byte);
40+
let step = IMA_STEP_TABLE[self.step_index as usize];
41+
let sign = (nibble & 0x08) != 0;
42+
let delta = (nibble & 0x07) as i32;
43+
let diff = ((2 * delta + 1) * step) >> 3;
44+
let predictor = if sign { self.predictor - diff } else { self.predictor + diff };
45+
self.predictor = clamp_i16(predictor) as i32;
46+
self.step_index = (self.step_index + IMA_INDEX_TABLE[nibble as usize]).clamp(0, 88);
47+
from_i16_shift!(self.predictor)
48+
}
49+
}

symphonia-codec-adpcm/src/lib.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,31 +21,37 @@ use symphonia_core::support_audio_codec;
2121
use symphonia_core::audio::{
2222
AsGenericAudioBufferRef, Audio, AudioBuffer, AudioMut, AudioSpec, GenericAudioBufferRef,
2323
};
24-
use symphonia_core::codecs::audio::well_known::{CODEC_ID_ADPCM_IMA_WAV, CODEC_ID_ADPCM_MS};
24+
use symphonia_core::codecs::audio::well_known::{
25+
CODEC_ID_ADPCM_IMA_QT, CODEC_ID_ADPCM_IMA_WAV, CODEC_ID_ADPCM_MS,
26+
};
2527
use symphonia_core::codecs::audio::{AudioCodecId, AudioCodecParameters, AudioDecoderOptions};
2628
use symphonia_core::codecs::audio::{AudioDecoder, FinalizeResult};
2729
use symphonia_core::errors::{unsupported_error, Result};
2830
use symphonia_core::formats::Packet;
2931
use symphonia_core::io::ReadBytes;
3032

31-
mod codec_ima;
33+
mod codec_ima_qt;
34+
mod codec_ima_wav;
3235
mod codec_ms;
3336
mod common;
37+
mod common_ima;
3438

3539
fn is_supported_adpcm_codec(codec_id: AudioCodecId) -> bool {
36-
matches!(codec_id, CODEC_ID_ADPCM_MS | CODEC_ID_ADPCM_IMA_WAV)
40+
matches!(codec_id, CODEC_ID_ADPCM_MS | CODEC_ID_ADPCM_IMA_WAV | CODEC_ID_ADPCM_IMA_QT)
3741
}
3842

3943
enum InnerDecoder {
4044
AdpcmMs,
4145
AdpcmIma,
46+
AdpcmImaQT,
4247
}
4348

4449
impl InnerDecoder {
4550
fn decode_mono_fn<B: ReadBytes>(&self) -> impl Fn(&mut B, &mut [i32], usize) -> Result<()> {
4651
match *self {
4752
InnerDecoder::AdpcmMs => codec_ms::decode_mono,
48-
InnerDecoder::AdpcmIma => codec_ima::decode_mono,
53+
InnerDecoder::AdpcmIma => codec_ima_wav::decode_mono,
54+
InnerDecoder::AdpcmImaQT => codec_ima_qt::decode_mono,
4955
}
5056
}
5157

@@ -54,7 +60,8 @@ impl InnerDecoder {
5460
) -> impl Fn(&mut B, [&mut [i32]; 2], usize) -> Result<()> {
5561
match *self {
5662
InnerDecoder::AdpcmMs => codec_ms::decode_stereo,
57-
InnerDecoder::AdpcmIma => codec_ima::decode_stereo,
63+
InnerDecoder::AdpcmIma => codec_ima_wav::decode_stereo,
64+
InnerDecoder::AdpcmImaQT => codec_ima_qt::decode_stereo,
5865
}
5966
}
6067
}
@@ -101,6 +108,7 @@ impl AdpcmDecoder {
101108
let inner_decoder = match params.codec {
102109
CODEC_ID_ADPCM_MS => InnerDecoder::AdpcmMs,
103110
CODEC_ID_ADPCM_IMA_WAV => InnerDecoder::AdpcmIma,
111+
CODEC_ID_ADPCM_IMA_QT => InnerDecoder::AdpcmImaQT,
104112
_ => return unsupported_error("adpcm: codec is unsupported"),
105113
};
106114

@@ -199,6 +207,7 @@ impl RegisterableAudioDecoder for AdpcmDecoder {
199207
&[
200208
support_audio_codec!(CODEC_ID_ADPCM_MS, "adpcm_ms", "Microsoft ADPCM"),
201209
support_audio_codec!(CODEC_ID_ADPCM_IMA_WAV, "adpcm_ima_wav", "ADPCM IMA WAV"),
210+
support_audio_codec!(CODEC_ID_ADPCM_IMA_QT, "adpcm_ima_qt", "ADPCM IMA QT"),
202211
]
203212
}
204213
}

0 commit comments

Comments
 (0)