Skip to content

Parsing sinf box for enca/encv track #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions mp4parse/src/boxes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,8 @@ box_database!(
MovieExtendsHeaderBox 0x6d656864, // "mehd"
QTWaveAtom 0x77617665, // "wave" - quicktime atom
ProtectionSystemSpecificHeaderBox 0x70737368, // "pssh"
SchemeInformationBox 0x73636869, // "schi"
TrackEncryptionBox 0x74656e63, // "tenc"
ProtectionSchemeInformationBox 0x73696e66, // "sinf"
OriginalFormatBox 0x66726d61, // "frma"
);
108 changes: 100 additions & 8 deletions mp4parse/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ pub struct AudioSampleEntry {
pub samplesize: u16,
pub samplerate: u32,
pub codec_specific: AudioCodecSpecific,
pub protection_info: Option<ProtectionSchemeInfoBox>,
}

#[derive(Debug, Clone)]
Expand All @@ -242,6 +243,7 @@ pub struct VideoSampleEntry {
pub width: u16,
pub height: u16,
pub codec_specific: VideoCodecSpecific,
pub protection_info: Option<ProtectionSchemeInfoBox>,
}

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

#[derive(Debug, Default, Clone)]
pub struct TrackEncryptionBox {
pub is_encrypted: u32,
pub iv_size: u8,
pub kid: Vec<u8>,
}

#[derive(Debug, Default, Clone)]
pub struct ProtectionSchemeInfoBox {
pub code_name: String,
pub tenc: TrackEncryptionBox,
}

/// Internal data structures.
#[derive(Debug, Default)]
pub struct MediaContext {
Expand Down Expand Up @@ -1476,6 +1491,7 @@ fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) ->

// Skip clap/pasp/etc. for now.
let mut codec_specific = None;
let mut protection_info = None;
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
match b.head.name {
Expand All @@ -1491,6 +1507,7 @@ fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) ->
return Err(Error::InvalidData("avcC box exceeds BUF_SIZE_LIMIT"));
}
let avcc = read_buf(&mut b.content, avcc_size as usize)?;
log!("{:?} (avcc)", avcc);
// TODO(kinetik): Parse avcC box? For now we just stash the data.
codec_specific = Some(VideoCodecSpecific::AVCConfig(avcc));
}
Expand All @@ -1503,6 +1520,14 @@ fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) ->
let vpcc = read_vpcc(&mut b)?;
codec_specific = Some(VideoCodecSpecific::VPxConfig(vpcc));
}
BoxType::ProtectionSchemeInformationBox => {
if name != BoxType::ProtectedVisualSampleEntry {
return Err(Error::InvalidData("malformed video sample entry"));
}
let sinf = read_sinf(&mut b)?;
log!("{:?} (sinf)", sinf);
protection_info = Some(sinf);
}
_ => skip_box_content(&mut b)?,
}
check_parser_state!(b.content);
Expand All @@ -1514,6 +1539,7 @@ fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) ->
width: width,
height: height,
codec_specific: codec_specific,
protection_info: protection_info,
}))
.ok_or_else(|| Error::InvalidData("malformed video sample entry"))
}
Expand All @@ -1537,14 +1563,6 @@ fn read_qt_wave_atom<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> {
/// Parse an audio description inside an stsd box.
fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleEntry> {
let name = src.get_header().name;
track.codec_type = match name {
// TODO(kinetik): stagefright inspects ESDS to detect MP3 (audio/mpeg).
BoxType::MP4AudioSampleEntry => CodecType::AAC,
BoxType::FLACSampleEntry => CodecType::FLAC,
BoxType::OpusSampleEntry => CodecType::Opus,
BoxType::ProtectedAudioSampleEntry => CodecType::EncryptedAudio,
_ => CodecType::Unknown,
};

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

// Skip chan/etc. for now.
let mut codec_specific = None;
let mut protection_info = None;
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
match b.head.name {
Expand Down Expand Up @@ -1617,6 +1636,15 @@ fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) ->
track.codec_type = qt_esds.audio_codec;
codec_specific = Some(AudioCodecSpecific::ES_Descriptor(qt_esds));
}
BoxType::ProtectionSchemeInformationBox => {
if name != BoxType::ProtectedAudioSampleEntry {
return Err(Error::InvalidData("malformed audio sample entry"));
}
let sinf = read_sinf(&mut b)?;
log!("{:?} (sinf)", sinf);
track.codec_type = CodecType::EncryptedAudio;
protection_info = Some(sinf);
}
_ => skip_box_content(&mut b)?,
}
check_parser_state!(b.content);
Expand All @@ -1629,6 +1657,7 @@ fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) ->
samplesize: samplesize,
samplerate: samplerate,
codec_specific: codec_specific,
protection_info: protection_info,
}))
.ok_or_else(|| Error::InvalidData("malformed audio sample entry"))
}
Expand Down Expand Up @@ -1682,6 +1711,69 @@ fn read_stsd<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleD
})
}

fn read_sinf<T: Read>(src: &mut BMFFBox<T>) -> Result<ProtectionSchemeInfoBox> {
let mut sinf = ProtectionSchemeInfoBox::default();

let mut has_frma_schi_boxes = (false, false);
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
match b.head.name {
BoxType::OriginalFormatBox => {
let frma = read_frma(&mut b)?;
sinf.code_name = frma;
has_frma_schi_boxes.0 = true;
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the cenc spec, but 14496-12 says the OriginalFormatBox is required. Can we reject streams without one here? We don't seem to check later whether this is what we expect.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

BoxType::SchemeInformationBox => {
// We only need tenc box in schi box so far.
let schi_tenc = read_schi(&mut b)?;
sinf.tenc = schi_tenc;
has_frma_schi_boxes.1 = true
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we depend on the tenc data? If so we should probably also reject streams with a sinf without an schi.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

_ => skip_box_content(&mut b)?,
}
check_parser_state!(b.content);
}

if has_frma_schi_boxes != (true, true) {
return Err(Error::InvalidData("malformat sinf box"));
}

Ok(sinf)
}

fn read_schi<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackEncryptionBox> {
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
match b.head.name {
BoxType::TrackEncryptionBox => {
return read_tenc(&mut b);
},
_ => skip_box_content(&mut b)?,
}
}

Err(Error::InvalidData("malformed schi box"))
}

fn read_tenc<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackEncryptionBox> {
let (_, _) = read_fullbox_extra(src)?;

let default_is_encrypted = be_u24(src)?;
let default_iv_size = src.read_u8()?;
let default_kid = read_buf(src, 16)?;

Ok(TrackEncryptionBox {
is_encrypted: default_is_encrypted,
iv_size: default_iv_size,
kid: default_kid,
})
}

fn read_frma<T: Read>(src: &mut BMFFBox<T>) -> Result<String> {
let code_name = read_buf(src, 4)?;
String::from_utf8(code_name).map_err(From::from)
}

/// Skip a number of bytes that we don't care to parse.
fn skip<T: Read>(src: &mut T, mut bytes: usize) -> Result<()> {
const BUF_SIZE: usize = 64 * 1024;
Expand Down
Binary file added mp4parse/tests/bipbop-cenc-audioinit.mp4
Binary file not shown.
90 changes: 80 additions & 10 deletions mp4parse/tests/public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ extern crate mp4parse as mp4;
use std::io::{Cursor, Read};
use std::fs::File;

static MINI_MP4: &'static str = "tests/minimal.mp4";
static AUDIO_EME_MP4: &'static str = "tests/bipbop-cenc-audioinit.mp4";
static VIDEO_EME_MP4: &'static str = "tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4";

// Taken from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53
#[test]
fn public_api() {
let mut fd = File::open("tests/minimal.mp4").expect("Unknown file");
let mut fd = File::open(MINI_MP4).expect("Unknown file");
let mut buf = Vec::new();
fd.read_to_end(&mut buf).expect("File error");

Expand Down Expand Up @@ -97,26 +101,92 @@ fn public_api() {
}

#[test]
fn public_cenc() {
let mut fd = File::open("tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4").expect("Unknown file");
fn public_audio_tenc() {
let kid =
vec![0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04,
0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04];

let mut fd = File::open(AUDIO_EME_MP4).expect("Unknown file");
let mut buf = Vec::new();
fd.read_to_end(&mut buf).expect("File error");

let mut c = Cursor::new(&buf);
let mut context = mp4::MediaContext::new();
mp4::read_mp4(&mut c, &mut context).expect("read_mp4 failed");
for track in context.tracks {
assert_eq!(track.codec_type, mp4::CodecType::EncryptedVideo);
assert_eq!(track.codec_type, mp4::CodecType::EncryptedAudio);
match track.data {
Some(mp4::SampleEntry::Audio(a)) => {
match a.protection_info {
Some(p) => {
assert_eq!(p.code_name, "mp4a");
assert!(p.tenc.is_encrypted > 0);
assert!(p.tenc.iv_size == 16);
assert!(p.tenc.kid == kid);
},
_ => {
// something is wrong in your patch...
assert!(false);
},
}
},
_ => {
// something is wrong in your patch...
assert!(false);
}
}
}
}

#[test]
fn public_video_cenc() {
let system_id =
vec![0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b];

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

let kid = vec![0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x11];
let pssh_box =
vec![0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68,
0x01, 0x00, 0x00, 0x00, 0x10, 0x77, 0xef, 0xec,
0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e,
0x52, 0xe2, 0xfb, 0x4b, 0x00, 0x00, 0x00, 0x01,
0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03,
0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x11,
0x00, 0x00, 0x00, 0x00];

let pssh_box = vec![0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, 0x01, 0x00, 0x00, 0x00, 0x10, 0x77,
0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, 0x00, 0x00,
0x00, 0x01, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57,
0x1d, 0x11, 0x00, 0x00, 0x00, 0x00];
let mut fd = File::open(VIDEO_EME_MP4).expect("Unknown file");
let mut buf = Vec::new();
fd.read_to_end(&mut buf).expect("File error");

let mut c = Cursor::new(&buf);
let mut context = mp4::MediaContext::new();
mp4::read_mp4(&mut c, &mut context).expect("read_mp4 failed");
for track in context.tracks {
assert_eq!(track.codec_type, mp4::CodecType::EncryptedVideo);
match track.data {
Some(mp4::SampleEntry::Video(v)) => {
match v.protection_info {
Some(p) => {
assert_eq!(p.code_name, "avc1");
assert!(p.tenc.is_encrypted > 0);
assert!(p.tenc.iv_size == 16);
assert!(p.tenc.kid == kid);
},
_ => {
// something is wrong in your patch...
assert!(false);
},
}
},
_ => {
// something is wrong in your patch...
assert!(false);
}
}
}

for pssh in context.psshs {
assert_eq!(pssh.system_id, system_id);
Expand Down
1 change: 1 addition & 0 deletions mp4parse_capi/examples/afl-capi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ fn doit() {
image_width: 0,
image_height: 0,
extra_data: mp4parse_byte_data::default(),
protected_data: Default::default(),
};
let rv = mp4parse_get_track_video_info(context, track, &mut video);
if rv == mp4parse_error::MP4PARSE_OK {
Expand Down
Loading