diff --git a/Cargo.lock b/Cargo.lock index 1db7eea552e2..5a3920ea5de0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1049,7 +1049,7 @@ dependencies = [ "jsrust_shared 0.1.0", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "mozurl 0.0.1", - "mp4parse_capi 0.10.1", + "mp4parse_capi 0.11.2", "netwerk_helper 0.0.1", "nserror 0.1.0", "nsstring 0.1.0", @@ -1609,7 +1609,7 @@ dependencies = [ [[package]] name = "mp4parse" -version = "0.10.1" +version = "0.11.2" dependencies = [ "bitreader 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1624,11 +1624,11 @@ version = "0.1.0" [[package]] name = "mp4parse_capi" -version = "0.10.1" +version = "0.11.2" dependencies = [ "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "mp4parse 0.10.1", + "mp4parse 0.11.2", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/dom/media/mp4/DecoderData.cpp b/dom/media/mp4/DecoderData.cpp index 377c201652d8..dae10ed77550 100644 --- a/dom/media/mp4/DecoderData.cpp +++ b/dom/media/mp4/DecoderData.cpp @@ -57,67 +57,136 @@ static void UpdateTrackProtectedInfo(mozilla::TrackInfo& aConfig, } } -void MP4AudioInfo::Update(const Mp4parseTrackInfo* track, - const Mp4parseTrackAudioInfo* audio) { - UpdateTrackProtectedInfo(*this, audio->protected_data); +MediaResult MP4AudioInfo::Update(const Mp4parseTrackInfo* track, + const Mp4parseTrackAudioInfo* audio) { + MOZ_DIAGNOSTIC_ASSERT(audio->sample_info_count > 0, + "Must have at least one audio sample info"); + if (audio->sample_info_count == 0) { + return MediaResult( + NS_ERROR_DOM_MEDIA_METADATA_ERR, + RESULT_DETAIL("Got 0 audio sample info while updating audio track")); + } - if (track->codec == MP4PARSE_CODEC_OPUS) { + bool hasCrypto = false; + Mp4parseCodec codecType = audio->sample_info[0].codec_type; + for (uint32_t i = 0; i < audio->sample_info_count; i++) { + if (audio->sample_info[0].codec_type != codecType) { + // Different codecs in a single track. We don't handle this. + return MediaResult( + NS_ERROR_DOM_MEDIA_METADATA_ERR, + RESULT_DETAIL( + "Multiple codecs encountered while updating audio track")); + } + + // Update our encryption info if any is present on the sample info. + if (audio->sample_info[i].protected_data.is_encrypted) { + if (hasCrypto) { + // Multiple crypto entries found. We don't handle this. + return MediaResult( + NS_ERROR_DOM_MEDIA_METADATA_ERR, + RESULT_DETAIL( + "Multiple crypto info encountered while updating audio track")); + } + UpdateTrackProtectedInfo(*this, audio->sample_info[i].protected_data); + hasCrypto = true; + } + } + + // We assume that the members of the first sample info are representative of + // the entire track. This code will need to be updated should this assumption + // ever not hold. E.g. if we need to handle different codecs in a single + // track, or if we have different numbers or channels in a single track. + Mp4parseByteData codecSpecificConfig = + audio->sample_info[0].codec_specific_config; + if (codecType == MP4PARSE_CODEC_OPUS) { mMimeType = NS_LITERAL_CSTRING("audio/opus"); // The Opus decoder expects the container's codec delay or // pre-skip value, in microseconds, as a 64-bit int at the // start of the codec-specific config blob. - if (audio->codec_specific_config.data && - audio->codec_specific_config.length >= 12) { - uint16_t preskip = mozilla::LittleEndian::readUint16( - audio->codec_specific_config.data + 10); + if (codecSpecificConfig.data && codecSpecificConfig.length >= 12) { + uint16_t preskip = + mozilla::LittleEndian::readUint16(codecSpecificConfig.data + 10); mozilla::OpusDataDecoder::AppendCodecDelay( mCodecSpecificConfig, mozilla::FramesToUsecs(preskip, 48000).value()); } else { // This file will error later as it will be rejected by the opus decoder. mozilla::OpusDataDecoder::AppendCodecDelay(mCodecSpecificConfig, 0); } - } else if (track->codec == MP4PARSE_CODEC_AAC) { + } else if (codecType == MP4PARSE_CODEC_AAC) { mMimeType = NS_LITERAL_CSTRING("audio/mp4a-latm"); - } else if (track->codec == MP4PARSE_CODEC_FLAC) { + } else if (codecType == MP4PARSE_CODEC_FLAC) { mMimeType = NS_LITERAL_CSTRING("audio/flac"); - } else if (track->codec == MP4PARSE_CODEC_MP3) { + } else if (codecType == MP4PARSE_CODEC_MP3) { mMimeType = NS_LITERAL_CSTRING("audio/mpeg"); } - mRate = audio->sample_rate; - mChannels = audio->channels; - mBitDepth = audio->bit_depth; - mExtendedProfile = audio->extended_profile; + mRate = audio->sample_info[0].sample_rate; + mChannels = audio->sample_info[0].channels; + mBitDepth = audio->sample_info[0].bit_depth; + mExtendedProfile = audio->sample_info[0].extended_profile; mDuration = TimeUnit::FromMicroseconds(track->duration); mMediaTime = TimeUnit::FromMicroseconds(track->media_time); mTrackId = track->track_id; // In stagefright, mProfile is kKeyAACProfile, mExtendedProfile is kKeyAACAOT. - if (audio->profile <= 4) { - mProfile = audio->profile; + if (audio->sample_info[0].profile <= 4) { + mProfile = audio->sample_info[0].profile; } - if (audio->extra_data.length > 0) { - mExtraData->AppendElements(audio->extra_data.data, - audio->extra_data.length); - } - - if (audio->codec_specific_config.length > 0) { - mCodecSpecificConfig->AppendElements(audio->codec_specific_config.data, - audio->codec_specific_config.length); - } + Mp4parseByteData extraData = audio->sample_info[0].extra_data; + // If length is 0 we append nothing + mExtraData->AppendElements(extraData.data, extraData.length); + mCodecSpecificConfig->AppendElements(codecSpecificConfig.data, + codecSpecificConfig.length); + return NS_OK; } -void MP4VideoInfo::Update(const Mp4parseTrackInfo* track, - const Mp4parseTrackVideoInfo* video) { - UpdateTrackProtectedInfo(*this, video->protected_data); - if (track->codec == MP4PARSE_CODEC_AVC) { +MediaResult MP4VideoInfo::Update(const Mp4parseTrackInfo* track, + const Mp4parseTrackVideoInfo* video) { + MOZ_DIAGNOSTIC_ASSERT(video->sample_info_count > 0, + "Must have at least one video sample info"); + if (video->sample_info_count == 0) { + return MediaResult( + NS_ERROR_DOM_MEDIA_METADATA_ERR, + RESULT_DETAIL("Got 0 audio sample info while updating video track")); + } + + bool hasCrypto = false; + Mp4parseCodec codecType = video->sample_info[0].codec_type; + for (uint32_t i = 0; i < video->sample_info_count; i++) { + if (video->sample_info[0].codec_type != codecType) { + // Different codecs in a single track. We don't handle this. + return MediaResult( + NS_ERROR_DOM_MEDIA_METADATA_ERR, + RESULT_DETAIL( + "Multiple codecs encountered while updating video track")); + } + + // Update our encryption info if any is present on the sample info. + if (video->sample_info[i].protected_data.is_encrypted) { + if (hasCrypto) { + // Multiple crypto entries found. We don't handle this. + return MediaResult( + NS_ERROR_DOM_MEDIA_METADATA_ERR, + RESULT_DETAIL( + "Multiple crypto info encountered while updating video track")); + } + UpdateTrackProtectedInfo(*this, video->sample_info[i].protected_data); + hasCrypto = true; + } + } + + // We assume that the members of the first sample info are representative of + // the entire track. This code will need to be updated should this assumption + // ever not hold. E.g. if we need to handle different codecs in a single + // track, or if we have different numbers or channels in a single track. + if (codecType == MP4PARSE_CODEC_AVC) { mMimeType = NS_LITERAL_CSTRING("video/avc"); - } else if (track->codec == MP4PARSE_CODEC_VP9) { + } else if (codecType == MP4PARSE_CODEC_VP9) { mMimeType = NS_LITERAL_CSTRING("video/vp9"); - } else if (track->codec == MP4PARSE_CODEC_AV1) { + } else if (codecType == MP4PARSE_CODEC_AV1) { mMimeType = NS_LITERAL_CSTRING("video/av1"); - } else if (track->codec == MP4PARSE_CODEC_MP4V) { + } else if (codecType == MP4PARSE_CODEC_MP4V) { mMimeType = NS_LITERAL_CSTRING("video/mp4v-es"); } mTrackId = track->track_id; @@ -125,13 +194,13 @@ void MP4VideoInfo::Update(const Mp4parseTrackInfo* track, mMediaTime = TimeUnit::FromMicroseconds(track->media_time); mDisplay.width = video->display_width; mDisplay.height = video->display_height; - mImage.width = video->image_width; - mImage.height = video->image_height; + mImage.width = video->sample_info[0].image_width; + mImage.height = video->sample_info[0].image_height; mRotation = ToSupportedRotation(video->rotation); - if (video->extra_data.data) { - mExtraData->AppendElements(video->extra_data.data, - video->extra_data.length); - } + Mp4parseByteData extraData = video->sample_info[0].extra_data; + // If length is 0 we append nothing + mExtraData->AppendElements(extraData.data, extraData.length); + return NS_OK; } bool MP4VideoInfo::IsValid() const { diff --git a/dom/media/mp4/DecoderData.h b/dom/media/mp4/DecoderData.h index dcf0b978c0df..094025061665 100644 --- a/dom/media/mp4/DecoderData.h +++ b/dom/media/mp4/DecoderData.h @@ -6,6 +6,7 @@ #define DECODER_DATA_H_ #include "MediaInfo.h" +#include "MediaResult.h" #include "mozilla/RefPtr.h" #include "mozilla/Result.h" #include "mozilla/Types.h" @@ -53,8 +54,8 @@ class MP4AudioInfo : public mozilla::AudioInfo { public: MP4AudioInfo() = default; - void Update(const Mp4parseTrackInfo* track, - const Mp4parseTrackAudioInfo* audio); + MediaResult Update(const Mp4parseTrackInfo* track, + const Mp4parseTrackAudioInfo* audio); virtual bool IsValid() const override; }; @@ -63,8 +64,8 @@ class MP4VideoInfo : public mozilla::VideoInfo { public: MP4VideoInfo() = default; - void Update(const Mp4parseTrackInfo* track, - const Mp4parseTrackVideoInfo* video); + MediaResult Update(const Mp4parseTrackInfo* track, + const Mp4parseTrackVideoInfo* video); virtual bool IsValid() const override; }; diff --git a/dom/media/mp4/MP4Metadata.cpp b/dom/media/mp4/MP4Metadata.cpp index 757baad050f1..5d9dff6aa8b7 100644 --- a/dom/media/mp4/MP4Metadata.cpp +++ b/dom/media/mp4/MP4Metadata.cpp @@ -155,7 +155,55 @@ MP4Metadata::ResultAndTrackCount MP4Metadata::GetNumberTracks( if (rv != MP4PARSE_STATUS_OK) { continue; } - if (track_info.codec == MP4PARSE_CODEC_UNKNOWN) { + + if (track_info.track_type == MP4PARSE_TRACK_TYPE_AUDIO) { + Mp4parseTrackAudioInfo audio; + auto rv = mp4parse_get_track_audio_info(mParser.get(), i, &audio); + if (rv != MP4PARSE_STATUS_OK) { + MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, + ("mp4parse_get_track_audio_info returned error %d", rv)); + continue; + } + MOZ_DIAGNOSTIC_ASSERT(audio.sample_info_count > 0, + "Must have at least one audio sample info"); + if (audio.sample_info_count == 0) { + return { + MediaResult( + NS_ERROR_DOM_MEDIA_METADATA_ERR, + RESULT_DETAIL( + "Got 0 audio sample info while checking number tracks")), + MP4Metadata::NumberTracksError()}; + } + // We assume the codec of the first sample info is representative of the + // whole track and skip it if we don't recognize the codec. + if (audio.sample_info[0].codec_type == MP4PARSE_CODEC_UNKNOWN) { + continue; + } + } else if (track_info.track_type == MP4PARSE_TRACK_TYPE_VIDEO) { + Mp4parseTrackVideoInfo video; + auto rv = mp4parse_get_track_video_info(mParser.get(), i, &video); + if (rv != MP4PARSE_STATUS_OK) { + MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, + ("mp4parse_get_track_video_info returned error %d", rv)); + continue; + } + MOZ_DIAGNOSTIC_ASSERT(video.sample_info_count > 0, + "Must have at least one video sample info"); + if (video.sample_info_count == 0) { + return { + MediaResult( + NS_ERROR_DOM_MEDIA_METADATA_ERR, + RESULT_DETAIL( + "Got 0 video sample info while checking number tracks")), + MP4Metadata::NumberTracksError()}; + } + // We assume the codec of the first sample info is representative of the + // whole track and skip it if we don't recognize the codec. + if (video.sample_info[0].codec_type == MP4PARSE_CODEC_UNKNOWN) { + continue; + } + } else { + // Only audio and video are supported continue; } if (TrackTypeEqual(aType, track_info.track_type)) { @@ -219,50 +267,71 @@ MP4Metadata::ResultAndTrackInfo MP4Metadata::GetTrackInfo( nullptr}; } #ifdef DEBUG - const char* codec_string = "unrecognized"; - switch (info.codec) { - case MP4PARSE_CODEC_UNKNOWN: - codec_string = "unknown"; - break; - case MP4PARSE_CODEC_AAC: - codec_string = "aac"; - break; - case MP4PARSE_CODEC_OPUS: - codec_string = "opus"; - break; - case MP4PARSE_CODEC_FLAC: - codec_string = "flac"; - break; - case MP4PARSE_CODEC_ALAC: - codec_string = "alac"; - break; - case MP4PARSE_CODEC_AVC: - codec_string = "h.264"; - break; - case MP4PARSE_CODEC_VP9: - codec_string = "vp9"; - break; - case MP4PARSE_CODEC_AV1: - codec_string = "av1"; - break; - case MP4PARSE_CODEC_MP3: - codec_string = "mp3"; - break; - case MP4PARSE_CODEC_MP4V: - codec_string = "mp4v"; - break; - case MP4PARSE_CODEC_JPEG: - codec_string = "jpeg"; - break; - case MP4PARSE_CODEC_AC3: - codec_string = "ac-3"; - break; - case MP4PARSE_CODEC_EC3: - codec_string = "ec-3"; - break; + bool haveSampleInfo = false; + const char* codecString = "unrecognized"; + Mp4parseCodec codecType = MP4PARSE_CODEC_UNKNOWN; + if (info.track_type == MP4PARSE_TRACK_TYPE_AUDIO) { + Mp4parseTrackAudioInfo audio; + auto rv = mp4parse_get_track_audio_info(mParser.get(), trackIndex.value(), + &audio); + if (rv == MP4PARSE_STATUS_OK && audio.sample_info_count > 0) { + codecType = audio.sample_info[0].codec_type; + haveSampleInfo = true; + } + } else if (info.track_type == MP4PARSE_TRACK_TYPE_VIDEO) { + Mp4parseTrackVideoInfo video; + auto rv = mp4parse_get_track_video_info(mParser.get(), trackIndex.value(), + &video); + if (rv == MP4PARSE_STATUS_OK && video.sample_info_count > 0) { + codecType = video.sample_info[0].codec_type; + haveSampleInfo = true; + } + } + if (haveSampleInfo) { + switch (codecType) { + case MP4PARSE_CODEC_UNKNOWN: + codecString = "unknown"; + break; + case MP4PARSE_CODEC_AAC: + codecString = "aac"; + break; + case MP4PARSE_CODEC_OPUS: + codecString = "opus"; + break; + case MP4PARSE_CODEC_FLAC: + codecString = "flac"; + break; + case MP4PARSE_CODEC_ALAC: + codecString = "alac"; + break; + case MP4PARSE_CODEC_AVC: + codecString = "h.264"; + break; + case MP4PARSE_CODEC_VP9: + codecString = "vp9"; + break; + case MP4PARSE_CODEC_AV1: + codecString = "av1"; + break; + case MP4PARSE_CODEC_MP3: + codecString = "mp3"; + break; + case MP4PARSE_CODEC_MP4V: + codecString = "mp4v"; + break; + case MP4PARSE_CODEC_JPEG: + codecString = "jpeg"; + break; + case MP4PARSE_CODEC_AC3: + codecString = "ac-3"; + break; + case MP4PARSE_CODEC_EC3: + codecString = "ec-3"; + break; + } } MOZ_LOG(gMP4MetadataLog, LogLevel::Debug, - ("track codec %s (%u)\n", codec_string, info.codec)); + ("track codec %s (%u)\n", codecString, codecType)); #endif // This specialization interface is crazy. @@ -281,7 +350,18 @@ MP4Metadata::ResultAndTrackInfo MP4Metadata::GetTrackInfo( nullptr}; } auto track = mozilla::MakeUnique(); - track->Update(&info, &audio); + MediaResult updateStatus = track->Update(&info, &audio); + if (updateStatus != NS_OK) { + MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, + ("Updating audio track failed with %s", + updateStatus.Message().get())); + return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR, + RESULT_DETAIL( + "Failed to update %s track #%zu with error: %s", + TrackTypeToStr(aType), aTrackNumber, + updateStatus.Message().get())), + nullptr}; + } e = std::move(track); } break; case TrackInfo::TrackType::kVideoTrack: { @@ -297,7 +377,18 @@ MP4Metadata::ResultAndTrackInfo MP4Metadata::GetTrackInfo( nullptr}; } auto track = mozilla::MakeUnique(); - track->Update(&info, &video); + MediaResult updateStatus = track->Update(&info, &video); + if (updateStatus != NS_OK) { + MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, + ("Updating video track failed with %s", + updateStatus.Message().get())); + return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR, + RESULT_DETAIL( + "Failed to update %s track #%zu with error: %s", + TrackTypeToStr(aType), aTrackNumber, + updateStatus.Message().get())), + nullptr}; + } e = std::move(track); } break; default: diff --git a/media/mp4parse-rust/mp4parse.h b/media/mp4parse-rust/mp4parse.h index 339c9b8a7972..b941b1c99e94 100644 --- a/media/mp4parse-rust/mp4parse.h +++ b/media/mp4parse-rust/mp4parse.h @@ -15,6 +15,14 @@ extern "C" { #include #include +typedef enum { + MP4_PARSE_ENCRYPTION_SCHEME_TYPE_NONE, + MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CENC, + MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CBC1, + MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CENS, + MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CBCS, +} Mp4ParseEncryptionSchemeType; + typedef enum { MP4PARSE_CODEC_UNKNOWN, MP4PARSE_CODEC_AAC, @@ -73,12 +81,17 @@ typedef struct { } Mp4parsePsshInfo; typedef struct { - uint32_t is_encrypted; + Mp4ParseEncryptionSchemeType scheme_type; + uint8_t is_encrypted; uint8_t iv_size; Mp4parseByteData kid; + uint8_t crypt_byte_block; + uint8_t skip_byte_block; + Mp4parseByteData constant_iv; } Mp4parseSinfInfo; typedef struct { + Mp4parseCodec codec_type; uint16_t channels; uint16_t bit_depth; uint32_t sample_rate; @@ -87,24 +100,34 @@ typedef struct { Mp4parseByteData codec_specific_config; Mp4parseByteData extra_data; Mp4parseSinfInfo protected_data; +} Mp4parseTrackAudioSampleInfo; + +typedef struct { + uint32_t sample_info_count; + const Mp4parseTrackAudioSampleInfo *sample_info; } Mp4parseTrackAudioInfo; typedef struct { Mp4parseTrackType track_type; - Mp4parseCodec codec; uint32_t track_id; uint64_t duration; int64_t media_time; } Mp4parseTrackInfo; typedef struct { - uint32_t display_width; - uint32_t display_height; + Mp4parseCodec codec_type; uint16_t image_width; uint16_t image_height; - uint16_t rotation; Mp4parseByteData extra_data; Mp4parseSinfInfo protected_data; +} Mp4parseTrackVideoSampleInfo; + +typedef struct { + uint32_t display_width; + uint32_t display_height; + uint16_t rotation; + uint32_t sample_info_count; + const Mp4parseTrackVideoSampleInfo *sample_info; } Mp4parseTrackVideoInfo; typedef struct { diff --git a/media/mp4parse-rust/mp4parse/Cargo.toml b/media/mp4parse-rust/mp4parse/Cargo.toml index ff702fa494f0..d61779949299 100644 --- a/media/mp4parse-rust/mp4parse/Cargo.toml +++ b/media/mp4parse-rust/mp4parse/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mp4parse" -version = "0.10.1" +version = "0.11.2" authors = [ "Ralph Giles ", "Matthew Gregan ", diff --git a/media/mp4parse-rust/mp4parse/src/boxes.rs b/media/mp4parse-rust/mp4parse/src/boxes.rs index a00747bc2e9b..37b838b0b0fe 100644 --- a/media/mp4parse-rust/mp4parse/src/boxes.rs +++ b/media/mp4parse-rust/mp4parse/src/boxes.rs @@ -40,7 +40,7 @@ macro_rules! box_database { } } -#[derive(Default, PartialEq)] +#[derive(Default, PartialEq, Clone)] pub struct FourCC { pub value: String } @@ -137,6 +137,7 @@ box_database!( TrackEncryptionBox 0x74656e63, // "tenc" ProtectionSchemeInformationBox 0x73696e66, // "sinf" OriginalFormatBox 0x66726d61, // "frma" + SchemeTypeBox 0x7363686d, // "schm" MP3AudioSampleEntry 0x2e6d7033, // ".mp3" - from F4V. CompositionOffsetBox 0x63747473, // "ctts" LPCMAudioSampleEntry 0x6C70636D, // "lpcm" - quicktime atom diff --git a/media/mp4parse-rust/mp4parse/src/lib.rs b/media/mp4parse-rust/mp4parse/src/lib.rs index 532c09813fa9..36a83feb7080 100644 --- a/media/mp4parse-rust/mp4parse/src/lib.rs +++ b/media/mp4parse-rust/mp4parse/src/lib.rs @@ -277,8 +277,8 @@ struct HandlerBox { // Sample description box 'stsd' #[derive(Debug)] -struct SampleDescriptionBox { - descriptions: Vec, +pub struct SampleDescriptionBox { + pub descriptions: Vec, } #[derive(Debug, Clone)] @@ -313,6 +313,7 @@ pub enum AudioCodecSpecific { #[derive(Debug, Clone)] pub struct AudioSampleEntry { + pub codec_type: CodecType, data_reference_index: u16, pub channelcount: u32, pub samplesize: u16, @@ -331,6 +332,7 @@ pub enum VideoCodecSpecific { #[derive(Debug, Clone)] pub struct VideoSampleEntry { + pub codec_type: CodecType, data_reference_index: u16, pub width: u16, pub height: u16, @@ -423,16 +425,28 @@ pub struct ProtectionSystemSpecificHeaderBox { pub box_content: ByteData, } +#[derive(Debug, Default, Clone)] +pub struct SchemeTypeBox { + pub scheme_type: FourCC, + pub scheme_version: u32, +} + #[derive(Debug, Default, Clone)] pub struct TrackEncryptionBox { - pub is_encrypted: u32, + pub is_encrypted: u8, pub iv_size: u8, pub kid: Vec, + // Members for pattern encryption schemes + pub crypt_byte_block_count: Option, + pub skip_byte_block_count: Option, + pub constant_iv: Option>, + // End pattern encryption scheme members } #[derive(Debug, Default, Clone)] pub struct ProtectionSchemeInfoBox { pub code_name: String, + pub scheme_type: Option, pub tenc: Option, } @@ -521,9 +535,8 @@ pub struct Track { pub timescale: Option>, pub duration: Option>, pub track_id: Option, - pub codec_type: CodecType, - pub data: Option, pub tkhd: Option, // TODO(kinetik): find a nicer way to export this. + pub stsd: Option, pub stts: Option, pub stsc: Option, pub stsz: Option, @@ -962,6 +975,7 @@ fn read_stbl(f: &mut BMFFBox, track: &mut Track) -> Result<()> { BoxType::SampleDescriptionBox => { let stsd = read_stsd(&mut b, track)?; debug!("{:?}", stsd); + track.stsd = Some(stsd); } BoxType::TimeToSampleBox => { let stts = read_stts(&mut b)?; @@ -1722,6 +1736,8 @@ fn read_es_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { fn read_esds(src: &mut BMFFBox) -> Result { let (_, _) = read_fullbox_extra(src)?; + // Subtract 4 extra to offset the members of fullbox not accounted for in + // head.offset let esds_size = src.head.size - src.head.offset - 4; let esds_array = read_buf(src, esds_size as usize)?; @@ -1889,7 +1905,7 @@ fn read_hdlr(src: &mut BMFFBox) -> Result { } /// Parse an video description inside an stsd box. -fn read_video_sample_entry(src: &mut BMFFBox) -> Result<(CodecType, SampleEntry)> { +fn read_video_sample_entry(src: &mut BMFFBox) -> Result { let name = src.get_header().name; let codec_type = match name { BoxType::AVCSampleEntry | BoxType::AVC3SampleEntry => CodecType::H264, @@ -1959,6 +1975,8 @@ fn read_video_sample_entry(src: &mut BMFFBox) -> Result<(CodecType, return Err(Error::InvalidData("malformed video sample entry")); } let (_, _) = read_fullbox_extra(&mut b.content)?; + // Subtract 4 extra to offset the members of fullbox not + // accounted for in head.offset let esds_size = b.head.size - b.head.offset - 4; let esds = read_buf(&mut b.content, esds_size as usize)?; codec_specific = Some(VideoCodecSpecific::ESDSConfig(esds)); @@ -1979,14 +1997,15 @@ fn read_video_sample_entry(src: &mut BMFFBox) -> Result<(CodecType, check_parser_state!(b.content); } - Ok(codec_specific.map_or((CodecType::Unknown, SampleEntry::Unknown), - |codec_specific| (codec_type, SampleEntry::Video(VideoSampleEntry { + Ok(codec_specific.map_or(SampleEntry::Unknown, + |codec_specific| SampleEntry::Video(VideoSampleEntry { + codec_type: codec_type, data_reference_index: data_reference_index, width: width, height: height, codec_specific: codec_specific, protection_info: protection_info, - }))) + })) ) } @@ -2007,7 +2026,7 @@ fn read_qt_wave_atom(src: &mut BMFFBox) -> Result { } /// Parse an audio description inside an stsd box. -fn read_audio_sample_entry(src: &mut BMFFBox) -> Result<(CodecType, SampleEntry)> { +fn read_audio_sample_entry(src: &mut BMFFBox) -> Result { let name = src.get_header().name; // Skip uninteresting fields. @@ -2119,15 +2138,16 @@ fn read_audio_sample_entry(src: &mut BMFFBox) -> Result<(CodecType, check_parser_state!(b.content); } - Ok(codec_specific.map_or((CodecType::Unknown, SampleEntry::Unknown), - |codec_specific| (codec_type, SampleEntry::Audio(AudioSampleEntry { + Ok(codec_specific.map_or(SampleEntry::Unknown, + |codec_specific| SampleEntry::Audio(AudioSampleEntry { + codec_type: codec_type, data_reference_index: data_reference_index, channelcount: channelcount, samplesize: samplesize, samplerate: samplerate, codec_specific: codec_specific, protection_info: protection_info, - }))) + })) ) } @@ -2139,7 +2159,6 @@ fn read_stsd(src: &mut BMFFBox, track: &mut Track) -> Result(src: &mut BMFFBox, track: &mut Track) -> Result Err(Error::Unsupported("unknown track type")), }; let description = match description { - Ok((codec_type, desc)) => { - track.codec_type = codec_type; - desc - } + Ok(desc) => desc, Err(Error::Unsupported(_)) => { // read_{audio,video}_desc may have returned Unsupported // after partially reading the box content, so we can't @@ -2160,14 +2176,9 @@ fn read_stsd(src: &mut BMFFBox, track: &mut Track) -> Result return Err(e), }; - if track.data.is_none() { - track.data = Some(description.clone()); - } else { - debug!("** don't know how to handle multiple descriptions **"); - } vec_push(&mut descriptions, description)?; check_parser_state!(b.content); if descriptions.len() == description_count as usize { @@ -2194,6 +2205,9 @@ fn read_sinf(src: &mut BMFFBox) -> Result { let frma = read_frma(&mut b)?; sinf.code_name = frma; }, + BoxType::SchemeTypeBox => { + sinf.scheme_type = Some(read_schm(&mut b)?); + } BoxType::SchemeInformationBox => { // We only need tenc box in schi box so far. sinf.tenc = read_schi(&mut b)?; @@ -2225,16 +2239,42 @@ fn read_schi(src: &mut BMFFBox) -> Result } fn read_tenc(src: &mut BMFFBox) -> Result { - let (_, _) = read_fullbox_extra(src)?; + let (version, _) = read_fullbox_extra(src)?; - let default_is_encrypted = be_u24(src)?; + // reserved byte + skip(src, 1)?; + // the next byte is used to signal the default pattern in version >= 1 + let (default_crypt_byte_block, default_skip_byte_block) = match version { + 0 => { + skip(src, 1)?; + (None, None) + }, + _ => { + let pattern_byte = src.read_u8()?; + let crypt_bytes = pattern_byte >> 4; + let skip_bytes = pattern_byte & 0x0f; + (Some(crypt_bytes), Some(skip_bytes)) + } + }; + let default_is_encrypted = src.read_u8()?; let default_iv_size = src.read_u8()?; let default_kid = read_buf(src, 16)?; + // If default_is_encrypted == 1 && default_iv_size == 0 we expect a default_constant_iv + let default_constant_iv = match (default_is_encrypted, default_iv_size) { + (1, 0) => { + let default_constant_iv_size = src.read_u8()?; + Some(read_buf(src, default_constant_iv_size as usize)?) + }, + _ => None, + }; Ok(TrackEncryptionBox { is_encrypted: default_is_encrypted, iv_size: default_iv_size, kid: default_kid, + crypt_byte_block_count: default_crypt_byte_block, + skip_byte_block_count: default_skip_byte_block, + constant_iv: default_constant_iv }) } @@ -2243,6 +2283,20 @@ fn read_frma(src: &mut BMFFBox) -> Result { String::from_utf8(code_name).map_err(From::from) } +fn read_schm(src: &mut BMFFBox) -> Result { + // Flags can be used to signal presence of URI in the box, but we don't + // use the URI so don't bother storing the flags. + let (_, _) = read_fullbox_extra(src)?; + let scheme_type = FourCC::from(be_u32(src)?); + let scheme_version = be_u32(src)?; + // Null terminated scheme URI may follow, but we don't use it right now. + skip_box_remain(src)?; + Ok(SchemeTypeBox { + scheme_type: scheme_type, + scheme_version: scheme_version, + }) +} + /// Skip a number of bytes that we don't care to parse. fn skip(src: &mut T, mut bytes: usize) -> Result<()> { const BUF_SIZE: usize = 64 * 1024; diff --git a/media/mp4parse-rust/mp4parse/src/tests.rs b/media/mp4parse-rust/mp4parse/src/tests.rs index 3ef09e4cb359..15d5c47962fa 100644 --- a/media/mp4parse-rust/mp4parse/src/tests.rs +++ b/media/mp4parse-rust/mp4parse/src/tests.rs @@ -942,9 +942,13 @@ fn read_qt_wave_atom() { let mut iter = super::BoxIter::new(&mut stream); let mut stream = iter.next_box().unwrap().unwrap(); - let (codec_type, _) = super::read_audio_sample_entry(&mut stream) + let sample_entry = super::read_audio_sample_entry(&mut stream) .expect("fail to read qt wave atom"); - assert_eq!(codec_type, super::CodecType::MP3); + match sample_entry { + super::SampleEntry::Audio(sample_entry) => + assert_eq!(sample_entry.codec_type, super::CodecType::MP3), + _ => assert!(false, "fail to read audio sample enctry"), + } } #[test] @@ -969,6 +973,7 @@ fn read_descriptor_80() { assert_eq!(es.audio_codec, super::CodecType::AAC); assert_eq!(es.audio_object_type, Some(2)); + assert_eq!(es.extended_audio_object_type, None); assert_eq!(es.audio_sample_rate, Some(48000)); assert_eq!(es.audio_channel_count, Some(2)); assert_eq!(es.codec_esds, aac_esds); @@ -998,12 +1003,46 @@ fn read_esds() { assert_eq!(es.audio_codec, super::CodecType::AAC); assert_eq!(es.audio_object_type, Some(2)); + assert_eq!(es.extended_audio_object_type, None); assert_eq!(es.audio_sample_rate, Some(24000)); assert_eq!(es.audio_channel_count, Some(6)); assert_eq!(es.codec_esds, aac_esds); assert_eq!(es.decoder_specific_data, aac_dc_descriptor); } +#[test] +fn read_esds_aac_type5() { + let aac_esds = + vec![ + 0x03, 0x80, 0x80, 0x80, + 0x2F, 0x00, 0x00, 0x00, 0x04, 0x80, 0x80, 0x80, + 0x21, 0x40, 0x15, 0x00, 0x15, 0x00, 0x00, 0x03, + 0xED, 0xAA, 0x00, 0x03, 0x6B, 0x00, 0x05, 0x80, + 0x80, 0x80, 0x0F, 0x2B, 0x01, 0x88, 0x02, 0xC4, + 0x04, 0x90, 0x2C, 0x10, 0x8C, 0x80, 0x00, 0x00, + 0xED, 0x40, 0x06, 0x80, 0x80, 0x80, 0x01, 0x02, + ]; + + let aac_dc_descriptor = &aac_esds[31 .. 46]; + + let mut stream = make_box(BoxSize::Auto, b"esds", |s| { + s.B32(0) // reserved + .append_bytes(aac_esds.as_slice()) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + + let es = super::read_esds(&mut stream).unwrap(); + + assert_eq!(es.audio_codec, super::CodecType::AAC); + assert_eq!(es.audio_object_type, Some(2)); + assert_eq!(es.extended_audio_object_type, Some(5)); + assert_eq!(es.audio_sample_rate, Some(24000)); + assert_eq!(es.audio_channel_count, Some(8)); + assert_eq!(es.codec_esds, aac_esds); + assert_eq!(es.decoder_specific_data, aac_dc_descriptor); +} + #[test] fn read_stsd_mp4v() { let mp4v = @@ -1033,12 +1072,11 @@ fn read_stsd_mp4v() { let mut iter = super::BoxIter::new(&mut stream); let mut stream = iter.next_box().unwrap().unwrap(); - let (codec_type, sample_entry) = super::read_video_sample_entry(&mut stream).unwrap(); - - assert_eq!(codec_type, super::CodecType::MP4V); + let sample_entry = super::read_video_sample_entry(&mut stream).unwrap(); match sample_entry { super::SampleEntry::Video(v) => { + assert_eq!(v.codec_type, super::CodecType::MP4V); assert_eq!(v.width, 720); assert_eq!(v.height, 480); match v.codec_specific { @@ -1074,6 +1112,7 @@ fn read_esds_one_byte_extension_descriptor() { assert_eq!(es.audio_codec, super::CodecType::AAC); assert_eq!(es.audio_object_type, Some(2)); + assert_eq!(es.extended_audio_object_type, None); assert_eq!(es.audio_sample_rate, Some(48000)); assert_eq!(es.audio_channel_count, Some(2)); } @@ -1110,9 +1149,13 @@ fn read_f4v_stsd() { let mut iter = super::BoxIter::new(&mut stream); let mut stream = iter.next_box().unwrap().unwrap(); - let (codec_type, _) = super::read_audio_sample_entry(&mut stream) + let sample_entry = super::read_audio_sample_entry(&mut stream) .expect("failed to read f4v stsd atom"); - assert_eq!(codec_type, super::CodecType::MP3); + match sample_entry { + super::SampleEntry::Audio(sample_entry) => + assert_eq!(sample_entry.codec_type, super::CodecType::MP3), + _ => assert!(false, "fail to read audio sample enctry"), + } } #[test] @@ -1152,7 +1195,7 @@ fn unknown_video_sample_entry() { let mut iter = super::BoxIter::new(&mut stream); let mut stream = iter.next_box().unwrap().unwrap(); match super::read_video_sample_entry(&mut stream) { - Ok((super::CodecType::Unknown, super::SampleEntry::Unknown)) => (), + Ok(super::SampleEntry::Unknown) => (), _ => panic!("expected a different error result"), } } @@ -1177,7 +1220,7 @@ fn unknown_audio_sample_entry() { let mut iter = super::BoxIter::new(&mut stream); let mut stream = iter.next_box().unwrap().unwrap(); match super::read_audio_sample_entry(&mut stream) { - Ok((super::CodecType::Unknown, super::SampleEntry::Unknown)) => (), + Ok(super::SampleEntry::Unknown) => (), _ => panic!("expected a different error result"), } } @@ -1191,7 +1234,7 @@ fn read_esds_invalid_descriptor() { 0x00, 0x04, 0x80, 0x80, 0x80, 0x14, 0x40, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x05, 0x80, 0x80, 0x80, 0x02, - 0xe8, 0x35, 0x06, 0xff, 0x7f, 0x00, 0x00, 0x02, + 0xe8, 0x35, 0x06, 0xff, 0x7f, 0x00, 0x00, ]; let mut stream = make_box(BoxSize::Auto, b"esds", |s| { @@ -1281,12 +1324,11 @@ fn read_stsd_lpcm() { let mut iter = super::BoxIter::new(&mut stream); let mut stream = iter.next_box().unwrap().unwrap(); - let (codec_type, sample_entry) = super::read_audio_sample_entry(&mut stream).unwrap(); - - assert_eq!(codec_type, super::CodecType::LPCM); + let sample_entry = super::read_audio_sample_entry(&mut stream).unwrap(); match sample_entry { super::SampleEntry::Audio(a) => { + assert_eq!(a.codec_type, super::CodecType::LPCM); assert_eq!(a.samplerate, 96000.0); assert_eq!(a.channelcount, 1); match a.codec_specific { diff --git a/media/mp4parse-rust/mp4parse/tests/public.rs b/media/mp4parse-rust/mp4parse/tests/public.rs index a82a7ff38d71..95700f37096d 100644 --- a/media/mp4parse-rust/mp4parse/tests/public.rs +++ b/media/mp4parse-rust/mp4parse/tests/public.rs @@ -10,11 +10,22 @@ 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"; +static AUDIO_EME_CENC_MP4: &'static str = "tests/bipbop-cenc-audioinit.mp4"; +static VIDEO_EME_CENC_MP4: &'static str = "tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4"; +// The cbcs files were created via shaka-packager from Firefox's test suite's bipbop.mp4 using: +// packager-win.exe +// in=bipbop.mp4,stream=audio,init_segment=bipbop_cbcs_audio_init.mp4,segment_template=bipbop_cbcs_audio_$Number$.m4s +// in=bipbop.mp4,stream=video,init_segment=bipbop_cbcs_video_init.mp4,segment_template=bipbop_cbcs_video_$Number$.m4s +// --protection_scheme cbcs --enable_raw_key_encryption +// --keys label=:key_id=7e571d047e571d047e571d047e571d21:key=7e5744447e5744447e5744447e574421 +// --iv 11223344556677889900112233445566 +// --generate_static_mpd --mpd_output bipbop_cbcs.mpd +// note: only the init files are needed for these tests +static AUDIO_EME_CBCS_MP4: &'static str = "tests/bipbop_cbcs_audio_init.mp4"; +static VIDEO_EME_CBCS_MP4: &'static str = "tests/bipbop_cbcs_video_init.mp4"; static VIDEO_AV1_MP4: &'static str = "tests/tiny_av1.mp4"; -// Taken from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53 +// Adapted from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53 #[test] fn public_api() { let mut fd = File::open(MINI_MP4).expect("Unknown file"); @@ -26,15 +37,13 @@ fn public_api() { mp4::read_mp4(&mut c, &mut context).expect("read_mp4 failed"); assert_eq!(context.timescale, Some(mp4::MediaTimeScale(1000))); for track in context.tracks { - match track.data { - Some(mp4::SampleEntry::Video(v)) => { + match track.track_type { + mp4::TrackType::Video => { // 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(12800, 0))); - assert_eq!(v.width, 320); - assert_eq!(v.height, 240); // track.tkhd part let tkhd = track.tkhd.unwrap(); @@ -43,13 +52,20 @@ fn public_api() { assert_eq!(tkhd.width, 20971520); assert_eq!(tkhd.height, 15728640); - // track.data part + // track.stsd part + let stsd = track.stsd.expect("expected an stsd"); + let v = match stsd.descriptions.first().expect("expected a SampleEntry") { + mp4::SampleEntry::Video(v) => v, + _ => panic!("expected a VideoSampleEntry"), + }; + assert_eq!(v.width, 320); + assert_eq!(v.height, 240); assert_eq!(match v.codec_specific { - mp4::VideoCodecSpecific::AVCConfig(v) => { - assert!(!v.is_empty()); + mp4::VideoCodecSpecific::AVCConfig(ref avc) => { + assert!(!avc.is_empty()); "AVC" } - mp4::VideoCodecSpecific::VPxConfig(vpx) => { + mp4::VideoCodecSpecific::VPxConfig(ref vpx) => { // We don't enter in here, we just check if fields are public. assert!(vpx.bit_depth > 0); assert!(vpx.color_space > 0); @@ -57,16 +73,16 @@ fn public_api() { assert!(!vpx.codec_init.is_empty()); "VPx" } - mp4::VideoCodecSpecific::ESDSConfig(mp4v) => { + mp4::VideoCodecSpecific::ESDSConfig(ref mp4v) => { assert!(!mp4v.is_empty()); "MP4V" } - mp4::VideoCodecSpecific::AV1Config(_av1c) => { + mp4::VideoCodecSpecific::AV1Config(ref _av1c) => { "AV1" } }, "AVC"); } - Some(mp4::SampleEntry::Audio(a)) => { + mp4::TrackType::Audio => { // track part assert_eq!(track.duration, Some(mp4::TrackScaledTime(2944, 1))); assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0))); @@ -80,27 +96,32 @@ fn public_api() { assert_eq!(tkhd.width, 0); assert_eq!(tkhd.height, 0); - // track.data part + // track.stsd part + let stsd = track.stsd.expect("expected an stsd"); + let a = match stsd.descriptions.first().expect("expected a SampleEntry") { + mp4::SampleEntry::Audio(a) => a, + _ => panic!("expected a AudioSampleEntry"), + }; assert_eq!(match a.codec_specific { - mp4::AudioCodecSpecific::ES_Descriptor(esds) => { + mp4::AudioCodecSpecific::ES_Descriptor(ref esds) => { assert_eq!(esds.audio_codec, mp4::CodecType::AAC); assert_eq!(esds.audio_sample_rate.unwrap(), 48000); assert_eq!(esds.audio_object_type.unwrap(), 2); "ES" } - mp4::AudioCodecSpecific::FLACSpecificBox(flac) => { + mp4::AudioCodecSpecific::FLACSpecificBox(ref flac) => { // STREAMINFO block must be present and first. assert!(!flac.blocks.is_empty()); assert_eq!(flac.blocks[0].block_type, 0); assert_eq!(flac.blocks[0].data.len(), 34); "FLAC" } - mp4::AudioCodecSpecific::OpusSpecificBox(opus) => { + mp4::AudioCodecSpecific::OpusSpecificBox(ref opus) => { // We don't enter in here, we just check if fields are public. assert!(opus.version > 0); "Opus" } - mp4::AudioCodecSpecific::ALACSpecificBox(alac) => { + mp4::AudioCodecSpecific::ALACSpecificBox(ref alac) => { assert!(alac.data.len() == 24 || alac.data.len() == 48); "ALAC" } @@ -114,7 +135,7 @@ fn public_api() { assert!(a.samplesize > 0); assert!(a.samplerate > 0.0); } - Some(mp4::SampleEntry::Unknown) | None => {} + mp4::TrackType::Metadata | mp4::TrackType::Unknown => {} } } } @@ -125,7 +146,7 @@ fn public_audio_tenc() { 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 fd = File::open(AUDIO_EME_CENC_MP4).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); @@ -133,28 +154,34 @@ fn public_audio_tenc() { 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::EncryptedAudio); - match track.data { - Some(mp4::SampleEntry::Audio(a)) => { - match a.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { - Some(p) => { - assert_eq!(p.code_name, "mp4a"); - if let Some(ref tenc) = p.tenc { - assert!(tenc.is_encrypted > 0); - assert_eq!(tenc.iv_size, 16); - assert_eq!(tenc.kid, kid); - } else { - assert!(false, "Invalid test condition"); - } - }, - _=> { - assert!(false, "Invalid test condition"); - }, + let stsd = track.stsd.expect("expected an stsd"); + let a = match stsd.descriptions.first().expect("expected a SampleEntry") { + mp4::SampleEntry::Audio(a) => a, + _ => panic!("expected a AudioSampleEntry"), + }; + assert_eq!(a.codec_type, mp4::CodecType::EncryptedAudio); + match a.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { + Some(ref p) => { + assert_eq!(p.code_name, "mp4a"); + if let Some(ref schm) = p.scheme_type { + assert_eq!(schm.scheme_type.value, "cenc"); + } else { + assert!(false, "Expected scheme type info"); + } + if let 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 { + assert!(false, "Invalid test condition"); } }, - _ => { + _=> { assert!(false, "Invalid test condition"); - } + }, } } } @@ -178,7 +205,7 @@ fn public_video_cenc() { 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 fd = File::open(VIDEO_EME_CENC_MP4).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); @@ -186,26 +213,32 @@ fn public_video_cenc() { 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.iter().find(|sinf| sinf.tenc.is_some()) { - Some(p) => { - assert_eq!(p.code_name, "avc1"); - if let Some(ref tenc) = p.tenc { - assert!(tenc.is_encrypted > 0); - assert_eq!(tenc.iv_size, 16); - assert_eq!(tenc.kid, kid); - } else { - assert!(false, "Invalid test condition"); - } - }, - _=> { - assert!(false, "Invalid test condition"); - }, + 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(ref p) => { + assert_eq!(p.code_name, "avc1"); + if let Some(ref schm) = p.scheme_type { + assert_eq!(schm.scheme_type.value, "cenc"); + } else { + assert!(false, "Expected scheme type info"); + } + if let 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 { + assert!(false, "Invalid test condition"); } }, - _ => { + _=> { assert!(false, "Invalid test condition"); } } @@ -222,54 +255,210 @@ fn public_video_cenc() { } #[test] -fn public_video_av1() { - let mut fd = File::open(VIDEO_AV1_MP4).expect("Unknown file"); - let mut buf = Vec::new(); - fd.read_to_end(&mut buf).expect("File error"); +fn publicaudio_cbcs() { + let system_id = + vec![0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, + 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b]; - 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::AV1); - match track.data { - Some(mp4::SampleEntry::Video(v)) => { - // 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))); - assert_eq!(v.width, 64); - assert_eq!(v.height, 64); + let kid = + vec![0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, + 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x21]; - // track.tkhd part - let tkhd = track.tkhd.unwrap(); - assert_eq!(tkhd.disabled, false); - assert_eq!(tkhd.duration, 42); - assert_eq!(tkhd.width, 4194304); - assert_eq!(tkhd.height, 4194304); + let default_iv = + vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; - match v.codec_specific { - mp4::VideoCodecSpecific::AV1Config(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_eq!(av1c.monochrome, false); - assert_eq!(av1c.chroma_subsampling_x, 1); - assert_eq!(av1c.chroma_subsampling_y, 1); - assert_eq!(av1c.chroma_sample_position, 0); - assert_eq!(av1c.initial_presentation_delay_present, false); - assert_eq!(av1c.initial_presentation_delay_minus_one, 0); - }, - _ => assert!(false, "Invalid test condition"), - } + 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, 0x04, 0x7e, 0x57, 0x1d, 0x04, + 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x21, + 0x00, 0x00, 0x00, 0x00]; - }, - _ => { - assert!(false, "Invalid test condition"); - } - } - } + let mut fd = File::open(AUDIO_EME_CBCS_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 { + let stsd = track.stsd.expect("expected an stsd"); + assert_eq!(stsd.descriptions.len(), 2); + let mut found_encrypted_sample_description = false; + for description in stsd.descriptions { + match description { + mp4::SampleEntry::Audio(ref a) => { + if let Some(p) = a.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { + found_encrypted_sample_description = true; + assert_eq!(p.code_name, "mp4a"); + if let Some(ref schm) = p.scheme_type { + assert_eq!(schm.scheme_type.value, "cbcs"); + } else { + assert!(false, "Expected scheme type info"); + } + if let 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())); + } else { + assert!(false, "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); + } +} + +#[test] +fn public_video_cbcs() { + 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, 0x04, 0x7e, 0x57, 0x1d, 0x04, + 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x21]; + + let default_iv = + vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, + 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + + 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, 0x04, 0x7e, 0x57, 0x1d, 0x04, + 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x21, + 0x00, 0x00, 0x00, 0x00]; + + let mut fd = File::open(VIDEO_EME_CBCS_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 { + let stsd = track.stsd.expect("expected an stsd"); + assert_eq!(stsd.descriptions.len(), 2); + let mut 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); + if let Some(p) = v.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { + found_encrypted_sample_description = true; + assert_eq!(p.code_name, "avc1"); + if let Some(ref schm) = p.scheme_type { + assert_eq!(schm.scheme_type.value, "cbcs"); + } else { + assert!(false, "Expected scheme type info"); + } + if let 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())); + } else { + assert!(false, "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); + } +} + +#[test] +fn public_video_av1() { + let mut fd = File::open(VIDEO_AV1_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 { + // 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_eq!(tkhd.disabled, false); + assert_eq!(tkhd.duration, 42); + assert_eq!(tkhd.width, 4194304); + assert_eq!(tkhd.height, 4194304); + + // 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_eq!(av1c.monochrome, false); + assert_eq!(av1c.chroma_subsampling_x, 1); + assert_eq!(av1c.chroma_subsampling_y, 1); + assert_eq!(av1c.chroma_sample_position, 0); + assert_eq!(av1c.initial_presentation_delay_present, false); + assert_eq!(av1c.initial_presentation_delay_minus_one, 0); + }, + _ => assert!(false, "Invalid test condition"), + } + } } diff --git a/media/mp4parse-rust/mp4parse_capi/Cargo.toml b/media/mp4parse-rust/mp4parse_capi/Cargo.toml index a04b1e977735..c5e4c6d8479d 100644 --- a/media/mp4parse-rust/mp4parse_capi/Cargo.toml +++ b/media/mp4parse-rust/mp4parse_capi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mp4parse_capi" -version = "0.10.1" +version = "0.11.2" authors = [ "Ralph Giles ", "Matthew Gregan ", @@ -26,7 +26,7 @@ travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" } [dependencies] byteorder = "1.2.1" log = "0.4" -mp4parse = {version = "0.10.1", path = "../mp4parse"} +mp4parse = {version = "0.11.2", path = "../mp4parse"} num-traits = "0.2.0" [dev-dependencies] diff --git a/media/mp4parse-rust/mp4parse_capi/src/lib.rs b/media/mp4parse-rust/mp4parse_capi/src/lib.rs index 7b6be9c5efda..5c8ece6be63f 100644 --- a/media/mp4parse-rust/mp4parse_capi/src/lib.rs +++ b/media/mp4parse-rust/mp4parse_capi/src/lib.rs @@ -107,11 +107,27 @@ impl Default for Mp4parseCodec { fn default() -> Self { Mp4parseCodec::Unknown } } +#[repr(C)] +#[derive(PartialEq, Debug)] +pub enum Mp4ParseEncryptionSchemeType { + None, + Cenc, + Cbc1, + Cens, + Cbcs, + // Schemes also have a version component. At the time of writing, this does + // not impact handling, so we do not expose it. Note that this may need to + // be exposed in future, should the spec change. +} + +impl Default for Mp4ParseEncryptionSchemeType { + fn default() -> Self { Mp4ParseEncryptionSchemeType::None } +} + #[repr(C)] #[derive(Default, Debug)] pub struct Mp4parseTrackInfo { pub track_type: Mp4parseTrackType, - pub codec: Mp4parseCodec, pub track_id: u32, pub duration: u64, pub media_time: i64, // wants to be u64? understand how elst adjustment works @@ -169,14 +185,22 @@ pub struct Mp4parsePsshInfo { #[repr(C)] #[derive(Default, Debug)] pub struct Mp4parseSinfInfo { - pub is_encrypted: u32, + pub scheme_type: Mp4ParseEncryptionSchemeType, + pub is_encrypted: u8, pub iv_size: u8, pub kid: Mp4parseByteData, + // Members for pattern encryption schemes, may be 0 (u8) or empty + // (Mp4parseByteData) if pattern encryption is not in use + pub crypt_byte_block: u8, + pub skip_byte_block: u8, + pub constant_iv: Mp4parseByteData, + // End pattern encryption scheme members } #[repr(C)] #[derive(Default, Debug)] -pub struct Mp4parseTrackAudioInfo { +pub struct Mp4parseTrackAudioSampleInfo { + pub codec_type: Mp4parseCodec, pub channels: u16, pub bit_depth: u16, pub sample_rate: u32, @@ -187,16 +211,52 @@ pub struct Mp4parseTrackAudioInfo { pub protected_data: Mp4parseSinfInfo, } +#[repr(C)] +#[derive(Debug)] +pub struct Mp4parseTrackAudioInfo { + pub sample_info_count: u32, + pub sample_info: *const Mp4parseTrackAudioSampleInfo, +} + +impl Default for Mp4parseTrackAudioInfo { + fn default() -> Self { + Self { + sample_info_count: 0, + sample_info: std::ptr::null(), + } + } +} + #[repr(C)] #[derive(Default, Debug)] +pub struct Mp4parseTrackVideoSampleInfo { + pub codec_type: Mp4parseCodec, + pub image_width: u16, + pub image_height: u16, + pub extra_data: Mp4parseByteData, + pub protected_data: Mp4parseSinfInfo, +} + +#[repr(C)] +#[derive(Debug)] pub struct Mp4parseTrackVideoInfo { pub display_width: u32, pub display_height: u32, - pub image_width: u16, - pub image_height: u16, pub rotation: u16, - pub extra_data: Mp4parseByteData, - pub protected_data: Mp4parseSinfInfo, + pub sample_info_count: u32, + pub sample_info: *const Mp4parseTrackVideoSampleInfo, +} + +impl Default for Mp4parseTrackVideoInfo { + fn default() -> Self { + Self { + display_width: 0, + display_height: 0, + rotation: 0, + sample_info_count: 0, + sample_info: std::ptr::null(), + } + } } #[repr(C)] @@ -214,6 +274,12 @@ pub struct Mp4parseParser { opus_header: HashMap>, pssh_data: Vec, sample_table: HashMap>, + // Store a mapping from track index (not id) to associated sample + // descriptions. Because each track has a variable number of sample + // descriptions, and because we need the data to live long enough to be + // copied out by callers, we store these on the parser struct. + audio_track_sample_descriptions: HashMap>, + video_track_sample_descriptions: HashMap>, } impl Mp4parseParser { @@ -289,6 +355,8 @@ pub unsafe extern fn mp4parse_new(io: *const Mp4parseIo) -> *mut Mp4parseParser opus_header: HashMap::new(), pssh_data: Vec::new(), sample_table: HashMap::new(), + audio_track_sample_descriptions: HashMap::new(), + video_track_sample_descriptions: HashMap::new(), }); Box::into_raw(parser) @@ -413,37 +481,6 @@ pub unsafe extern fn mp4parse_get_track_info(parser: *mut Mp4parseParser, track_ TrackType::Unknown => return Mp4parseStatus::Unsupported, }; - // Return UNKNOWN for unsupported format. - info.codec = match context.tracks[track_index].data { - Some(SampleEntry::Audio(ref audio)) => match audio.codec_specific { - AudioCodecSpecific::OpusSpecificBox(_) => - Mp4parseCodec::Opus, - AudioCodecSpecific::FLACSpecificBox(_) => - Mp4parseCodec::Flac, - AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC => - Mp4parseCodec::Aac, - AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 => - Mp4parseCodec::Mp3, - AudioCodecSpecific::ES_Descriptor(_) | AudioCodecSpecific::LPCM => - Mp4parseCodec::Unknown, - AudioCodecSpecific::MP3 => - Mp4parseCodec::Mp3, - AudioCodecSpecific::ALACSpecificBox(_) => - Mp4parseCodec::Alac, - }, - Some(SampleEntry::Video(ref video)) => match video.codec_specific { - VideoCodecSpecific::VPxConfig(_) => - Mp4parseCodec::Vp9, - VideoCodecSpecific::AV1Config(_) => - Mp4parseCodec::Av1, - VideoCodecSpecific::AVCConfig(_) => - Mp4parseCodec::Avc, - VideoCodecSpecific::ESDSConfig(_) => // MP4V (14496-2) video is unsupported. - Mp4parseCodec::Unknown, - }, - _ => Mp4parseCodec::Unknown, - }; - let track = &context.tracks[track_index]; if let (Some(track_timescale), @@ -494,7 +531,7 @@ pub unsafe extern fn mp4parse_get_track_audio_info(parser: *mut Mp4parseParser, // Initialize fields to default values to ensure all fields are always valid. *info = Default::default(); - let context = (*parser).context_mut(); + let context = (*parser).context(); if track_index as usize >= context.tracks.len() { return Mp4parseStatus::BadArg; @@ -502,89 +539,160 @@ pub unsafe extern fn mp4parse_get_track_audio_info(parser: *mut Mp4parseParser, let track = &context.tracks[track_index as usize]; - match track.track_type { - TrackType::Audio => {} - _ => return Mp4parseStatus::Invalid, + if track.track_type != TrackType::Audio { + return Mp4parseStatus::Invalid; + } + + // Handle track.stsd + let stsd = match track.stsd { + Some(ref stsd) => stsd, + None => return Mp4parseStatus::Invalid, // Stsd should be present }; - let audio = match track.data { - Some(ref data) => data, - None => return Mp4parseStatus::Invalid, - }; + if stsd.descriptions.len() == 0 { + return Mp4parseStatus::Invalid; // Should have at least 1 description + } - let audio = match *audio { - SampleEntry::Audio(ref x) => x, - _ => return Mp4parseStatus::Invalid, - }; + let mut audio_sample_infos = Vec:: with_capacity(stsd.descriptions.len()); + for description in stsd.descriptions.iter() { + let mut sample_info = Mp4parseTrackAudioSampleInfo::default(); + let audio = match description { + SampleEntry::Audio(a) => a, + _ => return Mp4parseStatus::Invalid, + }; - (*info).channels = audio.channelcount as u16; - (*info).bit_depth = audio.samplesize; - (*info).sample_rate = audio.samplerate as u32; + // UNKNOWN for unsupported format. + sample_info.codec_type = match audio.codec_specific { + AudioCodecSpecific::OpusSpecificBox(_) => + Mp4parseCodec::Opus, + AudioCodecSpecific::FLACSpecificBox(_) => + Mp4parseCodec::Flac, + AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC => + Mp4parseCodec::Aac, + AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 => + Mp4parseCodec::Mp3, + AudioCodecSpecific::ES_Descriptor(_) | AudioCodecSpecific::LPCM => + Mp4parseCodec::Unknown, + AudioCodecSpecific::MP3 => + Mp4parseCodec::Mp3, + AudioCodecSpecific::ALACSpecificBox(_) => + Mp4parseCodec::Alac, + }; + sample_info.channels = audio.channelcount as u16; + sample_info.bit_depth = audio.samplesize; + sample_info.sample_rate = audio.samplerate as u32; + // sample_info.profile is handled below on a per case basis - match audio.codec_specific { - AudioCodecSpecific::ES_Descriptor(ref v) => { - if v.codec_esds.len() > std::u32::MAX as usize { - return Mp4parseStatus::Invalid; - } - (*info).extra_data.length = v.codec_esds.len() as u32; - (*info).extra_data.data = v.codec_esds.as_ptr(); - (*info).codec_specific_config.length = v.decoder_specific_data.len() as u32; - (*info).codec_specific_config.data = v.decoder_specific_data.as_ptr(); - if let Some(rate) = v.audio_sample_rate { - (*info).sample_rate = rate; - } - if let Some(channels) = v.audio_channel_count { - (*info).channels = channels; - } - if let Some(profile) = v.audio_object_type { - (*info).profile = profile; - } - (*info).extended_profile = match v.extended_audio_object_type { - Some(extended_profile) => extended_profile, - _ => (*info).profile - }; - } - AudioCodecSpecific::FLACSpecificBox(ref flac) => { - // Return the STREAMINFO metadata block in the codec_specific. - let streaminfo = &flac.blocks[0]; - if streaminfo.block_type != 0 || streaminfo.data.len() != 34 { - return Mp4parseStatus::Invalid; - } - (*info).codec_specific_config.length = streaminfo.data.len() as u32; - (*info).codec_specific_config.data = streaminfo.data.as_ptr(); - } - AudioCodecSpecific::OpusSpecificBox(ref opus) => { - let mut v = Vec::new(); - match serialize_opus_header(opus, &mut v) { - Err(_) => { + match audio.codec_specific { + AudioCodecSpecific::ES_Descriptor(ref esds) => { + if esds.codec_esds.len() > std::u32::MAX as usize { return Mp4parseStatus::Invalid; } - Ok(_) => { - let header = (*parser).opus_header_mut(); - header.insert(track_index, v); - if let Some(v) = header.get(&track_index) { - if v.len() > std::u32::MAX as usize { - return Mp4parseStatus::Invalid; + sample_info.extra_data.length = esds.codec_esds.len() as u32; + sample_info.extra_data.data = esds.codec_esds.as_ptr(); + sample_info.codec_specific_config.length = esds.decoder_specific_data.len() as u32; + sample_info.codec_specific_config.data = esds.decoder_specific_data.as_ptr(); + if let Some(rate) = esds.audio_sample_rate { + sample_info.sample_rate = rate; + } + if let Some(channels) = esds.audio_channel_count { + sample_info.channels = channels; + } + if let Some(profile) = esds.audio_object_type { + sample_info.profile = profile; + } + sample_info.extended_profile = match esds.extended_audio_object_type { + Some(extended_profile) => extended_profile, + _ => sample_info.profile + }; + } + AudioCodecSpecific::FLACSpecificBox(ref flac) => { + // Return the STREAMINFO metadata block in the codec_specific. + let streaminfo = &flac.blocks[0]; + if streaminfo.block_type != 0 || streaminfo.data.len() != 34 { + return Mp4parseStatus::Invalid; + } + sample_info.codec_specific_config.length = streaminfo.data.len() as u32; + sample_info.codec_specific_config.data = streaminfo.data.as_ptr(); + } + AudioCodecSpecific::OpusSpecificBox(ref opus) => { + let mut v = Vec::new(); + match serialize_opus_header(opus, &mut v) { + Err(_) => { + return Mp4parseStatus::Invalid; + } + Ok(_) => { + let header = (*parser).opus_header_mut(); + header.insert(track_index, v); + if let Some(v) = header.get(&track_index) { + if v.len() > std::u32::MAX as usize { + return Mp4parseStatus::Invalid; + } + sample_info.codec_specific_config.length = v.len() as u32; + sample_info.codec_specific_config.data = v.as_ptr(); } - (*info).codec_specific_config.length = v.len() as u32; - (*info).codec_specific_config.data = v.as_ptr(); } } } + AudioCodecSpecific::ALACSpecificBox(ref alac) => { + sample_info.codec_specific_config.length = alac.data.len() as u32; + sample_info.codec_specific_config.data = alac.data.as_ptr(); + } + AudioCodecSpecific::MP3 | AudioCodecSpecific::LPCM => (), } - AudioCodecSpecific::ALACSpecificBox(ref alac) => { - (*info).codec_specific_config.length = alac.data.len() as u32; - (*info).codec_specific_config.data = alac.data.as_ptr(); + + if let Some(p) = audio.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { + sample_info.protected_data.scheme_type = match p.scheme_type { + Some(ref scheme_type_box) => { + match scheme_type_box.scheme_type.value.as_ref() { + "cenc" => Mp4ParseEncryptionSchemeType::Cenc, + "cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, + // We don't support other schemes, and shouldn't reach + // this case. Try to gracefully handle by treating as + // no encryption case. + _ => Mp4ParseEncryptionSchemeType::None, + } + }, + None => Mp4ParseEncryptionSchemeType::None, + }; + if let Some(ref tenc) = p.tenc { + sample_info.protected_data.is_encrypted = tenc.is_encrypted; + sample_info.protected_data.iv_size = tenc.iv_size; + sample_info.protected_data.kid.set_data(&(tenc.kid)); + sample_info.protected_data.crypt_byte_block = match tenc.crypt_byte_block_count { + Some(n) => n, + None => 0, + }; + sample_info.protected_data.skip_byte_block = match tenc.skip_byte_block_count { + Some(n) => n, + None => 0, + }; + match tenc.constant_iv { + Some(ref iv_vec) => { + if iv_vec.len() > std::u32::MAX as usize { + return Mp4parseStatus::Invalid; + } + sample_info.protected_data.constant_iv.set_data(iv_vec); + }, + None => {}, // Don't need to do anything, defaults are correct + }; + } } - AudioCodecSpecific::MP3 | AudioCodecSpecific::LPCM => (), + audio_sample_infos.push(sample_info); } - if let Some(p) = audio.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { - if let Some(ref tenc) = p.tenc { - (*info).protected_data.is_encrypted = tenc.is_encrypted; - (*info).protected_data.iv_size = tenc.iv_size; - (*info).protected_data.kid.set_data(&(tenc.kid)); - } + (*parser).audio_track_sample_descriptions.insert(track_index, audio_sample_infos); + match (*parser).audio_track_sample_descriptions.get(&track_index) { + Some(sample_info) => { + if sample_info.len() > std::u32::MAX as usize { + // Should never happen due to upper limits on number of sample + // descriptions a track can have, but lets be safe. + return Mp4parseStatus::Invalid; + } + (*info).sample_info_count = sample_info.len() as u32; + (*info).sample_info = sample_info.as_ptr(); + }, + None => return Mp4parseStatus::Invalid, // Shouldn't happen, we just inserted the info! } Mp4parseStatus::Ok @@ -600,7 +708,7 @@ pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut Mp4parseParser, // Initialize fields to default values to ensure all fields are always valid. *info = Default::default(); - let context = (*parser).context_mut(); + let context = (*parser).context(); if track_index as usize >= context.tracks.len() { return Mp4parseStatus::BadArg; @@ -608,21 +716,11 @@ pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut Mp4parseParser, let track = &context.tracks[track_index as usize]; - match track.track_type { - TrackType::Video => {} - _ => return Mp4parseStatus::Invalid, - }; - - let video = match track.data { - Some(ref data) => data, - None => return Mp4parseStatus::Invalid, - }; - - let video = match *video { - SampleEntry::Video(ref x) => x, - _ => return Mp4parseStatus::Invalid, - }; + if track.track_type != TrackType::Video { + return Mp4parseStatus::Invalid; + } + // Handle track.tkhd if let Some(ref tkhd) = track.tkhd { (*info).display_width = tkhd.width >> 16; // 16.16 fixed point (*info).display_height = tkhd.height >> 16; // 16.16 fixed point @@ -637,24 +735,99 @@ pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut Mp4parseParser, } else { return Mp4parseStatus::Invalid; } - (*info).image_width = video.width; - (*info).image_height = video.height; - match video.codec_specific { - VideoCodecSpecific::AVCConfig(ref data) | VideoCodecSpecific::ESDSConfig(ref data) => { - (*info).extra_data.set_data(data); - }, - _ => {} + // Handle track.stsd + let stsd = match track.stsd { + Some(ref stsd) => stsd, + None => return Mp4parseStatus::Invalid, // Stsd should be present + }; + + if stsd.descriptions.len() == 0 { + return Mp4parseStatus::Invalid; // Should have at least 1 description } - if let Some(p) = video.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { - if let Some(ref tenc) = p.tenc { - (*info).protected_data.is_encrypted = tenc.is_encrypted; - (*info).protected_data.iv_size = tenc.iv_size; - (*info).protected_data.kid.set_data(&(tenc.kid)); + let mut video_sample_infos = Vec:: with_capacity(stsd.descriptions.len()); + for description in stsd.descriptions.iter() { + let mut sample_info = Mp4parseTrackVideoSampleInfo::default(); + let video = match description { + SampleEntry::Video(v) => v, + _ => return Mp4parseStatus::Invalid, + }; + + // UNKNOWN for unsupported format. + sample_info.codec_type = match video.codec_specific { + VideoCodecSpecific::VPxConfig(_) => + Mp4parseCodec::Vp9, + VideoCodecSpecific::AV1Config(_) => + Mp4parseCodec::Av1, + VideoCodecSpecific::AVCConfig(_) => + Mp4parseCodec::Avc, + VideoCodecSpecific::ESDSConfig(_) => // MP4V (14496-2) video is unsupported. + Mp4parseCodec::Unknown, + }; + sample_info.image_width = video.width; + sample_info.image_height = video.height; + + match video.codec_specific { + VideoCodecSpecific::AVCConfig(ref data) | VideoCodecSpecific::ESDSConfig(ref data) => { + sample_info.extra_data.set_data(data); + }, + _ => {} } + + if let Some(p) = video.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { + sample_info.protected_data.scheme_type = match p.scheme_type { + Some(ref scheme_type_box) => { + match scheme_type_box.scheme_type.value.as_ref() { + "cenc" => Mp4ParseEncryptionSchemeType::Cenc, + "cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, + // We don't support other schemes, and shouldn't reach + // this case. Try to gracefully handle by treating as + // no encryption case. + _ => Mp4ParseEncryptionSchemeType::None, + } + }, + None => Mp4ParseEncryptionSchemeType::None, + }; + if let Some(ref tenc) = p.tenc { + sample_info.protected_data.is_encrypted = tenc.is_encrypted; + sample_info.protected_data.iv_size = tenc.iv_size; + sample_info.protected_data.kid.set_data(&(tenc.kid)); + sample_info.protected_data.crypt_byte_block = match tenc.crypt_byte_block_count { + Some(n) => n, + None => 0, + }; + sample_info.protected_data.skip_byte_block = match tenc.skip_byte_block_count { + Some(n) => n, + None => 0, + }; + match tenc.constant_iv { + Some(ref iv_vec) => { + if iv_vec.len() > std::u32::MAX as usize { + return Mp4parseStatus::Invalid; + } + sample_info.protected_data.constant_iv.set_data(iv_vec); + }, + None => {}, // Don't need to do anything, defaults are correct + }; + } + } + video_sample_infos.push(sample_info); } + (*parser).video_track_sample_descriptions.insert(track_index, video_sample_infos); + match (*parser).video_track_sample_descriptions.get(&track_index) { + Some(sample_info) => { + if sample_info.len() > std::u32::MAX as usize { + // Should never happen due to upper limits on number of sample + // descriptions a track can have, but lets be safe. + return Mp4parseStatus::Invalid; + } + (*info).sample_info_count = sample_info.len() as u32; + (*info).sample_info = sample_info.as_ptr(); + }, + None => return Mp4parseStatus::Invalid, // Shouldn't happen, we just inserted the info! + } Mp4parseStatus::Ok } @@ -1208,7 +1381,6 @@ fn arg_validation() { let mut dummy_info = Mp4parseTrackInfo { track_type: Mp4parseTrackType::Video, - codec: Mp4parseCodec::Unknown, track_id: 0, duration: 0, media_time: 0, @@ -1218,11 +1390,9 @@ fn arg_validation() { let mut dummy_video = Mp4parseTrackVideoInfo { display_width: 0, display_height: 0, - image_width: 0, - image_height: 0, rotation: 0, - extra_data: Mp4parseByteData::default(), - protected_data: Default::default(), + sample_info_count: 0, + sample_info: std::ptr::null(), }; assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video)); @@ -1255,7 +1425,6 @@ fn arg_validation_with_parser() { let mut dummy_info = Mp4parseTrackInfo { track_type: Mp4parseTrackType::Video, - codec: Mp4parseCodec::Unknown, track_id: 0, duration: 0, media_time: 0, @@ -1265,11 +1434,9 @@ fn arg_validation_with_parser() { let mut dummy_video = Mp4parseTrackVideoInfo { display_width: 0, display_height: 0, - image_width: 0, - image_height: 0, rotation: 0, - extra_data: Mp4parseByteData::default(), - protected_data: Default::default(), + sample_info_count: 0, + sample_info: std::ptr::null(), }; assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_video_info(parser, 0, &mut dummy_video)); @@ -1319,81 +1486,61 @@ fn arg_validation_with_data() { let mut info = Mp4parseTrackInfo { track_type: Mp4parseTrackType::Video, - codec: Mp4parseCodec::Unknown, track_id: 0, duration: 0, media_time: 0, }; assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_info(parser, 0, &mut info)); assert_eq!(info.track_type, Mp4parseTrackType::Video); - assert_eq!(info.codec, Mp4parseCodec::Avc); assert_eq!(info.track_id, 1); assert_eq!(info.duration, 40000); assert_eq!(info.media_time, 0); assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_info(parser, 1, &mut info)); assert_eq!(info.track_type, Mp4parseTrackType::Audio); - assert_eq!(info.codec, Mp4parseCodec::Aac); assert_eq!(info.track_id, 2); assert_eq!(info.duration, 61333); assert_eq!(info.media_time, 21333); - let mut video = Mp4parseTrackVideoInfo { - display_width: 0, - display_height: 0, - image_width: 0, - image_height: 0, - rotation: 0, - extra_data: Mp4parseByteData::default(), - protected_data: Default::default(), - }; + let mut video = Mp4parseTrackVideoInfo::default(); assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_video_info(parser, 0, &mut video)); assert_eq!(video.display_width, 320); assert_eq!(video.display_height, 240); - assert_eq!(video.image_width, 320); - assert_eq!(video.image_height, 240); + assert_eq!(video.sample_info_count, 1); - let mut audio = Default::default(); + assert_eq!((*video.sample_info).image_width, 320); + assert_eq!((*video.sample_info).image_height, 240); + + let mut audio = Mp4parseTrackAudioInfo::default(); assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_audio_info(parser, 1, &mut audio)); - assert_eq!(audio.channels, 1); - assert_eq!(audio.bit_depth, 16); - assert_eq!(audio.sample_rate, 48000); + assert_eq!(audio.sample_info_count, 1); + + assert_eq!((*audio.sample_info).channels, 1); + assert_eq!((*audio.sample_info).bit_depth, 16); + assert_eq!((*audio.sample_info).sample_rate, 48000); // Test with an invalid track number. let mut info = Mp4parseTrackInfo { track_type: Mp4parseTrackType::Video, - codec: Mp4parseCodec::Unknown, track_id: 0, duration: 0, media_time: 0, }; assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_info(parser, 3, &mut info)); assert_eq!(info.track_type, Mp4parseTrackType::Video); - assert_eq!(info.codec, Mp4parseCodec::Unknown); assert_eq!(info.track_id, 0); assert_eq!(info.duration, 0); assert_eq!(info.media_time, 0); - let mut video = Mp4parseTrackVideoInfo { - display_width: 0, - display_height: 0, - image_width: 0, - image_height: 0, - rotation: 0, - extra_data: Mp4parseByteData::default(), - protected_data: Default::default(), - }; + let mut video = Mp4parseTrackVideoInfo::default(); assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_video_info(parser, 3, &mut video)); assert_eq!(video.display_width, 0); assert_eq!(video.display_height, 0); - assert_eq!(video.image_width, 0); - assert_eq!(video.image_height, 0); + assert_eq!(video.sample_info_count, 0); let mut audio = Default::default(); assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_audio_info(parser, 3, &mut audio)); - assert_eq!(audio.channels, 0); - assert_eq!(audio.bit_depth, 0); - assert_eq!(audio.sample_rate, 0); + assert_eq!(audio.sample_info_count, 0); mp4parse_free(parser); } diff --git a/media/mp4parse-rust/update-rust.sh b/media/mp4parse-rust/update-rust.sh index 1b5f578f91bb..c1751865e8f4 100755 --- a/media/mp4parse-rust/update-rust.sh +++ b/media/mp4parse-rust/update-rust.sh @@ -4,7 +4,7 @@ set -e # Default version. -VER="f6032a118aa498525145adf611cd7b3bec0e0216" +VER="26e614bbcb4d7322dc2e4b7e15391bdb30b9f7be" # Accept version or commit from the command line. if test -n "$1"; then