зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
0f41ddb2a4
Коммит
d9995b9edc
|
@ -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)",
|
||||
]
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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<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);
|
||||
} break;
|
||||
case TrackInfo::TrackType::kVideoTrack: {
|
||||
|
@ -297,7 +377,18 @@ MP4Metadata::ResultAndTrackInfo MP4Metadata::GetTrackInfo(
|
|||
nullptr};
|
||||
}
|
||||
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);
|
||||
} break;
|
||||
default:
|
||||
|
|
|
@ -15,6 +15,14 @@ extern "C" {
|
|||
#include <stdlib.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 {
|
||||
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 {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "mp4parse"
|
||||
version = "0.10.1"
|
||||
version = "0.11.2"
|
||||
authors = [
|
||||
"Ralph Giles <giles@mozilla.com>",
|
||||
"Matthew Gregan <kinetik@flim.org>",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -277,8 +277,8 @@ struct HandlerBox {
|
|||
|
||||
// Sample description box 'stsd'
|
||||
#[derive(Debug)]
|
||||
struct SampleDescriptionBox {
|
||||
descriptions: Vec<SampleEntry>,
|
||||
pub struct SampleDescriptionBox {
|
||||
pub descriptions: Vec<SampleEntry>,
|
||||
}
|
||||
|
||||
#[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<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)]
|
||||
pub struct ProtectionSchemeInfoBox {
|
||||
pub code_name: String,
|
||||
pub scheme_type: Option<SchemeTypeBox>,
|
||||
pub tenc: Option<TrackEncryptionBox>,
|
||||
}
|
||||
|
||||
|
@ -521,9 +535,8 @@ pub struct Track {
|
|||
pub timescale: Option<TrackTimeScale<u64>>,
|
||||
pub duration: Option<TrackScaledTime<u64>>,
|
||||
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 stsd: Option<SampleDescriptionBox>,
|
||||
pub stts: Option<TimeToSampleBox>,
|
||||
pub stsc: Option<SampleToChunkBox>,
|
||||
pub stsz: Option<SampleSizeBox>,
|
||||
|
@ -962,6 +975,7 @@ fn read_stbl<T: Read>(f: &mut BMFFBox<T>, 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<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> {
|
||||
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<T: Read>(src: &mut BMFFBox<T>) -> Result<HandlerBox> {
|
|||
}
|
||||
|
||||
/// 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 codec_type = match name {
|
||||
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"));
|
||||
}
|
||||
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<T: Read>(src: &mut BMFFBox<T>) -> 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<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> {
|
|||
}
|
||||
|
||||
/// Parse an audio description inside an stsd box.
|
||||
fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<(CodecType, SampleEntry)> {
|
||||
fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleEntry> {
|
||||
let name = src.get_header().name;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleD
|
|||
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();
|
||||
while let Some(mut b) = iter.next_box()? {
|
||||
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")),
|
||||
};
|
||||
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<T: Read>(src: &mut BMFFBox<T>, track: &mut Track) -> Result<SampleD
|
|||
let to_skip = b.bytes_left();
|
||||
skip(&mut b, to_skip)?;
|
||||
SampleEntry::Unknown
|
||||
}
|
||||
},
|
||||
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)?;
|
||||
check_parser_state!(b.content);
|
||||
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)?;
|
||||
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<T: Read>(src: &mut BMFFBox<T>) -> Result<Option<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_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<T: Read>(src: &mut BMFFBox<T>) -> Result<String> {
|
|||
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.
|
||||
fn skip<T: Read>(src: &mut T, mut bytes: usize) -> Result<()> {
|
||||
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 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 {
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "mp4parse_capi"
|
||||
version = "0.10.1"
|
||||
version = "0.11.2"
|
||||
authors = [
|
||||
"Ralph Giles <giles@mozilla.com>",
|
||||
"Matthew Gregan <kinetik@flim.org>",
|
||||
|
@ -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]
|
||||
|
|
|
@ -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<u32, Vec<u8>>,
|
||||
pssh_data: Vec<u8>,
|
||||
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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче