/* -*- 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 "NextFrameSeekTask.h" #include "MediaDecoderReaderWrapper.h" #include "mozilla/AbstractThread.h" #include "mozilla/Assertions.h" #include "nsPrintfCString.h" namespace mozilla { extern LazyLogModule gMediaDecoderLog; extern LazyLogModule gMediaSampleLog; // avoid redefined macro in unified build #undef LOG #undef DECODER_LOG #undef VERBOSE_LOG #define LOG(m, l, x, ...) \ MOZ_LOG(m, l, ("[NextFrameSeekTask] Decoder=%p " x, mDecoderID, ##__VA_ARGS__)) #define DECODER_LOG(x, ...) \ LOG(gMediaDecoderLog, LogLevel::Debug, x, ##__VA_ARGS__) #define VERBOSE_LOG(x, ...) \ LOG(gMediaDecoderLog, LogLevel::Verbose, x, ##__VA_ARGS__) #define SAMPLE_LOG(x, ...) \ LOG(gMediaSampleLog, LogLevel::Debug, x, ##__VA_ARGS__) // Somehow MSVC doesn't correctly delete the comma before ##__VA_ARGS__ // when __VA_ARGS__ expands to nothing. This is a workaround for it. #define DECODER_WARN_HELPER(a, b) NS_WARNING b #define DECODER_WARN(x, ...) \ DECODER_WARN_HELPER(0, (nsPrintfCString("Decoder=%p " x, mDecoderID, ##__VA_ARGS__).get())) namespace media { NextFrameSeekTask::NextFrameSeekTask(const void* aDecoderID, AbstractThread* aThread, MediaDecoderReaderWrapper* aReader, SeekJob&& aSeekJob, const MediaInfo& aInfo, const media::TimeUnit& aDuration, int64_t aCurrentMediaTime, MediaQueue& aAudioQueue, MediaQueue& aVideoQueue) : SeekTask(aDecoderID, aThread, aReader, Move(aSeekJob)) , mAudioQueue(aAudioQueue) , mVideoQueue(aVideoQueue) , mCurrentTimeBeforeSeek(aCurrentMediaTime) , mHasAudio(aInfo.HasAudio()) , mHasVideo(aInfo.HasVideo()) , mDuration(aDuration) { AssertOwnerThread(); MOZ_ASSERT(HasVideo()); // Configure MediaDecoderReaderWrapper. SetMediaDecoderReaderWrapperCallback(); } NextFrameSeekTask::~NextFrameSeekTask() { AssertOwnerThread(); MOZ_ASSERT(mIsDiscarded); } bool NextFrameSeekTask::HasAudio() const { AssertOwnerThread(); return mHasAudio; } bool NextFrameSeekTask::HasVideo() const { AssertOwnerThread(); return mHasVideo; } void NextFrameSeekTask::Discard() { AssertOwnerThread(); // Disconnect MediaDecoder. mSeekJob.RejectIfExists(__func__); // Disconnect MDSM. RejectIfExist(__func__); // Disconnect MediaDecoderReader. CancelMediaDecoderReaderWrapperCallback(); mIsDiscarded = true; } bool NextFrameSeekTask::NeedToResetMDSM() const { AssertOwnerThread(); return false; } static int64_t FindNextFrame(MediaQueue& aQueue, int64_t aTime) { AutoTArray, 16> frames; aQueue.GetFirstElements(aQueue.GetSize(), &frames); for (auto&& frame : frames) { if (frame->mTime > aTime) { return frame->mTime; } } return -1; } static void DropFramesUntil(MediaQueue& aQueue, int64_t aTime) { while (aQueue.GetSize() > 0) { if (aQueue.PeekFront()->mTime < aTime) { RefPtr releaseMe = aQueue.PopFront(); continue; } break; } } static void DropAllFrames(MediaQueue& aQueue) { while(aQueue.GetSize() > 0) { RefPtr releaseMe = aQueue.PopFront(); } } static void DropAllMediaDataBeforeCurrentPosition(MediaQueue& aAudioQueue, MediaQueue& aVideoQueue, int64_t const aCurrentTimeBeforeSeek) { // Drop all audio/video data before GetMediaTime(); int64_t newPos = FindNextFrame(aVideoQueue, aCurrentTimeBeforeSeek); if (newPos < 0) { // In this case, we cannot find the next frame in the video queue, so // the NextFrameSeekTask needs to decode video data. DropAllFrames(aVideoQueue); if (aVideoQueue.IsFinished()) { DropAllFrames(aAudioQueue); } } else { DropFramesUntil(aVideoQueue, newPos); DropFramesUntil(aAudioQueue, newPos); // So now, the 1st data in the video queue should be the target of the // NextFrameSeekTask. } } RefPtr NextFrameSeekTask::Seek(const media::TimeUnit&) { AssertOwnerThread(); DropAllMediaDataBeforeCurrentPosition(mAudioQueue, mVideoQueue, mCurrentTimeBeforeSeek); // While creating this seek task object, MDSM might had already ask the // wrapper to decode a media sample or the MDSM is waiting a media data. // If so, we cannot resolve the SeekTaskPromise immediately because there is // a latency of running the resolving runnable. Instead, if there is a pending // media request, we wait for it. if ((mVideoQueue.GetSize() > 0 && !mReader->IsRequestingAudioData() && !mReader->IsWaitingAudioData() && !mReader->IsRequestingVideoData() && !mReader->IsWaitingVideoData()) || mVideoQueue.AtEndOfStream()) { UpdateSeekTargetTime(); SeekTaskResolveValue val = {}; // Zero-initialize data members. return SeekTask::SeekTaskPromise::CreateAndResolve(val, __func__); } else { // Only invoke EnsureVideoDecodeTaskQueued() if we have no video data; we // might be here because we are waiting audio data, and don't bother to make // more requests to reader in this case. if (mVideoQueue.GetSize() == 0) { EnsureVideoDecodeTaskQueued(); } return mSeekTaskPromise.Ensure(__func__); } } bool NextFrameSeekTask::IsVideoDecoding() const { AssertOwnerThread(); return HasVideo() && !mIsVideoQueueFinished; } nsresult NextFrameSeekTask::EnsureVideoDecodeTaskQueued() { AssertOwnerThread(); SAMPLE_LOG("EnsureVideoDecodeTaskQueued isDecoding=%d status=%s", IsVideoDecoding(), VideoRequestStatus()); if (!IsVideoDecoding() || mReader->IsRequestingVideoData() || mReader->IsWaitingVideoData()) { return NS_OK; } RequestVideoData(); return NS_OK; } const char* NextFrameSeekTask::VideoRequestStatus() { AssertOwnerThread(); if (mReader->IsRequestingVideoData()) { MOZ_DIAGNOSTIC_ASSERT(!mReader->IsWaitingVideoData()); return "pending"; } else if (mReader->IsWaitingVideoData()) { return "waiting"; } return "idle"; } void NextFrameSeekTask::RequestVideoData() { AssertOwnerThread(); SAMPLE_LOG("Queueing video task - queued=%i, decoder-queued=%o", !!mSeekedVideoData, mReader->SizeOfVideoQueueInFrames()); mReader->RequestVideoData(false, media::TimeUnit()); } bool NextFrameSeekTask::IsAudioSeekComplete() { AssertOwnerThread(); SAMPLE_LOG("IsAudioSeekComplete() curTarVal=%d aqFin=%d aqSz=%d req=%d wait=%d", mSeekJob.Exists(), mIsAudioQueueFinished, !!mSeekedAudioData, mReader->IsRequestingAudioData(), mReader->IsWaitingAudioData()); // Just make sure that we are not requesting or waiting for audio data. We // don't really need to get an decoded audio data or get EOS here. return !HasAudio() || (Exists() && !mReader->IsRequestingAudioData() && !mReader->IsWaitingAudioData()); } bool NextFrameSeekTask::IsVideoSeekComplete() { AssertOwnerThread(); SAMPLE_LOG("IsVideoSeekComplete() curTarVal=%d vqFin=%d vqSz=%d", mSeekJob.Exists(), mIsVideoQueueFinished, !!mSeekedVideoData); return !HasVideo() || (Exists() && (mIsVideoQueueFinished || mSeekedVideoData)); } void NextFrameSeekTask::CheckIfSeekComplete() { AssertOwnerThread(); const bool audioSeekComplete = IsAudioSeekComplete(); const bool videoSeekComplete = IsVideoSeekComplete(); if (HasVideo() && !videoSeekComplete) { // We haven't reached the target. Ensure we have requested another sample. if (NS_FAILED(EnsureVideoDecodeTaskQueued())) { DECODER_WARN("Failed to request video during seek"); RejectIfExist(__func__); } } SAMPLE_LOG("CheckIfSeekComplete() audioSeekComplete=%d videoSeekComplete=%d", audioSeekComplete, videoSeekComplete); if (audioSeekComplete && videoSeekComplete) { UpdateSeekTargetTime(); Resolve(__func__); // Call to MDSM::SeekCompleted(); } } void NextFrameSeekTask::OnAudioDecoded(MediaData* aAudioSample) { AssertOwnerThread(); MOZ_ASSERT(aAudioSample); // The MDSM::mDecodedAudioEndTime will be updated once the whole SeekTask is // resolved. SAMPLE_LOG("OnAudioDecoded [%lld,%lld] disc=%d", (aAudioSample ? aAudioSample->mTime : -1), (aAudioSample ? aAudioSample->GetEndTime() : -1), (aAudioSample ? aAudioSample->mDiscontinuity : 0)); if (!Exists()) { // We've received a sample from a previous decode. Discard it. return; } // We accept any audio data here. mSeekedAudioData = aAudioSample; CheckIfSeekComplete(); } void NextFrameSeekTask::OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason) { AssertOwnerThread(); SAMPLE_LOG("OnAudioNotDecoded (aReason=%u)", aReason); if (!Exists()) { // We've received a sample from a previous decode. Discard it. return; } // We don't really handle audio deocde error here. Let MDSM to trigger further // audio decoding tasks if it needs to play audio, and MDSM will then receive // the decoding state from MediaDecoderReader. CheckIfSeekComplete(); } void NextFrameSeekTask::OnVideoDecoded(MediaData* aVideoSample) { AssertOwnerThread(); MOZ_ASSERT(aVideoSample); // The MDSM::mDecodedVideoEndTime will be updated once the whole SeekTask is // resolved. SAMPLE_LOG("OnVideoDecoded [%lld,%lld] disc=%d", (aVideoSample ? aVideoSample->mTime : -1), (aVideoSample ? aVideoSample->GetEndTime() : -1), (aVideoSample ? aVideoSample->mDiscontinuity : 0)); if (!Exists()) { // We've received a sample from a previous decode. Discard it. return; } if (aVideoSample->mTime > mCurrentTimeBeforeSeek) { mSeekedVideoData = aVideoSample; } CheckIfSeekComplete(); } void NextFrameSeekTask::OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason) { AssertOwnerThread(); SAMPLE_LOG("OnVideoNotDecoded (aReason=%u)", aReason); if (!Exists()) { // We've received a sample from a previous decode. Discard it. return; } if (aReason == MediaDecoderReader::DECODE_ERROR) { if (mVideoQueue.GetSize() > 0) { // The video decoding request might be filed by MDSM not the // NextFrameSeekTask itself. So, the NextFrameSeekTask might has already // found its target in the VideoQueue but still waits the video decoding // request (which is filed by the MDSM) to be resolved. In this case, we // already have the target of this seek task, try to resolve this task. CheckIfSeekComplete(); return; } // Otherwise, we cannot get the target video frame of this seek task, // delegate the decode error to the generic error path. RejectIfExist(__func__); return; } // If the decoder is waiting for data, we tell it to call us back when the // data arrives. if (aReason == MediaDecoderReader::WAITING_FOR_DATA) { MOZ_ASSERT(mReader->IsWaitForDataSupported(), "Readers that send WAITING_FOR_DATA need to implement WaitForData"); mReader->WaitForData(MediaData::VIDEO_DATA); // We are out of data to decode and will enter buffering mode soon. // We want to play the frames we have already decoded, so we stop pre-rolling // and ensure that loadeddata is fired as required. mNeedToStopPrerollingVideo = true; return; } if (aReason == MediaDecoderReader::CANCELED) { EnsureVideoDecodeTaskQueued(); return; } if (aReason == MediaDecoderReader::END_OF_STREAM) { mIsVideoQueueFinished = true; CheckIfSeekComplete(); } } void NextFrameSeekTask::SetMediaDecoderReaderWrapperCallback() { AssertOwnerThread(); // Register dummy callbcak for audio decoding since we don't need to handle // the decoded audio samples. mAudioCallback = mReader->AudioCallback().Connect( OwnerThread(), [this] (AudioCallbackData aData) { if (aData.is()) { OnAudioDecoded(aData.as()); } else { OnAudioNotDecoded(aData.as()); } }); mVideoCallback = mReader->VideoCallback().Connect( OwnerThread(), [this] (VideoCallbackData aData) { typedef Tuple Type; if (aData.is()) { OnVideoDecoded(Get<0>(aData.as())); } else { OnVideoNotDecoded(aData.as()); } }); mAudioWaitCallback = mReader->AudioWaitCallback().Connect( OwnerThread(), [this] (WaitCallbackData aData) { // We don't make an audio decode request here, instead, let MDSM to // trigger further audio decode tasks if MDSM itself needs to play audio. }); mVideoWaitCallback = mReader->VideoWaitCallback().Connect( OwnerThread(), [this] (WaitCallbackData aData) { if (aData.is()) { EnsureVideoDecodeTaskQueued(); } }); } void NextFrameSeekTask::CancelMediaDecoderReaderWrapperCallback() { AssertOwnerThread(); mAudioCallback.Disconnect(); mVideoCallback.Disconnect(); mAudioWaitCallback.Disconnect(); mVideoWaitCallback.Disconnect(); } void NextFrameSeekTask::UpdateSeekTargetTime() { AssertOwnerThread(); RefPtr data = mVideoQueue.PeekFront(); if (data) { mSeekJob.mTarget.SetTime(TimeUnit::FromMicroseconds(data->mTime)); } else if (mSeekedVideoData) { mSeekJob.mTarget.SetTime(TimeUnit::FromMicroseconds(mSeekedVideoData->mTime)); } else if (mIsVideoQueueFinished || mVideoQueue.AtEndOfStream()) { mSeekJob.mTarget.SetTime(mDuration); } else { MOZ_ASSERT(false, "No data!"); } } } // namespace media } // namespace mozilla