Skip to content

Commit baba741

Browse files
authored
Merge pull request #138 from ebarnard/alac
Support alac (Apple lossless audio codec)
2 parents 77c1e14 + 52044c7 commit baba741

File tree

5 files changed

+74
-0
lines changed

5 files changed

+74
-0
lines changed

mp4parse/src/boxes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,5 @@ box_database!(
138138
MP3AudioSampleEntry 0x2e6d7033, // ".mp3" - from F4V.
139139
CompositionOffsetBox 0x63747473, // "ctts"
140140
LPCMAudioSampleEntry 0x6C70636D, // "lpcm" - quicktime atom
141+
ALACSpecificBox 0x616C6163, // "alac" - Also used by ALACSampleEntry
141142
);

mp4parse/src/lib.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ pub enum AudioCodecSpecific {
316316
ES_Descriptor(ES_Descriptor),
317317
FLACSpecificBox(FLACSpecificBox),
318318
OpusSpecificBox(OpusSpecificBox),
319+
ALACSpecificBox(ALACSpecificBox),
319320
MP3,
320321
LPCM,
321322
}
@@ -392,6 +393,13 @@ pub struct OpusSpecificBox {
392393
channel_mapping_table: Option<ChannelMappingTable>,
393394
}
394395

396+
/// Represent an ALACSpecificBox 'alac'
397+
#[derive(Debug, Clone)]
398+
pub struct ALACSpecificBox {
399+
version: u8,
400+
pub data: Vec<u8>,
401+
}
402+
395403
#[derive(Debug)]
396404
pub struct MovieExtendsBox {
397405
pub fragment_duration: Option<MediaScaledTime>,
@@ -464,6 +472,7 @@ pub enum CodecType {
464472
EncryptedVideo,
465473
EncryptedAudio,
466474
LPCM, // QT
475+
ALAC,
467476
}
468477

469478
impl Default for CodecType {
@@ -1689,6 +1698,28 @@ pub fn serialize_opus_header<W: byteorder::WriteBytesExt + std::io::Write>(opus:
16891698
Ok(())
16901699
}
16911700

1701+
/// Parse `ALACSpecificBox`.
1702+
fn read_alac<T: Read>(src: &mut BMFFBox<T>) -> Result<ALACSpecificBox> {
1703+
let (version, flags) = read_fullbox_extra(src)?;
1704+
if version != 0 {
1705+
return Err(Error::Unsupported("unknown alac (ALAC) version"));
1706+
}
1707+
if flags != 0 {
1708+
return Err(Error::InvalidData("no-zero alac (ALAC) flags"));
1709+
}
1710+
1711+
let length = src.bytes_left();
1712+
if length != 24 && length != 48 {
1713+
return Err(Error::InvalidData("ALACSpecificBox magic cookie is the wrong size"));
1714+
}
1715+
let data = read_buf(src, length)?;
1716+
1717+
Ok(ALACSpecificBox {
1718+
version: version,
1719+
data: data,
1720+
})
1721+
}
1722+
16921723
/// Parse a hdlr box.
16931724
fn read_hdlr<T: Read>(src: &mut BMFFBox<T>) -> Result<HandlerBox> {
16941725
let (_, _) = read_fullbox_extra(src)?;
@@ -1900,6 +1931,15 @@ fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<(CodecType,
19001931
codec_type = CodecType::Opus;
19011932
codec_specific = Some(AudioCodecSpecific::OpusSpecificBox(dops));
19021933
}
1934+
BoxType::ALACSpecificBox => {
1935+
if name != BoxType::ALACSpecificBox ||
1936+
codec_specific.is_some() {
1937+
return Err(Error::InvalidData("malformed audio sample entry"));
1938+
}
1939+
let alac = read_alac(&mut b)?;
1940+
codec_type = CodecType::ALAC;
1941+
codec_specific = Some(AudioCodecSpecific::ALACSpecificBox(alac));
1942+
}
19031943
BoxType::QTWaveAtom => {
19041944
let qt_esds = read_qt_wave_atom(&mut b)?;
19051945
codec_type = qt_esds.audio_codec;

mp4parse/src/tests.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,28 @@ fn serialize_opus_header() {
653653
]);
654654
}
655655

656+
#[test]
657+
fn read_alac() {
658+
let mut stream = make_box(BoxSize::Auto, b"alac", |s| {
659+
s.append_repeated(0, 6) // reserved
660+
.B16(1) // data reference index
661+
.B32(0) // reserved
662+
.B32(0) // reserved
663+
.B16(2) // channel count
664+
.B16(16) // bits per sample
665+
.B16(0) // pre_defined
666+
.B16(0) // reserved
667+
.B32(44100 << 16) // Sample rate
668+
.append_bytes(&make_fullbox(BoxSize::Auto, b"alac", 0, |s| {
669+
s.append_bytes(&vec![0xfa; 24])
670+
}).into_inner())
671+
});
672+
let mut iter = super::BoxIter::new(&mut stream);
673+
let mut stream = iter.next_box().unwrap().unwrap();
674+
let r = super::read_audio_sample_entry(&mut stream);
675+
assert!(r.is_ok());
676+
}
677+
656678
#[test]
657679
fn avcc_limit() {
658680
let mut stream = make_box(BoxSize::Auto, b"avc1", |s| {

mp4parse/tests/public.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ fn public_api() {
9696
assert!(opus.version > 0);
9797
"Opus"
9898
}
99+
mp4::AudioCodecSpecific::ALACSpecificBox(alac) => {
100+
assert!(alac.data.len() == 24 || alac.data.len() == 48);
101+
"ALAC"
102+
}
99103
mp4::AudioCodecSpecific::MP3 => {
100104
"MP3"
101105
}

mp4parse_capi/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ pub enum Mp4parseCodec {
9898
Jpeg, // for QT JPEG atom in video track
9999
Ac3,
100100
Ec3,
101+
Alac,
101102
}
102103

103104
impl Default for Mp4parseCodec {
@@ -429,6 +430,8 @@ pub unsafe extern fn mp4parse_get_track_info(parser: *mut Mp4parseParser, track_
429430
Mp4parseCodec::Unknown,
430431
AudioCodecSpecific::MP3 =>
431432
Mp4parseCodec::Mp3,
433+
AudioCodecSpecific::ALACSpecificBox(_) =>
434+
Mp4parseCodec::Alac,
432435
},
433436
Some(SampleEntry::Video(ref video)) => match video.codec_specific {
434437
VideoCodecSpecific::VPxConfig(_) =>
@@ -565,6 +568,10 @@ pub unsafe extern fn mp4parse_get_track_audio_info(parser: *mut Mp4parseParser,
565568
}
566569
}
567570
}
571+
AudioCodecSpecific::ALACSpecificBox(ref alac) => {
572+
(*info).extra_data.length = alac.data.len() as u32;
573+
(*info).extra_data.data = alac.data.as_ptr();
574+
}
568575
AudioCodecSpecific::MP3 | AudioCodecSpecific::LPCM => (),
569576
}
570577

0 commit comments

Comments
 (0)