WMFMediaDataDecoder.cpp (10429B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "WMFMediaDataDecoder.h" 8 9 #include "VideoUtils.h" 10 #include "WMFUtils.h" 11 #include "mozilla/Logging.h" 12 #include "mozilla/ProfilerMarkers.h" 13 #include "mozilla/SyncRunnable.h" 14 #include "mozilla/TaskQueue.h" 15 #include "nsTArray.h" 16 17 #define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) 18 19 namespace mozilla { 20 21 WMFMediaDataDecoder::WMFMediaDataDecoder(MFTManager* aMFTManager) 22 : mTaskQueue(TaskQueue::Create( 23 GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), 24 "WMFMediaDataDecoder")), 25 mMFTManager(aMFTManager) {} 26 27 WMFMediaDataDecoder::~WMFMediaDataDecoder() {} 28 29 RefPtr<MediaDataDecoder::InitPromise> WMFMediaDataDecoder::Init() { 30 MOZ_ASSERT(!mIsShutDown); 31 return InitPromise::CreateAndResolve(mMFTManager->GetType(), __func__); 32 } 33 34 RefPtr<ShutdownPromise> WMFMediaDataDecoder::Shutdown() { 35 MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown); 36 mIsShutDown = true; 37 38 return InvokeAsync(mTaskQueue, __func__, [self = RefPtr{this}, this] { 39 AUTO_PROFILER_LABEL("WMFMediaDataDecoder::Shutdown", MEDIA_PLAYBACK); 40 if (mMFTManager) { 41 mMFTManager->Shutdown(); 42 mMFTManager = nullptr; 43 } 44 return mTaskQueue->BeginShutdown(); 45 }); 46 } 47 48 // Inserts data into the decoder's pipeline. 49 RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::Decode( 50 MediaRawData* aSample) { 51 MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown); 52 53 return InvokeAsync<MediaRawData*>( 54 mTaskQueue, this, __func__, &WMFMediaDataDecoder::ProcessDecode, aSample); 55 } 56 57 RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::ProcessError( 58 HRESULT aError, const char* aReason) { 59 MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); 60 61 nsPrintfCString markerString( 62 "WMFMediaDataDecoder::ProcessError for decoder with description %s with " 63 "reason: %s", 64 GetDescriptionName().get(), aReason); 65 LOG("%s", markerString.get()); 66 PROFILER_MARKER_TEXT("WMFDecoder Error", MEDIA_PLAYBACK, {}, markerString); 67 68 // TODO: For the error DXGI_ERROR_DEVICE_RESET, we could return 69 // NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER to get the latest device. Maybe retry 70 // up to 3 times. 71 return DecodePromise::CreateAndReject( 72 MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, 73 RESULT_DETAIL("%s:%lx", aReason, aError)), 74 __func__); 75 } 76 77 RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::ProcessDecode( 78 MediaRawData* aSample) { 79 AUTO_PROFILER_LABEL("WMFMediaDataDecoder::ProcessDecode", MEDIA_PLAYBACK); 80 MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); 81 DecodedData results; 82 LOG("ProcessDecode, type=%s, sample=%" PRId64 ", duration=%" PRId64, 83 TrackTypeToStr(mMFTManager->GetType()), aSample->mTime.ToMicroseconds(), 84 aSample->mDuration.ToMicroseconds()); 85 HRESULT hr = mMFTManager->Input(aSample); 86 if (hr == MF_E_NOTACCEPTING) { 87 hr = ProcessOutput(results); 88 if (FAILED(hr) && hr != MF_E_TRANSFORM_NEED_MORE_INPUT) { 89 return ProcessError(hr, "MFTManager::Output(1)"); 90 } 91 hr = mMFTManager->Input(aSample); 92 } 93 94 if (FAILED(hr)) { 95 NS_WARNING("MFTManager rejected sample"); 96 return ProcessError(hr, "MFTManager::Input"); 97 } 98 99 if (mOutputsCount == 0) { 100 mInputTimesSet.insert(aSample->mTime.ToMicroseconds()); 101 } 102 103 if (!mLastTime || aSample->mTime > *mLastTime) { 104 mLastTime = Some(aSample->mTime); 105 mLastDuration = aSample->mDuration; 106 } 107 108 mSamplesCount++; 109 mDrainStatus = DrainStatus::DRAINABLE; 110 mLastStreamOffset = aSample->mOffset; 111 112 hr = ProcessOutput(results); 113 if (SUCCEEDED(hr) || hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { 114 return DecodePromise::CreateAndResolve(std::move(results), __func__); 115 } 116 return ProcessError(hr, "MFTManager::Output(2)"); 117 } 118 119 bool WMFMediaDataDecoder::ShouldGuardAgaintIncorrectFirstSample( 120 MediaData* aOutput) const { 121 // Incorrect first samples have only been observed in video tracks, so only 122 // guard video tracks. 123 if (mMFTManager->GetType() != TrackInfo::kVideoTrack) { 124 return false; 125 } 126 127 // This is not the first output sample so we don't need to guard it. 128 if (mOutputsCount != 0) { 129 return false; 130 } 131 132 // Output isn't in the map which contains the inputs we gave to the decoder. 133 // This is probably the invalid first sample. MFT decoder sometime will return 134 // incorrect first output to us, which always has 0 timestamp, even if the 135 // input we gave to MFT has timestamp that is way later than 0. 136 MOZ_ASSERT(!mInputTimesSet.empty()); 137 return mInputTimesSet.find(aOutput->mTime.ToMicroseconds()) == 138 mInputTimesSet.end() && 139 aOutput->mTime.ToMicroseconds() == 0; 140 } 141 142 HRESULT 143 WMFMediaDataDecoder::ProcessOutput(DecodedData& aResults) { 144 MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); 145 RefPtr<MediaData> output; 146 HRESULT hr = S_OK; 147 while (SUCCEEDED(hr = mMFTManager->Output(mLastStreamOffset, output))) { 148 MOZ_ASSERT(output.get(), "Upon success, we must receive an output"); 149 if (ShouldGuardAgaintIncorrectFirstSample(output)) { 150 LOG("Discarding sample with time %" PRId64 151 " because of ShouldGuardAgaintIncorrectFirstSample check", 152 output->mTime.ToMicroseconds()); 153 continue; 154 } 155 if (++mOutputsCount == 1) { 156 // Got first valid sample, don't need to guard following sample anymore. 157 mInputTimesSet.clear(); 158 } 159 aResults.AppendElement(std::move(output)); 160 // If zero-copy video is enabled, multiple WMFVideoMFTManager::Output() 161 // calls must be prevented within WMFMediaDataDecoder::ProcessOutput(). 162 if (mMFTManager->UseZeroCopyVideoFrame()) { 163 break; 164 } 165 if (mDrainStatus == DrainStatus::DRAINING) { 166 break; 167 } 168 } 169 return hr; 170 } 171 172 RefPtr<MediaDataDecoder::FlushPromise> WMFMediaDataDecoder::ProcessFlush() { 173 AUTO_PROFILER_LABEL("WMFMediaDataDecoder::ProcessFlush", MEDIA_PLAYBACK); 174 MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); 175 if (mMFTManager) { 176 mMFTManager->Flush(); 177 } 178 LOG("ProcessFlush, type=%s", TrackTypeToStr(mMFTManager->GetType())); 179 mDrainStatus = DrainStatus::DRAINED; 180 mSamplesCount = 0; 181 mOutputsCount = 0; 182 mLastTime.reset(); 183 mInputTimesSet.clear(); 184 return FlushPromise::CreateAndResolve(true, __func__); 185 } 186 187 RefPtr<MediaDataDecoder::FlushPromise> WMFMediaDataDecoder::Flush() { 188 MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown); 189 190 return InvokeAsync(mTaskQueue, this, __func__, 191 &WMFMediaDataDecoder::ProcessFlush); 192 } 193 194 RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::ProcessDrain() { 195 AUTO_PROFILER_LABEL("WMFMediaDataDecoder::ProcessDrain", MEDIA_PLAYBACK); 196 MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); 197 if (!mMFTManager || mDrainStatus == DrainStatus::DRAINED) { 198 return DecodePromise::CreateAndResolve(DecodedData(), __func__); 199 } 200 201 if (mDrainStatus != DrainStatus::DRAINING) { 202 // Order the decoder to drain... 203 mMFTManager->Drain(); 204 mDrainStatus = DrainStatus::DRAINING; 205 } 206 207 // Then extract all available output. 208 DecodedData results; 209 HRESULT hr = ProcessOutput(results); 210 if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { 211 mDrainStatus = DrainStatus::DRAINED; 212 } 213 if (SUCCEEDED(hr) || hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { 214 if (results.Length() > 0 && 215 results.LastElement()->mType == MediaData::Type::VIDEO_DATA) { 216 const RefPtr<MediaData>& data = results.LastElement(); 217 if (mSamplesCount == 1 && data->mTime == media::TimeUnit::Zero()) { 218 // WMF is unable to calculate a duration if only a single sample 219 // was parsed. Additionally, the pts always comes out at 0 under those 220 // circumstances. 221 // Seeing that we've only fed the decoder a single frame, the pts 222 // and duration are known, it's of the last sample. 223 data->mTime = *mLastTime; 224 } 225 if (data->mTime == *mLastTime) { 226 // The WMF Video decoder is sometimes unable to provide a valid duration 227 // on the last sample even if it has been first set through 228 // SetSampleTime (appears to always happen on Windows 7). So we force 229 // set the duration of the last sample as it was input. 230 data->mDuration = mLastDuration; 231 } 232 } else if (results.Length() == 1 && 233 results.LastElement()->mType == MediaData::Type::AUDIO_DATA) { 234 // When we drain the audio decoder and one frame was queued (such as with 235 // AAC) the MFT will re-calculate the starting time rather than use the 236 // value set on the IMF Sample. 237 // This is normally an okay thing to do; however when dealing with poorly 238 // muxed content that has incorrect start time, it could lead to broken 239 // A/V sync. So we ensure that we use the compressed sample's time 240 // instead. Additionally, this is what all other audio decoders are doing 241 // anyway. 242 MOZ_ASSERT(mLastTime, 243 "We must have attempted to decode at least one frame to get " 244 "one decoded output"); 245 results.LastElement()->As<AudioData>()->SetOriginalStartTime(*mLastTime); 246 } 247 return DecodePromise::CreateAndResolve(std::move(results), __func__); 248 } 249 return ProcessError(hr, "MFTManager::Output"); 250 } 251 252 RefPtr<MediaDataDecoder::DecodePromise> WMFMediaDataDecoder::Drain() { 253 MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown); 254 255 return InvokeAsync(mTaskQueue, this, __func__, 256 &WMFMediaDataDecoder::ProcessDrain); 257 } 258 259 bool WMFMediaDataDecoder::IsHardwareAccelerated( 260 nsACString& aFailureReason) const { 261 MOZ_ASSERT(!mIsShutDown); 262 263 return mMFTManager && mMFTManager->IsHardwareAccelerated(aFailureReason); 264 } 265 266 void WMFMediaDataDecoder::SetSeekThreshold(const media::TimeUnit& aTime) { 267 MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown); 268 269 RefPtr<WMFMediaDataDecoder> self = this; 270 nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction( 271 "WMFMediaDataDecoder::SetSeekThreshold", [self, aTime]() { 272 MOZ_ASSERT(self->mTaskQueue->IsCurrentThreadIn()); 273 media::TimeUnit threshold = aTime; 274 self->mMFTManager->SetSeekThreshold(threshold); 275 }); 276 nsresult rv = mTaskQueue->Dispatch(runnable.forget()); 277 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); 278 (void)rv; 279 } 280 281 } // namespace mozilla