/// Check if needed fields are still public. // 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 mp4parse as mp4;
usecrate::mp4::{ParseStrictness, Status}; use std::convert::TryInto; use std::fs::File; use std::io::{Cursor, Read, Seek};
// The 1 frame h264 mp4 file with pasp box generated by ffmpeg with command // ffmpeg -f lavfi -i color=c=white:s=640x480 -c:v libx264 -frames:v 1 -pix_fmt yuv420p -vf "setsar=16/9" h264_white_frame_sar_16_9.mp4 static VIDEO_H264_PASP_MP4: &str = "tests/h264_white_frame_sar_16_9.mp4";
// An h264 video sample entry with two nclx colr boxes (HLG transfer=18 followed // by a BT.2020 transfer=14 fallback), the backward-compatible HLG signaling // convention. Copy of mp4parse_capi/tests/video_colr_nclx_two_colr.mp4. static VIDEO_TWO_NCLX_COLR_MP4: &str = "tests/video_colr_nclx_two_colr.mp4"; // As above, but with the second colr box's colour_type patched from nclx to // rICC, giving one colr box of each colour_type as intended by // ISOBMFF (ISO 14496-12:2020) § 12.1.5. static VIDEO_NCLX_AND_ICC_COLR_MP4: &str = "tests/video_colr_nclx_and_icc.mp4";
#[test] fn public_invalid_metadata() { // Test that reading userdata containing invalid metadata is not fatal to parsing and that // expected values are still found. letmut fd = File::open(VIDEO_INVALID_USERDATA).expect("Unknown file"); letmut buf = Vec::new();
fd.read_to_end(&mut buf).expect("File error");
letmut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c, ParseStrictness::Normal).expect("read_mp4 failed"); // Should have userdata.
assert!(context.userdata.is_some()); // But it should contain an error.
assert!(context.userdata.unwrap().is_err()); // Smoke test that other data has been parsed. Don't check everything, just make sure some // values are as expected.
assert_eq!(context.tracks.len(), 2); for track in context.tracks { match track.track_type {
mp4::TrackType::Video => { // Check some of the values in the video tkhd. let tkhd = track.tkhd.unwrap();
assert!(!tkhd.disabled);
assert_eq!(tkhd.duration, 231232);
assert_eq!(tkhd.width, 83_886_080);
assert_eq!(tkhd.height, 47_185_920);
}
mp4::TrackType::Audio => { // Check some of the values in the audio tkhd. let tkhd = track.tkhd.unwrap();
assert!(!tkhd.disabled);
assert_eq!(tkhd.duration, 231338);
assert_eq!(tkhd.width, 0);
assert_eq!(tkhd.height, 0);
}
_ => panic!("File should not contain other tracks."),
}
}
}
letmut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c, ParseStrictness::Normal).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); let v = match stsd.descriptions.first().expect("expected a SampleEntry") {
mp4::SampleEntry::Video(ref v) => v,
_ => panic!("expected a VideoSampleEntry"),
};
assert_eq!(v.codec_type, mp4::CodecType::EncryptedVideo); match v.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
Some(p) => {
assert_eq!(p.original_format, b"avc1"); iflet Some(ref schm) = p.scheme_type {
assert_eq!(schm.scheme_type, b"cenc");
} else {
panic!("Expected scheme type info");
} iflet Some(ref tenc) = p.tenc {
assert!(tenc.is_encrypted > 0);
assert_eq!(tenc.iv_size, 16);
assert_eq!(tenc.kid, kid);
assert_eq!(tenc.crypt_byte_block_count, None);
assert_eq!(tenc.skip_byte_block_count, None);
assert_eq!(tenc.constant_iv, None);
} else {
panic!("Invalid test condition");
}
}
_ => {
panic!("Invalid test condition");
}
}
}
for pssh in context.psshs {
assert_eq!(pssh.system_id, system_id); for kid_id in pssh.kid {
assert_eq!(kid_id, kid);
}
assert!(pssh.data.is_empty());
assert_eq!(pssh.box_content, pssh_box);
}
}
letmut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c, ParseStrictness::Normal).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd");
assert_eq!(stsd.descriptions.len(), 2); letmut found_encrypted_sample_description = false; for description in stsd.descriptions { match description {
mp4::SampleEntry::Audio(ref a) => { iflet Some(p) = a.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
found_encrypted_sample_description = true;
assert_eq!(p.original_format, b"mp4a"); iflet Some(ref schm) = p.scheme_type {
assert_eq!(schm.scheme_type, b"cbcs");
} else {
panic!("Expected scheme type info");
} iflet Some(ref tenc) = p.tenc {
assert!(tenc.is_encrypted > 0);
assert_eq!(tenc.iv_size, 0);
assert_eq!(tenc.kid, kid); // Note: 0 for both crypt and skip seems odd but // that's what shaka-packager produced. It appears // to indicate full encryption.
assert_eq!(tenc.crypt_byte_block_count, Some(0));
assert_eq!(tenc.skip_byte_block_count, Some(0));
assert_eq!(tenc.constant_iv, Some(default_iv.clone().into()));
} else {
panic!("Invalid test condition");
}
}
}
_ => {
panic!("expected a VideoSampleEntry");
}
}
}
assert!(
found_encrypted_sample_description, "Should have found an encrypted sample description"
);
}
for pssh in context.psshs {
assert_eq!(pssh.system_id, system_id); for kid_id in pssh.kid {
assert_eq!(kid_id, kid);
}
assert!(pssh.data.is_empty());
assert_eq!(pssh.box_content, pssh_box);
}
}
letmut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c, ParseStrictness::Normal).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd");
assert_eq!(stsd.descriptions.len(), 2); letmut found_encrypted_sample_description = false; for description in stsd.descriptions { match description {
mp4::SampleEntry::Video(ref v) => {
assert_eq!(v.width, 400);
assert_eq!(v.height, 300); iflet Some(p) = v.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
found_encrypted_sample_description = true;
assert_eq!(p.original_format, b"avc1"); iflet Some(ref schm) = p.scheme_type {
assert_eq!(schm.scheme_type, b"cbcs");
} else {
panic!("Expected scheme type info");
} iflet Some(ref tenc) = p.tenc {
assert!(tenc.is_encrypted > 0);
assert_eq!(tenc.iv_size, 0);
assert_eq!(tenc.kid, kid);
assert_eq!(tenc.crypt_byte_block_count, Some(1));
assert_eq!(tenc.skip_byte_block_count, Some(9));
assert_eq!(tenc.constant_iv, Some(default_iv.clone().into()));
} else {
panic!("Invalid test condition");
}
}
}
_ => {
panic!("expected a VideoSampleEntry");
}
}
}
assert!(
found_encrypted_sample_description, "Should have found an encrypted sample description"
);
}
for pssh in context.psshs {
assert_eq!(pssh.system_id, system_id); for kid_id in pssh.kid {
assert_eq!(kid_id, kid);
}
assert!(pssh.data.is_empty());
assert_eq!(pssh.box_content, pssh_box);
}
}
letmut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c, ParseStrictness::Normal).expect("read_mp4 failed"); for track in context.tracks { // track part
assert_eq!(track.duration, Some(mp4::TrackScaledTime(512, 0)));
assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0)));
assert_eq!(track.media_time, Some(mp4::TrackScaledTime(0, 0)));
assert_eq!(track.timescale, Some(mp4::TrackTimeScale(12288, 0)));
// track.tkhd part let tkhd = track.tkhd.unwrap();
assert!(!tkhd.disabled);
assert_eq!(tkhd.duration, 42);
assert_eq!(tkhd.width, 4_194_304);
assert_eq!(tkhd.height, 4_194_304);
// track.stsd part let stsd = track.stsd.expect("expected an stsd"); let v = match stsd.descriptions.first().expect("expected a SampleEntry") {
mp4::SampleEntry::Video(ref v) => v,
_ => panic!("expected a VideoSampleEntry"),
};
assert_eq!(v.codec_type, mp4::CodecType::AV1);
assert_eq!(v.width, 64);
assert_eq!(v.height, 64);
match v.codec_specific {
mp4::VideoCodecSpecific::AV1Config(ref av1c) => { // TODO: test av1c fields once ffmpeg is updated
assert_eq!(av1c.profile, 0);
assert_eq!(av1c.level, 0);
assert_eq!(av1c.tier, 0);
assert_eq!(av1c.bit_depth, 8);
assert!(!av1c.monochrome);
assert_eq!(av1c.chroma_subsampling_x, 1);
assert_eq!(av1c.chroma_subsampling_y, 1);
assert_eq!(av1c.chroma_sample_position, 0);
assert!(!av1c.initial_presentation_delay_present);
assert_eq!(av1c.initial_presentation_delay_minus_one, 0);
}
_ => panic!("Invalid test condition"),
}
}
}
fn assert_video_nclx_colour_info(
result: mp4::Result<mp4::MediaContext>,
expected_transfer_characteristics: u8,
) { let context = result.expect("read_mp4 failed"); let track = context.tracks.first().expect("expected a track"); let stsd = track.stsd.as_ref().expect("expected an stsd"); let v = match stsd.descriptions.first().expect("expected a SampleEntry") {
mp4::SampleEntry::Video(v) => v,
_ => panic!("expected a VideoSampleEntry"),
}; match &v.colour_info {
Some(mp4::ColourInformation::Nclx(nclx)) => {
assert_eq!(
nclx.transfer_characteristics,
expected_transfer_characteristics
);
}
other => panic!("expected nclx colour info, got {:?}", other),
}
}
/// Two colr boxes with the same colour_type in a video sample entry are /// accepted (keeping the first) except under Strict parsing, which yields /// ColrBadQuantityBMFF; see ISOBMFF (ISO 14496-12:2020) § 12.1.5. #[test] fn public_video_two_nclx_colr_boxes() { for strictness in [ParseStrictness::Permissive, ParseStrictness::Normal] { let input = &mut File::open(VIDEO_TWO_NCLX_COLR_MP4).expect("Unknown file");
assert_video_nclx_colour_info(mp4::read_mp4(input, strictness), 18);
} let input = &mut File::open(VIDEO_TWO_NCLX_COLR_MP4).expect("Unknown file");
assert_eq!(
Status::from(mp4::read_mp4(input, ParseStrictness::Strict)),
Status::ColrBadQuantityBMFF
);
}
/// One colr box of each colour_type (nclx and rICC) in a video sample entry /// is valid per ISOBMFF (ISO 14496-12:2020) § 12.1.5 and accepted at all /// strictness levels, surfacing the first box. #[test] fn public_video_nclx_and_icc_colr_boxes() { for strictness in [
ParseStrictness::Permissive,
ParseStrictness::Normal,
ParseStrictness::Strict,
] { let input = &mut File::open(VIDEO_NCLX_AND_ICC_COLR_MP4).expect("Unknown file");
assert_video_nclx_colour_info(mp4::read_mp4(input, strictness), 18);
}
}
#[test] // TODO: convert this to a `assert_avif_shall` test to cover all `ParseStrictness` modes // that would require crafting an input that validly uses multiple ipma boxes, // which is kind of annoying to make pass the "should" requirements on flags and version // as well as the "shall" requirement on duplicate version and flags fn public_avif_ipma_duplicate_item_id() { let input = &mut File::open(IMAGE_AVIF_IPMA_DUPLICATE_ITEM_ID).expect("Unknown file");
assert_eq!(
Status::from(mp4::read_avif(input, ParseStrictness::Permissive)),
Status::IpmaDuplicateItemId
)
}
#[test] fn public_avif_hdlr_nonzero_reserved() { // This is a "should" despite the spec indicating a (somewhat ambiguous) // requirement that this field is set to zero. // See comments in read_hdlr
assert_avif_should(
IMAGE_AVIF_HDLR_NONZERO_RESERVED,
Status::HdlrReservedNonzero,
);
}
#[test] fn public_avif_hdlr_multiple_nul() { // This is a "should" despite the spec indicating a (somewhat ambiguous) // requirement about extra data in boxes // See comments in read_hdlr
assert_avif_should(IMAGE_AVIF_HDLR_MULTIPLE_NUL, Status::Ok);
}
#[allow(clippy::uninlined_format_args)] fn assert_unsupported_nonfatal(result: &mp4::Result<mp4::AvifContext>, feature: mp4::Feature) { match result {
Ok(context) => {
assert!(
context.unsupported_features.contains(feature), "context.unsupported_features missing expected {:?}",
feature
);
}
r => panic!( "Expected Ok with unsupported_features containing {:?}, found {:?}",
feature, r
),
}
}
// Assert that across all strictness levels the given feature is tracked as // being used, but unsupported. Additionally, if the feature is essential, // assert that the primary item is not processed unless using permissive mode. // TODO: Add similar tests for alpha fn assert_unsupported(path: &str, feature: mp4::Feature, essential: bool) {
for_strictness_result(path, |strictness, result| {
assert_unsupported_nonfatal(&result, feature); match result {
Ok(context) if essential => assert_eq!(
context.primary_item_coded_data().is_some(),
strictness == ParseStrictness::Permissive
),
Ok(context) if !essential => assert!(context.primary_item_coded_data().is_some()),
_ => panic!("Expected Ok, got {:?}", result),
}
});
}
letmut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c, ParseStrictness::Normal).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); let v = match stsd.descriptions.first().expect("expected a SampleEntry") {
mp4::SampleEntry::Video(ref v) => v,
_ => panic!("expected a VideoSampleEntry"),
};
assert_eq!(v.codec_type, mp4::CodecType::H263);
assert_eq!(v.width, 176);
assert_eq!(v.height, 144); let _codec_specific = match v.codec_specific {
mp4::VideoCodecSpecific::H263Config(_) => true,
_ => {
panic!("expected a H263Config",);
}
};
}
}
letmut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c, ParseStrictness::Normal).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); let v = match stsd.descriptions.first().expect("expected a SampleEntry") {
mp4::SampleEntry::Video(ref v) => v,
_ => panic!("expected a VideoSampleEntry"),
};
assert_eq!(v.codec_type, mp4::CodecType::HEVC);
assert_eq!(v.width, 640);
assert_eq!(v.height, 480); let _codec_specific = match &v.codec_specific {
mp4::VideoCodecSpecific::HEVCConfig(_) => true,
_ => {
panic!("expected a HEVCConfig",);
}
};
}
}
letmut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c, ParseStrictness::Normal).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); let v = match stsd.descriptions.first().expect("expected a SampleEntry") {
mp4::SampleEntry::Video(ref v) => v,
_ => panic!("expected a VideoSampleEntry"),
};
assert_eq!(v.codec_type, mp4::CodecType::H264);
assert_eq!(v.width, 640);
assert_eq!(v.height, 480);
assert!(
v.pixel_aspect_ratio.is_some(), "pixel_aspect_ratio should exist"
);
assert_eq!(
{ v.pixel_aspect_ratio.unwrap() }, 16.0 / 9.0, "pixel_aspect_ratio should be 16/9"
);
}
}
letmut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c, ParseStrictness::Normal).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); let a = match stsd.descriptions.first().expect("expected a SampleEntry") {
mp4::SampleEntry::Audio(ref v) => v,
_ => panic!("expected a AudioSampleEntry"),
};
assert!(a.codec_type == mp4::CodecType::AMRNB); let _codec_specific = match a.codec_specific {
mp4::AudioCodecSpecific::AMRSpecificBox(_) => true,
_ => {
panic!("expected a AMRSpecificBox",);
}
};
}
}
letmut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c, ParseStrictness::Normal).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); let a = match stsd.descriptions.first().expect("expected a SampleEntry") {
mp4::SampleEntry::Audio(ref v) => v,
_ => panic!("expected a AudioSampleEntry"),
};
assert!(a.codec_type == mp4::CodecType::AMRWB); let _codec_specific = match a.codec_specific {
mp4::AudioCodecSpecific::AMRSpecificBox(_) => true,
_ => {
panic!("expected a AMRSpecificBox",);
}
};
}
}
letmut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c, ParseStrictness::Normal).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); let v = match stsd.descriptions.first().expect("expected a SampleEntry") {
mp4::SampleEntry::Video(ref v) => v,
_ => panic!("expected a VideoSampleEntry"),
};
assert_eq!(v.codec_type, mp4::CodecType::MP4V);
assert_eq!(v.width, 176);
assert_eq!(v.height, 144); let _codec_specific = match v.codec_specific {
mp4::VideoCodecSpecific::ESDSConfig(_) => true,
_ => {
panic!("expected a ESDSConfig",);
}
};
}
}
letmut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c, ParseStrictness::Normal).expect("read_mp4 failed");
println!("xHE-AAC MP4 file parsed successfully");
println!("Number of tracks: {}", context.tracks.len());
// This file contains a single xHE-AAC audio track at 44.1kHz mono, ~14.6kbps, 3 seconds
assert_eq!(context.tracks.len(), 1, "Expected exactly one track");
// Check sample description let stsd = track.stsd.as_ref().expect("expected an stsd");
assert_eq!(
stsd.descriptions.len(), 1, "Expected one sample description"
);
let a = match stsd.descriptions.first().expect("expected a SampleEntry") {
mp4::SampleEntry::Audio(ref a) => a,
_ => panic!("expected an AudioSampleEntry"),
};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.