Skip to content

Commit 4e876e4

Browse files
authored
Merge pull request #48 from alfredoyang/parse_protect_box
Parsing sinf box for enca/encv track
2 parents 0c94936 + a96eabb commit 4e876e4

File tree

6 files changed

+219
-19
lines changed

6 files changed

+219
-19
lines changed

mp4parse/src/boxes.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,8 @@ box_database!(
6060
MovieExtendsHeaderBox 0x6d656864, // "mehd"
6161
QTWaveAtom 0x77617665, // "wave" - quicktime atom
6262
ProtectionSystemSpecificHeaderBox 0x70737368, // "pssh"
63+
SchemeInformationBox 0x73636869, // "schi"
64+
TrackEncryptionBox 0x74656e63, // "tenc"
65+
ProtectionSchemeInformationBox 0x73696e66, // "sinf"
66+
OriginalFormatBox 0x66726d61, // "frma"
6367
);

mp4parse/src/lib.rs

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ pub struct AudioSampleEntry {
228228
pub samplesize: u16,
229229
pub samplerate: u32,
230230
pub codec_specific: AudioCodecSpecific,
231+
pub protection_info: Option<ProtectionSchemeInfoBox>,
231232
}
232233

233234
#[derive(Debug, Clone)]
@@ -242,6 +243,7 @@ pub struct VideoSampleEntry {
242243
pub width: u16,
243244
pub height: u16,
244245
pub codec_specific: VideoCodecSpecific,
246+
pub protection_info: Option<ProtectionSchemeInfoBox>,
245247
}
246248

247249
/// Represent a Video Partition Codec Configuration 'vpcC' box (aka vp9).
@@ -306,6 +308,19 @@ pub struct ProtectionSystemSpecificHeaderBox {
306308
pub box_content: ByteData,
307309
}
308310

311+
#[derive(Debug, Default, Clone)]
312+
pub struct TrackEncryptionBox {
313+
pub is_encrypted: u32,
314+
pub iv_size: u8,
315+
pub kid: Vec<u8>,
316+
}
317+
318+
#[derive(Debug, Default, Clone)]
319+
pub struct ProtectionSchemeInfoBox {
320+
pub code_name: String,
321+
pub tenc: TrackEncryptionBox,
322+
}
323+
309324
/// Internal data structures.
310325
#[derive(Debug, Default)]
311326
pub struct MediaContext {
@@ -1476,6 +1491,7 @@ fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) ->
14761491

14771492
// Skip clap/pasp/etc. for now.
14781493
let mut codec_specific = None;
1494+
let mut protection_info = None;
14791495
let mut iter = src.box_iter();
14801496
while let Some(mut b) = iter.next_box()? {
14811497
match b.head.name {
@@ -1491,6 +1507,7 @@ fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) ->
14911507
return Err(Error::InvalidData("avcC box exceeds BUF_SIZE_LIMIT"));
14921508
}
14931509
let avcc = read_buf(&mut b.content, avcc_size as usize)?;
1510+
log!("{:?} (avcc)", avcc);
14941511
// TODO(kinetik): Parse avcC box? For now we just stash the data.
14951512
codec_specific = Some(VideoCodecSpecific::AVCConfig(avcc));
14961513
}
@@ -1503,6 +1520,14 @@ fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) ->
15031520
let vpcc = read_vpcc(&mut b)?;
15041521
codec_specific = Some(VideoCodecSpecific::VPxConfig(vpcc));
15051522
}
1523+
BoxType::ProtectionSchemeInformationBox => {
1524+
if name != BoxType::ProtectedVisualSampleEntry {
1525+
return Err(Error::InvalidData("malformed video sample entry"));
1526+
}
1527+
let sinf = read_sinf(&mut b)?;
1528+
log!("{:?} (sinf)", sinf);
1529+
protection_info = Some(sinf);
1530+
}
15061531
_ => skip_box_content(&mut b)?,
15071532
}
15081533
check_parser_state!(b.content);
@@ -1514,6 +1539,7 @@ fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) ->
15141539
width: width,
15151540
height: height,
15161541
codec_specific: codec_specific,
1542+
protection_info: protection_info,
15171543
}))
15181544
.ok_or_else(|| Error::InvalidData("malformed video sample entry"))
15191545
}
@@ -1537,14 +1563,6 @@ fn read_qt_wave_atom<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> {
15371563
/// Parse an audio description inside an stsd box.
15381564
fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleEntry> {
15391565
let name = src.get_header().name;
1540-
track.codec_type = match name {
1541-
// TODO(kinetik): stagefright inspects ESDS to detect MP3 (audio/mpeg).
1542-
BoxType::MP4AudioSampleEntry => CodecType::AAC,
1543-
BoxType::FLACSampleEntry => CodecType::FLAC,
1544-
BoxType::OpusSampleEntry => CodecType::Opus,
1545-
BoxType::ProtectedAudioSampleEntry => CodecType::EncryptedAudio,
1546-
_ => CodecType::Unknown,
1547-
};
15481566

15491567
// Skip uninteresting fields.
15501568
skip(src, 6)?;
@@ -1580,6 +1598,7 @@ fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) ->
15801598

15811599
// Skip chan/etc. for now.
15821600
let mut codec_specific = None;
1601+
let mut protection_info = None;
15831602
let mut iter = src.box_iter();
15841603
while let Some(mut b) = iter.next_box()? {
15851604
match b.head.name {
@@ -1617,6 +1636,15 @@ fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) ->
16171636
track.codec_type = qt_esds.audio_codec;
16181637
codec_specific = Some(AudioCodecSpecific::ES_Descriptor(qt_esds));
16191638
}
1639+
BoxType::ProtectionSchemeInformationBox => {
1640+
if name != BoxType::ProtectedAudioSampleEntry {
1641+
return Err(Error::InvalidData("malformed audio sample entry"));
1642+
}
1643+
let sinf = read_sinf(&mut b)?;
1644+
log!("{:?} (sinf)", sinf);
1645+
track.codec_type = CodecType::EncryptedAudio;
1646+
protection_info = Some(sinf);
1647+
}
16201648
_ => skip_box_content(&mut b)?,
16211649
}
16221650
check_parser_state!(b.content);
@@ -1629,6 +1657,7 @@ fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) ->
16291657
samplesize: samplesize,
16301658
samplerate: samplerate,
16311659
codec_specific: codec_specific,
1660+
protection_info: protection_info,
16321661
}))
16331662
.ok_or_else(|| Error::InvalidData("malformed audio sample entry"))
16341663
}
@@ -1682,6 +1711,69 @@ fn read_stsd<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleD
16821711
})
16831712
}
16841713

1714+
fn read_sinf<T: Read>(src: &mut BMFFBox<T>) -> Result<ProtectionSchemeInfoBox> {
1715+
let mut sinf = ProtectionSchemeInfoBox::default();
1716+
1717+
let mut has_frma_schi_boxes = (false, false);
1718+
let mut iter = src.box_iter();
1719+
while let Some(mut b) = iter.next_box()? {
1720+
match b.head.name {
1721+
BoxType::OriginalFormatBox => {
1722+
let frma = read_frma(&mut b)?;
1723+
sinf.code_name = frma;
1724+
has_frma_schi_boxes.0 = true;
1725+
},
1726+
BoxType::SchemeInformationBox => {
1727+
// We only need tenc box in schi box so far.
1728+
let schi_tenc = read_schi(&mut b)?;
1729+
sinf.tenc = schi_tenc;
1730+
has_frma_schi_boxes.1 = true
1731+
},
1732+
_ => skip_box_content(&mut b)?,
1733+
}
1734+
check_parser_state!(b.content);
1735+
}
1736+
1737+
if has_frma_schi_boxes != (true, true) {
1738+
return Err(Error::InvalidData("malformat sinf box"));
1739+
}
1740+
1741+
Ok(sinf)
1742+
}
1743+
1744+
fn read_schi<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackEncryptionBox> {
1745+
let mut iter = src.box_iter();
1746+
while let Some(mut b) = iter.next_box()? {
1747+
match b.head.name {
1748+
BoxType::TrackEncryptionBox => {
1749+
return read_tenc(&mut b);
1750+
},
1751+
_ => skip_box_content(&mut b)?,
1752+
}
1753+
}
1754+
1755+
Err(Error::InvalidData("malformed schi box"))
1756+
}
1757+
1758+
fn read_tenc<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackEncryptionBox> {
1759+
let (_, _) = read_fullbox_extra(src)?;
1760+
1761+
let default_is_encrypted = be_u24(src)?;
1762+
let default_iv_size = src.read_u8()?;
1763+
let default_kid = read_buf(src, 16)?;
1764+
1765+
Ok(TrackEncryptionBox {
1766+
is_encrypted: default_is_encrypted,
1767+
iv_size: default_iv_size,
1768+
kid: default_kid,
1769+
})
1770+
}
1771+
1772+
fn read_frma<T: Read>(src: &mut BMFFBox<T>) -> Result<String> {
1773+
let code_name = read_buf(src, 4)?;
1774+
String::from_utf8(code_name).map_err(From::from)
1775+
}
1776+
16851777
/// Skip a number of bytes that we don't care to parse.
16861778
fn skip<T: Read>(src: &mut T, mut bytes: usize) -> Result<()> {
16871779
const BUF_SIZE: usize = 64 * 1024;
1000 Bytes
Binary file not shown.

mp4parse/tests/public.rs

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ extern crate mp4parse as mp4;
99
use std::io::{Cursor, Read};
1010
use std::fs::File;
1111

12+
static MINI_MP4: &'static str = "tests/minimal.mp4";
13+
static AUDIO_EME_MP4: &'static str = "tests/bipbop-cenc-audioinit.mp4";
14+
static VIDEO_EME_MP4: &'static str = "tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4";
15+
1216
// Taken from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53
1317
#[test]
1418
fn public_api() {
15-
let mut fd = File::open("tests/minimal.mp4").expect("Unknown file");
19+
let mut fd = File::open(MINI_MP4).expect("Unknown file");
1620
let mut buf = Vec::new();
1721
fd.read_to_end(&mut buf).expect("File error");
1822

@@ -97,26 +101,92 @@ fn public_api() {
97101
}
98102

99103
#[test]
100-
fn public_cenc() {
101-
let mut fd = File::open("tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4").expect("Unknown file");
104+
fn public_audio_tenc() {
105+
let kid =
106+
vec![0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04,
107+
0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04];
108+
109+
let mut fd = File::open(AUDIO_EME_MP4).expect("Unknown file");
102110
let mut buf = Vec::new();
103111
fd.read_to_end(&mut buf).expect("File error");
104112

105113
let mut c = Cursor::new(&buf);
106114
let mut context = mp4::MediaContext::new();
107115
mp4::read_mp4(&mut c, &mut context).expect("read_mp4 failed");
108116
for track in context.tracks {
109-
assert_eq!(track.codec_type, mp4::CodecType::EncryptedVideo);
117+
assert_eq!(track.codec_type, mp4::CodecType::EncryptedAudio);
118+
match track.data {
119+
Some(mp4::SampleEntry::Audio(a)) => {
120+
match a.protection_info {
121+
Some(p) => {
122+
assert_eq!(p.code_name, "mp4a");
123+
assert!(p.tenc.is_encrypted > 0);
124+
assert!(p.tenc.iv_size == 16);
125+
assert!(p.tenc.kid == kid);
126+
},
127+
_ => {
128+
// something is wrong in your patch...
129+
assert!(false);
130+
},
131+
}
132+
},
133+
_ => {
134+
// something is wrong in your patch...
135+
assert!(false);
136+
}
137+
}
110138
}
139+
}
140+
141+
#[test]
142+
fn public_video_cenc() {
143+
let system_id =
144+
vec![0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
145+
0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b];
111146

112-
let system_id = vec![0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b];
147+
let kid =
148+
vec![0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03,
149+
0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x11];
113150

114-
let kid = vec![0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x11];
151+
let pssh_box =
152+
vec![0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68,
153+
0x01, 0x00, 0x00, 0x00, 0x10, 0x77, 0xef, 0xec,
154+
0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e,
155+
0x52, 0xe2, 0xfb, 0x4b, 0x00, 0x00, 0x00, 0x01,
156+
0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03,
157+
0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x11,
158+
0x00, 0x00, 0x00, 0x00];
115159

116-
let pssh_box = vec![0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, 0x01, 0x00, 0x00, 0x00, 0x10, 0x77,
117-
0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, 0x00, 0x00,
118-
0x00, 0x01, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57,
119-
0x1d, 0x11, 0x00, 0x00, 0x00, 0x00];
160+
let mut fd = File::open(VIDEO_EME_MP4).expect("Unknown file");
161+
let mut buf = Vec::new();
162+
fd.read_to_end(&mut buf).expect("File error");
163+
164+
let mut c = Cursor::new(&buf);
165+
let mut context = mp4::MediaContext::new();
166+
mp4::read_mp4(&mut c, &mut context).expect("read_mp4 failed");
167+
for track in context.tracks {
168+
assert_eq!(track.codec_type, mp4::CodecType::EncryptedVideo);
169+
match track.data {
170+
Some(mp4::SampleEntry::Video(v)) => {
171+
match v.protection_info {
172+
Some(p) => {
173+
assert_eq!(p.code_name, "avc1");
174+
assert!(p.tenc.is_encrypted > 0);
175+
assert!(p.tenc.iv_size == 16);
176+
assert!(p.tenc.kid == kid);
177+
},
178+
_ => {
179+
// something is wrong in your patch...
180+
assert!(false);
181+
},
182+
}
183+
},
184+
_ => {
185+
// something is wrong in your patch...
186+
assert!(false);
187+
}
188+
}
189+
}
120190

121191
for pssh in context.psshs {
122192
assert_eq!(pssh.system_id, system_id);

mp4parse_capi/examples/afl-capi.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ fn doit() {
5353
image_width: 0,
5454
image_height: 0,
5555
extra_data: mp4parse_byte_data::default(),
56+
protected_data: Default::default(),
5657
};
5758
let rv = mp4parse_get_track_video_info(context, track, &mut video);
5859
if rv == mp4parse_error::MP4PARSE_OK {

0 commit comments

Comments
 (0)