tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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, &currentSubtype));
    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, &currentLength);
    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