diff options
Diffstat (limited to 'dom/media/MediaDecoderReaderWrapper.cpp')
-rw-r--r-- | dom/media/MediaDecoderReaderWrapper.cpp | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/dom/media/MediaDecoderReaderWrapper.cpp b/dom/media/MediaDecoderReaderWrapper.cpp new file mode 100644 index 000000000..455e86f43 --- /dev/null +++ b/dom/media/MediaDecoderReaderWrapper.cpp @@ -0,0 +1,422 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "mozilla/MozPromise.h" +#include "MediaDecoderReaderWrapper.h" + +namespace mozilla { + +extern LazyLogModule gMediaDecoderLog; + +#undef LOG +#define LOG(...) \ + MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) + +// StartTimeRendezvous is a helper class that quarantines the first sample +// until it gets a sample from both channels, such that we can be guaranteed +// to know the start time by the time On{Audio,Video}Decoded is called on MDSM. +class StartTimeRendezvous { + typedef MediaDecoderReader::MediaDataPromise MediaDataPromise; + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StartTimeRendezvous); + +public: + StartTimeRendezvous(AbstractThread* aOwnerThread, + bool aHasAudio, + bool aHasVideo, + bool aForceZeroStartTime) + : mOwnerThread(aOwnerThread) + { + if (aForceZeroStartTime) { + mAudioStartTime.emplace(0); + mVideoStartTime.emplace(0); + return; + } + if (!aHasAudio) { + mAudioStartTime.emplace(INT64_MAX); + } + if (!aHasVideo) { + mVideoStartTime.emplace(INT64_MAX); + } + } + + void Destroy() + { + mAudioStartTime = Some(mAudioStartTime.refOr(INT64_MAX)); + mVideoStartTime = Some(mVideoStartTime.refOr(INT64_MAX)); + mHaveStartTimePromise.RejectIfExists(false, __func__); + } + + RefPtr<HaveStartTimePromise> AwaitStartTime() + { + if (HaveStartTime()) { + return HaveStartTimePromise::CreateAndResolve(true, __func__); + } + return mHaveStartTimePromise.Ensure(__func__); + } + + template<MediaData::Type SampleType> + RefPtr<MediaDataPromise> + ProcessFirstSample(MediaData* aData) + { + typedef typename MediaDataPromise::Private PromisePrivate; + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + + MaybeSetChannelStartTime<SampleType>(aData->mTime); + + RefPtr<PromisePrivate> p = new PromisePrivate(__func__); + RefPtr<MediaData> data = aData; + RefPtr<StartTimeRendezvous> self = this; + AwaitStartTime()->Then( + mOwnerThread, __func__, + [p, data, self] () { + MOZ_ASSERT(self->mOwnerThread->IsCurrentThreadIn()); + p->Resolve(data, __func__); + }, + [p] () { + p->Reject(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + }); + + return p.forget(); + } + + template<MediaData::Type SampleType> + void FirstSampleRejected(const MediaResult& aError) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) { + LOG("StartTimeRendezvous=%p SampleType(%d) Has no samples.", + this, SampleType); + MaybeSetChannelStartTime<SampleType>(INT64_MAX); + } else if (aError != NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) { + mHaveStartTimePromise.RejectIfExists(false, __func__); + } + } + + bool HaveStartTime() const + { + return mAudioStartTime.isSome() && mVideoStartTime.isSome(); + } + + int64_t StartTime() const + { + int64_t time = std::min(mAudioStartTime.ref(), mVideoStartTime.ref()); + return time == INT64_MAX ? 0 : time; + } + +private: + ~StartTimeRendezvous() {} + + template<MediaData::Type SampleType> + void MaybeSetChannelStartTime(int64_t aStartTime) + { + if (ChannelStartTime(SampleType).isSome()) { + // If we're initialized with aForceZeroStartTime=true, the channel start + // times are already set. + return; + } + + LOG("StartTimeRendezvous=%p Setting SampleType(%d) start time to %lld", + this, SampleType, aStartTime); + + ChannelStartTime(SampleType).emplace(aStartTime); + if (HaveStartTime()) { + mHaveStartTimePromise.ResolveIfExists(true, __func__); + } + } + + Maybe<int64_t>& ChannelStartTime(MediaData::Type aType) + { + return aType == MediaData::AUDIO_DATA ? mAudioStartTime : mVideoStartTime; + } + + MozPromiseHolder<HaveStartTimePromise> mHaveStartTimePromise; + RefPtr<AbstractThread> mOwnerThread; + Maybe<int64_t> mAudioStartTime; + Maybe<int64_t> mVideoStartTime; +}; + +MediaDecoderReaderWrapper::MediaDecoderReaderWrapper(AbstractThread* aOwnerThread, + MediaDecoderReader* aReader) + : mForceZeroStartTime(aReader->ForceZeroStartTime()) + , mOwnerThread(aOwnerThread) + , mReader(aReader) +{} + +MediaDecoderReaderWrapper::~MediaDecoderReaderWrapper() +{} + +media::TimeUnit +MediaDecoderReaderWrapper::StartTime() const +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mShutdown); + return media::TimeUnit::FromMicroseconds(mStartTimeRendezvous->StartTime()); +} + +RefPtr<MediaDecoderReaderWrapper::MetadataPromise> +MediaDecoderReaderWrapper::ReadMetadata() +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mShutdown); + return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__, + &MediaDecoderReader::AsyncReadMetadata) + ->Then(mOwnerThread, __func__, this, + &MediaDecoderReaderWrapper::OnMetadataRead, + &MediaDecoderReaderWrapper::OnMetadataNotRead) + ->CompletionPromise(); +} + +RefPtr<HaveStartTimePromise> +MediaDecoderReaderWrapper::AwaitStartTime() +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mShutdown); + return mStartTimeRendezvous->AwaitStartTime(); +} + +void +MediaDecoderReaderWrapper::RequestAudioData() +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mShutdown); + + auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__, + &MediaDecoderReader::RequestAudioData); + + if (!mStartTimeRendezvous->HaveStartTime()) { + p = p->Then(mOwnerThread, __func__, mStartTimeRendezvous.get(), + &StartTimeRendezvous::ProcessFirstSample<MediaData::AUDIO_DATA>, + &StartTimeRendezvous::FirstSampleRejected<MediaData::AUDIO_DATA>) + ->CompletionPromise(); + } + + RefPtr<MediaDecoderReaderWrapper> self = this; + mAudioDataRequest.Begin(p->Then(mOwnerThread, __func__, + [self] (MediaData* aAudioSample) { + self->mAudioDataRequest.Complete(); + aAudioSample->AdjustForStartTime(self->StartTime().ToMicroseconds()); + self->mAudioCallback.Notify(AsVariant(aAudioSample)); + }, + [self] (const MediaResult& aError) { + self->mAudioDataRequest.Complete(); + self->mAudioCallback.Notify(AsVariant(aError)); + })); +} + +void +MediaDecoderReaderWrapper::RequestVideoData(bool aSkipToNextKeyframe, + media::TimeUnit aTimeThreshold) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mShutdown); + + // Time the video decode and send this value back to callbacks who accept + // a TimeStamp as its second parameter. + TimeStamp videoDecodeStartTime = TimeStamp::Now(); + + if (aTimeThreshold.ToMicroseconds() > 0 && + mStartTimeRendezvous->HaveStartTime()) { + aTimeThreshold += StartTime(); + } + + auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__, + &MediaDecoderReader::RequestVideoData, + aSkipToNextKeyframe, aTimeThreshold.ToMicroseconds()); + + if (!mStartTimeRendezvous->HaveStartTime()) { + p = p->Then(mOwnerThread, __func__, mStartTimeRendezvous.get(), + &StartTimeRendezvous::ProcessFirstSample<MediaData::VIDEO_DATA>, + &StartTimeRendezvous::FirstSampleRejected<MediaData::VIDEO_DATA>) + ->CompletionPromise(); + } + + RefPtr<MediaDecoderReaderWrapper> self = this; + mVideoDataRequest.Begin(p->Then(mOwnerThread, __func__, + [self, videoDecodeStartTime] (MediaData* aVideoSample) { + self->mVideoDataRequest.Complete(); + aVideoSample->AdjustForStartTime(self->StartTime().ToMicroseconds()); + self->mVideoCallback.Notify(AsVariant(MakeTuple(aVideoSample, videoDecodeStartTime))); + }, + [self] (const MediaResult& aError) { + self->mVideoDataRequest.Complete(); + self->mVideoCallback.Notify(AsVariant(aError)); + })); +} + +bool +MediaDecoderReaderWrapper::IsRequestingAudioData() const +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return mAudioDataRequest.Exists(); +} + +bool +MediaDecoderReaderWrapper::IsRequestingVideoData() const +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return mVideoDataRequest.Exists(); +} + +bool +MediaDecoderReaderWrapper::IsWaitingAudioData() const +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return mAudioWaitRequest.Exists(); +} + +bool +MediaDecoderReaderWrapper::IsWaitingVideoData() const +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return mVideoWaitRequest.Exists(); +} + +RefPtr<MediaDecoderReader::SeekPromise> +MediaDecoderReaderWrapper::Seek(SeekTarget aTarget, media::TimeUnit aEndTime) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + aTarget.SetTime(aTarget.GetTime() + StartTime()); + return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__, + &MediaDecoderReader::Seek, aTarget, + aEndTime.ToMicroseconds()); +} + +void +MediaDecoderReaderWrapper::WaitForData(MediaData::Type aType) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + + auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__, + &MediaDecoderReader::WaitForData, aType); + + RefPtr<MediaDecoderReaderWrapper> self = this; + WaitRequestRef(aType).Begin(p->Then(mOwnerThread, __func__, + [self] (MediaData::Type aType) { + self->WaitRequestRef(aType).Complete(); + self->WaitCallbackRef(aType).Notify(AsVariant(aType)); + }, + [self, aType] (WaitForDataRejectValue aRejection) { + self->WaitRequestRef(aType).Complete(); + self->WaitCallbackRef(aType).Notify(AsVariant(aRejection)); + })); +} + +MediaCallbackExc<WaitCallbackData>& +MediaDecoderReaderWrapper::WaitCallbackRef(MediaData::Type aType) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return aType == MediaData::AUDIO_DATA ? mAudioWaitCallback : mVideoWaitCallback; +} + +MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise>& +MediaDecoderReaderWrapper::WaitRequestRef(MediaData::Type aType) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return aType == MediaData::AUDIO_DATA ? mAudioWaitRequest : mVideoWaitRequest; +} + +RefPtr<MediaDecoderReaderWrapper::BufferedUpdatePromise> +MediaDecoderReaderWrapper::UpdateBufferedWithPromise() +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__, + &MediaDecoderReader::UpdateBufferedWithPromise); +} + +void +MediaDecoderReaderWrapper::ReleaseResources() +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod(mReader, &MediaDecoderReader::ReleaseResources); + mReader->OwnerThread()->Dispatch(r.forget()); +} + +void +MediaDecoderReaderWrapper::SetIdle() +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod(mReader, &MediaDecoderReader::SetIdle); + mReader->OwnerThread()->Dispatch(r.forget()); +} + +void +MediaDecoderReaderWrapper::ResetDecode(TrackSet aTracks) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + + if (aTracks.contains(TrackInfo::kAudioTrack)) { + mAudioDataRequest.DisconnectIfExists(); + mAudioWaitRequest.DisconnectIfExists(); + } + + if (aTracks.contains(TrackInfo::kVideoTrack)) { + mVideoDataRequest.DisconnectIfExists(); + mVideoWaitRequest.DisconnectIfExists(); + } + + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod<TrackSet>(mReader, + &MediaDecoderReader::ResetDecode, + aTracks); + mReader->OwnerThread()->Dispatch(r.forget()); +} + +RefPtr<ShutdownPromise> +MediaDecoderReaderWrapper::Shutdown() +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mAudioDataRequest.Exists()); + MOZ_ASSERT(!mVideoDataRequest.Exists()); + + mShutdown = true; + if (mStartTimeRendezvous) { + mStartTimeRendezvous->Destroy(); + mStartTimeRendezvous = nullptr; + } + return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__, + &MediaDecoderReader::Shutdown); +} + +void +MediaDecoderReaderWrapper::OnMetadataRead(MetadataHolder* aMetadata) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + if (mShutdown) { + return; + } + // Set up the start time rendezvous if it doesn't already exist (which is + // generally the case, unless we're coming out of dormant mode). + if (!mStartTimeRendezvous) { + mStartTimeRendezvous = new StartTimeRendezvous( + mOwnerThread, aMetadata->mInfo.HasAudio(), + aMetadata->mInfo.HasVideo(), mForceZeroStartTime); + + RefPtr<MediaDecoderReaderWrapper> self = this; + mStartTimeRendezvous->AwaitStartTime()->Then( + mOwnerThread, __func__, + [self] () { + NS_ENSURE_TRUE_VOID(!self->mShutdown); + self->mReader->DispatchSetStartTime(self->StartTime().ToMicroseconds()); + }, + [] () { + NS_WARNING("Setting start time on reader failed"); + }); + } +} + +void +MediaDecoderReaderWrapper::SetVideoBlankDecode(bool aIsBlankDecode) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod<bool>(mReader, &MediaDecoderReader::SetVideoBlankDecode, + aIsBlankDecode); + mReader->OwnerThread()->Dispatch(r.forget()); +} + +} // namespace mozilla |