mirror of
https://github.com/roytam1/UXP.git
synced 2026-05-26 14:54:25 +00:00
@@ -53,7 +53,6 @@ MOZ_PROFILE_MIGRATOR=1
|
||||
MOZ_APP_STATIC_INI=1
|
||||
MOZ_WEBGL_CONFORMANT=1
|
||||
MOZ_JSDOWNLOADS=1
|
||||
MOZ_RUST_MP4PARSE=1
|
||||
MOZ_RUST_URLPARSE=1
|
||||
MOZ_WEBRTC=1
|
||||
MOZ_WEBEXTENSIONS=1
|
||||
|
||||
@@ -156,10 +156,6 @@ private:
|
||||
DECL_MEDIA_PREF("media.ogg.flac.enabled", FlacInOgg, bool, false);
|
||||
DECL_MEDIA_PREF("media.flac.enabled", FlacEnabled, bool, true);
|
||||
|
||||
#if defined(MOZ_RUST_MP4PARSE) && !defined(RELEASE_OR_BETA)
|
||||
DECL_MEDIA_PREF("media.rust.test_mode", RustTestMode, bool, false);
|
||||
#endif
|
||||
|
||||
public:
|
||||
// Manage the singleton:
|
||||
static MediaPrefs& GetSingleton();
|
||||
|
||||
@@ -13,10 +13,6 @@
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "include/ESDS.h"
|
||||
|
||||
#ifdef MOZ_RUST_MP4PARSE
|
||||
#include "mp4parse.h"
|
||||
#endif
|
||||
|
||||
using namespace stagefright;
|
||||
|
||||
namespace mp4_demuxer
|
||||
@@ -187,26 +183,6 @@ MP4VideoInfo::Update(const MetaData* aMetaData, const char* aMimeType)
|
||||
|
||||
}
|
||||
|
||||
#ifdef MOZ_RUST_MP4PARSE
|
||||
void
|
||||
MP4VideoInfo::Update(const mp4parse_track_info* track,
|
||||
const mp4parse_track_video_info* video)
|
||||
{
|
||||
if (track->codec == MP4PARSE_CODEC_AVC) {
|
||||
mMimeType = MEDIA_MIMETYPE_VIDEO_AVC;
|
||||
} else if (track->codec == MP4PARSE_CODEC_VP9) {
|
||||
mMimeType = NS_LITERAL_CSTRING("video/vp9");
|
||||
}
|
||||
mTrackId = track->track_id;
|
||||
mDuration = track->duration;
|
||||
mMediaTime = track->media_time;
|
||||
mDisplay.width = video->display_width;
|
||||
mDisplay.height = video->display_height;
|
||||
mImage.width = video->image_width;
|
||||
mImage.height = video->image_height;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool
|
||||
MP4VideoInfo::IsValid() const
|
||||
{
|
||||
|
||||
@@ -24,14 +24,6 @@
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef MOZ_RUST_MP4PARSE
|
||||
// OpusDecoder header is really needed only by MP4 in rust
|
||||
#include "OpusDecoder.h"
|
||||
#include "mp4parse.h"
|
||||
|
||||
struct FreeMP4Parser { void operator()(mp4parse_parser* aPtr) { mp4parse_free(aPtr); } };
|
||||
#endif
|
||||
|
||||
using namespace stagefright;
|
||||
|
||||
namespace mp4_demuxer
|
||||
@@ -104,65 +96,8 @@ private:
|
||||
bool mCanSeek;
|
||||
};
|
||||
|
||||
#ifdef MOZ_RUST_MP4PARSE
|
||||
|
||||
// Wrap an mp4_demuxer::Stream to remember the read offset.
|
||||
|
||||
class RustStreamAdaptor {
|
||||
public:
|
||||
explicit RustStreamAdaptor(Stream* aSource)
|
||||
: mSource(aSource)
|
||||
, mOffset(0)
|
||||
{
|
||||
}
|
||||
|
||||
~RustStreamAdaptor() {}
|
||||
|
||||
bool Read(uint8_t* buffer, uintptr_t size, size_t* bytes_read);
|
||||
|
||||
private:
|
||||
Stream* mSource;
|
||||
CheckedInt<size_t> mOffset;
|
||||
};
|
||||
|
||||
class MP4MetadataRust
|
||||
{
|
||||
public:
|
||||
explicit MP4MetadataRust(Stream* aSource);
|
||||
~MP4MetadataRust();
|
||||
|
||||
static bool HasCompleteMetadata(Stream* aSource);
|
||||
static already_AddRefed<mozilla::MediaByteBuffer> Metadata(Stream* aSource);
|
||||
uint32_t GetNumberTracks(mozilla::TrackInfo::TrackType aType) const;
|
||||
mozilla::UniquePtr<mozilla::TrackInfo> GetTrackInfo(mozilla::TrackInfo::TrackType aType,
|
||||
size_t aTrackNumber) const;
|
||||
bool CanSeek() const;
|
||||
|
||||
const CryptoFile& Crypto() const;
|
||||
|
||||
bool ReadTrackIndex(FallibleTArray<Index::Indice>& aDest, mozilla::TrackID aTrackID);
|
||||
|
||||
private:
|
||||
Maybe<uint32_t> TrackTypeToGlobalTrackIndex(mozilla::TrackInfo::TrackType aType, size_t aTrackNumber) const;
|
||||
|
||||
CryptoFile mCrypto;
|
||||
RefPtr<Stream> mSource;
|
||||
RustStreamAdaptor mRustSource;
|
||||
mozilla::UniquePtr<mp4parse_parser, FreeMP4Parser> mRustParser;
|
||||
};
|
||||
#endif
|
||||
|
||||
MP4Metadata::MP4Metadata(Stream* aSource)
|
||||
: mStagefright(MakeUnique<MP4MetadataStagefright>(aSource))
|
||||
#ifdef MOZ_RUST_MP4PARSE
|
||||
, mRust(MakeUnique<MP4MetadataRust>(aSource))
|
||||
, mPreferRust(false)
|
||||
, mReportedAudioTrackTelemetry(false)
|
||||
, mReportedVideoTrackTelemetry(false)
|
||||
#ifndef RELEASE_OR_BETA
|
||||
, mRustTestMode(MediaPrefs::RustTestMode())
|
||||
#endif
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
@@ -202,70 +137,9 @@ MP4Metadata::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const
|
||||
|
||||
uint32_t numTracks = mStagefright->GetNumberTracks(aType);
|
||||
|
||||
#ifdef MOZ_RUST_MP4PARSE
|
||||
if (!mRust) {
|
||||
return numTracks;
|
||||
}
|
||||
|
||||
uint32_t numTracksRust = mRust->GetNumberTracks(aType);
|
||||
MOZ_LOG(sLog, LogLevel::Info, ("%s tracks found: stagefright=%u rust=%u",
|
||||
TrackTypeToString(aType), numTracks, numTracksRust));
|
||||
|
||||
bool numTracksMatch = numTracks == numTracksRust;
|
||||
|
||||
if (aType == mozilla::TrackInfo::kAudioTrack && !mReportedAudioTrackTelemetry) {
|
||||
Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_TRACK_MATCH_AUDIO,
|
||||
numTracksMatch);
|
||||
mReportedAudioTrackTelemetry = true;
|
||||
} else if (aType == mozilla::TrackInfo::kVideoTrack && !mReportedVideoTrackTelemetry) {
|
||||
Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_TRACK_MATCH_VIDEO,
|
||||
numTracksMatch);
|
||||
mReportedVideoTrackTelemetry = true;
|
||||
}
|
||||
|
||||
if (mPreferRust || ShouldPreferRust()) {
|
||||
MOZ_LOG(sLog, LogLevel::Info, ("Preferring rust demuxer"));
|
||||
mPreferRust = true;
|
||||
return numTracksRust;
|
||||
}
|
||||
#endif // MOZ_RUST_MP4PARSE
|
||||
|
||||
return numTracks;
|
||||
}
|
||||
|
||||
#ifdef MOZ_RUST_MP4PARSE
|
||||
bool MP4Metadata::ShouldPreferRust() const {
|
||||
if (!mRust) {
|
||||
return false;
|
||||
}
|
||||
// See if there's an Opus track.
|
||||
uint32_t numTracks = mRust->GetNumberTracks(TrackInfo::kAudioTrack);
|
||||
for (auto i = 0; i < numTracks; i++) {
|
||||
auto info = mRust->GetTrackInfo(TrackInfo::kAudioTrack, i);
|
||||
if (!info) {
|
||||
return false;
|
||||
}
|
||||
if (info->mMimeType.EqualsASCII("audio/opus") ||
|
||||
info->mMimeType.EqualsASCII("audio/flac")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
numTracks = mRust->GetNumberTracks(TrackInfo::kVideoTrack);
|
||||
for (auto i = 0; i < numTracks; i++) {
|
||||
auto info = mRust->GetTrackInfo(TrackInfo::kVideoTrack, i);
|
||||
if (!info) {
|
||||
return false;
|
||||
}
|
||||
if (info->mMimeType.EqualsASCII("video/vp9")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Otherwise, fall back.
|
||||
return false;
|
||||
}
|
||||
#endif // MOZ_RUST_MP4PARSE
|
||||
|
||||
mozilla::UniquePtr<mozilla::TrackInfo>
|
||||
MP4Metadata::GetTrackInfo(mozilla::TrackInfo::TrackType aType,
|
||||
size_t aTrackNumber) const
|
||||
@@ -273,56 +147,6 @@ MP4Metadata::GetTrackInfo(mozilla::TrackInfo::TrackType aType,
|
||||
mozilla::UniquePtr<mozilla::TrackInfo> info =
|
||||
mStagefright->GetTrackInfo(aType, aTrackNumber);
|
||||
|
||||
#ifdef MOZ_RUST_MP4PARSE
|
||||
if (!mRust) {
|
||||
return info;
|
||||
}
|
||||
|
||||
mozilla::UniquePtr<mozilla::TrackInfo> infoRust =
|
||||
mRust->GetTrackInfo(aType, aTrackNumber);
|
||||
|
||||
#ifndef RELEASE_OR_BETA
|
||||
if (mRustTestMode && info) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(infoRust);
|
||||
MOZ_DIAGNOSTIC_ASSERT(infoRust->mId == info->mId);
|
||||
MOZ_DIAGNOSTIC_ASSERT(infoRust->mKind == info->mKind);
|
||||
MOZ_DIAGNOSTIC_ASSERT(infoRust->mLabel == info->mLabel);
|
||||
MOZ_DIAGNOSTIC_ASSERT(infoRust->mLanguage == info->mLanguage);
|
||||
MOZ_DIAGNOSTIC_ASSERT(infoRust->mEnabled == info->mEnabled);
|
||||
MOZ_DIAGNOSTIC_ASSERT(infoRust->mTrackId == info->mTrackId);
|
||||
MOZ_DIAGNOSTIC_ASSERT(infoRust->mMimeType == info->mMimeType);
|
||||
MOZ_DIAGNOSTIC_ASSERT(infoRust->mDuration == info->mDuration);
|
||||
MOZ_DIAGNOSTIC_ASSERT(infoRust->mMediaTime == info->mMediaTime);
|
||||
switch (aType) {
|
||||
case mozilla::TrackInfo::kAudioTrack: {
|
||||
AudioInfo *audioRust = infoRust->GetAsAudioInfo(), *audio = info->GetAsAudioInfo();
|
||||
MOZ_DIAGNOSTIC_ASSERT(audioRust->mRate == audio->mRate);
|
||||
MOZ_DIAGNOSTIC_ASSERT(audioRust->mChannels == audio->mChannels);
|
||||
MOZ_DIAGNOSTIC_ASSERT(audioRust->mBitDepth == audio->mBitDepth);
|
||||
// TODO: These fields aren't implemented in the Rust demuxer yet.
|
||||
//MOZ_DIAGNOSTIC_ASSERT(audioRust->mProfile != audio->mProfile);
|
||||
//MOZ_DIAGNOSTIC_ASSERT(audioRust->mExtendedProfile != audio->mExtendedProfile);
|
||||
break;
|
||||
}
|
||||
case mozilla::TrackInfo::kVideoTrack: {
|
||||
VideoInfo *videoRust = infoRust->GetAsVideoInfo(), *video = info->GetAsVideoInfo();
|
||||
MOZ_DIAGNOSTIC_ASSERT(videoRust->mDisplay == video->mDisplay);
|
||||
MOZ_DIAGNOSTIC_ASSERT(videoRust->mImage == video->mImage);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!mPreferRust) {
|
||||
return info;
|
||||
}
|
||||
MOZ_ASSERT(infoRust);
|
||||
return infoRust;
|
||||
#endif
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -341,12 +165,6 @@ MP4Metadata::Crypto() const
|
||||
bool
|
||||
MP4Metadata::ReadTrackIndex(FallibleTArray<Index::Indice>& aDest, mozilla::TrackID aTrackID)
|
||||
{
|
||||
#ifdef MOZ_RUST_MP4PARSE
|
||||
if (mRust && mPreferRust && mRust->ReadTrackIndex(aDest, aTrackID)) {
|
||||
return true;
|
||||
}
|
||||
aDest.Clear();
|
||||
#endif
|
||||
return mStagefright->ReadTrackIndex(aDest, aTrackID);
|
||||
}
|
||||
|
||||
@@ -589,292 +407,4 @@ MP4MetadataStagefright::Metadata(Stream* aSource)
|
||||
return parser->Metadata();
|
||||
}
|
||||
|
||||
#ifdef MOZ_RUST_MP4PARSE
|
||||
bool
|
||||
RustStreamAdaptor::Read(uint8_t* buffer, uintptr_t size, size_t* bytes_read)
|
||||
{
|
||||
if (!mOffset.isValid()) {
|
||||
static LazyLogModule sLog("MP4Metadata");
|
||||
MOZ_LOG(sLog, LogLevel::Error, ("Overflow in source stream offset"));
|
||||
return false;
|
||||
}
|
||||
bool rv = mSource->ReadAt(mOffset.value(), buffer, size, bytes_read);
|
||||
if (rv) {
|
||||
mOffset += *bytes_read;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Wrapper to allow rust to call our read adaptor.
|
||||
static intptr_t
|
||||
read_source(uint8_t* buffer, uintptr_t size, void* userdata)
|
||||
{
|
||||
MOZ_ASSERT(buffer);
|
||||
MOZ_ASSERT(userdata);
|
||||
|
||||
auto source = reinterpret_cast<RustStreamAdaptor*>(userdata);
|
||||
size_t bytes_read = 0;
|
||||
bool rv = source->Read(buffer, size, &bytes_read);
|
||||
if (!rv) {
|
||||
static LazyLogModule sLog("MP4Metadata");
|
||||
MOZ_LOG(sLog, LogLevel::Warning, ("Error reading source data"));
|
||||
return -1;
|
||||
}
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
MP4MetadataRust::MP4MetadataRust(Stream* aSource)
|
||||
: mSource(aSource)
|
||||
, mRustSource(aSource)
|
||||
{
|
||||
mp4parse_io io = { read_source, &mRustSource };
|
||||
mRustParser.reset(mp4parse_new(&io));
|
||||
MOZ_ASSERT(mRustParser);
|
||||
|
||||
static LazyLogModule sLog("MP4Metadata");
|
||||
mp4parse_error rv = mp4parse_read(mRustParser.get());
|
||||
MOZ_LOG(sLog, LogLevel::Debug, ("rust parser returned %d\n", rv));
|
||||
Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_SUCCESS,
|
||||
rv == MP4PARSE_OK);
|
||||
if (rv != MP4PARSE_OK) {
|
||||
MOZ_ASSERT(rv > 0);
|
||||
Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_ERROR_CODE, rv);
|
||||
}
|
||||
}
|
||||
|
||||
MP4MetadataRust::~MP4MetadataRust()
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
TrackTypeEqual(TrackInfo::TrackType aLHS, mp4parse_track_type aRHS)
|
||||
{
|
||||
switch (aLHS) {
|
||||
case TrackInfo::kAudioTrack:
|
||||
return aRHS == MP4PARSE_TRACK_TYPE_AUDIO;
|
||||
case TrackInfo::kVideoTrack:
|
||||
return aRHS == MP4PARSE_TRACK_TYPE_VIDEO;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
MP4MetadataRust::GetNumberTracks(mozilla::TrackInfo::TrackType aType) const
|
||||
{
|
||||
static LazyLogModule sLog("MP4Metadata");
|
||||
|
||||
uint32_t tracks;
|
||||
auto rv = mp4parse_get_track_count(mRustParser.get(), &tracks);
|
||||
if (rv != MP4PARSE_OK) {
|
||||
MOZ_LOG(sLog, LogLevel::Warning,
|
||||
("rust parser error %d counting tracks", rv));
|
||||
return 0;
|
||||
}
|
||||
MOZ_LOG(sLog, LogLevel::Info, ("rust parser found %u tracks", tracks));
|
||||
|
||||
uint32_t total = 0;
|
||||
for (uint32_t i = 0; i < tracks; ++i) {
|
||||
mp4parse_track_info track_info;
|
||||
rv = mp4parse_get_track_info(mRustParser.get(), i, &track_info);
|
||||
if (rv != MP4PARSE_OK) {
|
||||
continue;
|
||||
}
|
||||
if (TrackTypeEqual(aType, track_info.track_type)) {
|
||||
total += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
Maybe<uint32_t>
|
||||
MP4MetadataRust::TrackTypeToGlobalTrackIndex(mozilla::TrackInfo::TrackType aType, size_t aTrackNumber) const
|
||||
{
|
||||
uint32_t tracks;
|
||||
auto rv = mp4parse_get_track_count(mRustParser.get(), &tracks);
|
||||
if (rv != MP4PARSE_OK) {
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
/* The MP4Metadata API uses a per-TrackType index of tracks, but mp4parse
|
||||
(and libstagefright) use a global track index. Convert the index by
|
||||
counting the tracks of the requested type and returning the global
|
||||
track index when a match is found. */
|
||||
uint32_t perType = 0;
|
||||
for (uint32_t i = 0; i < tracks; ++i) {
|
||||
mp4parse_track_info track_info;
|
||||
rv = mp4parse_get_track_info(mRustParser.get(), i, &track_info);
|
||||
if (rv != MP4PARSE_OK) {
|
||||
continue;
|
||||
}
|
||||
if (TrackTypeEqual(aType, track_info.track_type)) {
|
||||
if (perType == aTrackNumber) {
|
||||
return Some(i);
|
||||
}
|
||||
perType += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
mozilla::UniquePtr<mozilla::TrackInfo>
|
||||
MP4MetadataRust::GetTrackInfo(mozilla::TrackInfo::TrackType aType,
|
||||
size_t aTrackNumber) const
|
||||
{
|
||||
static LazyLogModule sLog("MP4Metadata");
|
||||
|
||||
Maybe<uint32_t> trackIndex = TrackTypeToGlobalTrackIndex(aType, aTrackNumber);
|
||||
if (trackIndex.isNothing()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mp4parse_track_info info;
|
||||
auto rv = mp4parse_get_track_info(mRustParser.get(), trackIndex.value(), &info);
|
||||
if (rv != MP4PARSE_OK) {
|
||||
MOZ_LOG(sLog, LogLevel::Warning, ("mp4parse_get_track_info returned %d", rv));
|
||||
return 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_AVC: codec_string = "h.264"; break;
|
||||
case MP4PARSE_CODEC_VP9: codec_string = "vp9"; break;
|
||||
case MP4PARSE_CODEC_MP3: codec_string = "mp3"; break;
|
||||
}
|
||||
MOZ_LOG(sLog, LogLevel::Debug, ("track codec %s (%u)\n",
|
||||
codec_string, info.codec));
|
||||
#endif
|
||||
|
||||
// This specialization interface is crazy.
|
||||
UniquePtr<mozilla::TrackInfo> e;
|
||||
switch (aType) {
|
||||
case TrackInfo::TrackType::kAudioTrack: {
|
||||
mp4parse_track_audio_info audio;
|
||||
auto rv = mp4parse_get_track_audio_info(mRustParser.get(), trackIndex.value(), &audio);
|
||||
if (rv != MP4PARSE_OK) {
|
||||
MOZ_LOG(sLog, LogLevel::Warning, ("mp4parse_get_track_audio_info returned error %d", rv));
|
||||
return nullptr;
|
||||
}
|
||||
auto track = mozilla::MakeUnique<mozilla::AudioInfo>();
|
||||
if (info.codec == MP4PARSE_CODEC_OPUS) {
|
||||
track->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.
|
||||
MOZ_ASSERT(audio.codec_specific_config.data);
|
||||
MOZ_ASSERT(audio.codec_specific_config.length >= 12);
|
||||
uint16_t preskip =
|
||||
LittleEndian::readUint16(audio.codec_specific_config.data + 10);
|
||||
MOZ_LOG(sLog, LogLevel::Debug,
|
||||
("Copying opus pre-skip value of %d as CodecDelay.",(int)preskip));
|
||||
OpusDataDecoder::AppendCodecDelay(track->mCodecSpecificConfig,
|
||||
mozilla::FramesToUsecs(preskip, 48000).value());
|
||||
} else if (info.codec == MP4PARSE_CODEC_AAC) {
|
||||
track->mMimeType = MEDIA_MIMETYPE_AUDIO_AAC;
|
||||
} else if (info.codec == MP4PARSE_CODEC_FLAC) {
|
||||
track->mMimeType = MEDIA_MIMETYPE_AUDIO_FLAC;
|
||||
} else if (info.codec == MP4PARSE_CODEC_MP3) {
|
||||
track->mMimeType = MEDIA_MIMETYPE_AUDIO_MPEG;
|
||||
}
|
||||
track->mCodecSpecificConfig->AppendElements(
|
||||
audio.codec_specific_config.data,
|
||||
audio.codec_specific_config.length);
|
||||
track->mRate = audio.sample_rate;
|
||||
track->mChannels = audio.channels;
|
||||
track->mBitDepth = audio.bit_depth;
|
||||
track->mDuration = info.duration;
|
||||
track->mMediaTime = info.media_time;
|
||||
track->mTrackId = info.track_id;
|
||||
e = Move(track);
|
||||
}
|
||||
break;
|
||||
case TrackInfo::TrackType::kVideoTrack: {
|
||||
mp4parse_track_video_info video;
|
||||
auto rv = mp4parse_get_track_video_info(mRustParser.get(), trackIndex.value(), &video);
|
||||
if (rv != MP4PARSE_OK) {
|
||||
MOZ_LOG(sLog, LogLevel::Warning, ("mp4parse_get_track_video_info returned error %d", rv));
|
||||
return nullptr;
|
||||
}
|
||||
auto track = mozilla::MakeUnique<MP4VideoInfo>();
|
||||
track->Update(&info, &video);
|
||||
e = Move(track);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
MOZ_LOG(sLog, LogLevel::Warning, ("unhandled track type %d", aType));
|
||||
return nullptr;
|
||||
break;
|
||||
}
|
||||
|
||||
// No duration in track, use fragment_duration.
|
||||
if (e && !e->mDuration) {
|
||||
mp4parse_fragment_info info;
|
||||
auto rv = mp4parse_get_fragment_info(mRustParser.get(), &info);
|
||||
if (rv == MP4PARSE_OK) {
|
||||
e->mDuration = info.fragment_duration;
|
||||
}
|
||||
}
|
||||
|
||||
if (e && e->IsValid()) {
|
||||
return e;
|
||||
}
|
||||
MOZ_LOG(sLog, LogLevel::Debug, ("TrackInfo didn't validate"));
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
MP4MetadataRust::CanSeek() const
|
||||
{
|
||||
MOZ_ASSERT(false, "Not yet implemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
const CryptoFile&
|
||||
MP4MetadataRust::Crypto() const
|
||||
{
|
||||
MOZ_ASSERT(false, "Not yet implemented");
|
||||
return mCrypto;
|
||||
}
|
||||
|
||||
bool
|
||||
MP4MetadataRust::ReadTrackIndex(FallibleTArray<Index::Indice>& aDest, mozilla::TrackID aTrackID)
|
||||
{
|
||||
uint8_t fragmented = false;
|
||||
auto rv = mp4parse_is_fragmented(mRustParser.get(), aTrackID, &fragmented);
|
||||
if (rv != MP4PARSE_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fragmented) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// For non-fragmented mp4.
|
||||
NS_WARNING("Not yet implemented");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*static*/ bool
|
||||
MP4MetadataRust::HasCompleteMetadata(Stream* aSource)
|
||||
{
|
||||
MOZ_ASSERT(false, "Not yet implemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*static*/ already_AddRefed<mozilla::MediaByteBuffer>
|
||||
MP4MetadataRust::Metadata(Stream* aSource)
|
||||
{
|
||||
MOZ_ASSERT(false, "Not yet implemented");
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mp4_demuxer
|
||||
|
||||
@@ -19,14 +19,6 @@ namespace stagefright
|
||||
class MetaData;
|
||||
}
|
||||
|
||||
#ifdef MOZ_RUST_MP4PARSE
|
||||
extern "C" {
|
||||
typedef struct mp4parse_track_info mp4parse_track_info;
|
||||
typedef struct mp4parse_track_audio_info mp4parse_track_audio_info;
|
||||
typedef struct mp4parse_track_video_info mp4parse_track_video_info;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace mp4_demuxer
|
||||
{
|
||||
|
||||
@@ -80,11 +72,6 @@ public:
|
||||
void Update(const stagefright::MetaData* aMetaData,
|
||||
const char* aMimeType);
|
||||
|
||||
#ifdef MOZ_RUST_MP4PARSE
|
||||
void Update(const mp4parse_track_info* track,
|
||||
const mp4parse_track_video_info* video);
|
||||
#endif
|
||||
|
||||
virtual bool IsValid() const override;
|
||||
};
|
||||
|
||||
|
||||
@@ -37,16 +37,6 @@ public:
|
||||
|
||||
private:
|
||||
UniquePtr<MP4MetadataStagefright> mStagefright;
|
||||
#ifdef MOZ_RUST_MP4PARSE
|
||||
UniquePtr<MP4MetadataRust> mRust;
|
||||
mutable bool mPreferRust;
|
||||
mutable bool mReportedAudioTrackTelemetry;
|
||||
mutable bool mReportedVideoTrackTelemetry;
|
||||
#ifndef RELEASE_OR_BETA
|
||||
mutable bool mRustTestMode;
|
||||
#endif
|
||||
bool ShouldPreferRust() const;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace mp4_demuxer
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
|
||||
#ifndef cheddar_generated_mp4parse_h
|
||||
#define cheddar_generated_mp4parse_h
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// THIS FILE IS AUTOGENERATED BY mp4parse-rust/build.rs - DO NOT EDIT
|
||||
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
typedef enum mp4parse_error {
|
||||
MP4PARSE_OK = 0,
|
||||
MP4PARSE_ERROR_BADARG = 1,
|
||||
MP4PARSE_ERROR_INVALID = 2,
|
||||
MP4PARSE_ERROR_UNSUPPORTED = 3,
|
||||
MP4PARSE_ERROR_EOF = 4,
|
||||
MP4PARSE_ERROR_IO = 5,
|
||||
} mp4parse_error;
|
||||
|
||||
typedef enum mp4parse_track_type {
|
||||
MP4PARSE_TRACK_TYPE_VIDEO = 0,
|
||||
MP4PARSE_TRACK_TYPE_AUDIO = 1,
|
||||
} mp4parse_track_type;
|
||||
|
||||
typedef enum mp4parse_codec {
|
||||
MP4PARSE_CODEC_UNKNOWN,
|
||||
MP4PARSE_CODEC_AAC,
|
||||
MP4PARSE_CODEC_FLAC,
|
||||
MP4PARSE_CODEC_OPUS,
|
||||
MP4PARSE_CODEC_AVC,
|
||||
MP4PARSE_CODEC_VP9,
|
||||
MP4PARSE_CODEC_MP3,
|
||||
} mp4parse_codec;
|
||||
|
||||
typedef struct mp4parse_track_info {
|
||||
mp4parse_track_type track_type;
|
||||
mp4parse_codec codec;
|
||||
uint32_t track_id;
|
||||
uint64_t duration;
|
||||
int64_t media_time;
|
||||
} mp4parse_track_info;
|
||||
|
||||
typedef struct mp4parse_codec_specific_config {
|
||||
uint32_t length;
|
||||
uint8_t const* data;
|
||||
} mp4parse_codec_specific_config;
|
||||
|
||||
typedef struct mp4parse_track_audio_info {
|
||||
uint16_t channels;
|
||||
uint16_t bit_depth;
|
||||
uint32_t sample_rate;
|
||||
mp4parse_codec_specific_config codec_specific_config;
|
||||
} mp4parse_track_audio_info;
|
||||
|
||||
typedef struct mp4parse_track_video_info {
|
||||
uint32_t display_width;
|
||||
uint32_t display_height;
|
||||
uint16_t image_width;
|
||||
uint16_t image_height;
|
||||
} mp4parse_track_video_info;
|
||||
|
||||
typedef struct mp4parse_fragment_info {
|
||||
uint64_t fragment_duration;
|
||||
} mp4parse_fragment_info;
|
||||
|
||||
typedef struct mp4parse_parser mp4parse_parser;
|
||||
|
||||
typedef struct mp4parse_io {
|
||||
intptr_t (*read)(uint8_t* buffer, uintptr_t size, void* userdata);
|
||||
void* userdata;
|
||||
} mp4parse_io;
|
||||
|
||||
/// Allocate an `mp4parse_parser*` to read from the supplied `mp4parse_io`.
|
||||
mp4parse_parser* mp4parse_new(mp4parse_io const* io);
|
||||
|
||||
/// Free an `mp4parse_parser*` allocated by `mp4parse_new()`.
|
||||
void mp4parse_free(mp4parse_parser* parser);
|
||||
|
||||
/// Run the `mp4parse_parser*` allocated by `mp4parse_new()` until EOF or error.
|
||||
mp4parse_error mp4parse_read(mp4parse_parser* parser);
|
||||
|
||||
/// Return the number of tracks parsed by previous `mp4parse_read()` call.
|
||||
mp4parse_error mp4parse_get_track_count(mp4parse_parser const* parser, uint32_t* count);
|
||||
|
||||
/// Fill the supplied `mp4parse_track_info` with metadata for `track`.
|
||||
mp4parse_error mp4parse_get_track_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_info* info);
|
||||
|
||||
/// Fill the supplied `mp4parse_track_audio_info` with metadata for `track`.
|
||||
mp4parse_error mp4parse_get_track_audio_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_audio_info* info);
|
||||
|
||||
/// Fill the supplied `mp4parse_track_video_info` with metadata for `track`.
|
||||
mp4parse_error mp4parse_get_track_video_info(mp4parse_parser* parser, uint32_t track_index, mp4parse_track_video_info* info);
|
||||
|
||||
mp4parse_error mp4parse_get_fragment_info(mp4parse_parser* parser, mp4parse_fragment_info* info);
|
||||
|
||||
mp4parse_error mp4parse_is_fragmented(mp4parse_parser* parser, uint32_t track_id, uint8_t* fragmented);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,45 +0,0 @@
|
||||
diff --git a/media/libstagefright/binding/mp4parse/Cargo.toml b/media/libstagefright/binding/mp4parse/Cargo.toml
|
||||
index ff9422c..814c4c6 100644
|
||||
--- a/media/libstagefright/binding/mp4parse/Cargo.toml
|
||||
+++ b/media/libstagefright/binding/mp4parse/Cargo.toml
|
||||
@@ -18,17 +18,11 @@ exclude = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
-byteorder = "0.5.0"
|
||||
-afl = { version = "0.1.1", optional = true }
|
||||
-afl-plugin = { version = "0.1.1", optional = true }
|
||||
-abort_on_panic = { version = "1.0.0", optional = true }
|
||||
+byteorder = "0.5.0"
|
||||
|
||||
[dev-dependencies]
|
||||
test-assembler = "0.1.2"
|
||||
|
||||
-[features]
|
||||
-fuzz = ["afl", "afl-plugin", "abort_on_panic"]
|
||||
-
|
||||
# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
|
||||
[profile.release]
|
||||
debug-assertions = true
|
||||
diff --git a/media/libstagefright/binding/mp4parse_capi/Cargo.toml b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
|
||||
index aeeebc65..5c0836a 100644
|
||||
--- a/media/libstagefright/binding/mp4parse_capi/Cargo.toml
|
||||
+++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
|
||||
@@ -18,17 +18,9 @@ exclude = [
|
||||
"*.mp4",
|
||||
]
|
||||
|
||||
-build = "build.rs"
|
||||
-
|
||||
[dependencies]
|
||||
"mp4parse" = {version = "0.6.0", path = "../mp4parse"}
|
||||
|
||||
-[build-dependencies]
|
||||
-rusty-cheddar = "0.3.2"
|
||||
-
|
||||
-[features]
|
||||
-fuzz = ["mp4parse/fuzz"]
|
||||
-
|
||||
# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
|
||||
[profile.release]
|
||||
debug-assertions = true
|
||||
@@ -1,29 +0,0 @@
|
||||
[package]
|
||||
name = "mp4parse"
|
||||
version = "0.6.0"
|
||||
authors = [
|
||||
"Ralph Giles <giles@mozilla.com>",
|
||||
"Matthew Gregan <kinetik@flim.org>",
|
||||
"Alfredo Yang <ayang@mozilla.com>",
|
||||
]
|
||||
|
||||
description = "Parser for ISO base media file format (mp4)"
|
||||
documentation = "https://mp4parse-docs.surge.sh/mp4parse/"
|
||||
license = "MPL-2.0"
|
||||
|
||||
repository = "https://github.com/mozilla/mp4parse-rust"
|
||||
|
||||
# Avoid complaints about trying to package test files.
|
||||
exclude = [
|
||||
"*.mp4",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "0.5.0"
|
||||
|
||||
[dev-dependencies]
|
||||
test-assembler = "0.1.2"
|
||||
|
||||
# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
|
||||
[profile.release]
|
||||
debug-assertions = true
|
||||
@@ -1,62 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
macro_rules! box_database {
|
||||
($($boxenum:ident $boxtype:expr),*,) => {
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum BoxType {
|
||||
$($boxenum),*,
|
||||
UnknownBox(u32),
|
||||
}
|
||||
|
||||
impl From<u32> for BoxType {
|
||||
fn from(t: u32) -> BoxType {
|
||||
use self::BoxType::*;
|
||||
match t {
|
||||
$($boxtype => $boxenum),*,
|
||||
_ => UnknownBox(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
box_database!(
|
||||
FileTypeBox 0x66747970, // "ftyp"
|
||||
MovieBox 0x6d6f6f76, // "moov"
|
||||
MovieHeaderBox 0x6d766864, // "mvhd"
|
||||
TrackBox 0x7472616b, // "trak"
|
||||
TrackHeaderBox 0x746b6864, // "tkhd"
|
||||
EditBox 0x65647473, // "edts"
|
||||
MediaBox 0x6d646961, // "mdia"
|
||||
EditListBox 0x656c7374, // "elst"
|
||||
MediaHeaderBox 0x6d646864, // "mdhd"
|
||||
HandlerBox 0x68646c72, // "hdlr"
|
||||
MediaInformationBox 0x6d696e66, // "minf"
|
||||
SampleTableBox 0x7374626c, // "stbl"
|
||||
SampleDescriptionBox 0x73747364, // "stsd"
|
||||
TimeToSampleBox 0x73747473, // "stts"
|
||||
SampleToChunkBox 0x73747363, // "stsc"
|
||||
SampleSizeBox 0x7374737a, // "stsz"
|
||||
ChunkOffsetBox 0x7374636f, // "stco"
|
||||
ChunkLargeOffsetBox 0x636f3634, // "co64"
|
||||
SyncSampleBox 0x73747373, // "stss"
|
||||
AVCSampleEntry 0x61766331, // "avc1"
|
||||
AVC3SampleEntry 0x61766333, // "avc3" - Need to check official name in spec.
|
||||
AVCConfigurationBox 0x61766343, // "avcC"
|
||||
MP4AudioSampleEntry 0x6d703461, // "mp4a"
|
||||
ESDBox 0x65736473, // "esds"
|
||||
VP8SampleEntry 0x76703038, // "vp08"
|
||||
VP9SampleEntry 0x76703039, // "vp09"
|
||||
VPCodecConfigurationBox 0x76706343, // "vpcC"
|
||||
FLACSampleEntry 0x664c6143, // "fLaC"
|
||||
FLACSpecificBox 0x64664c61, // "dfLa"
|
||||
OpusSampleEntry 0x4f707573, // "Opus"
|
||||
OpusSpecificBox 0x644f7073, // "dOps"
|
||||
ProtectedVisualSampleEntry 0x656e6376, // "encv" - Need to check official name in spec.
|
||||
ProtectedAudioSampleEntry 0x656e6361, // "enca" - Need to check official name in spec.
|
||||
MovieExtendsBox 0x6d766578, // "mvex"
|
||||
MovieExtendsHeaderBox 0x6d656864, // "mehd"
|
||||
QTWaveAtom 0x77617665, // "wave" - quicktime atom
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,860 +0,0 @@
|
||||
//! Module for parsing ISO Base Media Format aka video/mp4 streams.
|
||||
//! Internal unit tests.
|
||||
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use std::io::Cursor;
|
||||
use super::read_mp4;
|
||||
use super::MediaContext;
|
||||
use super::Error;
|
||||
extern crate test_assembler;
|
||||
use self::test_assembler::*;
|
||||
|
||||
use boxes::BoxType;
|
||||
|
||||
enum BoxSize {
|
||||
Short(u32),
|
||||
Long(u64),
|
||||
UncheckedShort(u32),
|
||||
UncheckedLong(u64),
|
||||
Auto,
|
||||
}
|
||||
|
||||
fn make_box<F>(size: BoxSize, name: &[u8; 4], func: F) -> Cursor<Vec<u8>>
|
||||
where F: Fn(Section) -> Section
|
||||
{
|
||||
let mut section = Section::new();
|
||||
let box_size = Label::new();
|
||||
section = match size {
|
||||
BoxSize::Short(size) | BoxSize::UncheckedShort(size) => section.B32(size),
|
||||
BoxSize::Long(_) | BoxSize::UncheckedLong(_) => section.B32(1),
|
||||
BoxSize::Auto => section.B32(&box_size),
|
||||
};
|
||||
section = section.append_bytes(name);
|
||||
section = match size {
|
||||
// The spec allows the 32-bit size to be 0 to indicate unknown
|
||||
// length streams. It's not clear if this is valid when using a
|
||||
// 64-bit size, so prohibit it for now.
|
||||
BoxSize::Long(size) => {
|
||||
assert!(size > 0);
|
||||
section.B64(size)
|
||||
}
|
||||
BoxSize::UncheckedLong(size) => section.B64(size),
|
||||
_ => section,
|
||||
};
|
||||
section = func(section);
|
||||
match size {
|
||||
BoxSize::Short(size) => {
|
||||
if size > 0 {
|
||||
assert_eq!(size as u64, section.size())
|
||||
}
|
||||
}
|
||||
BoxSize::Long(size) => assert_eq!(size, section.size()),
|
||||
BoxSize::Auto => {
|
||||
assert!(section.size() <= u32::max_value() as u64,
|
||||
"Tried to use a long box with BoxSize::Auto");
|
||||
box_size.set_const(section.size());
|
||||
}
|
||||
// Skip checking BoxSize::Unchecked* cases.
|
||||
_ => (),
|
||||
}
|
||||
Cursor::new(section.get_contents().unwrap())
|
||||
}
|
||||
|
||||
fn make_fullbox<F>(size: BoxSize, name: &[u8; 4], version: u8, func: F) -> Cursor<Vec<u8>>
|
||||
where F: Fn(Section) -> Section
|
||||
{
|
||||
make_box(size, name, |s| {
|
||||
func(s.B8(version)
|
||||
.B8(0)
|
||||
.B8(0)
|
||||
.B8(0))
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_box_header_short() {
|
||||
let mut stream = make_box(BoxSize::Short(8), b"test", |s| s);
|
||||
let header = super::read_box_header(&mut stream).unwrap();
|
||||
assert_eq!(header.name, BoxType::UnknownBox(0x74657374)); // "test"
|
||||
assert_eq!(header.size, 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_box_header_long() {
|
||||
let mut stream = make_box(BoxSize::Long(16), b"test", |s| s);
|
||||
let header = super::read_box_header(&mut stream).unwrap();
|
||||
assert_eq!(header.name, BoxType::UnknownBox(0x74657374)); // "test"
|
||||
assert_eq!(header.size, 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_box_header_short_unknown_size() {
|
||||
let mut stream = make_box(BoxSize::Short(0), b"test", |s| s);
|
||||
match super::read_box_header(&mut stream) {
|
||||
Err(Error::Unsupported(s)) => assert_eq!(s, "unknown sized box"),
|
||||
_ => panic!("unexpected result reading box with unknown size"),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_box_header_short_invalid_size() {
|
||||
let mut stream = make_box(BoxSize::UncheckedShort(2), b"test", |s| s);
|
||||
match super::read_box_header(&mut stream) {
|
||||
Err(Error::InvalidData(s)) => assert_eq!(s, "malformed size"),
|
||||
_ => panic!("unexpected result reading box with invalid size"),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_box_header_long_invalid_size() {
|
||||
let mut stream = make_box(BoxSize::UncheckedLong(2), b"test", |s| s);
|
||||
match super::read_box_header(&mut stream) {
|
||||
Err(Error::InvalidData(s)) => assert_eq!(s, "malformed wide size"),
|
||||
_ => panic!("unexpected result reading box with invalid size"),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_ftyp() {
|
||||
let mut stream = make_box(BoxSize::Short(24), b"ftyp", |s| {
|
||||
s.append_bytes(b"mp42")
|
||||
.B32(0) // minor version
|
||||
.append_bytes(b"isom")
|
||||
.append_bytes(b"mp42")
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::FileTypeBox);
|
||||
assert_eq!(stream.head.size, 24);
|
||||
let parsed = super::read_ftyp(&mut stream).unwrap();
|
||||
assert_eq!(parsed.major_brand, 0x6d703432); // mp42
|
||||
assert_eq!(parsed.minor_version, 0);
|
||||
assert_eq!(parsed.compatible_brands.len(), 2);
|
||||
assert_eq!(parsed.compatible_brands[0], 0x69736f6d); // isom
|
||||
assert_eq!(parsed.compatible_brands[1], 0x6d703432); // mp42
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_truncated_ftyp() {
|
||||
// We declare a 24 byte box, but only write 20 bytes.
|
||||
let mut stream = make_box(BoxSize::UncheckedShort(24), b"ftyp", |s| {
|
||||
s.append_bytes(b"mp42")
|
||||
.B32(0) // minor version
|
||||
.append_bytes(b"isom")
|
||||
});
|
||||
let mut context = MediaContext::new();
|
||||
match read_mp4(&mut stream, &mut context) {
|
||||
Err(Error::UnexpectedEOF) => (),
|
||||
Ok(_) => assert!(false, "expected an error result"),
|
||||
_ => assert!(false, "expected a different error result"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_ftyp_case() {
|
||||
// Brands in BMFF are represented as a u32, so it would seem clear that
|
||||
// 0x6d703432 ("mp42") is not equal to 0x4d503432 ("MP42"), but some
|
||||
// demuxers treat these as case-insensitive strings, e.g. street.mp4's
|
||||
// major brand is "MP42". I haven't seen case-insensitive
|
||||
// compatible_brands (which we also test here), but it doesn't seem
|
||||
// unlikely given the major_brand behaviour.
|
||||
let mut stream = make_box(BoxSize::Auto, b"ftyp", |s| {
|
||||
s.append_bytes(b"MP42")
|
||||
.B32(0) // minor version
|
||||
.append_bytes(b"ISOM")
|
||||
.append_bytes(b"MP42")
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::FileTypeBox);
|
||||
assert_eq!(stream.head.size, 24);
|
||||
let parsed = super::read_ftyp(&mut stream).unwrap();
|
||||
assert_eq!(parsed.major_brand, 0x4d503432);
|
||||
assert_eq!(parsed.minor_version, 0);
|
||||
assert_eq!(parsed.compatible_brands.len(), 2);
|
||||
assert_eq!(parsed.compatible_brands[0], 0x49534f4d); // ISOM
|
||||
assert_eq!(parsed.compatible_brands[1], 0x4d503432); // MP42
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_elst_v0() {
|
||||
let mut stream = make_fullbox(BoxSize::Short(28), b"elst", 0, |s| {
|
||||
s.B32(1) // list count
|
||||
// first entry
|
||||
.B32(1234) // duration
|
||||
.B32(5678) // time
|
||||
.B16(12) // rate integer
|
||||
.B16(34) // rate fraction
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::EditListBox);
|
||||
assert_eq!(stream.head.size, 28);
|
||||
let parsed = super::read_elst(&mut stream).unwrap();
|
||||
assert_eq!(parsed.edits.len(), 1);
|
||||
assert_eq!(parsed.edits[0].segment_duration, 1234);
|
||||
assert_eq!(parsed.edits[0].media_time, 5678);
|
||||
assert_eq!(parsed.edits[0].media_rate_integer, 12);
|
||||
assert_eq!(parsed.edits[0].media_rate_fraction, 34);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_elst_v1() {
|
||||
let mut stream = make_fullbox(BoxSize::Short(56), b"elst", 1, |s| {
|
||||
s.B32(2) // list count
|
||||
// first entry
|
||||
.B64(1234) // duration
|
||||
.B64(5678) // time
|
||||
.B16(12) // rate integer
|
||||
.B16(34) // rate fraction
|
||||
// second entry
|
||||
.B64(1234) // duration
|
||||
.B64(5678) // time
|
||||
.B16(12) // rate integer
|
||||
.B16(34) // rate fraction
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::EditListBox);
|
||||
assert_eq!(stream.head.size, 56);
|
||||
let parsed = super::read_elst(&mut stream).unwrap();
|
||||
assert_eq!(parsed.edits.len(), 2);
|
||||
assert_eq!(parsed.edits[1].segment_duration, 1234);
|
||||
assert_eq!(parsed.edits[1].media_time, 5678);
|
||||
assert_eq!(parsed.edits[1].media_rate_integer, 12);
|
||||
assert_eq!(parsed.edits[1].media_rate_fraction, 34);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_mdhd_v0() {
|
||||
let mut stream = make_fullbox(BoxSize::Short(32), b"mdhd", 0, |s| {
|
||||
s.B32(0)
|
||||
.B32(0)
|
||||
.B32(1234) // timescale
|
||||
.B32(5678) // duration
|
||||
.B32(0)
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::MediaHeaderBox);
|
||||
assert_eq!(stream.head.size, 32);
|
||||
let parsed = super::read_mdhd(&mut stream).unwrap();
|
||||
assert_eq!(parsed.timescale, 1234);
|
||||
assert_eq!(parsed.duration, 5678);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_mdhd_v1() {
|
||||
let mut stream = make_fullbox(BoxSize::Short(44), b"mdhd", 1, |s| {
|
||||
s.B64(0)
|
||||
.B64(0)
|
||||
.B32(1234) // timescale
|
||||
.B64(5678) // duration
|
||||
.B32(0)
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::MediaHeaderBox);
|
||||
assert_eq!(stream.head.size, 44);
|
||||
let parsed = super::read_mdhd(&mut stream).unwrap();
|
||||
assert_eq!(parsed.timescale, 1234);
|
||||
assert_eq!(parsed.duration, 5678);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_mdhd_unknown_duration() {
|
||||
let mut stream = make_fullbox(BoxSize::Short(32), b"mdhd", 0, |s| {
|
||||
s.B32(0)
|
||||
.B32(0)
|
||||
.B32(1234) // timescale
|
||||
.B32(::std::u32::MAX) // duration
|
||||
.B32(0)
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::MediaHeaderBox);
|
||||
assert_eq!(stream.head.size, 32);
|
||||
let parsed = super::read_mdhd(&mut stream).unwrap();
|
||||
assert_eq!(parsed.timescale, 1234);
|
||||
assert_eq!(parsed.duration, ::std::u64::MAX);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_mdhd_invalid_timescale() {
|
||||
let mut stream = make_fullbox(BoxSize::Short(44), b"mdhd", 1, |s| {
|
||||
s.B64(0)
|
||||
.B64(0)
|
||||
.B32(0) // timescale
|
||||
.B64(5678) // duration
|
||||
.B32(0)
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::MediaHeaderBox);
|
||||
assert_eq!(stream.head.size, 44);
|
||||
let r = super::parse_mdhd(&mut stream, &mut super::Track::new(0));
|
||||
assert_eq!(r.is_err(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_mvhd_v0() {
|
||||
let mut stream = make_fullbox(BoxSize::Short(108), b"mvhd", 0, |s| {
|
||||
s.B32(0)
|
||||
.B32(0)
|
||||
.B32(1234)
|
||||
.B32(5678)
|
||||
.append_repeated(0, 80)
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
|
||||
assert_eq!(stream.head.size, 108);
|
||||
let parsed = super::read_mvhd(&mut stream).unwrap();
|
||||
assert_eq!(parsed.timescale, 1234);
|
||||
assert_eq!(parsed.duration, 5678);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_mvhd_v1() {
|
||||
let mut stream = make_fullbox(BoxSize::Short(120), b"mvhd", 1, |s| {
|
||||
s.B64(0)
|
||||
.B64(0)
|
||||
.B32(1234)
|
||||
.B64(5678)
|
||||
.append_repeated(0, 80)
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
|
||||
assert_eq!(stream.head.size, 120);
|
||||
let parsed = super::read_mvhd(&mut stream).unwrap();
|
||||
assert_eq!(parsed.timescale, 1234);
|
||||
assert_eq!(parsed.duration, 5678);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_mvhd_invalid_timescale() {
|
||||
let mut stream = make_fullbox(BoxSize::Short(120), b"mvhd", 1, |s| {
|
||||
s.B64(0)
|
||||
.B64(0)
|
||||
.B32(0)
|
||||
.B64(5678)
|
||||
.append_repeated(0, 80)
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
|
||||
assert_eq!(stream.head.size, 120);
|
||||
let r = super::parse_mvhd(&mut stream);
|
||||
assert_eq!(r.is_err(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_mvhd_unknown_duration() {
|
||||
let mut stream = make_fullbox(BoxSize::Short(108), b"mvhd", 0, |s| {
|
||||
s.B32(0)
|
||||
.B32(0)
|
||||
.B32(1234)
|
||||
.B32(::std::u32::MAX)
|
||||
.append_repeated(0, 80)
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
|
||||
assert_eq!(stream.head.size, 108);
|
||||
let parsed = super::read_mvhd(&mut stream).unwrap();
|
||||
assert_eq!(parsed.timescale, 1234);
|
||||
assert_eq!(parsed.duration, ::std::u64::MAX);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_vpcc() {
|
||||
let data_length = 12u16;
|
||||
let mut stream = make_fullbox(BoxSize::Auto, b"vpcC", 0, |s| {
|
||||
s.B8(2)
|
||||
.B8(0)
|
||||
.B8(0x82)
|
||||
.B8(0)
|
||||
.B16(data_length)
|
||||
.append_repeated(42, data_length as usize)
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::VPCodecConfigurationBox);
|
||||
let r = super::read_vpcc(&mut stream);
|
||||
assert!(r.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_hdlr() {
|
||||
let mut stream = make_fullbox(BoxSize::Short(45), b"hdlr", 0, |s| {
|
||||
s.B32(0)
|
||||
.append_bytes(b"vide")
|
||||
.B32(0)
|
||||
.B32(0)
|
||||
.B32(0)
|
||||
.append_bytes(b"VideoHandler")
|
||||
.B8(0) // null-terminate string
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::HandlerBox);
|
||||
assert_eq!(stream.head.size, 45);
|
||||
let parsed = super::read_hdlr(&mut stream).unwrap();
|
||||
assert_eq!(parsed.handler_type, 0x76696465); // vide
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_hdlr_short_name() {
|
||||
let mut stream = make_fullbox(BoxSize::Short(33), b"hdlr", 0, |s| {
|
||||
s.B32(0)
|
||||
.append_bytes(b"vide")
|
||||
.B32(0)
|
||||
.B32(0)
|
||||
.B32(0)
|
||||
.B8(0) // null-terminate string
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::HandlerBox);
|
||||
assert_eq!(stream.head.size, 33);
|
||||
let parsed = super::read_hdlr(&mut stream).unwrap();
|
||||
assert_eq!(parsed.handler_type, 0x76696465); // vide
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_hdlr_zero_length_name() {
|
||||
let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 0, |s| {
|
||||
s.B32(0)
|
||||
.append_bytes(b"vide")
|
||||
.B32(0)
|
||||
.B32(0)
|
||||
.B32(0)
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::HandlerBox);
|
||||
assert_eq!(stream.head.size, 32);
|
||||
let parsed = super::read_hdlr(&mut stream).unwrap();
|
||||
assert_eq!(parsed.handler_type, 0x76696465); // vide
|
||||
}
|
||||
|
||||
fn flac_streaminfo() -> Vec<u8> {
|
||||
vec![
|
||||
0x10, 0x00, 0x10, 0x00, 0x00, 0x0a, 0x11, 0x00,
|
||||
0x38, 0x32, 0x0a, 0xc4, 0x42, 0xf0, 0x00, 0xc9,
|
||||
0xdf, 0xae, 0xb5, 0x66, 0xfc, 0x02, 0x15, 0xa3,
|
||||
0xb1, 0x54, 0x61, 0x47, 0x0f, 0xfb, 0x05, 0x00,
|
||||
0x33, 0xad,
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_flac() {
|
||||
let mut stream = make_box(BoxSize::Auto, b"fLaC", |s| {
|
||||
s.append_repeated(0, 6) // reserved
|
||||
.B16(1) // data reference index
|
||||
.B32(0) // reserved
|
||||
.B32(0) // reserved
|
||||
.B16(2) // channel count
|
||||
.B16(16) // bits per sample
|
||||
.B16(0) // pre_defined
|
||||
.B16(0) // reserved
|
||||
.B32(44100 << 16) // Sample rate
|
||||
.append_bytes(&make_dfla(FlacBlockType::StreamInfo, true,
|
||||
&flac_streaminfo(), FlacBlockLength::Correct)
|
||||
.into_inner())
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
let mut track = super::Track::new(0);
|
||||
let r = super::read_audio_sample_entry(&mut stream, &mut track);
|
||||
r.unwrap();
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum FlacBlockType {
|
||||
StreamInfo = 0,
|
||||
_Padding = 1,
|
||||
_Application = 2,
|
||||
_Seektable = 3,
|
||||
_Comment = 4,
|
||||
_Cuesheet = 5,
|
||||
_Picture = 6,
|
||||
_Reserved,
|
||||
_Invalid = 127,
|
||||
}
|
||||
|
||||
enum FlacBlockLength {
|
||||
Correct,
|
||||
Incorrect(usize),
|
||||
}
|
||||
|
||||
fn make_dfla(block_type: FlacBlockType, last: bool, data: &Vec<u8>,
|
||||
data_length: FlacBlockLength) -> Cursor<Vec<u8>> {
|
||||
assert!(data.len() < 1<<24);
|
||||
make_fullbox(BoxSize::Auto, b"dfLa", 0, |s| {
|
||||
let flag = match last {
|
||||
true => 1,
|
||||
false => 0,
|
||||
};
|
||||
let size = match data_length {
|
||||
FlacBlockLength::Correct => (data.len() as u32) & 0xffffff,
|
||||
FlacBlockLength::Incorrect(size) => {
|
||||
assert!(size < 1<<24);
|
||||
(size as u32) & 0xffffff
|
||||
}
|
||||
};
|
||||
let block_type = (block_type as u32) & 0x7f;
|
||||
s.B32(flag << 31 | block_type << 24 | size)
|
||||
.append_bytes(data)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_dfla() {
|
||||
let mut stream = make_dfla(FlacBlockType::StreamInfo, true,
|
||||
&flac_streaminfo(), FlacBlockLength::Correct);
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::FLACSpecificBox);
|
||||
let dfla = super::read_dfla(&mut stream).unwrap();
|
||||
assert_eq!(dfla.version, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_flac_metadata() {
|
||||
let streaminfo = flac_streaminfo();
|
||||
let mut stream = make_dfla(FlacBlockType::StreamInfo, true,
|
||||
&streaminfo,
|
||||
FlacBlockLength::Incorrect(streaminfo.len() + 4));
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::FLACSpecificBox);
|
||||
let r = super::read_dfla(&mut stream);
|
||||
assert!(r.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_opus() {
|
||||
let mut stream = make_box(BoxSize::Auto, b"Opus", |s| {
|
||||
s.append_repeated(0, 6)
|
||||
.B16(1) // data reference index
|
||||
.B32(0)
|
||||
.B32(0)
|
||||
.B16(2) // channel count
|
||||
.B16(16) // bits per sample
|
||||
.B16(0)
|
||||
.B16(0)
|
||||
.B32(48000 << 16) // Sample rate is always 48 kHz for Opus.
|
||||
.append_bytes(&make_dops().into_inner())
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
let mut track = super::Track::new(0);
|
||||
let r = super::read_audio_sample_entry(&mut stream, &mut track);
|
||||
assert!(r.is_ok());
|
||||
}
|
||||
|
||||
fn make_dops() -> Cursor<Vec<u8>> {
|
||||
make_box(BoxSize::Auto, b"dOps", |s| {
|
||||
s.B8(0) // version
|
||||
.B8(2) // channel count
|
||||
.B16(348) // pre-skip
|
||||
.B32(44100) // original sample rate
|
||||
.B16(0) // gain
|
||||
.B8(0) // channel mapping
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_dops() {
|
||||
let mut stream = make_dops();
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
assert_eq!(stream.head.name, BoxType::OpusSpecificBox);
|
||||
let r = super::read_dops(&mut stream);
|
||||
assert!(r.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_opus_header() {
|
||||
let opus = super::OpusSpecificBox {
|
||||
version: 0,
|
||||
output_channel_count: 1,
|
||||
pre_skip: 342,
|
||||
input_sample_rate: 24000,
|
||||
output_gain: 0,
|
||||
channel_mapping_family: 0,
|
||||
channel_mapping_table: None,
|
||||
};
|
||||
let mut v = Vec::<u8>::new();
|
||||
super::serialize_opus_header(&opus, &mut v).unwrap();
|
||||
assert!(v.len() == 19);
|
||||
assert!(v == vec![
|
||||
0x4f, 0x70, 0x75, 0x73, 0x48,0x65, 0x61, 0x64,
|
||||
0x01, 0x01, 0x56, 0x01,
|
||||
0xc0, 0x5d, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
]);
|
||||
let opus = super::OpusSpecificBox {
|
||||
version: 0,
|
||||
output_channel_count: 6,
|
||||
pre_skip: 152,
|
||||
input_sample_rate: 48000,
|
||||
output_gain: 0,
|
||||
channel_mapping_family: 1,
|
||||
channel_mapping_table: Some(super::ChannelMappingTable {
|
||||
stream_count: 4,
|
||||
coupled_count: 2,
|
||||
channel_mapping: vec![0, 4, 1, 2, 3, 5],
|
||||
}),
|
||||
};
|
||||
let mut v = Vec::<u8>::new();
|
||||
super::serialize_opus_header(&opus, &mut v).unwrap();
|
||||
assert!(v.len() == 27);
|
||||
assert!(v == vec![
|
||||
0x4f, 0x70, 0x75, 0x73, 0x48,0x65, 0x61, 0x64,
|
||||
0x01, 0x06, 0x98, 0x00,
|
||||
0x80, 0xbb, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x04, 0x02,
|
||||
0x00, 0x04, 0x01, 0x02, 0x03, 0x05,
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn avcc_limit() {
|
||||
let mut stream = make_box(BoxSize::Auto, b"avc1", |s| {
|
||||
s.append_repeated(0, 6)
|
||||
.B16(1)
|
||||
.append_repeated(0, 16)
|
||||
.B16(320)
|
||||
.B16(240)
|
||||
.append_repeated(0, 14)
|
||||
.append_repeated(0, 32)
|
||||
.append_repeated(0, 4)
|
||||
.B32(0xffffffff)
|
||||
.append_bytes(b"avcC")
|
||||
.append_repeated(0, 100)
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
let mut track = super::Track::new(0);
|
||||
match super::read_video_sample_entry(&mut stream, &mut track) {
|
||||
Err(Error::InvalidData(s)) => assert_eq!(s, "avcC box exceeds BUF_SIZE_LIMIT"),
|
||||
Ok(_) => assert!(false, "expected an error result"),
|
||||
_ => assert!(false, "expected a different error result"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn esds_limit() {
|
||||
let mut stream = make_box(BoxSize::Auto, b"mp4a", |s| {
|
||||
s.append_repeated(0, 6)
|
||||
.B16(1)
|
||||
.B32(0)
|
||||
.B32(0)
|
||||
.B16(2)
|
||||
.B16(16)
|
||||
.B16(0)
|
||||
.B16(0)
|
||||
.B32(48000 << 16)
|
||||
.B32(0xffffffff)
|
||||
.append_bytes(b"esds")
|
||||
.append_repeated(0, 100)
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
let mut track = super::Track::new(0);
|
||||
match super::read_audio_sample_entry(&mut stream, &mut track) {
|
||||
Err(Error::InvalidData(s)) => assert_eq!(s, "esds box exceeds BUF_SIZE_LIMIT"),
|
||||
Ok(_) => assert!(false, "expected an error result"),
|
||||
_ => assert!(false, "expected a different error result"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn esds_limit_2() {
|
||||
let mut stream = make_box(BoxSize::Auto, b"mp4a", |s| {
|
||||
s.append_repeated(0, 6)
|
||||
.B16(1)
|
||||
.B32(0)
|
||||
.B32(0)
|
||||
.B16(2)
|
||||
.B16(16)
|
||||
.B16(0)
|
||||
.B16(0)
|
||||
.B32(48000 << 16)
|
||||
.B32(8)
|
||||
.append_bytes(b"esds")
|
||||
.append_repeated(0, 4)
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
let mut track = super::Track::new(0);
|
||||
match super::read_audio_sample_entry(&mut stream, &mut track) {
|
||||
Err(Error::UnexpectedEOF) => (),
|
||||
Ok(_) => assert!(false, "expected an error result"),
|
||||
_ => assert!(false, "expected a different error result"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_elst_zero_entries() {
|
||||
let mut stream = make_fullbox(BoxSize::Auto, b"elst", 0, |s| {
|
||||
s.B32(0)
|
||||
.B16(12)
|
||||
.B16(34)
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
match super::read_elst(&mut stream) {
|
||||
Err(Error::InvalidData(s)) => assert_eq!(s, "invalid edit count"),
|
||||
Ok(_) => assert!(false, "expected an error result"),
|
||||
_ => assert!(false, "expected a different error result"),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_elst() -> Cursor<Vec<u8>> {
|
||||
make_fullbox(BoxSize::Auto, b"elst", 1, |s| {
|
||||
s.B32(1)
|
||||
// first entry
|
||||
.B64(1234) // duration
|
||||
.B64(0xffffffffffffffff) // time
|
||||
.B16(12) // rate integer
|
||||
.B16(34) // rate fraction
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_edts_bogus() {
|
||||
// First edit list entry has a media_time of -1, so we expect a second
|
||||
// edit list entry to be present to provide a valid media_time.
|
||||
let mut stream = make_box(BoxSize::Auto, b"edts", |s| {
|
||||
s.append_bytes(&make_elst().into_inner())
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
let mut track = super::Track::new(0);
|
||||
match super::read_edts(&mut stream, &mut track) {
|
||||
Err(Error::InvalidData(s)) => assert_eq!(s, "expected additional edit"),
|
||||
Ok(_) => assert!(false, "expected an error result"),
|
||||
_ => assert!(false, "expected a different error result"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_pascal_string() {
|
||||
// String claims to be 32 bytes long (we provide 33 bytes to account for
|
||||
// the 1 byte length prefix).
|
||||
let pstr = "\x20xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
||||
let mut stream = Cursor::new(pstr);
|
||||
// Reader wants to limit the total read length to 32 bytes, so any
|
||||
// returned string must be no longer than 31 bytes.
|
||||
let s = super::read_fixed_length_pascal_string(&mut stream, 32).unwrap();
|
||||
assert_eq!(s.len(), 31);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_padding_in_boxes() {
|
||||
// Padding data could be added in the end of these boxes. Parser needs to skip
|
||||
// them instead of returning error.
|
||||
let box_names = vec![b"stts", b"stsc", b"stsz", b"stco", b"co64", b"stss"];
|
||||
|
||||
for name in box_names {
|
||||
let mut stream = make_fullbox(BoxSize::Auto, name, 1, |s| {
|
||||
s.append_repeated(0, 100) // add padding data
|
||||
});
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
match name {
|
||||
b"stts" => {
|
||||
super::read_stts(&mut stream).expect("fail to skip padding: stts");
|
||||
},
|
||||
b"stsc" => {
|
||||
super::read_stsc(&mut stream).expect("fail to skip padding: stsc");
|
||||
},
|
||||
b"stsz" => {
|
||||
super::read_stsz(&mut stream).expect("fail to skip padding: stsz");
|
||||
},
|
||||
b"stco" => {
|
||||
super::read_stco(&mut stream).expect("fail to skip padding: stco");
|
||||
},
|
||||
b"co64" => {
|
||||
super::read_co64(&mut stream).expect("fail to skip padding: co64");
|
||||
},
|
||||
b"stss" => {
|
||||
super::read_stss(&mut stream).expect("fail to skip padding: stss");
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skip_padding_in_stsd() {
|
||||
// Padding data could be added in the end of stsd boxes. Parser needs to skip
|
||||
// them instead of returning error.
|
||||
let avc = make_box(BoxSize::Auto, b"avc1", |s| {
|
||||
s.append_repeated(0, 6)
|
||||
.B16(1)
|
||||
.append_repeated(0, 16)
|
||||
.B16(320)
|
||||
.B16(240)
|
||||
.append_repeated(0, 14)
|
||||
.append_repeated(0, 32)
|
||||
.append_repeated(0, 4)
|
||||
.B32(0xffffffff)
|
||||
.append_bytes(b"avcC")
|
||||
.append_repeated(0, 100)
|
||||
}).into_inner();
|
||||
let mut stream = make_fullbox(BoxSize::Auto, b"stsd", 0, |s| {
|
||||
s.B32(1)
|
||||
.append_bytes(avc.as_slice())
|
||||
.append_repeated(0, 100) // add padding data
|
||||
});
|
||||
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
super::read_stsd(&mut stream, &mut super::Track::new(0))
|
||||
.expect("fail to skip padding: stsd");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_qt_wave_atom() {
|
||||
let esds = make_fullbox(BoxSize::Auto, b"esds", 0, |s| {
|
||||
s.B8(0x03) // elementary stream descriptor tag
|
||||
.B8(0x0b) // esds length
|
||||
.append_repeated(0, 2)
|
||||
.B8(0x00) // flags
|
||||
.B8(0x04) // decoder config descriptor tag
|
||||
.B8(0x0d) // dcds length
|
||||
.B8(0x6b) // mp3
|
||||
.append_repeated(0, 12)
|
||||
}).into_inner();
|
||||
let wave = make_box(BoxSize::Auto, b"wave", |s| {
|
||||
s.append_bytes(esds.as_slice())
|
||||
}).into_inner();
|
||||
let mut stream = make_box(BoxSize::Auto, b"mp4a", |s| {
|
||||
s.append_repeated(0, 6)
|
||||
.B16(1) // data_reference_count
|
||||
.B16(1) // verion: qt -> 1
|
||||
.append_repeated(0, 6)
|
||||
.B16(2)
|
||||
.B16(16)
|
||||
.append_repeated(0, 4)
|
||||
.B32(48000 << 16)
|
||||
.append_repeated(0, 16)
|
||||
.append_bytes(wave.as_slice())
|
||||
});
|
||||
|
||||
let mut iter = super::BoxIter::new(&mut stream);
|
||||
let mut stream = iter.next_box().unwrap().unwrap();
|
||||
let mut track = super::Track::new(0);
|
||||
super::read_audio_sample_entry(&mut stream, &mut track)
|
||||
.expect("fail to read qt wave atom");
|
||||
assert_eq!(track.codec_type, super::CodecType::MP3);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/// Regression tests from American Fuzzy Lop test cases.
|
||||
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
/// These all caused panics at some point during development.
|
||||
|
||||
extern crate mp4parse;
|
||||
|
||||
use std::io::Cursor;
|
||||
|
||||
/// https://github.com/mozilla/mp4parse-rust/issues/2
|
||||
///
|
||||
/// Test a box with 4-byte size, smaller than the smallest header.
|
||||
#[test]
|
||||
fn fuzz_2() {
|
||||
let mut c = Cursor::new(b"\x00\x00\x00\x04\xa6\x00\x04\xa6".to_vec());
|
||||
let mut context = mp4parse::MediaContext::new();
|
||||
let _ = mp4parse::read_mp4(&mut c, &mut context);
|
||||
}
|
||||
|
||||
/// https://github.com/mozilla/mp4parse-rust/issues/4
|
||||
///
|
||||
/// Test a large (64 bit) box header with zero declared size.
|
||||
#[test]
|
||||
fn fuzz_4() {
|
||||
let mut c = Cursor::new(b"\x00\x00\x00\x01\x30\x30\x30\x30\x00\x00\x00\x00\x00\x00\x00\x00".to_vec());
|
||||
let mut context = mp4parse::MediaContext::new();
|
||||
let _ = mp4parse::read_mp4(&mut c, &mut context);
|
||||
}
|
||||
|
||||
/// https://github.com/mozilla/mp4parse-rust/issues/5
|
||||
///
|
||||
/// Declares 202116104 compatible brands but does not supply them,
|
||||
/// verifying read is properly bounded at the end of the stream.
|
||||
#[test]
|
||||
fn fuzz_5() {
|
||||
let mut c = Cursor::new(b"\x30\x30\x30\x30\x66\x74\x79\x70\x30\x30\x30\x30\x30\x30\x30\x30".to_vec());
|
||||
let mut context = mp4parse::MediaContext::new();
|
||||
let _ = mp4parse::read_mp4(&mut c, &mut context);
|
||||
}
|
||||
|
||||
/// https://github.com/mozilla/mp4parse-rust/issues/6
|
||||
///
|
||||
/// Declares an ftyp box with a single invalid (short - 3 byte) compatible
|
||||
/// brand and excludes the extra 3 bytes from the stream.
|
||||
#[test]
|
||||
fn fuzz_6() {
|
||||
let mut c = Cursor::new(b"\x00\x00\x00\x13\x66\x74\x79\x70\x30\x30\x30\x30\x30\x30\x30\x30".to_vec());
|
||||
let mut context = mp4parse::MediaContext::new();
|
||||
let _ = mp4parse::read_mp4(&mut c, &mut context);
|
||||
}
|
||||
Binary file not shown.
@@ -1,97 +0,0 @@
|
||||
/// Check if needed fields are still public.
|
||||
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
extern crate mp4parse as mp4;
|
||||
|
||||
use std::io::{Cursor, Read};
|
||||
use std::fs::File;
|
||||
|
||||
// Taken from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53
|
||||
#[test]
|
||||
fn public_api() {
|
||||
let mut fd = File::open("tests/minimal.mp4").expect("Unknown file");
|
||||
let mut 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");
|
||||
assert_eq!(context.timescale, Some(mp4::MediaTimeScale(1000)));
|
||||
for track in context.tracks {
|
||||
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(12800, 0)));
|
||||
assert_eq!(v.width, 320);
|
||||
assert_eq!(v.height, 240);
|
||||
|
||||
// track.tkhd part
|
||||
let tkhd = track.tkhd.unwrap();
|
||||
assert_eq!(tkhd.disabled, false);
|
||||
assert_eq!(tkhd.duration, 40);
|
||||
assert_eq!(tkhd.width, 20971520);
|
||||
assert_eq!(tkhd.height, 15728640);
|
||||
|
||||
// track.data part
|
||||
assert_eq!(match v.codec_specific {
|
||||
mp4::VideoCodecSpecific::AVCConfig(v) => {
|
||||
assert!(v.len() > 0);
|
||||
"AVC"
|
||||
}
|
||||
mp4::VideoCodecSpecific::VPxConfig(vpx) => {
|
||||
// We don't enter in here, we just check if fields are public.
|
||||
assert!(vpx.bit_depth > 0);
|
||||
assert!(vpx.color_space > 0);
|
||||
assert!(vpx.chroma_subsampling > 0);
|
||||
assert!(vpx.codec_init.len() > 0);
|
||||
"VPx"
|
||||
}
|
||||
}, "AVC");
|
||||
}
|
||||
Some(mp4::SampleEntry::Audio(a)) => {
|
||||
// track part
|
||||
assert_eq!(track.duration, Some(mp4::TrackScaledTime(2944, 1)));
|
||||
assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0)));
|
||||
assert_eq!(track.media_time, Some(mp4::TrackScaledTime(1024, 1)));
|
||||
assert_eq!(track.timescale, Some(mp4::TrackTimeScale(48000, 1)));
|
||||
|
||||
// track.tkhd part
|
||||
let tkhd = track.tkhd.unwrap();
|
||||
assert_eq!(tkhd.disabled, false);
|
||||
assert_eq!(tkhd.duration, 62);
|
||||
assert_eq!(tkhd.width, 0);
|
||||
assert_eq!(tkhd.height, 0);
|
||||
|
||||
// track.data part
|
||||
assert_eq!(match a.codec_specific {
|
||||
mp4::AudioCodecSpecific::ES_Descriptor(esds) => {
|
||||
assert_eq!(esds.audio_codec, mp4::CodecType::AAC);
|
||||
assert_eq!(esds.audio_sample_rate.unwrap(), 48000);
|
||||
"ES"
|
||||
}
|
||||
mp4::AudioCodecSpecific::FLACSpecificBox(flac) => {
|
||||
// STREAMINFO block must be present and first.
|
||||
assert!(flac.blocks.len() > 0);
|
||||
assert!(flac.blocks[0].block_type == 0);
|
||||
assert!(flac.blocks[0].data.len() == 34);
|
||||
"FLAC"
|
||||
}
|
||||
mp4::AudioCodecSpecific::OpusSpecificBox(opus) => {
|
||||
// We don't enter in here, we just check if fields are public.
|
||||
assert!(opus.version > 0);
|
||||
"Opus"
|
||||
}
|
||||
}, "ES");
|
||||
assert!(a.samplesize > 0);
|
||||
assert!(a.samplerate > 0);
|
||||
}
|
||||
Some(mp4::SampleEntry::Unknown) | None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
[package]
|
||||
name = "mp4parse_capi"
|
||||
version = "0.6.0"
|
||||
authors = [
|
||||
"Ralph Giles <giles@mozilla.com>",
|
||||
"Matthew Gregan <kinetik@flim.org>",
|
||||
"Alfredo Yang <ayang@mozilla.com>",
|
||||
]
|
||||
|
||||
description = "Parser for ISO base media file format (mp4)"
|
||||
documentation = "https://mp4parse-docs.surge.sh/mp4parse/"
|
||||
license = "MPL-2.0"
|
||||
|
||||
repository = "https://github.com/mozilla/mp4parse-rust"
|
||||
|
||||
# Avoid complaints about trying to package test files.
|
||||
exclude = [
|
||||
"*.mp4",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
"mp4parse" = {version = "0.6.0", path = "../mp4parse"}
|
||||
|
||||
# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
|
||||
[profile.release]
|
||||
debug-assertions = true
|
||||
@@ -1,12 +0,0 @@
|
||||
extern crate cheddar;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=src/lib.rs");
|
||||
// Generate mp4parse.h.
|
||||
cheddar::Cheddar::new().expect("could not read manifest")
|
||||
.insert_code("// THIS FILE IS AUTOGENERATED BY mp4parse-rust/build.rs - DO NOT EDIT\n\n")
|
||||
.insert_code("// This Source Code Form is subject to the terms of the Mozilla Public\n")
|
||||
.insert_code("// License, v. 2.0. If a copy of the MPL was not distributed with this\n")
|
||||
.insert_code("// file, You can obtain one at https://mozilla.org/MPL/2.0/.")
|
||||
.run_build("include/mp4parse.h");
|
||||
}
|
||||
@@ -1,870 +0,0 @@
|
||||
//! C API for mp4parse module.
|
||||
//!
|
||||
//! Parses ISO Base Media Format aka video/mp4 streams.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```rust
|
||||
//! extern crate mp4parse_capi;
|
||||
//! use std::io::Read;
|
||||
//!
|
||||
//! extern fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
|
||||
//! let mut input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
|
||||
//! let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
|
||||
//! match input.read(&mut buf) {
|
||||
//! Ok(n) => n as isize,
|
||||
//! Err(_) => -1,
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap();
|
||||
//! let io = mp4parse_capi::mp4parse_io {
|
||||
//! read: buf_read,
|
||||
//! userdata: &mut file as *mut _ as *mut std::os::raw::c_void
|
||||
//! };
|
||||
//! unsafe {
|
||||
//! let parser = mp4parse_capi::mp4parse_new(&io);
|
||||
//! let rv = mp4parse_capi::mp4parse_read(parser);
|
||||
//! assert_eq!(rv, mp4parse_capi::mp4parse_error::MP4PARSE_OK);
|
||||
//! mp4parse_capi::mp4parse_free(parser);
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
extern crate mp4parse;
|
||||
|
||||
use std::io::Read;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Symbols we need from our rust api.
|
||||
use mp4parse::MediaContext;
|
||||
use mp4parse::TrackType;
|
||||
use mp4parse::read_mp4;
|
||||
use mp4parse::Error;
|
||||
use mp4parse::SampleEntry;
|
||||
use mp4parse::AudioCodecSpecific;
|
||||
use mp4parse::VideoCodecSpecific;
|
||||
use mp4parse::MediaTimeScale;
|
||||
use mp4parse::MediaScaledTime;
|
||||
use mp4parse::TrackTimeScale;
|
||||
use mp4parse::TrackScaledTime;
|
||||
use mp4parse::serialize_opus_header;
|
||||
use mp4parse::CodecType;
|
||||
|
||||
// rusty-cheddar's C enum generation doesn't namespace enum members by
|
||||
// prefixing them, so we're forced to do it in our member names until
|
||||
// https://github.com/Sean1708/rusty-cheddar/pull/35 is fixed. Importing
|
||||
// the members into the module namespace avoids doubling up on the
|
||||
// namespacing on the Rust side.
|
||||
use mp4parse_error::*;
|
||||
use mp4parse_track_type::*;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum mp4parse_error {
|
||||
MP4PARSE_OK = 0,
|
||||
MP4PARSE_ERROR_BADARG = 1,
|
||||
MP4PARSE_ERROR_INVALID = 2,
|
||||
MP4PARSE_ERROR_UNSUPPORTED = 3,
|
||||
MP4PARSE_ERROR_EOF = 4,
|
||||
MP4PARSE_ERROR_IO = 5,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum mp4parse_track_type {
|
||||
MP4PARSE_TRACK_TYPE_VIDEO = 0,
|
||||
MP4PARSE_TRACK_TYPE_AUDIO = 1,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum mp4parse_codec {
|
||||
MP4PARSE_CODEC_UNKNOWN,
|
||||
MP4PARSE_CODEC_AAC,
|
||||
MP4PARSE_CODEC_FLAC,
|
||||
MP4PARSE_CODEC_OPUS,
|
||||
MP4PARSE_CODEC_AVC,
|
||||
MP4PARSE_CODEC_VP9,
|
||||
MP4PARSE_CODEC_MP3,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct mp4parse_track_info {
|
||||
pub track_type: mp4parse_track_type,
|
||||
pub codec: mp4parse_codec,
|
||||
pub track_id: u32,
|
||||
pub duration: u64,
|
||||
pub media_time: i64, // wants to be u64? understand how elst adjustment works
|
||||
// TODO(kinetik): include crypto guff
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct mp4parse_codec_specific_config {
|
||||
pub length: u32,
|
||||
pub data: *const u8,
|
||||
}
|
||||
|
||||
impl Default for mp4parse_codec_specific_config {
|
||||
fn default() -> Self {
|
||||
mp4parse_codec_specific_config {
|
||||
length: 0,
|
||||
data: std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[repr(C)]
|
||||
pub struct mp4parse_track_audio_info {
|
||||
pub channels: u16,
|
||||
pub bit_depth: u16,
|
||||
pub sample_rate: u32,
|
||||
// TODO(kinetik):
|
||||
// int32_t profile;
|
||||
// int32_t extended_profile; // check types
|
||||
codec_specific_config: mp4parse_codec_specific_config,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct mp4parse_track_video_info {
|
||||
pub display_width: u32,
|
||||
pub display_height: u32,
|
||||
pub image_width: u16,
|
||||
pub image_height: u16,
|
||||
// TODO(kinetik):
|
||||
// extra_data
|
||||
// codec_specific_config
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct mp4parse_fragment_info {
|
||||
pub fragment_duration: u64,
|
||||
// TODO:
|
||||
// info in trex box.
|
||||
}
|
||||
|
||||
// Even though mp4parse_parser is opaque to C, rusty-cheddar won't let us
|
||||
// use more than one member, so we introduce *another* wrapper.
|
||||
struct Wrap {
|
||||
context: MediaContext,
|
||||
io: mp4parse_io,
|
||||
poisoned: bool,
|
||||
opus_header: HashMap<u32, Vec<u8>>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct mp4parse_parser(Wrap);
|
||||
|
||||
impl mp4parse_parser {
|
||||
fn context(&self) -> &MediaContext {
|
||||
&self.0.context
|
||||
}
|
||||
|
||||
fn context_mut(&mut self) -> &mut MediaContext {
|
||||
&mut self.0.context
|
||||
}
|
||||
|
||||
fn io_mut(&mut self) -> &mut mp4parse_io {
|
||||
&mut self.0.io
|
||||
}
|
||||
|
||||
fn poisoned(&self) -> bool {
|
||||
self.0.poisoned
|
||||
}
|
||||
|
||||
fn set_poisoned(&mut self, poisoned: bool) {
|
||||
self.0.poisoned = poisoned;
|
||||
}
|
||||
|
||||
fn opus_header_mut(&mut self) -> &mut HashMap<u32, Vec<u8>> {
|
||||
&mut self.0.opus_header
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub struct mp4parse_io {
|
||||
pub read: extern fn(buffer: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize,
|
||||
pub userdata: *mut std::os::raw::c_void,
|
||||
}
|
||||
|
||||
impl Read for mp4parse_io {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
if buf.len() > isize::max_value() as usize {
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::Other, "buf length overflow in mp4parse_io Read impl"));
|
||||
}
|
||||
let rv = (self.read)(buf.as_mut_ptr(), buf.len(), self.userdata);
|
||||
if rv >= 0 {
|
||||
Ok(rv as usize)
|
||||
} else {
|
||||
Err(std::io::Error::new(std::io::ErrorKind::Other, "I/O error in mp4parse_io Read impl"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// C API wrapper functions.
|
||||
|
||||
/// Allocate an `mp4parse_parser*` to read from the supplied `mp4parse_io`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn mp4parse_new(io: *const mp4parse_io) -> *mut mp4parse_parser {
|
||||
if io.is_null() || (*io).userdata.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
// is_null() isn't available on a fn type because it can't be null (in
|
||||
// Rust) by definition. But since this value is coming from the C API,
|
||||
// it *could* be null. Ideally, we'd wrap it in an Option to represent
|
||||
// reality, but this causes rusty-cheddar to emit the wrong type
|
||||
// (https://github.com/Sean1708/rusty-cheddar/issues/30).
|
||||
if ((*io).read as *mut std::os::raw::c_void).is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
let parser = Box::new(mp4parse_parser(Wrap {
|
||||
context: MediaContext::new(),
|
||||
io: (*io).clone(),
|
||||
poisoned: false,
|
||||
opus_header: HashMap::new(),
|
||||
}));
|
||||
Box::into_raw(parser)
|
||||
}
|
||||
|
||||
/// Free an `mp4parse_parser*` allocated by `mp4parse_new()`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn mp4parse_free(parser: *mut mp4parse_parser) {
|
||||
assert!(!parser.is_null());
|
||||
let _ = Box::from_raw(parser);
|
||||
}
|
||||
|
||||
/// Run the `mp4parse_parser*` allocated by `mp4parse_new()` until EOF or error.
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn mp4parse_read(parser: *mut mp4parse_parser) -> mp4parse_error {
|
||||
// Validate arguments from C.
|
||||
if parser.is_null() || (*parser).poisoned() {
|
||||
return MP4PARSE_ERROR_BADARG;
|
||||
}
|
||||
|
||||
let mut context = (*parser).context_mut();
|
||||
let mut io = (*parser).io_mut();
|
||||
|
||||
let r = read_mp4(io, context);
|
||||
match r {
|
||||
Ok(_) => MP4PARSE_OK,
|
||||
Err(Error::NoMoov) | Err(Error::InvalidData(_)) => {
|
||||
// Block further calls. We've probable lost sync.
|
||||
(*parser).set_poisoned(true);
|
||||
MP4PARSE_ERROR_INVALID
|
||||
}
|
||||
Err(Error::Unsupported(_)) => MP4PARSE_ERROR_UNSUPPORTED,
|
||||
Err(Error::UnexpectedEOF) => MP4PARSE_ERROR_EOF,
|
||||
Err(Error::Io(_)) => {
|
||||
// Block further calls after a read failure.
|
||||
// Getting std::io::ErrorKind::UnexpectedEof is normal
|
||||
// but our From trait implementation should have converted
|
||||
// those to our Error::UnexpectedEOF variant.
|
||||
(*parser).set_poisoned(true);
|
||||
MP4PARSE_ERROR_IO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of tracks parsed by previous `mp4parse_read()` call.
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn mp4parse_get_track_count(parser: *const mp4parse_parser, count: *mut u32) -> mp4parse_error {
|
||||
// Validate arguments from C.
|
||||
if parser.is_null() || count.is_null() || (*parser).poisoned() {
|
||||
return MP4PARSE_ERROR_BADARG;
|
||||
}
|
||||
let context = (*parser).context();
|
||||
|
||||
// Make sure the track count fits in a u32.
|
||||
if context.tracks.len() > u32::max_value() as usize {
|
||||
return MP4PARSE_ERROR_INVALID;
|
||||
}
|
||||
*count = context.tracks.len() as u32;
|
||||
MP4PARSE_OK
|
||||
}
|
||||
|
||||
/// Calculate numerator * scale / denominator, if possible.
|
||||
///
|
||||
/// Applying the associativity of integer arithmetic, we divide first
|
||||
/// and add the remainder after multiplying each term separately
|
||||
/// to preserve precision while leaving more headroom. That is,
|
||||
/// (n * s) / d is split into floor(n / d) * s + (n % d) * s / d.
|
||||
///
|
||||
/// Return None on overflow or if the denominator is zero.
|
||||
fn rational_scale(numerator: u64, denominator: u64, scale: u64) -> Option<u64> {
|
||||
if denominator == 0 {
|
||||
return None;
|
||||
}
|
||||
let integer = numerator / denominator;
|
||||
let remainder = numerator % denominator;
|
||||
match integer.checked_mul(scale) {
|
||||
Some(integer) => remainder.checked_mul(scale)
|
||||
.and_then(|remainder| (remainder/denominator).checked_add(integer)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn media_time_to_us(time: MediaScaledTime, scale: MediaTimeScale) -> Option<u64> {
|
||||
let microseconds_per_second = 1000000;
|
||||
rational_scale(time.0, scale.0, microseconds_per_second)
|
||||
}
|
||||
|
||||
fn track_time_to_us(time: TrackScaledTime, scale: TrackTimeScale) -> Option<u64> {
|
||||
assert!(time.1 == scale.1);
|
||||
let microseconds_per_second = 1000000;
|
||||
rational_scale(time.0, scale.0, microseconds_per_second)
|
||||
}
|
||||
|
||||
/// Fill the supplied `mp4parse_track_info` with metadata for `track`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn mp4parse_get_track_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_info) -> mp4parse_error {
|
||||
if parser.is_null() || info.is_null() || (*parser).poisoned() {
|
||||
return MP4PARSE_ERROR_BADARG;
|
||||
}
|
||||
|
||||
let context = (*parser).context_mut();
|
||||
let track_index: usize = track_index as usize;
|
||||
let info: &mut mp4parse_track_info = &mut *info;
|
||||
|
||||
if track_index >= context.tracks.len() {
|
||||
return MP4PARSE_ERROR_BADARG;
|
||||
}
|
||||
|
||||
info.track_type = match context.tracks[track_index].track_type {
|
||||
TrackType::Video => MP4PARSE_TRACK_TYPE_VIDEO,
|
||||
TrackType::Audio => MP4PARSE_TRACK_TYPE_AUDIO,
|
||||
TrackType::Unknown => return MP4PARSE_ERROR_UNSUPPORTED,
|
||||
};
|
||||
|
||||
info.codec = match context.tracks[track_index].data {
|
||||
Some(SampleEntry::Audio(ref audio)) => match audio.codec_specific {
|
||||
AudioCodecSpecific::OpusSpecificBox(_) =>
|
||||
mp4parse_codec::MP4PARSE_CODEC_OPUS,
|
||||
AudioCodecSpecific::FLACSpecificBox(_) =>
|
||||
mp4parse_codec::MP4PARSE_CODEC_FLAC,
|
||||
AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC =>
|
||||
mp4parse_codec::MP4PARSE_CODEC_AAC,
|
||||
AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 =>
|
||||
mp4parse_codec::MP4PARSE_CODEC_MP3,
|
||||
AudioCodecSpecific::ES_Descriptor(_) =>
|
||||
mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
|
||||
},
|
||||
Some(SampleEntry::Video(ref video)) => match video.codec_specific {
|
||||
VideoCodecSpecific::VPxConfig(_) =>
|
||||
mp4parse_codec::MP4PARSE_CODEC_VP9,
|
||||
VideoCodecSpecific::AVCConfig(_) =>
|
||||
mp4parse_codec::MP4PARSE_CODEC_AVC,
|
||||
},
|
||||
_ => mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
|
||||
};
|
||||
|
||||
let track = &context.tracks[track_index];
|
||||
|
||||
if let (Some(track_timescale),
|
||||
Some(context_timescale)) = (track.timescale,
|
||||
context.timescale) {
|
||||
let media_time =
|
||||
match track.media_time.map_or(Some(0), |media_time| {
|
||||
track_time_to_us(media_time, track_timescale) }) {
|
||||
Some(time) => time as i64,
|
||||
None => return MP4PARSE_ERROR_INVALID,
|
||||
};
|
||||
let empty_duration =
|
||||
match track.empty_duration.map_or(Some(0), |empty_duration| {
|
||||
media_time_to_us(empty_duration, context_timescale) }) {
|
||||
Some(time) => time as i64,
|
||||
None => return MP4PARSE_ERROR_INVALID,
|
||||
};
|
||||
info.media_time = media_time - empty_duration;
|
||||
|
||||
if let Some(track_duration) = track.duration {
|
||||
match track_time_to_us(track_duration, track_timescale) {
|
||||
Some(duration) => info.duration = duration,
|
||||
None => return MP4PARSE_ERROR_INVALID,
|
||||
}
|
||||
} else {
|
||||
// Duration unknown; stagefright returns 0 for this.
|
||||
info.duration = 0
|
||||
}
|
||||
} else {
|
||||
return MP4PARSE_ERROR_INVALID
|
||||
}
|
||||
|
||||
info.track_id = match track.track_id {
|
||||
Some(track_id) => track_id,
|
||||
None => return MP4PARSE_ERROR_INVALID,
|
||||
};
|
||||
|
||||
MP4PARSE_OK
|
||||
}
|
||||
|
||||
/// Fill the supplied `mp4parse_track_audio_info` with metadata for `track`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn mp4parse_get_track_audio_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_audio_info) -> mp4parse_error {
|
||||
if parser.is_null() || info.is_null() || (*parser).poisoned() {
|
||||
return MP4PARSE_ERROR_BADARG;
|
||||
}
|
||||
|
||||
let context = (*parser).context_mut();
|
||||
|
||||
if track_index as usize >= context.tracks.len() {
|
||||
return MP4PARSE_ERROR_BADARG;
|
||||
}
|
||||
|
||||
let track = &context.tracks[track_index as usize];
|
||||
|
||||
match track.track_type {
|
||||
TrackType::Audio => {}
|
||||
_ => return MP4PARSE_ERROR_INVALID,
|
||||
};
|
||||
|
||||
let audio = match track.data {
|
||||
Some(ref data) => data,
|
||||
None => return MP4PARSE_ERROR_INVALID,
|
||||
};
|
||||
|
||||
let audio = match *audio {
|
||||
SampleEntry::Audio(ref x) => x,
|
||||
_ => return MP4PARSE_ERROR_INVALID,
|
||||
};
|
||||
|
||||
(*info).channels = audio.channelcount;
|
||||
(*info).bit_depth = audio.samplesize;
|
||||
(*info).sample_rate = audio.samplerate >> 16; // 16.16 fixed point
|
||||
|
||||
match audio.codec_specific {
|
||||
AudioCodecSpecific::ES_Descriptor(ref v) => {
|
||||
if v.codec_specific_config.len() > std::u32::MAX as usize {
|
||||
return MP4PARSE_ERROR_INVALID;
|
||||
}
|
||||
(*info).codec_specific_config.length = v.codec_specific_config.len() as u32;
|
||||
(*info).codec_specific_config.data = v.codec_specific_config.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;
|
||||
}
|
||||
}
|
||||
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 MP4PARSE_ERROR_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 MP4PARSE_ERROR_INVALID;
|
||||
}
|
||||
Ok(_) => {
|
||||
let header = (*parser).opus_header_mut();
|
||||
header.insert(track_index, v);
|
||||
match header.get(&track_index) {
|
||||
None => {}
|
||||
Some(v) => {
|
||||
if v.len() > std::u32::MAX as usize {
|
||||
return MP4PARSE_ERROR_INVALID;
|
||||
}
|
||||
(*info).codec_specific_config.length = v.len() as u32;
|
||||
(*info).codec_specific_config.data = v.as_ptr();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MP4PARSE_OK
|
||||
}
|
||||
|
||||
/// Fill the supplied `mp4parse_track_video_info` with metadata for `track`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn mp4parse_get_track_video_info(parser: *mut mp4parse_parser, track_index: u32, info: *mut mp4parse_track_video_info) -> mp4parse_error {
|
||||
if parser.is_null() || info.is_null() || (*parser).poisoned() {
|
||||
return MP4PARSE_ERROR_BADARG;
|
||||
}
|
||||
|
||||
let context = (*parser).context_mut();
|
||||
|
||||
if track_index as usize >= context.tracks.len() {
|
||||
return MP4PARSE_ERROR_BADARG;
|
||||
}
|
||||
|
||||
let track = &context.tracks[track_index as usize];
|
||||
|
||||
match track.track_type {
|
||||
TrackType::Video => {}
|
||||
_ => return MP4PARSE_ERROR_INVALID,
|
||||
};
|
||||
|
||||
let video = match track.data {
|
||||
Some(ref data) => data,
|
||||
None => return MP4PARSE_ERROR_INVALID,
|
||||
};
|
||||
|
||||
let video = match *video {
|
||||
SampleEntry::Video(ref x) => x,
|
||||
_ => return MP4PARSE_ERROR_INVALID,
|
||||
};
|
||||
|
||||
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
|
||||
} else {
|
||||
return MP4PARSE_ERROR_INVALID;
|
||||
}
|
||||
(*info).image_width = video.width;
|
||||
(*info).image_height = video.height;
|
||||
|
||||
MP4PARSE_OK
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn mp4parse_get_fragment_info(parser: *mut mp4parse_parser, info: *mut mp4parse_fragment_info) -> mp4parse_error {
|
||||
if parser.is_null() || info.is_null() || (*parser).poisoned() {
|
||||
return MP4PARSE_ERROR_BADARG;
|
||||
}
|
||||
|
||||
let context = (*parser).context();
|
||||
let info: &mut mp4parse_fragment_info = &mut *info;
|
||||
|
||||
info.fragment_duration = 0;
|
||||
|
||||
let duration = match context.mvex {
|
||||
Some(ref mvex) => mvex.fragment_duration,
|
||||
None => return MP4PARSE_ERROR_INVALID,
|
||||
};
|
||||
|
||||
if let (Some(time), Some(scale)) = (duration, context.timescale) {
|
||||
info.fragment_duration = match media_time_to_us(time, scale) {
|
||||
Some(time_us) => time_us as u64,
|
||||
None => return MP4PARSE_ERROR_INVALID,
|
||||
}
|
||||
}
|
||||
|
||||
MP4PARSE_OK
|
||||
}
|
||||
|
||||
// A fragmented file needs mvex table and contains no data in stts, stsc, and stco boxes.
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn mp4parse_is_fragmented(parser: *mut mp4parse_parser, track_id: u32, fragmented: *mut u8) -> mp4parse_error {
|
||||
if parser.is_null() || (*parser).poisoned() {
|
||||
return MP4PARSE_ERROR_BADARG;
|
||||
}
|
||||
|
||||
let context = (*parser).context_mut();
|
||||
let tracks = &context.tracks;
|
||||
(*fragmented) = false as u8;
|
||||
|
||||
if context.mvex.is_none() {
|
||||
return MP4PARSE_OK;
|
||||
}
|
||||
|
||||
// check sample tables.
|
||||
let mut iter = tracks.iter();
|
||||
match iter.find(|track| track.track_id == Some(track_id)) {
|
||||
Some(track) if track.empty_sample_boxes.all_empty() => (*fragmented) = true as u8,
|
||||
Some(_) => {},
|
||||
None => return MP4PARSE_ERROR_BADARG,
|
||||
}
|
||||
|
||||
MP4PARSE_OK
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
extern fn panic_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
|
||||
panic!("panic_read shouldn't be called in these tests");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
extern fn error_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
|
||||
-1
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
extern fn valid_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
|
||||
let mut input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
|
||||
|
||||
let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
|
||||
match input.read(&mut buf) {
|
||||
Ok(n) => n as isize,
|
||||
Err(_) => -1,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_parser() {
|
||||
let mut dummy_value: u32 = 42;
|
||||
let io = mp4parse_io {
|
||||
read: panic_read,
|
||||
userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
|
||||
};
|
||||
unsafe {
|
||||
let parser = mp4parse_new(&io);
|
||||
assert!(!parser.is_null());
|
||||
mp4parse_free(parser);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "assertion failed")]
|
||||
fn free_null_parser() {
|
||||
unsafe {
|
||||
mp4parse_free(std::ptr::null_mut());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_track_count_null_parser() {
|
||||
unsafe {
|
||||
let mut count: u32 = 0;
|
||||
let rv = mp4parse_get_track_count(std::ptr::null(), std::ptr::null_mut());
|
||||
assert!(rv == MP4PARSE_ERROR_BADARG);
|
||||
let rv = mp4parse_get_track_count(std::ptr::null(), &mut count);
|
||||
assert!(rv == MP4PARSE_ERROR_BADARG);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arg_validation() {
|
||||
unsafe {
|
||||
// Passing a null mp4parse_io is an error.
|
||||
let parser = mp4parse_new(std::ptr::null());
|
||||
assert!(parser.is_null());
|
||||
|
||||
let null_mut: *mut std::os::raw::c_void = std::ptr::null_mut();
|
||||
|
||||
// Passing an mp4parse_io with null members is an error.
|
||||
let io = mp4parse_io { read: std::mem::transmute(null_mut),
|
||||
userdata: null_mut };
|
||||
let parser = mp4parse_new(&io);
|
||||
assert!(parser.is_null());
|
||||
|
||||
let io = mp4parse_io { read: panic_read,
|
||||
userdata: null_mut };
|
||||
let parser = mp4parse_new(&io);
|
||||
assert!(parser.is_null());
|
||||
|
||||
let mut dummy_value = 42;
|
||||
let io = mp4parse_io {
|
||||
read: std::mem::transmute(null_mut),
|
||||
userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
|
||||
};
|
||||
let parser = mp4parse_new(&io);
|
||||
assert!(parser.is_null());
|
||||
|
||||
// Passing a null mp4parse_parser is an error.
|
||||
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_read(std::ptr::null_mut()));
|
||||
|
||||
let mut dummy_info = mp4parse_track_info {
|
||||
track_type: MP4PARSE_TRACK_TYPE_VIDEO,
|
||||
codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
|
||||
track_id: 0,
|
||||
duration: 0,
|
||||
media_time: 0,
|
||||
};
|
||||
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(std::ptr::null_mut(), 0, &mut dummy_info));
|
||||
|
||||
let mut dummy_video = mp4parse_track_video_info {
|
||||
display_width: 0,
|
||||
display_height: 0,
|
||||
image_width: 0,
|
||||
image_height: 0,
|
||||
};
|
||||
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video));
|
||||
|
||||
let mut dummy_audio = Default::default();
|
||||
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(std::ptr::null_mut(), 0, &mut dummy_audio));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arg_validation_with_parser() {
|
||||
unsafe {
|
||||
let mut dummy_value = 42;
|
||||
let io = mp4parse_io {
|
||||
read: error_read,
|
||||
userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
|
||||
};
|
||||
let parser = mp4parse_new(&io);
|
||||
assert!(!parser.is_null());
|
||||
|
||||
// Our mp4parse_io read should simply fail with an error.
|
||||
assert_eq!(MP4PARSE_ERROR_IO, mp4parse_read(parser));
|
||||
|
||||
// The parser is now poisoned and unusable.
|
||||
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_read(parser));
|
||||
|
||||
// Null info pointers are an error.
|
||||
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 0, std::ptr::null_mut()));
|
||||
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 0, std::ptr::null_mut()));
|
||||
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 0, std::ptr::null_mut()));
|
||||
|
||||
let mut dummy_info = mp4parse_track_info {
|
||||
track_type: MP4PARSE_TRACK_TYPE_VIDEO,
|
||||
codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
|
||||
track_id: 0,
|
||||
duration: 0,
|
||||
media_time: 0,
|
||||
};
|
||||
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 0, &mut dummy_info));
|
||||
|
||||
let mut dummy_video = mp4parse_track_video_info {
|
||||
display_width: 0,
|
||||
display_height: 0,
|
||||
image_width: 0,
|
||||
image_height: 0,
|
||||
};
|
||||
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_video_info(parser, 0, &mut dummy_video));
|
||||
|
||||
let mut dummy_audio = Default::default();
|
||||
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_audio_info(parser, 0, &mut dummy_audio));
|
||||
|
||||
mp4parse_free(parser);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_track_count_poisoned_parser() {
|
||||
unsafe {
|
||||
let mut dummy_value = 42;
|
||||
let io = mp4parse_io {
|
||||
read: error_read,
|
||||
userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
|
||||
};
|
||||
let parser = mp4parse_new(&io);
|
||||
assert!(!parser.is_null());
|
||||
|
||||
// Our mp4parse_io read should simply fail with an error.
|
||||
assert_eq!(MP4PARSE_ERROR_IO, mp4parse_read(parser));
|
||||
|
||||
let mut count: u32 = 0;
|
||||
let rv = mp4parse_get_track_count(parser, &mut count);
|
||||
assert!(rv == MP4PARSE_ERROR_BADARG);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arg_validation_with_data() {
|
||||
unsafe {
|
||||
let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap();
|
||||
let io = mp4parse_io { read: valid_read,
|
||||
userdata: &mut file as *mut _ as *mut std::os::raw::c_void };
|
||||
let parser = mp4parse_new(&io);
|
||||
assert!(!parser.is_null());
|
||||
|
||||
assert_eq!(MP4PARSE_OK, mp4parse_read(parser));
|
||||
|
||||
let mut count: u32 = 0;
|
||||
assert_eq!(MP4PARSE_OK, mp4parse_get_track_count(parser, &mut count));
|
||||
assert_eq!(2, count);
|
||||
|
||||
let mut info = mp4parse_track_info {
|
||||
track_type: MP4PARSE_TRACK_TYPE_VIDEO,
|
||||
codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
|
||||
track_id: 0,
|
||||
duration: 0,
|
||||
media_time: 0,
|
||||
};
|
||||
assert_eq!(MP4PARSE_OK, mp4parse_get_track_info(parser, 0, &mut info));
|
||||
assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_VIDEO);
|
||||
assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_AVC);
|
||||
assert_eq!(info.track_id, 1);
|
||||
assert_eq!(info.duration, 40000);
|
||||
assert_eq!(info.media_time, 0);
|
||||
|
||||
assert_eq!(MP4PARSE_OK, mp4parse_get_track_info(parser, 1, &mut info));
|
||||
assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_AUDIO);
|
||||
assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_AAC);
|
||||
assert_eq!(info.track_id, 2);
|
||||
assert_eq!(info.duration, 61333);
|
||||
assert_eq!(info.media_time, 21333);
|
||||
|
||||
let mut video = mp4parse_track_video_info {
|
||||
display_width: 0,
|
||||
display_height: 0,
|
||||
image_width: 0,
|
||||
image_height: 0,
|
||||
};
|
||||
assert_eq!(MP4PARSE_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);
|
||||
|
||||
let mut audio = Default::default();
|
||||
assert_eq!(MP4PARSE_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);
|
||||
|
||||
// Test with an invalid track number.
|
||||
let mut info = mp4parse_track_info {
|
||||
track_type: MP4PARSE_TRACK_TYPE_VIDEO,
|
||||
codec: mp4parse_codec::MP4PARSE_CODEC_UNKNOWN,
|
||||
track_id: 0,
|
||||
duration: 0,
|
||||
media_time: 0,
|
||||
};
|
||||
assert_eq!(MP4PARSE_ERROR_BADARG, mp4parse_get_track_info(parser, 3, &mut info));
|
||||
assert_eq!(info.track_type, MP4PARSE_TRACK_TYPE_VIDEO);
|
||||
assert_eq!(info.codec, mp4parse_codec::MP4PARSE_CODEC_UNKNOWN);
|
||||
assert_eq!(info.track_id, 0);
|
||||
assert_eq!(info.duration, 0);
|
||||
assert_eq!(info.media_time, 0);
|
||||
|
||||
let mut video = mp4parse_track_video_info { display_width: 0,
|
||||
display_height: 0,
|
||||
image_width: 0,
|
||||
image_height: 0 };
|
||||
assert_eq!(MP4PARSE_ERROR_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);
|
||||
|
||||
let mut audio = Default::default();
|
||||
assert_eq!(MP4PARSE_ERROR_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);
|
||||
|
||||
mp4parse_free(parser);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rational_scale_overflow() {
|
||||
assert_eq!(rational_scale(17, 3, 1000), Some(5666));
|
||||
let large = 0x4000_0000_0000_0000;
|
||||
assert_eq!(rational_scale(large, 2, 2), Some(large));
|
||||
assert_eq!(rational_scale(large, 4, 4), Some(large));
|
||||
assert_eq!(rational_scale(large, 2, 8), None);
|
||||
assert_eq!(rational_scale(large, 8, 4), Some(large/2));
|
||||
assert_eq!(rational_scale(large + 1, 4, 4), Some(large+1));
|
||||
assert_eq!(rational_scale(large, 40, 1000), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn media_time_overflow() {
|
||||
let scale = MediaTimeScale(90000);
|
||||
let duration = MediaScaledTime(9007199254710000);
|
||||
assert_eq!(media_time_to_us(duration, scale), Some(100079991719000000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn track_time_overflow() {
|
||||
let scale = TrackTimeScale(44100, 0);
|
||||
let duration = TrackScaledTime(4413527634807900, 0);
|
||||
assert_eq!(track_time_to_us(duration, scale), Some(100079991719000000));
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/bin/sh -e
|
||||
# Script to update mp4parse-rust sources to latest upstream
|
||||
|
||||
# Default version.
|
||||
VER=v0.6.0
|
||||
|
||||
# Accept version or commit from the command line.
|
||||
if test -n "$1"; then
|
||||
VER=$1
|
||||
fi
|
||||
|
||||
echo "Fetching sources..."
|
||||
rm -rf _upstream
|
||||
git clone https://github.com/mozilla/mp4parse-rust _upstream/mp4parse
|
||||
pushd _upstream/mp4parse
|
||||
git checkout ${VER}
|
||||
echo "Verifying sources..."
|
||||
pushd mp4parse
|
||||
cargo test
|
||||
popd
|
||||
echo "Constructing C api header..."
|
||||
pushd mp4parse_capi
|
||||
cargo build
|
||||
echo "Verifying sources..."
|
||||
cargo test
|
||||
popd
|
||||
popd
|
||||
rm -rf mp4parse
|
||||
mkdir -p mp4parse/src
|
||||
cp _upstream/mp4parse/mp4parse/Cargo.toml mp4parse/
|
||||
cp _upstream/mp4parse/mp4parse/src/*.rs mp4parse/src/
|
||||
mkdir -p mp4parse/tests
|
||||
cp _upstream/mp4parse/mp4parse/tests/*.rs mp4parse/tests/
|
||||
cp _upstream/mp4parse/mp4parse/tests/*.mp4 mp4parse/tests/
|
||||
rm -rf mp4parse_capi
|
||||
mkdir -p mp4parse_capi/src
|
||||
cp _upstream/mp4parse/mp4parse_capi/Cargo.toml mp4parse_capi/
|
||||
cp _upstream/mp4parse/mp4parse_capi/build.rs mp4parse_capi/
|
||||
cp _upstream/mp4parse/mp4parse_capi/include/mp4parse.h include/
|
||||
cp _upstream/mp4parse/mp4parse_capi/src/*.rs mp4parse_capi/src/
|
||||
|
||||
echo "Applying patches..."
|
||||
patch -p4 < mp4parse-cargo.patch
|
||||
|
||||
echo "Cleaning up..."
|
||||
rm -rf _upstream
|
||||
|
||||
echo "Updating gecko Cargo.lock..."
|
||||
pushd ../../../toolkit/library/rust/
|
||||
cargo update --package mp4parse_capi
|
||||
popd
|
||||
pushd ../../../toolkit/library/gtest/rust/
|
||||
cargo update --package mp4parse_capi
|
||||
popd
|
||||
|
||||
echo "Updated to ${VER}."
|
||||
@@ -31,8 +31,6 @@ MOZ_XULRUNNER=
|
||||
MOZ_CAPTURE=1
|
||||
MOZ_RAW=1
|
||||
|
||||
MOZ_RUST_MP4PARSE=1
|
||||
|
||||
# use custom widget for html:select
|
||||
MOZ_USE_NATIVE_POPUP_WINDOWS=1
|
||||
|
||||
|
||||
@@ -2432,9 +2432,6 @@ fi
|
||||
|
||||
# Propagate feature switches for code written in rust from confvars.sh
|
||||
if test -n "$MOZ_RUST"; then
|
||||
if test -n "$MOZ_RUST_MP4PARSE"; then
|
||||
AC_DEFINE(MOZ_RUST_MP4PARSE)
|
||||
fi
|
||||
if test -n "$MOZ_RUST_URLPARSE"; then
|
||||
AC_DEFINE(MOZ_RUST_URLPARSE)
|
||||
fi
|
||||
|
||||
@@ -6926,39 +6926,6 @@
|
||||
"n_buckets": 1000,
|
||||
"description": "The time (in milliseconds) that it took to display a selected source to the user."
|
||||
},
|
||||
"MEDIA_RUST_MP4PARSE_SUCCESS": {
|
||||
"alert_emails": ["giles@mozilla.com", "kinetik@flim.org"],
|
||||
"expires_in_version": "55",
|
||||
"kind": "boolean",
|
||||
"bug_numbers": [1220885],
|
||||
"description": "(Bug 1220885) Whether the rust mp4 demuxer successfully parsed a stream segment.",
|
||||
"cpp_guard": "MOZ_RUST_MP4PARSE"
|
||||
},
|
||||
"MEDIA_RUST_MP4PARSE_ERROR_CODE": {
|
||||
"alert_emails": ["giles@mozilla.com", "kinetik@flim.org"],
|
||||
"expires_in_version": "55",
|
||||
"kind": "enumerated",
|
||||
"n_values": 32,
|
||||
"bug_numbers": [1238420],
|
||||
"description": "The error code reported when an MP4 parse attempt has failed.0 = OK, 1 = bad argument, 2 = invalid data, 3 = unsupported, 4 = unexpected end of file, 5 = read error.",
|
||||
"cpp_guard": "MOZ_RUST_MP4PARSE"
|
||||
},
|
||||
"MEDIA_RUST_MP4PARSE_TRACK_MATCH_AUDIO": {
|
||||
"alert_emails": ["giles@mozilla.com", "kinetik@flim.org"],
|
||||
"expires_in_version": "55",
|
||||
"kind": "boolean",
|
||||
"bug_numbers": [1231169],
|
||||
"description": "Whether rust and stagefight mp4 parser audio track results match.",
|
||||
"cpp_guard": "MOZ_RUST_MP4PARSE"
|
||||
},
|
||||
"MEDIA_RUST_MP4PARSE_TRACK_MATCH_VIDEO": {
|
||||
"alert_emails": ["giles@mozilla.com", "kinetik@flim.org"],
|
||||
"expires_in_version": "55",
|
||||
"kind": "boolean",
|
||||
"bug_numbers": [1231169],
|
||||
"description": "Whether rust and stagefight mp4 parser video track results match.",
|
||||
"cpp_guard": "MOZ_RUST_MP4PARSE"
|
||||
},
|
||||
"MEDIA_WMF_DECODE_ERROR": {
|
||||
"expires_in_version": "55",
|
||||
"kind": "enumerated",
|
||||
|
||||
Generated
-20
@@ -3,7 +3,6 @@ name = "gkrust-gtest"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"gkrust-shared 0.1.0",
|
||||
"mp4parse-gtest 0.1.0",
|
||||
"nsstring-gtest 0.1.0",
|
||||
]
|
||||
|
||||
@@ -16,7 +15,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
name = "gkrust-shared"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"mp4parse_capi 0.6.0",
|
||||
"nsstring 0.1.0",
|
||||
"rust_url_capi 0.0.1",
|
||||
]
|
||||
@@ -41,24 +39,6 @@ name = "matches"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "mp4parse"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mp4parse-gtest"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "mp4parse_capi"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"mp4parse 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nsstring"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -6,7 +6,6 @@ license = "MPL-2.0"
|
||||
description = "Testing code for libgkrust"
|
||||
|
||||
[dependencies]
|
||||
mp4parse-gtest = { path = "../../../../dom/media/gtest" }
|
||||
nsstring-gtest = { path = "../../../../xpcom/rust/nsstring/gtest" }
|
||||
gkrust-shared = { path = "../../rust/shared" }
|
||||
|
||||
|
||||
@@ -3,5 +3,4 @@
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
extern crate gkrust_shared;
|
||||
extern crate mp4parse_gtest;
|
||||
extern crate nsstring_gtest;
|
||||
|
||||
Generated
-15
@@ -14,7 +14,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
name = "gkrust-shared"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"mp4parse_capi 0.6.0",
|
||||
"nsstring 0.1.0",
|
||||
"rust_url_capi 0.0.1",
|
||||
]
|
||||
@@ -39,20 +38,6 @@ name = "matches"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "mp4parse"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mp4parse_capi"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"mp4parse 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nsstring"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -6,7 +6,6 @@ license = "MPL-2.0"
|
||||
description = "Shared Rust code for libxul"
|
||||
|
||||
[dependencies]
|
||||
mp4parse_capi = { path = "../../../../media/libstagefright/binding/mp4parse_capi" }
|
||||
nsstring = { path = "../../../../xpcom/rust/nsstring" }
|
||||
rust_url_capi = { path = "../../../../netwerk/base/rust-url-capi" }
|
||||
|
||||
|
||||
@@ -2,6 +2,5 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
extern crate mp4parse_capi;
|
||||
extern crate nsstring;
|
||||
extern crate rust_url_capi;
|
||||
|
||||
Reference in New Issue
Block a user