MFTDecoder.cpp (17145B)
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 "MFTDecoder.h" 8 9 #include "PlatformDecoderModule.h" 10 #include "WMFUtils.h" 11 #include "mozilla/Logging.h" 12 #include "mozilla/mscom/COMWrappers.h" 13 #include "mozilla/mscom/Utils.h" 14 #include "nsThreadUtils.h" 15 16 #define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) 17 #define LOGV(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Verbose, (__VA_ARGS__)) 18 19 namespace mozilla { 20 MFTDecoder::MFTDecoder() { 21 memset(&mInputStreamInfo, 0, sizeof(MFT_INPUT_STREAM_INFO)); 22 memset(&mOutputStreamInfo, 0, sizeof(MFT_OUTPUT_STREAM_INFO)); 23 } 24 25 MFTDecoder::~MFTDecoder() { 26 if (mActivate) { 27 // Releases all internal references to the created IMFTransform. 28 // https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/nf-mfobjects-imfactivate-shutdownobject 29 mActivate->ShutdownObject(); 30 } 31 } 32 33 HRESULT MFTDecoder::Create(const GUID& aCLSID) { 34 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 35 36 HRESULT hr = mscom::wrapped::CoCreateInstance( 37 aCLSID, nullptr, CLSCTX_INPROC_SERVER, 38 IID_PPV_ARGS(static_cast<IMFTransform**>(getter_AddRefs(mDecoder)))); 39 NS_WARNING_ASSERTION(SUCCEEDED(hr), "Failed to create MFT by CLSID"); 40 return hr; 41 } 42 43 HRESULT 44 MFTDecoder::Create(const GUID& aCategory, const GUID& aInSubtype, 45 const GUID& aOutSubtype) { 46 // Note: IMFTransform is documented to only be safe on MTA threads. 47 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 48 49 // Use video by default, but select audio if necessary. 50 mMajorType = aCategory == MFT_CATEGORY_AUDIO_DECODER ? MFMediaType_Audio 51 : MFMediaType_Video; 52 53 // Ignore null GUIDs to allow searching for all decoders supporting 54 // just one input or output type. 55 auto createInfo = [](const GUID& majortype, 56 const GUID& subtype) -> MFT_REGISTER_TYPE_INFO* { 57 if (IsEqualGUID(subtype, GUID_NULL)) { 58 return nullptr; 59 } 60 61 MFT_REGISTER_TYPE_INFO* info = new MFT_REGISTER_TYPE_INFO(); 62 info->guidMajorType = majortype; 63 info->guidSubtype = subtype; 64 return info; 65 }; 66 const MFT_REGISTER_TYPE_INFO* inInfo = createInfo(mMajorType, aInSubtype); 67 const MFT_REGISTER_TYPE_INFO* outInfo = createInfo(mMajorType, aOutSubtype); 68 69 // Request a decoder from the Windows API. 70 HRESULT hr; 71 IMFActivate** acts = nullptr; 72 UINT32 actsNum = 0; 73 74 hr = wmf::MFTEnumEx(aCategory, MFT_ENUM_FLAG_SORTANDFILTER, inInfo, outInfo, 75 &acts, &actsNum); 76 delete inInfo; 77 delete outInfo; 78 if (FAILED(hr)) { 79 NS_WARNING(nsPrintfCString("MFTEnumEx failed with code %lx", hr).get()); 80 return hr; 81 } 82 if (actsNum == 0) { 83 NS_WARNING("MFTEnumEx returned no IMFActivate instances"); 84 return WINCODEC_ERR_COMPONENTNOTFOUND; 85 } 86 auto guard = MakeScopeExit([&] { 87 // Start from index 1, acts[0] will be stored as a RefPtr to release later. 88 for (UINT32 i = 1; i < actsNum; i++) { 89 acts[i]->Release(); 90 } 91 CoTaskMemFree(acts); 92 }); 93 94 // Create the IMFTransform to do the decoding. 95 // Note: Ideally we would cache the IMFActivate and call 96 // IMFActivate::DetachObject, but doing so causes the MFTs to fail on 97 // MFT_MESSAGE_SET_D3D_MANAGER. 98 mActivate = RefPtr<IMFActivate>(acts[0]); 99 hr = mActivate->ActivateObject( 100 IID_PPV_ARGS(static_cast<IMFTransform**>(getter_AddRefs(mDecoder)))); 101 NS_WARNING_ASSERTION( 102 SUCCEEDED(hr), 103 nsPrintfCString("IMFActivate::ActivateObject failed with code %lx", hr) 104 .get()); 105 LOG("MFTDecoder::Create, created decoder, input=%s, output=%s", 106 GetSubTypeStr(aInSubtype).get(), GetSubTypeStr(aOutSubtype).get()); 107 return hr; 108 } 109 110 HRESULT 111 MFTDecoder::SetMediaTypes(IMFMediaType* aInputType, IMFMediaType* aOutputType, 112 const GUID& aFallbackSubType, 113 std::function<HRESULT(IMFMediaType*)>&& aCallback) { 114 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 115 116 // Set the input type to the one the caller gave us... 117 RETURN_IF_FAILED(mDecoder->SetInputType(0, aInputType, 0)); 118 119 GUID currentSubtype = {0}; 120 RETURN_IF_FAILED(aOutputType->GetGUID(MF_MT_SUBTYPE, ¤tSubtype)); 121 RETURN_IF_FAILED(SetDecoderOutputType(currentSubtype, aFallbackSubType, 122 aOutputType, std::move(aCallback))); 123 RETURN_IF_FAILED(mDecoder->GetInputStreamInfo(0, &mInputStreamInfo)); 124 RETURN_IF_FAILED(SendMFTMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0)); 125 RETURN_IF_FAILED(SendMFTMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0)); 126 return S_OK; 127 } 128 129 already_AddRefed<IMFAttributes> MFTDecoder::GetAttributes() { 130 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 131 RefPtr<IMFAttributes> attr; 132 HRESULT hr = mDecoder->GetAttributes(getter_AddRefs(attr)); 133 NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); 134 return attr.forget(); 135 } 136 137 already_AddRefed<IMFAttributes> MFTDecoder::GetOutputStreamAttributes() { 138 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 139 RefPtr<IMFAttributes> attr; 140 HRESULT hr = mDecoder->GetOutputStreamAttributes(0, getter_AddRefs(attr)); 141 NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); 142 return attr.forget(); 143 } 144 145 HRESULT 146 MFTDecoder::FindDecoderOutputType(const GUID& aFallbackSubType) { 147 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 148 MOZ_ASSERT(mOutputType, "SetDecoderTypes must have been called once"); 149 150 return FindDecoderOutputTypeWithSubtype(mOutputSubType, aFallbackSubType); 151 } 152 153 HRESULT 154 MFTDecoder::FindDecoderOutputTypeWithSubtype(const GUID& aSubType, 155 const GUID& aFallbackSubType) { 156 return SetDecoderOutputType(aSubType, aFallbackSubType, nullptr, 157 [](IMFMediaType*) { return S_OK; }); 158 } 159 160 // This method first attempts to find the provided aSubType in the compatible 161 // list reported by the decoder, if found it will be set up. 162 // 163 // If aSubType is not found in the compatible list, and aFallbackSubType is 164 // GUID_NULL, this will promptly return E_FAIL instead of attempting fallbacks. 165 // 166 // If aSubType is not found in the compatible list, and aFallbackSubType is not 167 // GUID_NULL, this method will attempt to find aFallbackSubType in the 168 // compatible list, if found it will be set up. 169 // 170 // If aSubType is not found in the compatible list, and aFallbackSubType is not 171 // GUID_NULL, but aFallbackSubType is also not found in the compatible list, 172 // this method will set up the last available compatible type reported by the 173 // decoder. 174 // 175 // Callers that do not want a fallback behavior must pass GUID_NULL as 176 // aFallbackSubType. 177 HRESULT 178 MFTDecoder::SetDecoderOutputType( 179 const GUID& aSubType, const GUID& aFallbackSubType, 180 IMFMediaType* aTypeToUse, 181 std::function<HRESULT(IMFMediaType*)>&& aCallback) { 182 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 183 NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER); 184 185 if (!aTypeToUse) { 186 aTypeToUse = mOutputType; 187 } 188 189 // https://learn.microsoft.com/en-us/windows/win32/api/mftransform/nf-mftransform-imftransform-getoutputavailabletype 190 // Enumerate the output types in order to find a compatible type. However, not 191 // every MFT is required to implement this, and some MFT even can not return 192 // an accurate list of output types until the MFT receives the first input 193 // sample. Therefore, if we can't find one, we will use the last available 194 // type as fallback type. 195 RefPtr<IMFMediaType> outputType, lastOutputType; 196 GUID lastOutputSubtype; 197 enum class Result : uint8_t { 198 eNotFound, 199 eFoundCompatibleType, 200 eFoundPreferredType, 201 }; 202 Result foundType = Result::eNotFound; 203 UINT32 typeIndex = 0; 204 while (SUCCEEDED(mDecoder->GetOutputAvailableType( 205 0, typeIndex++, getter_AddRefs(outputType)))) { 206 GUID outSubtype = {0}; 207 RETURN_IF_FAILED(outputType->GetGUID(MF_MT_SUBTYPE, &outSubtype)); 208 LOGV("Searching compatible type, input=%s, output=%s", 209 GetSubTypeStr(aSubType).get(), GetSubTypeStr(outSubtype).get()); 210 lastOutputType = outputType; 211 lastOutputSubtype = outSubtype; 212 if (aSubType == outSubtype) { 213 foundType = Result::eFoundCompatibleType; 214 break; 215 } 216 outputType = nullptr; 217 } 218 219 if (foundType == Result::eNotFound) { 220 if (aFallbackSubType == GUID_NULL) { 221 // The caller specifically did not want a fallback, so just return. 222 return E_FAIL; 223 } 224 typeIndex = 0; 225 LOG("Can't find a compatible output type, searching with the preferred " 226 "type instead"); 227 const GUID preferredSubtype = aFallbackSubType; 228 while (SUCCEEDED(mDecoder->GetOutputAvailableType( 229 0, typeIndex++, getter_AddRefs(outputType)))) { 230 GUID outSubtype = {0}; 231 RETURN_IF_FAILED(outputType->GetGUID(MF_MT_SUBTYPE, &outSubtype)); 232 LOGV("Searching preferred type, input=%s, output=%s", 233 GetSubTypeStr(preferredSubtype).get(), 234 GetSubTypeStr(outSubtype).get()); 235 lastOutputType = outputType; 236 lastOutputSubtype = outSubtype; 237 if (preferredSubtype == outSubtype) { 238 foundType = Result::eFoundPreferredType; 239 break; 240 } 241 outputType = nullptr; 242 } 243 } 244 245 if (!lastOutputType) { 246 LOG("No available output type!"); 247 return E_FAIL; 248 } 249 250 if (foundType != Result::eNotFound) { 251 LOG("Found %s type %s", 252 foundType == Result::eFoundCompatibleType ? "compatible" : "preferred", 253 GetSubTypeStr(lastOutputSubtype).get()); 254 } else { 255 LOG("Can't find compatible and preferred type, use the last available type " 256 "%s", 257 GetSubTypeStr(lastOutputSubtype).get()); 258 } 259 RETURN_IF_FAILED(aCallback(lastOutputType)); 260 RETURN_IF_FAILED(mDecoder->SetOutputType(0, lastOutputType, 0)); 261 RETURN_IF_FAILED(mDecoder->GetOutputStreamInfo(0, &mOutputStreamInfo)); 262 mMFTProvidesOutputSamples = 263 IsFlagSet(mOutputStreamInfo.dwFlags, MFT_OUTPUT_STREAM_PROVIDES_SAMPLES); 264 mOutputType = lastOutputType; 265 mOutputSubType = lastOutputSubtype; 266 return S_OK; 267 } 268 269 HRESULT 270 MFTDecoder::SendMFTMessage(MFT_MESSAGE_TYPE aMsg, ULONG_PTR aData) { 271 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 272 NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER); 273 LOG("Send message '%s'", MFTMessageTypeToStr(aMsg)); 274 HRESULT hr = mDecoder->ProcessMessage(aMsg, aData); 275 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 276 return S_OK; 277 } 278 279 HRESULT 280 MFTDecoder::CreateInputSample(const uint8_t* aData, uint32_t aDataSize, 281 int64_t aTimestamp, int64_t aDuration, 282 RefPtr<IMFSample>* aOutSample) { 283 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 284 NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER); 285 286 HRESULT hr; 287 RefPtr<IMFSample> sample; 288 hr = wmf::MFCreateSample(getter_AddRefs(sample)); 289 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 290 291 RefPtr<IMFMediaBuffer> buffer; 292 int32_t bufferSize = 293 std::max<uint32_t>(uint32_t(mInputStreamInfo.cbSize), aDataSize); 294 UINT32 alignment = 295 (mInputStreamInfo.cbAlignment > 1) ? mInputStreamInfo.cbAlignment - 1 : 0; 296 hr = wmf::MFCreateAlignedMemoryBuffer(bufferSize, alignment, 297 getter_AddRefs(buffer)); 298 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 299 300 DWORD maxLength = 0; 301 DWORD currentLength = 0; 302 BYTE* dst = nullptr; 303 hr = buffer->Lock(&dst, &maxLength, ¤tLength); 304 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 305 306 // Copy data into sample's buffer. 307 memcpy(dst, aData, aDataSize); 308 309 hr = buffer->Unlock(); 310 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 311 312 hr = buffer->SetCurrentLength(aDataSize); 313 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 314 315 hr = sample->AddBuffer(buffer); 316 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 317 318 hr = sample->SetSampleTime(UsecsToHNs(aTimestamp)); 319 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 320 321 if (aDuration == 0) { 322 // If the sample duration is 0, the decoder will try and estimate the 323 // duration. In practice this can lead to some wildly incorrect durations, 324 // as in bug 1560440. The Microsoft docs seem conflicting here with 325 // `IMFSample::SetSampleDuration` stating 'The duration can also be zero. 326 // This might be valid for some types of data.' However, 327 // `IMFSample::GetSampleDuration method` states 'If the retrieved duration 328 // is zero, or if the method returns MF_E_NO_SAMPLE_DURATION, the duration 329 // is unknown. In that case, it might be possible to calculate the duration 330 // from the media type--for example, by using the video frame rate or the 331 // audio sampling rate.' The latter of those seems to be how the decoder 332 // handles 0 duration, hence why it estimates. 333 // 334 // Since our demuxing pipeline can create 0 duration samples, and since the 335 // decoder will override them to something positive anyway, setting them to 336 // have a trivial duration seems like the lesser of evils. 337 aDuration = 1; 338 } 339 hr = sample->SetSampleDuration(UsecsToHNs(aDuration)); 340 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 341 342 *aOutSample = sample.forget(); 343 344 return S_OK; 345 } 346 347 HRESULT 348 MFTDecoder::CreateOutputSample(RefPtr<IMFSample>* aOutSample) { 349 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 350 NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER); 351 352 HRESULT hr; 353 RefPtr<IMFSample> sample; 354 hr = wmf::MFCreateSample(getter_AddRefs(sample)); 355 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 356 357 RefPtr<IMFMediaBuffer> buffer; 358 int32_t bufferSize = mOutputStreamInfo.cbSize; 359 UINT32 alignment = (mOutputStreamInfo.cbAlignment > 1) 360 ? mOutputStreamInfo.cbAlignment - 1 361 : 0; 362 hr = wmf::MFCreateAlignedMemoryBuffer(bufferSize, alignment, 363 getter_AddRefs(buffer)); 364 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 365 366 hr = sample->AddBuffer(buffer); 367 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 368 369 *aOutSample = sample.forget(); 370 371 return S_OK; 372 } 373 374 HRESULT 375 MFTDecoder::Output(RefPtr<IMFSample>* aOutput) { 376 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 377 NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER); 378 379 HRESULT hr; 380 381 MFT_OUTPUT_DATA_BUFFER output = {0}; 382 383 bool providedSample = false; 384 RefPtr<IMFSample> sample; 385 if (*aOutput) { 386 output.pSample = *aOutput; 387 providedSample = true; 388 } else if (!mMFTProvidesOutputSamples) { 389 hr = CreateOutputSample(&sample); 390 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 391 output.pSample = sample; 392 } 393 394 DWORD status = 0; 395 hr = mDecoder->ProcessOutput(0, 1, &output, &status); 396 if (output.pEvents) { 397 // We must release this, as per the IMFTransform::ProcessOutput() 398 // MSDN documentation. 399 output.pEvents->Release(); 400 output.pEvents = nullptr; 401 } 402 403 if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { 404 return MF_E_TRANSFORM_STREAM_CHANGE; 405 } 406 407 if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { 408 // Not enough input to produce output. This is an expected failure, 409 // so don't warn on encountering it. 410 return hr; 411 } 412 // Treat other errors as unexpected, and warn. 413 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 414 415 if (!output.pSample) { 416 return S_OK; 417 } 418 419 if (mDiscontinuity) { 420 output.pSample->SetUINT32(MFSampleExtension_Discontinuity, TRUE); 421 mDiscontinuity = false; 422 } 423 424 *aOutput = output.pSample; // AddRefs 425 if (mMFTProvidesOutputSamples && !providedSample) { 426 // If the MFT is providing samples, we must release the sample here. 427 // Typically only the H.264 MFT provides samples when using DXVA, 428 // and it always re-uses the same sample, so if we don't release it 429 // MFT::ProcessOutput() deadlocks waiting for the sample to be released. 430 output.pSample->Release(); 431 output.pSample = nullptr; 432 } 433 434 return S_OK; 435 } 436 437 HRESULT 438 MFTDecoder::Input(const uint8_t* aData, uint32_t aDataSize, int64_t aTimestamp, 439 int64_t aDuration) { 440 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 441 NS_ENSURE_TRUE(mDecoder != nullptr, E_POINTER); 442 443 RefPtr<IMFSample> input; 444 HRESULT hr = 445 CreateInputSample(aData, aDataSize, aTimestamp, aDuration, &input); 446 NS_ENSURE_TRUE(SUCCEEDED(hr) && input != nullptr, hr); 447 448 return Input(input); 449 } 450 451 HRESULT 452 MFTDecoder::Input(IMFSample* aSample) { 453 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 454 HRESULT hr = mDecoder->ProcessInput(0, aSample, 0); 455 if (hr == MF_E_NOTACCEPTING) { 456 // MFT *already* has enough data to produce a sample. Retrieve it. 457 return MF_E_NOTACCEPTING; 458 } 459 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 460 461 return S_OK; 462 } 463 464 HRESULT 465 MFTDecoder::Flush() { 466 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 467 HRESULT hr = SendMFTMessage(MFT_MESSAGE_COMMAND_FLUSH, 0); 468 NS_ENSURE_TRUE(SUCCEEDED(hr), hr); 469 470 mDiscontinuity = true; 471 472 return S_OK; 473 } 474 475 HRESULT 476 MFTDecoder::GetInputMediaType(RefPtr<IMFMediaType>& aMediaType) { 477 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 478 NS_ENSURE_TRUE(mDecoder, E_POINTER); 479 return mDecoder->GetInputCurrentType(0, getter_AddRefs(aMediaType)); 480 } 481 482 HRESULT 483 MFTDecoder::GetOutputMediaType(RefPtr<IMFMediaType>& aMediaType) { 484 MOZ_ASSERT(mscom::IsCurrentThreadMTA()); 485 NS_ENSURE_TRUE(mDecoder, E_POINTER); 486 return mDecoder->GetOutputCurrentType(0, getter_AddRefs(aMediaType)); 487 } 488 489 #undef LOG 490 491 } // namespace mozilla