From 18828fc51da2650594409032acc5216e220675ec Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Mon, 11 May 2015 20:57:20 +1000 Subject: [PATCH] Bug 1159027: Part3. Add MP4Demuxer object. r=cpearce --- dom/media/fmp4/MP4Demuxer.cpp | 323 ++++++++++++++++++++++++++++++++++ dom/media/fmp4/MP4Demuxer.h | 97 ++++++++++ dom/media/fmp4/moz.build | 2 + 3 files changed, 422 insertions(+) create mode 100644 dom/media/fmp4/MP4Demuxer.cpp create mode 100644 dom/media/fmp4/MP4Demuxer.h diff --git a/dom/media/fmp4/MP4Demuxer.cpp b/dom/media/fmp4/MP4Demuxer.cpp new file mode 100644 index 00000000000..2b02993578d --- /dev/null +++ b/dom/media/fmp4/MP4Demuxer.cpp @@ -0,0 +1,323 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include + +#include "MP4Demuxer.h" +#include "mp4_demuxer/Index.h" +#include "mp4_demuxer/MoofParser.h" +#include "mp4_demuxer/MP4Metadata.h" +#include "mp4_demuxer/ResourceStream.h" +#include "mp4_demuxer/BufferStream.h" + +namespace mozilla { + +MP4Demuxer::MP4Demuxer(MediaResource* aResource) + : mResource(aResource) + , mStream(new mp4_demuxer::ResourceStream(aResource)) + , mInitData(new MediaLargeByteBuffer) +{ +} + +nsRefPtr +MP4Demuxer::Init() +{ + AutoPinned stream(mStream); + + // Check that we have enough data to read the metadata. + MediaByteRange br = mp4_demuxer::MP4Metadata::MetadataRange(stream); + if (br.IsNull()) { + return InitPromise::CreateAndReject(DemuxerFailureReason::WAITING_FOR_DATA, __func__); + } + + if (!mInitData->SetLength(br.Length())) { + // OOM + return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + } + + size_t size; + mStream->ReadAt(br.mStart, mInitData->Elements(), br.Length(), &size); + if (size != size_t(br.Length())) { + return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + } + + nsRefPtr bufferstream = + new mp4_demuxer::BufferStream(mInitData); + + mMetadata = MakeUnique(bufferstream); + + if (!mMetadata->GetNumberTracks(mozilla::TrackInfo::kAudioTrack) && + !mMetadata->GetNumberTracks(mozilla::TrackInfo::kVideoTrack)) { + return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + } + + return InitPromise::CreateAndResolve(NS_OK, __func__); +} + +already_AddRefed +MP4Demuxer::Clone() const +{ + nsRefPtr demuxer = new MP4Demuxer(mResource); + demuxer->mInitData = mInitData; + nsRefPtr bufferstream = + new mp4_demuxer::BufferStream(mInitData); + demuxer->mMetadata = MakeUnique(bufferstream); + if (!mMetadata->GetNumberTracks(mozilla::TrackInfo::kAudioTrack) && + !mMetadata->GetNumberTracks(mozilla::TrackInfo::kVideoTrack)) { + NS_WARNING("Couldn't recreate MP4Demuxer"); + return nullptr; + } + return demuxer.forget(); +} + +bool +MP4Demuxer::HasTrackType(TrackInfo::TrackType aType) const +{ + return !!GetNumberTracks(aType); +} + +uint32_t +MP4Demuxer::GetNumberTracks(TrackInfo::TrackType aType) const +{ + return mMetadata->GetNumberTracks(aType); +} + +already_AddRefed +MP4Demuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber) +{ + if (mMetadata->GetNumberTracks(aType) <= aTrackNumber) { + return nullptr; + } + nsRefPtr e = + new MP4TrackDemuxer(this, aType, aTrackNumber); + return e.forget(); +} + +bool +MP4Demuxer::IsSeekable() const +{ + return mMetadata->CanSeek(); +} + +void +MP4Demuxer::NotifyDataArrived(uint32_t aLength, int64_t aOffset) +{ + // TODO. May not be required for our use +} + +UniquePtr +MP4Demuxer::GetCrypto() +{ + const mp4_demuxer::CryptoFile& cryptoFile = mMetadata->Crypto(); + if (!cryptoFile.valid) { + return nullptr; + } + + const nsTArray& psshs = cryptoFile.pssh; + nsTArray initData; + for (uint32_t i = 0; i < psshs.Length(); i++) { + initData.AppendElements(psshs[i].data); + } + + if (initData.IsEmpty()) { + return nullptr; + } + + auto crypto = MakeUnique(); + crypto->AddInitData(NS_LITERAL_STRING("cenc"), Move(initData)); + + return crypto; +} + +MP4TrackDemuxer::MP4TrackDemuxer(MP4Demuxer* aParent, + TrackInfo::TrackType aType, + uint32_t aTrackNumber) + : mParent(aParent) + , mStream(new mp4_demuxer::ResourceStream(mParent->mResource)) + , mMonitor("MP4TrackDemuxer") +{ + mInfo = mParent->mMetadata->GetTrackInfo(aType, aTrackNumber); + + MOZ_ASSERT(mInfo); + + nsTArray indices; + if (!mParent->mMetadata->ReadTrackIndex(indices, mInfo->mTrackId)) { + MOZ_ASSERT(false); + } + mIndex = new mp4_demuxer::Index(indices, + mStream, + mInfo->mTrackId, + mInfo->IsAudio(), + &mMonitor); + mIterator = MakeUnique(mIndex); +} + +UniquePtr +MP4TrackDemuxer::GetInfo() const +{ + return mInfo->Clone(); +} + +nsRefPtr +MP4TrackDemuxer::Seek(media::TimeUnit aTime) +{ + int64_t seekTime = aTime.ToMicroseconds(); + mQueuedSample = nullptr; + + MonitorAutoLock mon(mMonitor); + mIterator->Seek(seekTime); + + // Check what time we actually seeked to. + mQueuedSample = mIterator->GetNext(); + if (mQueuedSample) { + seekTime = mQueuedSample->mTime; + } + + return SeekPromise::CreateAndResolve(media::TimeUnit::FromMicroseconds(seekTime), __func__); +} + +nsRefPtr +MP4TrackDemuxer::GetSamples(int32_t aNumSamples) +{ + nsRefPtr samples = new SamplesHolder; + if (!aNumSamples) { + return SamplesPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); + } + + if (mQueuedSample) { + samples->mSamples.AppendElement(mQueuedSample); + mQueuedSample = nullptr; + aNumSamples--; + } + MonitorAutoLock mon(mMonitor); + nsRefPtr sample; + while (aNumSamples && (sample = mIterator->GetNext())) { + samples->mSamples.AppendElement(sample); + aNumSamples--; + } + + if (samples->mSamples.IsEmpty()) { + return SamplesPromise::CreateAndReject(DemuxerFailureReason::END_OF_STREAM, __func__); + } else { + UpdateSamples(samples->mSamples); + return SamplesPromise::CreateAndResolve(samples, __func__); + } +} + +void +MP4TrackDemuxer::Reset() +{ + mQueuedSample = nullptr; + // TODO, Seek to first frame available, which isn't always 0. + MonitorAutoLock mon(mMonitor); + mIterator->Seek(0); +} + +void +MP4TrackDemuxer::UpdateSamples(nsTArray>& aSamples) +{ + for (size_t i = 0; i < aSamples.Length(); i++) { + MediaRawData* sample = aSamples[i]; + if (sample->mCrypto.mValid) { + nsAutoPtr writer(sample->CreateWriter()); + writer->mCrypto.mMode = mInfo->mCrypto.mMode; + writer->mCrypto.mIVSize = mInfo->mCrypto.mIVSize; + writer->mCrypto.mKeyId.AppendElements(mInfo->mCrypto.mKeyId); + } + if (mInfo->GetAsVideoInfo()) { + sample->mExtraData = mInfo->GetAsVideoInfo()->mExtraData; + } + } + if (mNextKeyframeTime.isNothing() || + aSamples.LastElement()->mTime >= mNextKeyframeTime.value().ToMicroseconds()) { + mNextKeyframeTime.reset(); + mp4_demuxer::Microseconds frameTime = mIterator->GetNextKeyframeTime(); + if (frameTime != -1) { + mNextKeyframeTime.emplace( + media::TimeUnit::FromMicroseconds(frameTime)); + } + } +} + +nsresult +MP4TrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime) +{ + if (mNextKeyframeTime.isNothing()) { + // There's no next key frame. + *aTime = + media::TimeUnit::FromMicroseconds(std::numeric_limits::max()); + } else { + *aTime = mNextKeyframeTime.value(); + } + return NS_OK; +} + +nsRefPtr +MP4TrackDemuxer::SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) +{ + MonitorAutoLock mon(mMonitor); + mQueuedSample = nullptr; + // Loop until we reach the next keyframe after the threshold. + uint32_t parsed = 0; + bool found = false; + nsRefPtr sample; + while (!found && (sample = mIterator->GetNext())) { + parsed++; + if (sample->mKeyframe && sample->mTime >= aTimeThreshold.ToMicroseconds()) { + found = true; + mQueuedSample = sample; + } + } + if (found) { + return SkipAccessPointPromise::CreateAndResolve(parsed, __func__); + } else { + SkipFailureHolder failure(DemuxerFailureReason::END_OF_STREAM, parsed); + return SkipAccessPointPromise::CreateAndReject(Move(failure), __func__); + } +} + +int64_t +MP4TrackDemuxer::GetEvictionOffset(media::TimeUnit aTime) +{ + MonitorAutoLock mon(mMonitor); + return int64_t(mIndex->GetEvictionOffset(aTime.ToMicroseconds())); +} + +media::TimeIntervals +MP4TrackDemuxer::GetBuffered() +{ + AutoPinned resource(mParent->mResource); + nsTArray byteRanges; + nsresult rv = resource->GetCachedRanges(byteRanges); + + if (NS_FAILED(rv)) { + return media::TimeIntervals(); + } + nsTArray> timeRanges; + + MonitorAutoLock mon(mMonitor); + mIndex->UpdateMoofIndex(byteRanges); + int64_t endComposition = + mIndex->GetEndCompositionIfBuffered(byteRanges); + + mIndex->ConvertByteRangesToTimeRanges(byteRanges, &timeRanges); + if (endComposition) { + mp4_demuxer::Interval::SemiNormalAppend( + timeRanges, mp4_demuxer::Interval(endComposition, endComposition)); + } + // convert timeRanges. + media::TimeIntervals ranges; + for (size_t i = 0; i < timeRanges.Length(); i++) { + ranges += + media::TimeInterval(media::TimeUnit::FromMicroseconds(timeRanges[i].start), + media::TimeUnit::FromMicroseconds(timeRanges[i].end)); + } + return ranges; +} + +} // namespace mozilla diff --git a/dom/media/fmp4/MP4Demuxer.h b/dom/media/fmp4/MP4Demuxer.h new file mode 100644 index 00000000000..eb962b1a56a --- /dev/null +++ b/dom/media/fmp4/MP4Demuxer.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 http://mozilla.org/MPL/2.0/. */ + +#if !defined(MP4Demuxer_h_) +#define MP4Demuxer_h_ + +#include "mozilla/Maybe.h" +#include "mozilla/Monitor.h" +#include "MediaDataDemuxer.h" +#include "MediaResource.h" + +namespace mp4_demuxer { +class Index; +class MP4Metadata; +class ResourceStream; +class SampleIterator; +} + +namespace mozilla { + +class MP4TrackDemuxer; + +class MP4Demuxer : public MediaDataDemuxer +{ +public: + explicit MP4Demuxer(MediaResource* aResource); + + virtual nsRefPtr Init() override; + + virtual already_AddRefed Clone() const override; + + virtual bool HasTrackType(TrackInfo::TrackType aType) const override; + + virtual uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override; + + virtual already_AddRefed GetTrackDemuxer(TrackInfo::TrackType aType, + uint32_t aTrackNumber) override; + + virtual bool IsSeekable() const override; + + virtual UniquePtr GetCrypto() override; + + virtual void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override; + +private: + friend class MP4TrackDemuxer; + nsRefPtr mResource; + nsRefPtr mStream; + nsRefPtr mInitData; + UniquePtr mMetadata; +}; + +class MP4TrackDemuxer : public MediaTrackDemuxer +{ +public: + MP4TrackDemuxer(MP4Demuxer* aParent, + TrackInfo::TrackType aType, + uint32_t aTrackNumber); + + virtual UniquePtr GetInfo() const override; + + virtual nsRefPtr Seek(media::TimeUnit aTime) override; + + virtual nsRefPtr GetSamples(int32_t aNumSamples = 1) override; + + virtual void Reset() override; + + virtual nsresult GetNextRandomAccessPoint(media::TimeUnit* aTime) override; + + nsRefPtr SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) override; + + virtual media::TimeIntervals GetBuffered() override; + + virtual int64_t GetEvictionOffset(media::TimeUnit aTime) override; + +private: + void UpdateSamples(nsTArray>& aSamples); + nsRefPtr mParent; + nsRefPtr mIndex; + UniquePtr mIterator; + UniquePtr mInfo; + nsRefPtr mStream; + Maybe mNextKeyframeTime; + // Queued samples extracted by the demuxer, but not yet returned. + nsRefPtr mQueuedSample; + + // We do not actually need a monitor, however MoofParser will assert + // if a monitor isn't held. + Monitor mMonitor; +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/fmp4/moz.build b/dom/media/fmp4/moz.build index 0e19cc128ed..d91ed0ec86c 100644 --- a/dom/media/fmp4/moz.build +++ b/dom/media/fmp4/moz.build @@ -6,6 +6,7 @@ EXPORTS += [ 'MP4Decoder.h', + 'MP4Demuxer.h', 'MP4Reader.h', 'MP4Stream.h', 'PlatformDecoderModule.h', @@ -23,6 +24,7 @@ UNIFIED_SOURCES += [ ] SOURCES += [ + 'MP4Demuxer.cpp', 'MP4Reader.cpp', ]