Bug 1513042 - Update mp4parse-rust to v0.11.2. r=jya

Update mp4parse-rust update script and pull the new version.

This update changes the mp4parse C-API. Specifically, each track can now
have multiple sample descriptions. Previously we'd just exposed the first for
the entire track, and if others were available they were not exposed via the
API. Because of the API change, we update the C++ interface with mp4parse-rust.

We now inspect the sample info to make sure they're consistent with the parsers
expectations:
- Only a single codec is present for a track, multiple codecs in a track will
  result in us returning an error.
- Only 0 or 1 crypto info is present for a track, more than one set of info will
  result in us returning an error.

We still generalize some of the first sample info to the samples of the track,
as we did before this patch. However, we will now catch the above cases
explicitly.

We now handle crypto information if it is not present on the first sample info.
The parser will iterate through sample infos and use the first set of crypto
info it finds (and fail if it finds 2+).

Differential Revision: https://phabricator.services.mozilla.com/D14107

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Bryce Van Dyk 2018-12-12 15:04:18 +00:00
Родитель 0f41ddb2a4
Коммит d9995b9edc
13 изменённых файлов: 1041 добавлений и 424 удалений

8
Cargo.lock сгенерированный
Просмотреть файл

@ -1049,7 +1049,7 @@ dependencies = [
"jsrust_shared 0.1.0", "jsrust_shared 0.1.0",
"log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"mozurl 0.0.1", "mozurl 0.0.1",
"mp4parse_capi 0.10.1", "mp4parse_capi 0.11.2",
"netwerk_helper 0.0.1", "netwerk_helper 0.0.1",
"nserror 0.1.0", "nserror 0.1.0",
"nsstring 0.1.0", "nsstring 0.1.0",
@ -1609,7 +1609,7 @@ dependencies = [
[[package]] [[package]]
name = "mp4parse" name = "mp4parse"
version = "0.10.1" version = "0.11.2"
dependencies = [ dependencies = [
"bitreader 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1624,11 +1624,11 @@ version = "0.1.0"
[[package]] [[package]]
name = "mp4parse_capi" name = "mp4parse_capi"
version = "0.10.1" version = "0.11.2"
dependencies = [ dependencies = [
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
] ]

Просмотреть файл

@ -57,67 +57,136 @@ static void UpdateTrackProtectedInfo(mozilla::TrackInfo& aConfig,
} }
} }
void MP4AudioInfo::Update(const Mp4parseTrackInfo* track, MediaResult MP4AudioInfo::Update(const Mp4parseTrackInfo* track,
const Mp4parseTrackAudioInfo* audio) { const Mp4parseTrackAudioInfo* audio) {
UpdateTrackProtectedInfo(*this, audio->protected_data); 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"); mMimeType = NS_LITERAL_CSTRING("audio/opus");
// The Opus decoder expects the container's codec delay or // The Opus decoder expects the container's codec delay or
// pre-skip value, in microseconds, as a 64-bit int at the // pre-skip value, in microseconds, as a 64-bit int at the
// start of the codec-specific config blob. // start of the codec-specific config blob.
if (audio->codec_specific_config.data && if (codecSpecificConfig.data && codecSpecificConfig.length >= 12) {
audio->codec_specific_config.length >= 12) { uint16_t preskip =
uint16_t preskip = mozilla::LittleEndian::readUint16( mozilla::LittleEndian::readUint16(codecSpecificConfig.data + 10);
audio->codec_specific_config.data + 10);
mozilla::OpusDataDecoder::AppendCodecDelay( mozilla::OpusDataDecoder::AppendCodecDelay(
mCodecSpecificConfig, mozilla::FramesToUsecs(preskip, 48000).value()); mCodecSpecificConfig, mozilla::FramesToUsecs(preskip, 48000).value());
} else { } else {
// This file will error later as it will be rejected by the opus decoder. // This file will error later as it will be rejected by the opus decoder.
mozilla::OpusDataDecoder::AppendCodecDelay(mCodecSpecificConfig, 0); 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"); 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"); 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"); mMimeType = NS_LITERAL_CSTRING("audio/mpeg");
} }
mRate = audio->sample_rate; mRate = audio->sample_info[0].sample_rate;
mChannels = audio->channels; mChannels = audio->sample_info[0].channels;
mBitDepth = audio->bit_depth; mBitDepth = audio->sample_info[0].bit_depth;
mExtendedProfile = audio->extended_profile; mExtendedProfile = audio->sample_info[0].extended_profile;
mDuration = TimeUnit::FromMicroseconds(track->duration); mDuration = TimeUnit::FromMicroseconds(track->duration);
mMediaTime = TimeUnit::FromMicroseconds(track->media_time); mMediaTime = TimeUnit::FromMicroseconds(track->media_time);
mTrackId = track->track_id; mTrackId = track->track_id;
// In stagefright, mProfile is kKeyAACProfile, mExtendedProfile is kKeyAACAOT. // In stagefright, mProfile is kKeyAACProfile, mExtendedProfile is kKeyAACAOT.
if (audio->profile <= 4) { if (audio->sample_info[0].profile <= 4) {
mProfile = audio->profile; mProfile = audio->sample_info[0].profile;
} }
if (audio->extra_data.length > 0) { Mp4parseByteData extraData = audio->sample_info[0].extra_data;
mExtraData->AppendElements(audio->extra_data.data, // If length is 0 we append nothing
audio->extra_data.length); mExtraData->AppendElements(extraData.data, extraData.length);
} mCodecSpecificConfig->AppendElements(codecSpecificConfig.data,
codecSpecificConfig.length);
if (audio->codec_specific_config.length > 0) { return NS_OK;
mCodecSpecificConfig->AppendElements(audio->codec_specific_config.data,
audio->codec_specific_config.length);
}
} }
void MP4VideoInfo::Update(const Mp4parseTrackInfo* track, MediaResult MP4VideoInfo::Update(const Mp4parseTrackInfo* track,
const Mp4parseTrackVideoInfo* video) { const Mp4parseTrackVideoInfo* video) {
UpdateTrackProtectedInfo(*this, video->protected_data); MOZ_DIAGNOSTIC_ASSERT(video->sample_info_count > 0,
if (track->codec == MP4PARSE_CODEC_AVC) { "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"); 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"); 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"); 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"); mMimeType = NS_LITERAL_CSTRING("video/mp4v-es");
} }
mTrackId = track->track_id; mTrackId = track->track_id;
@ -125,13 +194,13 @@ void MP4VideoInfo::Update(const Mp4parseTrackInfo* track,
mMediaTime = TimeUnit::FromMicroseconds(track->media_time); mMediaTime = TimeUnit::FromMicroseconds(track->media_time);
mDisplay.width = video->display_width; mDisplay.width = video->display_width;
mDisplay.height = video->display_height; mDisplay.height = video->display_height;
mImage.width = video->image_width; mImage.width = video->sample_info[0].image_width;
mImage.height = video->image_height; mImage.height = video->sample_info[0].image_height;
mRotation = ToSupportedRotation(video->rotation); mRotation = ToSupportedRotation(video->rotation);
if (video->extra_data.data) { Mp4parseByteData extraData = video->sample_info[0].extra_data;
mExtraData->AppendElements(video->extra_data.data, // If length is 0 we append nothing
video->extra_data.length); mExtraData->AppendElements(extraData.data, extraData.length);
} return NS_OK;
} }
bool MP4VideoInfo::IsValid() const { bool MP4VideoInfo::IsValid() const {

Просмотреть файл

@ -6,6 +6,7 @@
#define DECODER_DATA_H_ #define DECODER_DATA_H_
#include "MediaInfo.h" #include "MediaInfo.h"
#include "MediaResult.h"
#include "mozilla/RefPtr.h" #include "mozilla/RefPtr.h"
#include "mozilla/Result.h" #include "mozilla/Result.h"
#include "mozilla/Types.h" #include "mozilla/Types.h"
@ -53,8 +54,8 @@ class MP4AudioInfo : public mozilla::AudioInfo {
public: public:
MP4AudioInfo() = default; MP4AudioInfo() = default;
void Update(const Mp4parseTrackInfo* track, MediaResult Update(const Mp4parseTrackInfo* track,
const Mp4parseTrackAudioInfo* audio); const Mp4parseTrackAudioInfo* audio);
virtual bool IsValid() const override; virtual bool IsValid() const override;
}; };
@ -63,8 +64,8 @@ class MP4VideoInfo : public mozilla::VideoInfo {
public: public:
MP4VideoInfo() = default; MP4VideoInfo() = default;
void Update(const Mp4parseTrackInfo* track, MediaResult Update(const Mp4parseTrackInfo* track,
const Mp4parseTrackVideoInfo* video); const Mp4parseTrackVideoInfo* video);
virtual bool IsValid() const override; virtual bool IsValid() const override;
}; };

Просмотреть файл

@ -155,7 +155,55 @@ MP4Metadata::ResultAndTrackCount MP4Metadata::GetNumberTracks(
if (rv != MP4PARSE_STATUS_OK) { if (rv != MP4PARSE_STATUS_OK) {
continue; 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; continue;
} }
if (TrackTypeEqual(aType, track_info.track_type)) { if (TrackTypeEqual(aType, track_info.track_type)) {
@ -219,50 +267,71 @@ MP4Metadata::ResultAndTrackInfo MP4Metadata::GetTrackInfo(
nullptr}; nullptr};
} }
#ifdef DEBUG #ifdef DEBUG
const char* codec_string = "unrecognized"; bool haveSampleInfo = false;
switch (info.codec) { const char* codecString = "unrecognized";
case MP4PARSE_CODEC_UNKNOWN: Mp4parseCodec codecType = MP4PARSE_CODEC_UNKNOWN;
codec_string = "unknown"; if (info.track_type == MP4PARSE_TRACK_TYPE_AUDIO) {
break; Mp4parseTrackAudioInfo audio;
case MP4PARSE_CODEC_AAC: auto rv = mp4parse_get_track_audio_info(mParser.get(), trackIndex.value(),
codec_string = "aac"; &audio);
break; if (rv == MP4PARSE_STATUS_OK && audio.sample_info_count > 0) {
case MP4PARSE_CODEC_OPUS: codecType = audio.sample_info[0].codec_type;
codec_string = "opus"; haveSampleInfo = true;
break; }
case MP4PARSE_CODEC_FLAC: } else if (info.track_type == MP4PARSE_TRACK_TYPE_VIDEO) {
codec_string = "flac"; Mp4parseTrackVideoInfo video;
break; auto rv = mp4parse_get_track_video_info(mParser.get(), trackIndex.value(),
case MP4PARSE_CODEC_ALAC: &video);
codec_string = "alac"; if (rv == MP4PARSE_STATUS_OK && video.sample_info_count > 0) {
break; codecType = video.sample_info[0].codec_type;
case MP4PARSE_CODEC_AVC: haveSampleInfo = true;
codec_string = "h.264"; }
break; }
case MP4PARSE_CODEC_VP9: if (haveSampleInfo) {
codec_string = "vp9"; switch (codecType) {
break; case MP4PARSE_CODEC_UNKNOWN:
case MP4PARSE_CODEC_AV1: codecString = "unknown";
codec_string = "av1"; break;
break; case MP4PARSE_CODEC_AAC:
case MP4PARSE_CODEC_MP3: codecString = "aac";
codec_string = "mp3"; break;
break; case MP4PARSE_CODEC_OPUS:
case MP4PARSE_CODEC_MP4V: codecString = "opus";
codec_string = "mp4v"; break;
break; case MP4PARSE_CODEC_FLAC:
case MP4PARSE_CODEC_JPEG: codecString = "flac";
codec_string = "jpeg"; break;
break; case MP4PARSE_CODEC_ALAC:
case MP4PARSE_CODEC_AC3: codecString = "alac";
codec_string = "ac-3"; break;
break; case MP4PARSE_CODEC_AVC:
case MP4PARSE_CODEC_EC3: codecString = "h.264";
codec_string = "ec-3"; break;
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, MOZ_LOG(gMP4MetadataLog, LogLevel::Debug,
("track codec %s (%u)\n", codec_string, info.codec)); ("track codec %s (%u)\n", codecString, codecType));
#endif #endif
// This specialization interface is crazy. // This specialization interface is crazy.
@ -281,7 +350,18 @@ MP4Metadata::ResultAndTrackInfo MP4Metadata::GetTrackInfo(
nullptr}; nullptr};
} }
auto track = mozilla::MakeUnique<MP4AudioInfo>(); auto track = mozilla::MakeUnique<MP4AudioInfo>();
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); e = std::move(track);
} break; } break;
case TrackInfo::TrackType::kVideoTrack: { case TrackInfo::TrackType::kVideoTrack: {
@ -297,7 +377,18 @@ MP4Metadata::ResultAndTrackInfo MP4Metadata::GetTrackInfo(
nullptr}; nullptr};
} }
auto track = mozilla::MakeUnique<MP4VideoInfo>(); auto track = mozilla::MakeUnique<MP4VideoInfo>();
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); e = std::move(track);
} break; } break;
default: default:

Просмотреть файл

@ -15,6 +15,14 @@ extern "C" {
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h> #include <stdbool.h>
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 { typedef enum {
MP4PARSE_CODEC_UNKNOWN, MP4PARSE_CODEC_UNKNOWN,
MP4PARSE_CODEC_AAC, MP4PARSE_CODEC_AAC,
@ -73,12 +81,17 @@ typedef struct {
} Mp4parsePsshInfo; } Mp4parsePsshInfo;
typedef struct { typedef struct {
uint32_t is_encrypted; Mp4ParseEncryptionSchemeType scheme_type;
uint8_t is_encrypted;
uint8_t iv_size; uint8_t iv_size;
Mp4parseByteData kid; Mp4parseByteData kid;
uint8_t crypt_byte_block;
uint8_t skip_byte_block;
Mp4parseByteData constant_iv;
} Mp4parseSinfInfo; } Mp4parseSinfInfo;
typedef struct { typedef struct {
Mp4parseCodec codec_type;
uint16_t channels; uint16_t channels;
uint16_t bit_depth; uint16_t bit_depth;
uint32_t sample_rate; uint32_t sample_rate;
@ -87,24 +100,34 @@ typedef struct {
Mp4parseByteData codec_specific_config; Mp4parseByteData codec_specific_config;
Mp4parseByteData extra_data; Mp4parseByteData extra_data;
Mp4parseSinfInfo protected_data; Mp4parseSinfInfo protected_data;
} Mp4parseTrackAudioSampleInfo;
typedef struct {
uint32_t sample_info_count;
const Mp4parseTrackAudioSampleInfo *sample_info;
} Mp4parseTrackAudioInfo; } Mp4parseTrackAudioInfo;
typedef struct { typedef struct {
Mp4parseTrackType track_type; Mp4parseTrackType track_type;
Mp4parseCodec codec;
uint32_t track_id; uint32_t track_id;
uint64_t duration; uint64_t duration;
int64_t media_time; int64_t media_time;
} Mp4parseTrackInfo; } Mp4parseTrackInfo;
typedef struct { typedef struct {
uint32_t display_width; Mp4parseCodec codec_type;
uint32_t display_height;
uint16_t image_width; uint16_t image_width;
uint16_t image_height; uint16_t image_height;
uint16_t rotation;
Mp4parseByteData extra_data; Mp4parseByteData extra_data;
Mp4parseSinfInfo protected_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; } Mp4parseTrackVideoInfo;
typedef struct { typedef struct {

Просмотреть файл

@ -1,6 +1,6 @@
[package] [package]
name = "mp4parse" name = "mp4parse"
version = "0.10.1" version = "0.11.2"
authors = [ authors = [
"Ralph Giles <giles@mozilla.com>", "Ralph Giles <giles@mozilla.com>",
"Matthew Gregan <kinetik@flim.org>", "Matthew Gregan <kinetik@flim.org>",

Просмотреть файл

@ -40,7 +40,7 @@ macro_rules! box_database {
} }
} }
#[derive(Default, PartialEq)] #[derive(Default, PartialEq, Clone)]
pub struct FourCC { pub struct FourCC {
pub value: String pub value: String
} }
@ -137,6 +137,7 @@ box_database!(
TrackEncryptionBox 0x74656e63, // "tenc" TrackEncryptionBox 0x74656e63, // "tenc"
ProtectionSchemeInformationBox 0x73696e66, // "sinf" ProtectionSchemeInformationBox 0x73696e66, // "sinf"
OriginalFormatBox 0x66726d61, // "frma" OriginalFormatBox 0x66726d61, // "frma"
SchemeTypeBox 0x7363686d, // "schm"
MP3AudioSampleEntry 0x2e6d7033, // ".mp3" - from F4V. MP3AudioSampleEntry 0x2e6d7033, // ".mp3" - from F4V.
CompositionOffsetBox 0x63747473, // "ctts" CompositionOffsetBox 0x63747473, // "ctts"
LPCMAudioSampleEntry 0x6C70636D, // "lpcm" - quicktime atom LPCMAudioSampleEntry 0x6C70636D, // "lpcm" - quicktime atom

Просмотреть файл

@ -277,8 +277,8 @@ struct HandlerBox {
// Sample description box 'stsd' // Sample description box 'stsd'
#[derive(Debug)] #[derive(Debug)]
struct SampleDescriptionBox { pub struct SampleDescriptionBox {
descriptions: Vec<SampleEntry>, pub descriptions: Vec<SampleEntry>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -313,6 +313,7 @@ pub enum AudioCodecSpecific {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct AudioSampleEntry { pub struct AudioSampleEntry {
pub codec_type: CodecType,
data_reference_index: u16, data_reference_index: u16,
pub channelcount: u32, pub channelcount: u32,
pub samplesize: u16, pub samplesize: u16,
@ -331,6 +332,7 @@ pub enum VideoCodecSpecific {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct VideoSampleEntry { pub struct VideoSampleEntry {
pub codec_type: CodecType,
data_reference_index: u16, data_reference_index: u16,
pub width: u16, pub width: u16,
pub height: u16, pub height: u16,
@ -423,16 +425,28 @@ pub struct ProtectionSystemSpecificHeaderBox {
pub box_content: ByteData, pub box_content: ByteData,
} }
#[derive(Debug, Default, Clone)]
pub struct SchemeTypeBox {
pub scheme_type: FourCC,
pub scheme_version: u32,
}
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct TrackEncryptionBox { pub struct TrackEncryptionBox {
pub is_encrypted: u32, pub is_encrypted: u8,
pub iv_size: u8, pub iv_size: u8,
pub kid: Vec<u8>, pub kid: Vec<u8>,
// Members for pattern encryption schemes
pub crypt_byte_block_count: Option<u8>,
pub skip_byte_block_count: Option<u8>,
pub constant_iv: Option<Vec<u8>>,
// End pattern encryption scheme members
} }
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct ProtectionSchemeInfoBox { pub struct ProtectionSchemeInfoBox {
pub code_name: String, pub code_name: String,
pub scheme_type: Option<SchemeTypeBox>,
pub tenc: Option<TrackEncryptionBox>, pub tenc: Option<TrackEncryptionBox>,
} }
@ -521,9 +535,8 @@ pub struct Track {
pub timescale: Option<TrackTimeScale<u64>>, pub timescale: Option<TrackTimeScale<u64>>,
pub duration: Option<TrackScaledTime<u64>>, pub duration: Option<TrackScaledTime<u64>>,
pub track_id: Option<u32>, pub track_id: Option<u32>,
pub codec_type: CodecType,
pub data: Option<SampleEntry>,
pub tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this. pub tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this.
pub stsd: Option<SampleDescriptionBox>,
pub stts: Option<TimeToSampleBox>, pub stts: Option<TimeToSampleBox>,
pub stsc: Option<SampleToChunkBox>, pub stsc: Option<SampleToChunkBox>,
pub stsz: Option<SampleSizeBox>, pub stsz: Option<SampleSizeBox>,
@ -962,6 +975,7 @@ fn read_stbl<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> {
BoxType::SampleDescriptionBox => { BoxType::SampleDescriptionBox => {
let stsd = read_stsd(&mut b, track)?; let stsd = read_stsd(&mut b, track)?;
debug!("{:?}", stsd); debug!("{:?}", stsd);
track.stsd = Some(stsd);
} }
BoxType::TimeToSampleBox => { BoxType::TimeToSampleBox => {
let stts = read_stts(&mut b)?; let stts = read_stts(&mut b)?;
@ -1722,6 +1736,8 @@ fn read_es_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> {
fn read_esds<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> { fn read_esds<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> {
let (_, _) = read_fullbox_extra(src)?; 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_size = src.head.size - src.head.offset - 4;
let esds_array = read_buf(src, esds_size as usize)?; let esds_array = read_buf(src, esds_size as usize)?;
@ -1889,7 +1905,7 @@ fn read_hdlr<T: Read>(src: &mut BMFFBox<T>) -> Result<HandlerBox> {
} }
/// Parse an video description inside an stsd box. /// Parse an video description inside an stsd box.
fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<(CodecType, SampleEntry)> { fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleEntry> {
let name = src.get_header().name; let name = src.get_header().name;
let codec_type = match name { let codec_type = match name {
BoxType::AVCSampleEntry | BoxType::AVC3SampleEntry => CodecType::H264, BoxType::AVCSampleEntry | BoxType::AVC3SampleEntry => CodecType::H264,
@ -1959,6 +1975,8 @@ fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<(CodecType,
return Err(Error::InvalidData("malformed video sample entry")); return Err(Error::InvalidData("malformed video sample entry"));
} }
let (_, _) = read_fullbox_extra(&mut b.content)?; 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_size = b.head.size - b.head.offset - 4;
let esds = read_buf(&mut b.content, esds_size as usize)?; let esds = read_buf(&mut b.content, esds_size as usize)?;
codec_specific = Some(VideoCodecSpecific::ESDSConfig(esds)); codec_specific = Some(VideoCodecSpecific::ESDSConfig(esds));
@ -1979,14 +1997,15 @@ fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<(CodecType,
check_parser_state!(b.content); check_parser_state!(b.content);
} }
Ok(codec_specific.map_or((CodecType::Unknown, SampleEntry::Unknown), Ok(codec_specific.map_or(SampleEntry::Unknown,
|codec_specific| (codec_type, SampleEntry::Video(VideoSampleEntry { |codec_specific| SampleEntry::Video(VideoSampleEntry {
codec_type: codec_type,
data_reference_index: data_reference_index, data_reference_index: data_reference_index,
width: width, width: width,
height: height, height: height,
codec_specific: codec_specific, codec_specific: codec_specific,
protection_info: protection_info, protection_info: protection_info,
}))) }))
) )
} }
@ -2007,7 +2026,7 @@ fn read_qt_wave_atom<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> {
} }
/// Parse an audio description inside an stsd box. /// Parse an audio description inside an stsd box.
fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<(CodecType, SampleEntry)> { fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleEntry> {
let name = src.get_header().name; let name = src.get_header().name;
// Skip uninteresting fields. // Skip uninteresting fields.
@ -2119,15 +2138,16 @@ fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<(CodecType,
check_parser_state!(b.content); check_parser_state!(b.content);
} }
Ok(codec_specific.map_or((CodecType::Unknown, SampleEntry::Unknown), Ok(codec_specific.map_or(SampleEntry::Unknown,
|codec_specific| (codec_type, SampleEntry::Audio(AudioSampleEntry { |codec_specific| SampleEntry::Audio(AudioSampleEntry {
codec_type: codec_type,
data_reference_index: data_reference_index, data_reference_index: data_reference_index,
channelcount: channelcount, channelcount: channelcount,
samplesize: samplesize, samplesize: samplesize,
samplerate: samplerate, samplerate: samplerate,
codec_specific: codec_specific, codec_specific: codec_specific,
protection_info: protection_info, protection_info: protection_info,
}))) }))
) )
} }
@ -2139,7 +2159,6 @@ fn read_stsd<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleD
let mut descriptions = Vec::new(); let mut descriptions = Vec::new();
{ {
// TODO(kinetik): check if/when more than one desc per track? do we need to support?
let mut iter = src.box_iter(); let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? { while let Some(mut b) = iter.next_box()? {
let description = match track.track_type { let description = match track.track_type {
@ -2149,10 +2168,7 @@ fn read_stsd<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleD
TrackType::Unknown => Err(Error::Unsupported("unknown track type")), TrackType::Unknown => Err(Error::Unsupported("unknown track type")),
}; };
let description = match description { let description = match description {
Ok((codec_type, desc)) => { Ok(desc) => desc,
track.codec_type = codec_type;
desc
}
Err(Error::Unsupported(_)) => { Err(Error::Unsupported(_)) => {
// read_{audio,video}_desc may have returned Unsupported // read_{audio,video}_desc may have returned Unsupported
// after partially reading the box content, so we can't // after partially reading the box content, so we can't
@ -2160,14 +2176,9 @@ fn read_stsd<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleD
let to_skip = b.bytes_left(); let to_skip = b.bytes_left();
skip(&mut b, to_skip)?; skip(&mut b, to_skip)?;
SampleEntry::Unknown SampleEntry::Unknown
} },
Err(e) => return Err(e), Err(e) => 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)?; vec_push(&mut descriptions, description)?;
check_parser_state!(b.content); check_parser_state!(b.content);
if descriptions.len() == description_count as usize { if descriptions.len() == description_count as usize {
@ -2194,6 +2205,9 @@ fn read_sinf<T: Read>(src: &mut BMFFBox<T>) -> Result<ProtectionSchemeInfoBox> {
let frma = read_frma(&mut b)?; let frma = read_frma(&mut b)?;
sinf.code_name = frma; sinf.code_name = frma;
}, },
BoxType::SchemeTypeBox => {
sinf.scheme_type = Some(read_schm(&mut b)?);
}
BoxType::SchemeInformationBox => { BoxType::SchemeInformationBox => {
// We only need tenc box in schi box so far. // We only need tenc box in schi box so far.
sinf.tenc = read_schi(&mut b)?; sinf.tenc = read_schi(&mut b)?;
@ -2225,16 +2239,42 @@ fn read_schi<T: Read>(src: &mut BMFFBox<T>) -> Result<Option<TrackEncryptionBox>
} }
fn read_tenc<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackEncryptionBox> { fn read_tenc<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackEncryptionBox> {
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_iv_size = src.read_u8()?;
let default_kid = read_buf(src, 16)?; 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 { Ok(TrackEncryptionBox {
is_encrypted: default_is_encrypted, is_encrypted: default_is_encrypted,
iv_size: default_iv_size, iv_size: default_iv_size,
kid: default_kid, 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<T: Read>(src: &mut BMFFBox<T>) -> Result<String> {
String::from_utf8(code_name).map_err(From::from) String::from_utf8(code_name).map_err(From::from)
} }
fn read_schm<T: Read>(src: &mut BMFFBox<T>) -> Result<SchemeTypeBox> {
// 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. /// Skip a number of bytes that we don't care to parse.
fn skip<T: Read>(src: &mut T, mut bytes: usize) -> Result<()> { fn skip<T: Read>(src: &mut T, mut bytes: usize) -> Result<()> {
const BUF_SIZE: usize = 64 * 1024; const BUF_SIZE: usize = 64 * 1024;

Просмотреть файл

@ -942,9 +942,13 @@ fn read_qt_wave_atom() {
let mut iter = super::BoxIter::new(&mut stream); let mut iter = super::BoxIter::new(&mut stream);
let mut stream = iter.next_box().unwrap().unwrap(); 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"); .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] #[test]
@ -969,6 +973,7 @@ fn read_descriptor_80() {
assert_eq!(es.audio_codec, super::CodecType::AAC); assert_eq!(es.audio_codec, super::CodecType::AAC);
assert_eq!(es.audio_object_type, Some(2)); 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_sample_rate, Some(48000));
assert_eq!(es.audio_channel_count, Some(2)); assert_eq!(es.audio_channel_count, Some(2));
assert_eq!(es.codec_esds, aac_esds); 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_codec, super::CodecType::AAC);
assert_eq!(es.audio_object_type, Some(2)); 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_sample_rate, Some(24000));
assert_eq!(es.audio_channel_count, Some(6)); assert_eq!(es.audio_channel_count, Some(6));
assert_eq!(es.codec_esds, aac_esds); assert_eq!(es.codec_esds, aac_esds);
assert_eq!(es.decoder_specific_data, aac_dc_descriptor); 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] #[test]
fn read_stsd_mp4v() { fn read_stsd_mp4v() {
let mp4v = let mp4v =
@ -1033,12 +1072,11 @@ fn read_stsd_mp4v() {
let mut iter = super::BoxIter::new(&mut stream); let mut iter = super::BoxIter::new(&mut stream);
let mut stream = iter.next_box().unwrap().unwrap(); let mut stream = iter.next_box().unwrap().unwrap();
let (codec_type, sample_entry) = super::read_video_sample_entry(&mut stream).unwrap(); let sample_entry = super::read_video_sample_entry(&mut stream).unwrap();
assert_eq!(codec_type, super::CodecType::MP4V);
match sample_entry { match sample_entry {
super::SampleEntry::Video(v) => { super::SampleEntry::Video(v) => {
assert_eq!(v.codec_type, super::CodecType::MP4V);
assert_eq!(v.width, 720); assert_eq!(v.width, 720);
assert_eq!(v.height, 480); assert_eq!(v.height, 480);
match v.codec_specific { 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_codec, super::CodecType::AAC);
assert_eq!(es.audio_object_type, Some(2)); 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_sample_rate, Some(48000));
assert_eq!(es.audio_channel_count, Some(2)); 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 iter = super::BoxIter::new(&mut stream);
let mut stream = iter.next_box().unwrap().unwrap(); 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"); .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] #[test]
@ -1152,7 +1195,7 @@ fn unknown_video_sample_entry() {
let mut iter = super::BoxIter::new(&mut stream); let mut iter = super::BoxIter::new(&mut stream);
let mut stream = iter.next_box().unwrap().unwrap(); let mut stream = iter.next_box().unwrap().unwrap();
match super::read_video_sample_entry(&mut stream) { 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"), _ => 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 iter = super::BoxIter::new(&mut stream);
let mut stream = iter.next_box().unwrap().unwrap(); let mut stream = iter.next_box().unwrap().unwrap();
match super::read_audio_sample_entry(&mut stream) { 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"), _ => 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, 0x80, 0x80, 0x80, 0x14, 0x40, 0x01,
0x00, 0x04, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00,
0x00, 0xfa, 0x00, 0x05, 0x80, 0x80, 0x80, 0x02, 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| { 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 iter = super::BoxIter::new(&mut stream);
let mut stream = iter.next_box().unwrap().unwrap(); let mut stream = iter.next_box().unwrap().unwrap();
let (codec_type, sample_entry) = super::read_audio_sample_entry(&mut stream).unwrap(); let sample_entry = super::read_audio_sample_entry(&mut stream).unwrap();
assert_eq!(codec_type, super::CodecType::LPCM);
match sample_entry { match sample_entry {
super::SampleEntry::Audio(a) => { super::SampleEntry::Audio(a) => {
assert_eq!(a.codec_type, super::CodecType::LPCM);
assert_eq!(a.samplerate, 96000.0); assert_eq!(a.samplerate, 96000.0);
assert_eq!(a.channelcount, 1); assert_eq!(a.channelcount, 1);
match a.codec_specific { match a.codec_specific {

Просмотреть файл

@ -10,11 +10,22 @@ use std::io::{Cursor, Read};
use std::fs::File; use std::fs::File;
static MINI_MP4: &'static str = "tests/minimal.mp4"; static MINI_MP4: &'static str = "tests/minimal.mp4";
static AUDIO_EME_MP4: &'static str = "tests/bipbop-cenc-audioinit.mp4"; static AUDIO_EME_CENC_MP4: &'static str = "tests/bipbop-cenc-audioinit.mp4";
static VIDEO_EME_MP4: &'static str = "tests/bipbop_480wp_1001kbps-cenc-video-key1-init.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"; 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] #[test]
fn public_api() { fn public_api() {
let mut fd = File::open(MINI_MP4).expect("Unknown file"); 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"); mp4::read_mp4(&mut c, &mut context).expect("read_mp4 failed");
assert_eq!(context.timescale, Some(mp4::MediaTimeScale(1000))); assert_eq!(context.timescale, Some(mp4::MediaTimeScale(1000)));
for track in context.tracks { for track in context.tracks {
match track.data { match track.track_type {
Some(mp4::SampleEntry::Video(v)) => { mp4::TrackType::Video => {
// track part // track part
assert_eq!(track.duration, Some(mp4::TrackScaledTime(512, 0))); assert_eq!(track.duration, Some(mp4::TrackScaledTime(512, 0)));
assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0))); assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0)));
assert_eq!(track.media_time, Some(mp4::TrackScaledTime(0, 0))); assert_eq!(track.media_time, Some(mp4::TrackScaledTime(0, 0)));
assert_eq!(track.timescale, Some(mp4::TrackTimeScale(12800, 0))); assert_eq!(track.timescale, Some(mp4::TrackTimeScale(12800, 0)));
assert_eq!(v.width, 320);
assert_eq!(v.height, 240);
// track.tkhd part // track.tkhd part
let tkhd = track.tkhd.unwrap(); let tkhd = track.tkhd.unwrap();
@ -43,13 +52,20 @@ fn public_api() {
assert_eq!(tkhd.width, 20971520); assert_eq!(tkhd.width, 20971520);
assert_eq!(tkhd.height, 15728640); 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 { assert_eq!(match v.codec_specific {
mp4::VideoCodecSpecific::AVCConfig(v) => { mp4::VideoCodecSpecific::AVCConfig(ref avc) => {
assert!(!v.is_empty()); assert!(!avc.is_empty());
"AVC" "AVC"
} }
mp4::VideoCodecSpecific::VPxConfig(vpx) => { mp4::VideoCodecSpecific::VPxConfig(ref vpx) => {
// We don't enter in here, we just check if fields are public. // We don't enter in here, we just check if fields are public.
assert!(vpx.bit_depth > 0); assert!(vpx.bit_depth > 0);
assert!(vpx.color_space > 0); assert!(vpx.color_space > 0);
@ -57,16 +73,16 @@ fn public_api() {
assert!(!vpx.codec_init.is_empty()); assert!(!vpx.codec_init.is_empty());
"VPx" "VPx"
} }
mp4::VideoCodecSpecific::ESDSConfig(mp4v) => { mp4::VideoCodecSpecific::ESDSConfig(ref mp4v) => {
assert!(!mp4v.is_empty()); assert!(!mp4v.is_empty());
"MP4V" "MP4V"
} }
mp4::VideoCodecSpecific::AV1Config(_av1c) => { mp4::VideoCodecSpecific::AV1Config(ref _av1c) => {
"AV1" "AV1"
} }
}, "AVC"); }, "AVC");
} }
Some(mp4::SampleEntry::Audio(a)) => { mp4::TrackType::Audio => {
// track part // track part
assert_eq!(track.duration, Some(mp4::TrackScaledTime(2944, 1))); assert_eq!(track.duration, Some(mp4::TrackScaledTime(2944, 1)));
assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0))); assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0)));
@ -80,27 +96,32 @@ fn public_api() {
assert_eq!(tkhd.width, 0); assert_eq!(tkhd.width, 0);
assert_eq!(tkhd.height, 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 { 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_codec, mp4::CodecType::AAC);
assert_eq!(esds.audio_sample_rate.unwrap(), 48000); assert_eq!(esds.audio_sample_rate.unwrap(), 48000);
assert_eq!(esds.audio_object_type.unwrap(), 2); assert_eq!(esds.audio_object_type.unwrap(), 2);
"ES" "ES"
} }
mp4::AudioCodecSpecific::FLACSpecificBox(flac) => { mp4::AudioCodecSpecific::FLACSpecificBox(ref flac) => {
// STREAMINFO block must be present and first. // STREAMINFO block must be present and first.
assert!(!flac.blocks.is_empty()); assert!(!flac.blocks.is_empty());
assert_eq!(flac.blocks[0].block_type, 0); assert_eq!(flac.blocks[0].block_type, 0);
assert_eq!(flac.blocks[0].data.len(), 34); assert_eq!(flac.blocks[0].data.len(), 34);
"FLAC" "FLAC"
} }
mp4::AudioCodecSpecific::OpusSpecificBox(opus) => { mp4::AudioCodecSpecific::OpusSpecificBox(ref opus) => {
// We don't enter in here, we just check if fields are public. // We don't enter in here, we just check if fields are public.
assert!(opus.version > 0); assert!(opus.version > 0);
"Opus" "Opus"
} }
mp4::AudioCodecSpecific::ALACSpecificBox(alac) => { mp4::AudioCodecSpecific::ALACSpecificBox(ref alac) => {
assert!(alac.data.len() == 24 || alac.data.len() == 48); assert!(alac.data.len() == 24 || alac.data.len() == 48);
"ALAC" "ALAC"
} }
@ -114,7 +135,7 @@ fn public_api() {
assert!(a.samplesize > 0); assert!(a.samplesize > 0);
assert!(a.samplerate > 0.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, vec![0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04,
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(); let mut buf = Vec::new();
fd.read_to_end(&mut buf).expect("File error"); fd.read_to_end(&mut buf).expect("File error");
@ -133,28 +154,34 @@ fn public_audio_tenc() {
let mut context = mp4::MediaContext::new(); let mut context = mp4::MediaContext::new();
mp4::read_mp4(&mut c, &mut context).expect("read_mp4 failed"); mp4::read_mp4(&mut c, &mut context).expect("read_mp4 failed");
for track in context.tracks { for track in context.tracks {
assert_eq!(track.codec_type, mp4::CodecType::EncryptedAudio); let stsd = track.stsd.expect("expected an stsd");
match track.data { let a = match stsd.descriptions.first().expect("expected a SampleEntry") {
Some(mp4::SampleEntry::Audio(a)) => { mp4::SampleEntry::Audio(a) => a,
match a.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { _ => panic!("expected a AudioSampleEntry"),
Some(p) => { };
assert_eq!(p.code_name, "mp4a"); assert_eq!(a.codec_type, mp4::CodecType::EncryptedAudio);
if let Some(ref tenc) = p.tenc { match a.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
assert!(tenc.is_encrypted > 0); Some(ref p) => {
assert_eq!(tenc.iv_size, 16); assert_eq!(p.code_name, "mp4a");
assert_eq!(tenc.kid, kid); if let Some(ref schm) = p.scheme_type {
} else { assert_eq!(schm.scheme_type.value, "cenc");
assert!(false, "Invalid test condition"); } else {
} assert!(false, "Expected scheme type info");
}, }
_=> { if let Some(ref tenc) = p.tenc {
assert!(false, "Invalid test condition"); 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"); assert!(false, "Invalid test condition");
} },
} }
} }
} }
@ -178,7 +205,7 @@ fn public_video_cenc() {
0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x11, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x11,
0x00, 0x00, 0x00, 0x00]; 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(); let mut buf = Vec::new();
fd.read_to_end(&mut buf).expect("File error"); fd.read_to_end(&mut buf).expect("File error");
@ -186,26 +213,32 @@ fn public_video_cenc() {
let mut context = mp4::MediaContext::new(); let mut context = mp4::MediaContext::new();
mp4::read_mp4(&mut c, &mut context).expect("read_mp4 failed"); mp4::read_mp4(&mut c, &mut context).expect("read_mp4 failed");
for track in context.tracks { for track in context.tracks {
assert_eq!(track.codec_type, mp4::CodecType::EncryptedVideo); let stsd = track.stsd.expect("expected an stsd");
match track.data { let v = match stsd.descriptions.first().expect("expected a SampleEntry") {
Some(mp4::SampleEntry::Video(v)) => { mp4::SampleEntry::Video(ref v) => v,
match v.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { _ => panic!("expected a VideoSampleEntry"),
Some(p) => { };
assert_eq!(p.code_name, "avc1"); assert_eq!(v.codec_type, mp4::CodecType::EncryptedVideo);
if let Some(ref tenc) = p.tenc { match v.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
assert!(tenc.is_encrypted > 0); Some(ref p) => {
assert_eq!(tenc.iv_size, 16); assert_eq!(p.code_name, "avc1");
assert_eq!(tenc.kid, kid); if let Some(ref schm) = p.scheme_type {
} else { assert_eq!(schm.scheme_type.value, "cenc");
assert!(false, "Invalid test condition"); } else {
} assert!(false, "Expected scheme type info");
}, }
_=> { if let Some(ref tenc) = p.tenc {
assert!(false, "Invalid test condition"); 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"); assert!(false, "Invalid test condition");
} }
} }
@ -222,54 +255,210 @@ fn public_video_cenc() {
} }
#[test] #[test]
fn public_video_av1() { fn publicaudio_cbcs() {
let mut fd = File::open(VIDEO_AV1_MP4).expect("Unknown file"); let system_id =
let mut buf = Vec::new(); vec![0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
fd.read_to_end(&mut buf).expect("File error"); 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b];
let mut c = Cursor::new(&buf); let kid =
let mut context = mp4::MediaContext::new(); vec![0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04,
mp4::read_mp4(&mut c, &mut context).expect("read_mp4 failed"); 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x21];
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);
// track.tkhd part let default_iv =
let tkhd = track.tkhd.unwrap(); vec![0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
assert_eq!(tkhd.disabled, false); 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
assert_eq!(tkhd.duration, 42);
assert_eq!(tkhd.width, 4194304);
assert_eq!(tkhd.height, 4194304);
match v.codec_specific { let pssh_box =
mp4::VideoCodecSpecific::AV1Config(av1c) => { vec![0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68,
// TODO: test av1c fields once ffmpeg is updated 0x01, 0x00, 0x00, 0x00, 0x10, 0x77, 0xef, 0xec,
assert_eq!(av1c.profile, 0); 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e,
assert_eq!(av1c.level, 0); 0x52, 0xe2, 0xfb, 0x4b, 0x00, 0x00, 0x00, 0x01,
assert_eq!(av1c.tier, 0); 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04,
assert_eq!(av1c.bit_depth, 8); 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x21,
assert_eq!(av1c.monochrome, false); 0x00, 0x00, 0x00, 0x00];
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 mut fd = File::open(AUDIO_EME_CBCS_MP4).expect("Unknown file");
_ => { let mut buf = Vec::new();
assert!(false, "Invalid test condition"); 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"),
}
}
} }

Просмотреть файл

@ -1,6 +1,6 @@
[package] [package]
name = "mp4parse_capi" name = "mp4parse_capi"
version = "0.10.1" version = "0.11.2"
authors = [ authors = [
"Ralph Giles <giles@mozilla.com>", "Ralph Giles <giles@mozilla.com>",
"Matthew Gregan <kinetik@flim.org>", "Matthew Gregan <kinetik@flim.org>",
@ -26,7 +26,7 @@ travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
[dependencies] [dependencies]
byteorder = "1.2.1" byteorder = "1.2.1"
log = "0.4" log = "0.4"
mp4parse = {version = "0.10.1", path = "../mp4parse"} mp4parse = {version = "0.11.2", path = "../mp4parse"}
num-traits = "0.2.0" num-traits = "0.2.0"
[dev-dependencies] [dev-dependencies]

Просмотреть файл

@ -107,11 +107,27 @@ impl Default for Mp4parseCodec {
fn default() -> Self { Mp4parseCodec::Unknown } 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)] #[repr(C)]
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Mp4parseTrackInfo { pub struct Mp4parseTrackInfo {
pub track_type: Mp4parseTrackType, pub track_type: Mp4parseTrackType,
pub codec: Mp4parseCodec,
pub track_id: u32, pub track_id: u32,
pub duration: u64, pub duration: u64,
pub media_time: i64, // wants to be u64? understand how elst adjustment works pub media_time: i64, // wants to be u64? understand how elst adjustment works
@ -169,14 +185,22 @@ pub struct Mp4parsePsshInfo {
#[repr(C)] #[repr(C)]
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Mp4parseSinfInfo { pub struct Mp4parseSinfInfo {
pub is_encrypted: u32, pub scheme_type: Mp4ParseEncryptionSchemeType,
pub is_encrypted: u8,
pub iv_size: u8, pub iv_size: u8,
pub kid: Mp4parseByteData, 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)] #[repr(C)]
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Mp4parseTrackAudioInfo { pub struct Mp4parseTrackAudioSampleInfo {
pub codec_type: Mp4parseCodec,
pub channels: u16, pub channels: u16,
pub bit_depth: u16, pub bit_depth: u16,
pub sample_rate: u32, pub sample_rate: u32,
@ -187,16 +211,52 @@ pub struct Mp4parseTrackAudioInfo {
pub protected_data: Mp4parseSinfInfo, 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)] #[repr(C)]
#[derive(Default, Debug)] #[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 struct Mp4parseTrackVideoInfo {
pub display_width: u32, pub display_width: u32,
pub display_height: u32, pub display_height: u32,
pub image_width: u16,
pub image_height: u16,
pub rotation: u16, pub rotation: u16,
pub extra_data: Mp4parseByteData, pub sample_info_count: u32,
pub protected_data: Mp4parseSinfInfo, 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)] #[repr(C)]
@ -214,6 +274,12 @@ pub struct Mp4parseParser {
opus_header: HashMap<u32, Vec<u8>>, opus_header: HashMap<u32, Vec<u8>>,
pssh_data: Vec<u8>, pssh_data: Vec<u8>,
sample_table: HashMap<u32, Vec<Mp4parseIndice>>, sample_table: HashMap<u32, Vec<Mp4parseIndice>>,
// 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<u32, Vec<Mp4parseTrackAudioSampleInfo>>,
video_track_sample_descriptions: HashMap<u32, Vec<Mp4parseTrackVideoSampleInfo>>,
} }
impl Mp4parseParser { impl Mp4parseParser {
@ -289,6 +355,8 @@ pub unsafe extern fn mp4parse_new(io: *const Mp4parseIo) -> *mut Mp4parseParser
opus_header: HashMap::new(), opus_header: HashMap::new(),
pssh_data: Vec::new(), pssh_data: Vec::new(),
sample_table: HashMap::new(), sample_table: HashMap::new(),
audio_track_sample_descriptions: HashMap::new(),
video_track_sample_descriptions: HashMap::new(),
}); });
Box::into_raw(parser) 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, 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]; let track = &context.tracks[track_index];
if let (Some(track_timescale), 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. // Initialize fields to default values to ensure all fields are always valid.
*info = Default::default(); *info = Default::default();
let context = (*parser).context_mut(); let context = (*parser).context();
if track_index as usize >= context.tracks.len() { if track_index as usize >= context.tracks.len() {
return Mp4parseStatus::BadArg; 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]; let track = &context.tracks[track_index as usize];
match track.track_type { if track.track_type != TrackType::Audio {
TrackType::Audio => {} return Mp4parseStatus::Invalid;
_ => 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 { if stsd.descriptions.len() == 0 {
Some(ref data) => data, return Mp4parseStatus::Invalid; // Should have at least 1 description
None => return Mp4parseStatus::Invalid, }
};
let audio = match *audio { let mut audio_sample_infos = Vec:: with_capacity(stsd.descriptions.len());
SampleEntry::Audio(ref x) => x, for description in stsd.descriptions.iter() {
_ => return Mp4parseStatus::Invalid, let mut sample_info = Mp4parseTrackAudioSampleInfo::default();
}; let audio = match description {
SampleEntry::Audio(a) => a,
_ => return Mp4parseStatus::Invalid,
};
(*info).channels = audio.channelcount as u16; // UNKNOWN for unsupported format.
(*info).bit_depth = audio.samplesize; sample_info.codec_type = match audio.codec_specific {
(*info).sample_rate = audio.samplerate as u32; 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 { match audio.codec_specific {
AudioCodecSpecific::ES_Descriptor(ref v) => { AudioCodecSpecific::ES_Descriptor(ref esds) => {
if v.codec_esds.len() > std::u32::MAX as usize { if esds.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(_) => {
return Mp4parseStatus::Invalid; return Mp4parseStatus::Invalid;
} }
Ok(_) => { sample_info.extra_data.length = esds.codec_esds.len() as u32;
let header = (*parser).opus_header_mut(); sample_info.extra_data.data = esds.codec_esds.as_ptr();
header.insert(track_index, v); sample_info.codec_specific_config.length = esds.decoder_specific_data.len() as u32;
if let Some(v) = header.get(&track_index) { sample_info.codec_specific_config.data = esds.decoder_specific_data.as_ptr();
if v.len() > std::u32::MAX as usize { if let Some(rate) = esds.audio_sample_rate {
return Mp4parseStatus::Invalid; 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; if let Some(p) = audio.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
(*info).codec_specific_config.data = alac.data.as_ptr(); 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()) { (*parser).audio_track_sample_descriptions.insert(track_index, audio_sample_infos);
if let Some(ref tenc) = p.tenc { match (*parser).audio_track_sample_descriptions.get(&track_index) {
(*info).protected_data.is_encrypted = tenc.is_encrypted; Some(sample_info) => {
(*info).protected_data.iv_size = tenc.iv_size; if sample_info.len() > std::u32::MAX as usize {
(*info).protected_data.kid.set_data(&(tenc.kid)); // 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 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. // Initialize fields to default values to ensure all fields are always valid.
*info = Default::default(); *info = Default::default();
let context = (*parser).context_mut(); let context = (*parser).context();
if track_index as usize >= context.tracks.len() { if track_index as usize >= context.tracks.len() {
return Mp4parseStatus::BadArg; 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]; let track = &context.tracks[track_index as usize];
match track.track_type { if track.track_type != TrackType::Video {
TrackType::Video => {} return Mp4parseStatus::Invalid;
_ => 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,
};
// Handle track.tkhd
if let Some(ref tkhd) = track.tkhd { if let Some(ref tkhd) = track.tkhd {
(*info).display_width = tkhd.width >> 16; // 16.16 fixed point (*info).display_width = tkhd.width >> 16; // 16.16 fixed point
(*info).display_height = tkhd.height >> 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 { } else {
return Mp4parseStatus::Invalid; return Mp4parseStatus::Invalid;
} }
(*info).image_width = video.width;
(*info).image_height = video.height;
match video.codec_specific { // Handle track.stsd
VideoCodecSpecific::AVCConfig(ref data) | VideoCodecSpecific::ESDSConfig(ref data) => { let stsd = match track.stsd {
(*info).extra_data.set_data(data); 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()) { let mut video_sample_infos = Vec:: with_capacity(stsd.descriptions.len());
if let Some(ref tenc) = p.tenc { for description in stsd.descriptions.iter() {
(*info).protected_data.is_encrypted = tenc.is_encrypted; let mut sample_info = Mp4parseTrackVideoSampleInfo::default();
(*info).protected_data.iv_size = tenc.iv_size; let video = match description {
(*info).protected_data.kid.set_data(&(tenc.kid)); 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 Mp4parseStatus::Ok
} }
@ -1208,7 +1381,6 @@ fn arg_validation() {
let mut dummy_info = Mp4parseTrackInfo { let mut dummy_info = Mp4parseTrackInfo {
track_type: Mp4parseTrackType::Video, track_type: Mp4parseTrackType::Video,
codec: Mp4parseCodec::Unknown,
track_id: 0, track_id: 0,
duration: 0, duration: 0,
media_time: 0, media_time: 0,
@ -1218,11 +1390,9 @@ fn arg_validation() {
let mut dummy_video = Mp4parseTrackVideoInfo { let mut dummy_video = Mp4parseTrackVideoInfo {
display_width: 0, display_width: 0,
display_height: 0, display_height: 0,
image_width: 0,
image_height: 0,
rotation: 0, rotation: 0,
extra_data: Mp4parseByteData::default(), sample_info_count: 0,
protected_data: Default::default(), sample_info: std::ptr::null(),
}; };
assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video)); 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 { let mut dummy_info = Mp4parseTrackInfo {
track_type: Mp4parseTrackType::Video, track_type: Mp4parseTrackType::Video,
codec: Mp4parseCodec::Unknown,
track_id: 0, track_id: 0,
duration: 0, duration: 0,
media_time: 0, media_time: 0,
@ -1265,11 +1434,9 @@ fn arg_validation_with_parser() {
let mut dummy_video = Mp4parseTrackVideoInfo { let mut dummy_video = Mp4parseTrackVideoInfo {
display_width: 0, display_width: 0,
display_height: 0, display_height: 0,
image_width: 0,
image_height: 0,
rotation: 0, rotation: 0,
extra_data: Mp4parseByteData::default(), sample_info_count: 0,
protected_data: Default::default(), sample_info: std::ptr::null(),
}; };
assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_video_info(parser, 0, &mut dummy_video)); 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 { let mut info = Mp4parseTrackInfo {
track_type: Mp4parseTrackType::Video, track_type: Mp4parseTrackType::Video,
codec: Mp4parseCodec::Unknown,
track_id: 0, track_id: 0,
duration: 0, duration: 0,
media_time: 0, media_time: 0,
}; };
assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_info(parser, 0, &mut info)); assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_info(parser, 0, &mut info));
assert_eq!(info.track_type, Mp4parseTrackType::Video); assert_eq!(info.track_type, Mp4parseTrackType::Video);
assert_eq!(info.codec, Mp4parseCodec::Avc);
assert_eq!(info.track_id, 1); assert_eq!(info.track_id, 1);
assert_eq!(info.duration, 40000); assert_eq!(info.duration, 40000);
assert_eq!(info.media_time, 0); assert_eq!(info.media_time, 0);
assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_info(parser, 1, &mut info)); assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_info(parser, 1, &mut info));
assert_eq!(info.track_type, Mp4parseTrackType::Audio); assert_eq!(info.track_type, Mp4parseTrackType::Audio);
assert_eq!(info.codec, Mp4parseCodec::Aac);
assert_eq!(info.track_id, 2); assert_eq!(info.track_id, 2);
assert_eq!(info.duration, 61333); assert_eq!(info.duration, 61333);
assert_eq!(info.media_time, 21333); assert_eq!(info.media_time, 21333);
let mut video = Mp4parseTrackVideoInfo { let mut video = Mp4parseTrackVideoInfo::default();
display_width: 0,
display_height: 0,
image_width: 0,
image_height: 0,
rotation: 0,
extra_data: Mp4parseByteData::default(),
protected_data: Default::default(),
};
assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_video_info(parser, 0, &mut video)); assert_eq!(Mp4parseStatus::Ok, mp4parse_get_track_video_info(parser, 0, &mut video));
assert_eq!(video.display_width, 320); assert_eq!(video.display_width, 320);
assert_eq!(video.display_height, 240); assert_eq!(video.display_height, 240);
assert_eq!(video.image_width, 320); assert_eq!(video.sample_info_count, 1);
assert_eq!(video.image_height, 240);
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!(Mp4parseStatus::Ok, mp4parse_get_track_audio_info(parser, 1, &mut audio));
assert_eq!(audio.channels, 1); assert_eq!(audio.sample_info_count, 1);
assert_eq!(audio.bit_depth, 16);
assert_eq!(audio.sample_rate, 48000); 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. // Test with an invalid track number.
let mut info = Mp4parseTrackInfo { let mut info = Mp4parseTrackInfo {
track_type: Mp4parseTrackType::Video, track_type: Mp4parseTrackType::Video,
codec: Mp4parseCodec::Unknown,
track_id: 0, track_id: 0,
duration: 0, duration: 0,
media_time: 0, media_time: 0,
}; };
assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_info(parser, 3, &mut info)); assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_info(parser, 3, &mut info));
assert_eq!(info.track_type, Mp4parseTrackType::Video); assert_eq!(info.track_type, Mp4parseTrackType::Video);
assert_eq!(info.codec, Mp4parseCodec::Unknown);
assert_eq!(info.track_id, 0); assert_eq!(info.track_id, 0);
assert_eq!(info.duration, 0); assert_eq!(info.duration, 0);
assert_eq!(info.media_time, 0); assert_eq!(info.media_time, 0);
let mut video = Mp4parseTrackVideoInfo { let mut video = Mp4parseTrackVideoInfo::default();
display_width: 0,
display_height: 0,
image_width: 0,
image_height: 0,
rotation: 0,
extra_data: Mp4parseByteData::default(),
protected_data: Default::default(),
};
assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_video_info(parser, 3, &mut video)); assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_video_info(parser, 3, &mut video));
assert_eq!(video.display_width, 0); assert_eq!(video.display_width, 0);
assert_eq!(video.display_height, 0); assert_eq!(video.display_height, 0);
assert_eq!(video.image_width, 0); assert_eq!(video.sample_info_count, 0);
assert_eq!(video.image_height, 0);
let mut audio = Default::default(); let mut audio = Default::default();
assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_audio_info(parser, 3, &mut audio)); assert_eq!(Mp4parseStatus::BadArg, mp4parse_get_track_audio_info(parser, 3, &mut audio));
assert_eq!(audio.channels, 0); assert_eq!(audio.sample_info_count, 0);
assert_eq!(audio.bit_depth, 0);
assert_eq!(audio.sample_rate, 0);
mp4parse_free(parser); mp4parse_free(parser);
} }

Просмотреть файл

@ -4,7 +4,7 @@
set -e set -e
# Default version. # Default version.
VER="f6032a118aa498525145adf611cd7b3bec0e0216" VER="26e614bbcb4d7322dc2e4b7e15391bdb30b9f7be"
# Accept version or commit from the command line. # Accept version or commit from the command line.
if test -n "$1"; then if test -n "$1"; then