summaryrefslogtreecommitdiffstats
path: root/dom/media/mediasource/MediaSourceDemuxer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/mediasource/MediaSourceDemuxer.cpp')
-rw-r--r--dom/media/mediasource/MediaSourceDemuxer.cpp499
1 files changed, 499 insertions, 0 deletions
diff --git a/dom/media/mediasource/MediaSourceDemuxer.cpp b/dom/media/mediasource/MediaSourceDemuxer.cpp
new file mode 100644
index 000000000..73950e1a8
--- /dev/null
+++ b/dom/media/mediasource/MediaSourceDemuxer.cpp
@@ -0,0 +1,499 @@
+/* -*- 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 <algorithm>
+#include <limits>
+#include <stdint.h>
+
+#include "MediaSourceDemuxer.h"
+#include "MediaSourceUtils.h"
+#include "SourceBufferList.h"
+#include "nsPrintfCString.h"
+#include "OpusDecoder.h"
+
+namespace mozilla {
+
+typedef TrackInfo::TrackType TrackType;
+using media::TimeUnit;
+using media::TimeIntervals;
+
+MediaSourceDemuxer::MediaSourceDemuxer()
+ : mTaskQueue(new AutoTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
+ /* aSupportsTailDispatch = */ false))
+ , mMonitor("MediaSourceDemuxer")
+{
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+// Due to inaccuracies in determining buffer end
+// frames (Bug 1065207). This value is based on videos seen in the wild.
+const TimeUnit MediaSourceDemuxer::EOS_FUZZ = media::TimeUnit::FromMicroseconds(500000);
+
+RefPtr<MediaSourceDemuxer::InitPromise>
+MediaSourceDemuxer::Init()
+{
+ return InvokeAsync(GetTaskQueue(), this, __func__,
+ &MediaSourceDemuxer::AttemptInit);
+}
+
+RefPtr<MediaSourceDemuxer::InitPromise>
+MediaSourceDemuxer::AttemptInit()
+{
+ MOZ_ASSERT(OnTaskQueue());
+
+ if (ScanSourceBuffersForContent()) {
+ return InitPromise::CreateAndResolve(NS_OK, __func__);
+ }
+
+ RefPtr<InitPromise> p = mInitPromise.Ensure(__func__);
+
+ return p;
+}
+
+void
+MediaSourceDemuxer::AddSizeOfResources(MediaSourceDecoder::ResourceSizes* aSizes)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // NB: The track buffers must only be accessed on the TaskQueue.
+ RefPtr<MediaSourceDemuxer> self = this;
+ RefPtr<MediaSourceDecoder::ResourceSizes> sizes = aSizes;
+ nsCOMPtr<nsIRunnable> task =
+ NS_NewRunnableFunction([self, sizes] () {
+ for (TrackBuffersManager* manager : self->mSourceBuffers) {
+ manager->AddSizeOfResources(sizes);
+ }
+ });
+
+ GetTaskQueue()->Dispatch(task.forget());
+}
+
+void MediaSourceDemuxer::NotifyDataArrived()
+{
+ RefPtr<MediaSourceDemuxer> self = this;
+ nsCOMPtr<nsIRunnable> task =
+ NS_NewRunnableFunction([self] () {
+ if (self->mInitPromise.IsEmpty()) {
+ return;
+ }
+ if (self->ScanSourceBuffersForContent()) {
+ self->mInitPromise.ResolveIfExists(NS_OK, __func__);
+ }
+ });
+ GetTaskQueue()->Dispatch(task.forget());
+}
+
+bool
+MediaSourceDemuxer::ScanSourceBuffersForContent()
+{
+ MOZ_ASSERT(OnTaskQueue());
+
+ if (mSourceBuffers.IsEmpty()) {
+ return false;
+ }
+
+ MonitorAutoLock mon(mMonitor);
+
+ bool haveEmptySourceBuffer = false;
+ for (const auto& sourceBuffer : mSourceBuffers) {
+ MediaInfo info = sourceBuffer->GetMetadata();
+ if (!info.HasAudio() && !info.HasVideo()) {
+ haveEmptySourceBuffer = true;
+ }
+ if (info.HasAudio() && !mAudioTrack) {
+ mInfo.mAudio = info.mAudio;
+ mAudioTrack = sourceBuffer;
+ }
+ if (info.HasVideo() && !mVideoTrack) {
+ mInfo.mVideo = info.mVideo;
+ mVideoTrack = sourceBuffer;
+ }
+ if (info.IsEncrypted() && !mInfo.IsEncrypted()) {
+ mInfo.mCrypto = info.mCrypto;
+ }
+ }
+ if (mInfo.HasAudio() && mInfo.HasVideo()) {
+ // We have both audio and video. We can ignore non-ready source buffer.
+ return true;
+ }
+ return !haveEmptySourceBuffer;
+}
+
+bool
+MediaSourceDemuxer::HasTrackType(TrackType aType) const
+{
+ MonitorAutoLock mon(mMonitor);
+
+ switch (aType) {
+ case TrackType::kAudioTrack:
+ return mInfo.HasAudio();
+ case TrackType::kVideoTrack:
+ return mInfo.HasVideo();
+ default:
+ return false;
+ }
+}
+
+uint32_t
+MediaSourceDemuxer::GetNumberTracks(TrackType aType) const
+{
+ return HasTrackType(aType) ? 1u : 0;
+}
+
+already_AddRefed<MediaTrackDemuxer>
+MediaSourceDemuxer::GetTrackDemuxer(TrackType aType, uint32_t aTrackNumber)
+{
+ RefPtr<TrackBuffersManager> manager = GetManager(aType);
+ if (!manager) {
+ return nullptr;
+ }
+ RefPtr<MediaSourceTrackDemuxer> e =
+ new MediaSourceTrackDemuxer(this, aType, manager);
+ mDemuxers.AppendElement(e);
+ return e.forget();
+}
+
+bool
+MediaSourceDemuxer::IsSeekable() const
+{
+ return true;
+}
+
+UniquePtr<EncryptionInfo>
+MediaSourceDemuxer::GetCrypto()
+{
+ MonitorAutoLock mon(mMonitor);
+ auto crypto = MakeUnique<EncryptionInfo>();
+ *crypto = mInfo.mCrypto;
+ return crypto;
+}
+
+void
+MediaSourceDemuxer::AttachSourceBuffer(TrackBuffersManager* aSourceBuffer)
+{
+ nsCOMPtr<nsIRunnable> task =
+ NewRunnableMethod<TrackBuffersManager*>(
+ this, &MediaSourceDemuxer::DoAttachSourceBuffer,
+ aSourceBuffer);
+ GetTaskQueue()->Dispatch(task.forget());
+}
+
+void
+MediaSourceDemuxer::DoAttachSourceBuffer(mozilla::TrackBuffersManager* aSourceBuffer)
+{
+ MOZ_ASSERT(OnTaskQueue());
+ mSourceBuffers.AppendElement(aSourceBuffer);
+ ScanSourceBuffersForContent();
+}
+
+void
+MediaSourceDemuxer::DetachSourceBuffer(TrackBuffersManager* aSourceBuffer)
+{
+ nsCOMPtr<nsIRunnable> task =
+ NewRunnableMethod<TrackBuffersManager*>(
+ this, &MediaSourceDemuxer::DoDetachSourceBuffer,
+ aSourceBuffer);
+ GetTaskQueue()->Dispatch(task.forget());
+}
+
+void
+MediaSourceDemuxer::DoDetachSourceBuffer(TrackBuffersManager* aSourceBuffer)
+{
+ MOZ_ASSERT(OnTaskQueue());
+ for (uint32_t i = 0; i < mSourceBuffers.Length(); i++) {
+ if (mSourceBuffers[i].get() == aSourceBuffer) {
+ mSourceBuffers.RemoveElementAt(i);
+ }
+ }
+ if (aSourceBuffer == mAudioTrack) {
+ mAudioTrack = nullptr;
+ }
+ if (aSourceBuffer == mVideoTrack) {
+ mVideoTrack = nullptr;
+ }
+ ScanSourceBuffersForContent();
+}
+
+TrackInfo*
+MediaSourceDemuxer::GetTrackInfo(TrackType aTrack)
+{
+ MonitorAutoLock mon(mMonitor);
+ switch (aTrack) {
+ case TrackType::kAudioTrack:
+ return &mInfo.mAudio;
+ case TrackType::kVideoTrack:
+ return &mInfo.mVideo;
+ default:
+ return nullptr;
+ }
+}
+
+TrackBuffersManager*
+MediaSourceDemuxer::GetManager(TrackType aTrack)
+{
+ MonitorAutoLock mon(mMonitor);
+ switch (aTrack) {
+ case TrackType::kAudioTrack:
+ return mAudioTrack;
+ case TrackType::kVideoTrack:
+ return mVideoTrack;
+ default:
+ return nullptr;
+ }
+}
+
+MediaSourceDemuxer::~MediaSourceDemuxer()
+{
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+}
+
+void
+MediaSourceDemuxer::GetMozDebugReaderData(nsAString& aString)
+{
+ MonitorAutoLock mon(mMonitor);
+ nsAutoCString result;
+ result += nsPrintfCString("Dumping data for demuxer %p:\n", this);
+ if (mAudioTrack) {
+ result += nsPrintfCString("\tDumping Audio Track Buffer(%s): - mLastAudioTime: %f\n"
+ "\t\tNumSamples:%u Size:%u Evictable:%u NextGetSampleIndex:%u NextInsertionIndex:%d\n",
+ mAudioTrack->mAudioTracks.mInfo->mMimeType.get(),
+ mAudioTrack->mAudioTracks.mNextSampleTime.ToSeconds(),
+ mAudioTrack->mAudioTracks.mBuffers[0].Length(),
+ mAudioTrack->mAudioTracks.mSizeBuffer,
+ mAudioTrack->Evictable(TrackInfo::kAudioTrack),
+ mAudioTrack->mAudioTracks.mNextGetSampleIndex.valueOr(-1),
+ mAudioTrack->mAudioTracks.mNextInsertionIndex.valueOr(-1));
+
+ result += nsPrintfCString("\t\tBuffered: ranges=%s\n",
+ DumpTimeRanges(mAudioTrack->SafeBuffered(TrackInfo::kAudioTrack)).get());
+ }
+ if (mVideoTrack) {
+ result += nsPrintfCString("\tDumping Video Track Buffer(%s) - mLastVideoTime: %f\n"
+ "\t\tNumSamples:%u Size:%u Evictable:%u NextGetSampleIndex:%u NextInsertionIndex:%d\n",
+ mVideoTrack->mVideoTracks.mInfo->mMimeType.get(),
+ mVideoTrack->mVideoTracks.mNextSampleTime.ToSeconds(),
+ mVideoTrack->mVideoTracks.mBuffers[0].Length(),
+ mVideoTrack->mVideoTracks.mSizeBuffer,
+ mVideoTrack->Evictable(TrackInfo::kVideoTrack),
+ mVideoTrack->mVideoTracks.mNextGetSampleIndex.valueOr(-1),
+ mVideoTrack->mVideoTracks.mNextInsertionIndex.valueOr(-1));
+
+ result += nsPrintfCString("\t\tBuffered: ranges=%s\n",
+ DumpTimeRanges(mVideoTrack->SafeBuffered(TrackInfo::kVideoTrack)).get());
+ }
+ aString += NS_ConvertUTF8toUTF16(result);
+}
+
+MediaSourceTrackDemuxer::MediaSourceTrackDemuxer(MediaSourceDemuxer* aParent,
+ TrackInfo::TrackType aType,
+ TrackBuffersManager* aManager)
+ : mParent(aParent)
+ , mManager(aManager)
+ , mType(aType)
+ , mMonitor("MediaSourceTrackDemuxer")
+ , mReset(true)
+ , mPreRoll(
+ TimeUnit::FromMicroseconds(
+ OpusDataDecoder::IsOpus(mParent->GetTrackInfo(mType)->mMimeType)
+ ? 80000 : 0))
+{
+}
+
+UniquePtr<TrackInfo>
+MediaSourceTrackDemuxer::GetInfo() const
+{
+ return mParent->GetTrackInfo(mType)->Clone();
+}
+
+RefPtr<MediaSourceTrackDemuxer::SeekPromise>
+MediaSourceTrackDemuxer::Seek(media::TimeUnit aTime)
+{
+ MOZ_ASSERT(mParent, "Called after BreackCycle()");
+ return InvokeAsync(mParent->GetTaskQueue(), this, __func__,
+ &MediaSourceTrackDemuxer::DoSeek, aTime);
+}
+
+RefPtr<MediaSourceTrackDemuxer::SamplesPromise>
+MediaSourceTrackDemuxer::GetSamples(int32_t aNumSamples)
+{
+ MOZ_ASSERT(mParent, "Called after BreackCycle()");
+ return InvokeAsync(mParent->GetTaskQueue(), this, __func__,
+ &MediaSourceTrackDemuxer::DoGetSamples, aNumSamples);
+}
+
+void
+MediaSourceTrackDemuxer::Reset()
+{
+ MOZ_ASSERT(mParent, "Called after BreackCycle()");
+ RefPtr<MediaSourceTrackDemuxer> self = this;
+ nsCOMPtr<nsIRunnable> task =
+ NS_NewRunnableFunction([self] () {
+ self->mNextSample.reset();
+ self->mReset = true;
+ self->mManager->Seek(self->mType, TimeUnit(), TimeUnit());
+ {
+ MonitorAutoLock mon(self->mMonitor);
+ self->mNextRandomAccessPoint =
+ self->mManager->GetNextRandomAccessPoint(self->mType,
+ MediaSourceDemuxer::EOS_FUZZ);
+ }
+ });
+ mParent->GetTaskQueue()->Dispatch(task.forget());
+}
+
+nsresult
+MediaSourceTrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime)
+{
+ MonitorAutoLock mon(mMonitor);
+ *aTime = mNextRandomAccessPoint;
+ return NS_OK;
+}
+
+RefPtr<MediaSourceTrackDemuxer::SkipAccessPointPromise>
+MediaSourceTrackDemuxer::SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold)
+{
+ return InvokeAsync(mParent->GetTaskQueue(), this, __func__,
+ &MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint,
+ aTimeThreshold);
+}
+
+media::TimeIntervals
+MediaSourceTrackDemuxer::GetBuffered()
+{
+ return mManager->Buffered();
+}
+
+void
+MediaSourceTrackDemuxer::BreakCycles()
+{
+ RefPtr<MediaSourceTrackDemuxer> self = this;
+ nsCOMPtr<nsIRunnable> task =
+ NS_NewRunnableFunction([self]() {
+ self->mParent = nullptr;
+ self->mManager = nullptr;
+ } );
+ mParent->GetTaskQueue()->Dispatch(task.forget());
+}
+
+RefPtr<MediaSourceTrackDemuxer::SeekPromise>
+MediaSourceTrackDemuxer::DoSeek(media::TimeUnit aTime)
+{
+ TimeIntervals buffered = mManager->Buffered(mType);
+ // Fuzz factor represents a +/- threshold. So when seeking it allows the gap
+ // to be twice as big as the fuzz value. We only want to allow EOS_FUZZ gap.
+ buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
+ TimeUnit seekTime = std::max(aTime - mPreRoll, TimeUnit::FromMicroseconds(0));
+
+ if (mManager->IsEnded() && seekTime >= buffered.GetEnd()) {
+ // We're attempting to seek past the end time. Cap seekTime so that we seek
+ // to the last sample instead.
+ seekTime =
+ std::max(mManager->HighestStartTime(mType) - mPreRoll,
+ TimeUnit::FromMicroseconds(0));
+ }
+ if (!buffered.ContainsWithStrictEnd(seekTime)) {
+ if (!buffered.ContainsWithStrictEnd(aTime)) {
+ // We don't have the data to seek to.
+ return SeekPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA,
+ __func__);
+ }
+ // Theoretically we should reject the promise with WAITING_FOR_DATA,
+ // however, to avoid unwanted regressions we assume that if at this time
+ // we don't have the wanted data it won't come later.
+ // Instead of using the pre-rolled time, use the earliest time available in
+ // the interval.
+ TimeIntervals::IndexType index = buffered.Find(aTime);
+ MOZ_ASSERT(index != TimeIntervals::NoIndex);
+ seekTime = buffered[index].mStart;
+ }
+ seekTime = mManager->Seek(mType, seekTime, MediaSourceDemuxer::EOS_FUZZ);
+ MediaResult result = NS_OK;
+ RefPtr<MediaRawData> sample =
+ mManager->GetSample(mType,
+ media::TimeUnit(),
+ result);
+ MOZ_ASSERT(NS_SUCCEEDED(result) && sample);
+ mNextSample = Some(sample);
+ mReset = false;
+ {
+ MonitorAutoLock mon(mMonitor);
+ mNextRandomAccessPoint =
+ mManager->GetNextRandomAccessPoint(mType, MediaSourceDemuxer::EOS_FUZZ);
+ }
+ return SeekPromise::CreateAndResolve(seekTime, __func__);
+}
+
+RefPtr<MediaSourceTrackDemuxer::SamplesPromise>
+MediaSourceTrackDemuxer::DoGetSamples(int32_t aNumSamples)
+{
+ if (mReset) {
+ // If a seek (or reset) was recently performed, we ensure that the data
+ // we are about to retrieve is still available.
+ TimeIntervals buffered = mManager->Buffered(mType);
+ buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
+
+ if (!buffered.Length() && mManager->IsEnded()) {
+ return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM,
+ __func__);
+ }
+ if (!buffered.ContainsWithStrictEnd(TimeUnit::FromMicroseconds(0))) {
+ return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA,
+ __func__);
+ }
+ mReset = false;
+ }
+ RefPtr<MediaRawData> sample;
+ if (mNextSample) {
+ sample = mNextSample.ref();
+ mNextSample.reset();
+ } else {
+ MediaResult result = NS_OK;
+ sample = mManager->GetSample(mType, MediaSourceDemuxer::EOS_FUZZ, result);
+ if (!sample) {
+ if (result == NS_ERROR_DOM_MEDIA_END_OF_STREAM ||
+ result == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
+ return SamplesPromise::CreateAndReject(
+ (result == NS_ERROR_DOM_MEDIA_END_OF_STREAM && mManager->IsEnded())
+ ? NS_ERROR_DOM_MEDIA_END_OF_STREAM
+ : NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__);
+ }
+ return SamplesPromise::CreateAndReject(result, __func__);
+ }
+ }
+ RefPtr<SamplesHolder> samples = new SamplesHolder;
+ samples->mSamples.AppendElement(sample);
+ if (mNextRandomAccessPoint.ToMicroseconds() <= sample->mTime) {
+ MonitorAutoLock mon(mMonitor);
+ mNextRandomAccessPoint =
+ mManager->GetNextRandomAccessPoint(mType, MediaSourceDemuxer::EOS_FUZZ);
+ }
+ return SamplesPromise::CreateAndResolve(samples, __func__);
+}
+
+RefPtr<MediaSourceTrackDemuxer::SkipAccessPointPromise>
+MediaSourceTrackDemuxer::DoSkipToNextRandomAccessPoint(media::TimeUnit aTimeThreadshold)
+{
+ uint32_t parsed = 0;
+ // Ensure that the data we are about to skip to is still available.
+ TimeIntervals buffered = mManager->Buffered(mType);
+ buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
+ if (buffered.ContainsWithStrictEnd(aTimeThreadshold)) {
+ bool found;
+ parsed = mManager->SkipToNextRandomAccessPoint(mType,
+ aTimeThreadshold,
+ MediaSourceDemuxer::EOS_FUZZ,
+ found);
+ if (found) {
+ return SkipAccessPointPromise::CreateAndResolve(parsed, __func__);
+ }
+ }
+ SkipFailureHolder holder(
+ mManager->IsEnded() ? NS_ERROR_DOM_MEDIA_END_OF_STREAM :
+ NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, parsed);
+ return SkipAccessPointPromise::CreateAndReject(holder, __func__);
+}
+
+} // namespace mozilla