diff options
Diffstat (limited to 'dom/media/platforms/wmf/WMFMediaDataDecoder.cpp')
-rw-r--r-- | dom/media/platforms/wmf/WMFMediaDataDecoder.cpp | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp b/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp new file mode 100644 index 0000000000..73589d02c2 --- /dev/null +++ b/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp @@ -0,0 +1,279 @@ +/* -*- 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 "WMFMediaDataDecoder.h" + +#include "VideoUtils.h" +#include "WMFUtils.h" +#include "mozilla/Logging.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/Telemetry.h" +#include "mozilla/WindowsVersion.h" +#include "nsTArray.h" + +#define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) + +namespace mozilla { + +WMFMediaDataDecoder::WMFMediaDataDecoder(MFTManager* aMFTManager) + : mTaskQueue(TaskQueue::Create( + GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), + "WMFMediaDataDecoder")), + mMFTManager(aMFTManager) {} + +WMFMediaDataDecoder::~WMFMediaDataDecoder() {} + +RefPtr<MediaDataDecoder::InitPromise> WMFMediaDataDecoder::Init() { + MOZ_ASSERT(!mIsShutDown); + return InitPromise::CreateAndResolve(mMFTManager->GetType(), __func__); +} + +RefPtr<ShutdownPromise> WMFMediaDataDecoder::Shutdown() { + MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown); + mIsShutDown = true; + + return InvokeAsync(mTaskQueue, __func__, [self = RefPtr{this}, this] { + if (mMFTManager) { + mMFTManager->Shutdown(); + mMFTManager = nullptr; + } + return mTaskQueue->BeginShutdown(); + }); +} + +// Inserts data into the decoder's pipeline. +RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::Decode( + MediaRawData* aSample) { + MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown); + + return InvokeAsync<MediaRawData*>( + mTaskQueue, this, __func__, &WMFMediaDataDecoder::ProcessDecode, aSample); +} + +RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::ProcessError( + HRESULT aError, const char* aReason) { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + + nsPrintfCString markerString( + "WMFMediaDataDecoder::ProcessError for decoder with description %s with " + "reason: %s", + GetDescriptionName().get(), aReason); + LOG("%s", markerString.get()); + PROFILER_MARKER_TEXT("WMFDecoder Error", MEDIA_PLAYBACK, {}, markerString); + + // TODO: For the error DXGI_ERROR_DEVICE_RESET, we could return + // NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER to get the latest device. Maybe retry + // up to 3 times. + return DecodePromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("%s:%lx", aReason, aError)), + __func__); +} + +RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::ProcessDecode( + MediaRawData* aSample) { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + DecodedData results; + LOG("ProcessDecode, type=%s, sample=%" PRId64, + TrackTypeToStr(mMFTManager->GetType()), aSample->mTime.ToMicroseconds()); + HRESULT hr = mMFTManager->Input(aSample); + if (hr == MF_E_NOTACCEPTING) { + hr = ProcessOutput(results); + if (FAILED(hr) && hr != MF_E_TRANSFORM_NEED_MORE_INPUT) { + return ProcessError(hr, "MFTManager::Output(1)"); + } + hr = mMFTManager->Input(aSample); + } + + if (FAILED(hr)) { + NS_WARNING("MFTManager rejected sample"); + return ProcessError(hr, "MFTManager::Input"); + } + + if (mOutputsCount == 0) { + mInputTimesSet.insert(aSample->mTime.ToMicroseconds()); + } + + if (!mLastTime || aSample->mTime > *mLastTime) { + mLastTime = Some(aSample->mTime); + mLastDuration = aSample->mDuration; + } + + mSamplesCount++; + mDrainStatus = DrainStatus::DRAINABLE; + mLastStreamOffset = aSample->mOffset; + + hr = ProcessOutput(results); + if (SUCCEEDED(hr) || hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + return DecodePromise::CreateAndResolve(std::move(results), __func__); + } + return ProcessError(hr, "MFTManager::Output(2)"); +} + +bool WMFMediaDataDecoder::ShouldGuardAgaintIncorrectFirstSample( + MediaData* aOutput) const { + // Incorrect first samples have only been observed in video tracks, so only + // guard video tracks. + if (mMFTManager->GetType() != TrackInfo::kVideoTrack) { + return false; + } + + // By observation so far this issue only happens on Windows 10 so we don't + // need to enable this on other versions. + if (!IsWin10OrLater()) { + return false; + } + + // This is not the first output sample so we don't need to guard it. + if (mOutputsCount != 0) { + return false; + } + + // Output isn't in the map which contains the inputs we gave to the decoder. + // This is probably the invalid first sample. MFT decoder sometime will return + // incorrect first output to us, which always has 0 timestamp, even if the + // input we gave to MFT has timestamp that is way later than 0. + MOZ_ASSERT(!mInputTimesSet.empty()); + return mInputTimesSet.find(aOutput->mTime.ToMicroseconds()) == + mInputTimesSet.end() && + aOutput->mTime.ToMicroseconds() == 0; +} + +HRESULT +WMFMediaDataDecoder::ProcessOutput(DecodedData& aResults) { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + RefPtr<MediaData> output; + HRESULT hr = S_OK; + while (SUCCEEDED(hr = mMFTManager->Output(mLastStreamOffset, output))) { + MOZ_ASSERT(output.get(), "Upon success, we must receive an output"); + if (ShouldGuardAgaintIncorrectFirstSample(output)) { + LOG("Discarding sample with time %" PRId64 + " because of ShouldGuardAgaintIncorrectFirstSample check", + output->mTime.ToMicroseconds()); + continue; + } + if (++mOutputsCount == 1) { + // Got first valid sample, don't need to guard following sample anymore. + mInputTimesSet.clear(); + } + aResults.AppendElement(std::move(output)); + if (mDrainStatus == DrainStatus::DRAINING) { + break; + } + } + return hr; +} + +RefPtr<MediaDataDecoder::FlushPromise> WMFMediaDataDecoder::ProcessFlush() { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + if (mMFTManager) { + mMFTManager->Flush(); + } + LOG("ProcessFlush, type=%s", TrackTypeToStr(mMFTManager->GetType())); + mDrainStatus = DrainStatus::DRAINED; + mSamplesCount = 0; + mOutputsCount = 0; + mLastTime.reset(); + mInputTimesSet.clear(); + return FlushPromise::CreateAndResolve(true, __func__); +} + +RefPtr<MediaDataDecoder::FlushPromise> WMFMediaDataDecoder::Flush() { + MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown); + + return InvokeAsync(mTaskQueue, this, __func__, + &WMFMediaDataDecoder::ProcessFlush); +} + +RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::ProcessDrain() { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + if (!mMFTManager || mDrainStatus == DrainStatus::DRAINED) { + return DecodePromise::CreateAndResolve(DecodedData(), __func__); + } + + if (mDrainStatus != DrainStatus::DRAINING) { + // Order the decoder to drain... + mMFTManager->Drain(); + mDrainStatus = DrainStatus::DRAINING; + } + + // Then extract all available output. + DecodedData results; + HRESULT hr = ProcessOutput(results); + if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + mDrainStatus = DrainStatus::DRAINED; + } + if (SUCCEEDED(hr) || hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + if (results.Length() > 0 && + results.LastElement()->mType == MediaData::Type::VIDEO_DATA) { + const RefPtr<MediaData>& data = results.LastElement(); + if (mSamplesCount == 1 && data->mTime == media::TimeUnit::Zero()) { + // WMF is unable to calculate a duration if only a single sample + // was parsed. Additionally, the pts always comes out at 0 under those + // circumstances. + // Seeing that we've only fed the decoder a single frame, the pts + // and duration are known, it's of the last sample. + data->mTime = *mLastTime; + } + if (data->mTime == *mLastTime) { + // The WMF Video decoder is sometimes unable to provide a valid duration + // on the last sample even if it has been first set through + // SetSampleTime (appears to always happen on Windows 7). So we force + // set the duration of the last sample as it was input. + data->mDuration = mLastDuration; + } + } else if (results.Length() == 1 && + results.LastElement()->mType == MediaData::Type::AUDIO_DATA) { + // When we drain the audio decoder and one frame was queued (such as with + // AAC) the MFT will re-calculate the starting time rather than use the + // value set on the IMF Sample. + // This is normally an okay thing to do; however when dealing with poorly + // muxed content that has incorrect start time, it could lead to broken + // A/V sync. So we ensure that we use the compressed sample's time + // instead. Additionally, this is what all other audio decoders are doing + // anyway. + MOZ_ASSERT(mLastTime, + "We must have attempted to decode at least one frame to get " + "one decoded output"); + results.LastElement()->As<AudioData>()->SetOriginalStartTime(*mLastTime); + } + return DecodePromise::CreateAndResolve(std::move(results), __func__); + } + return ProcessError(hr, "MFTManager::Output"); +} + +RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::Drain() { + MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown); + + return InvokeAsync(mTaskQueue, this, __func__, + &WMFMediaDataDecoder::ProcessDrain); +} + +bool WMFMediaDataDecoder::IsHardwareAccelerated( + nsACString& aFailureReason) const { + MOZ_ASSERT(!mIsShutDown); + + return mMFTManager && mMFTManager->IsHardwareAccelerated(aFailureReason); +} + +void WMFMediaDataDecoder::SetSeekThreshold(const media::TimeUnit& aTime) { + MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown); + + RefPtr<WMFMediaDataDecoder> self = this; + nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction( + "WMFMediaDataDecoder::SetSeekThreshold", [self, aTime]() { + MOZ_ASSERT(self->mTaskQueue->IsCurrentThreadIn()); + media::TimeUnit threshold = aTime; + self->mMFTManager->SetSeekThreshold(threshold); + }); + nsresult rv = mTaskQueue->Dispatch(runnable.forget()); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + Unused << rv; +} + +} // namespace mozilla |