tor-browser

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

MFMediaEngineVideoStream.cpp (18488B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "MFMediaEngineVideoStream.h"
      6 
      7 #include "MFMediaEngineUtils.h"
      8 #include "mozilla/StaticPrefs_media.h"
      9 #include "mozilla/layers/DcompSurfaceImage.h"
     10 
     11 namespace mozilla {
     12 
     13 #define LOG(msg, ...)                           \
     14  MOZ_LOG(gMFMediaEngineLog, LogLevel::Debug,   \
     15          ("MFMediaStream=%p (%s), " msg, this, \
     16           this->GetDescriptionName().get(), ##__VA_ARGS__))
     17 
     18 #define LOGV(msg, ...)                          \
     19  MOZ_LOG(gMFMediaEngineLog, LogLevel::Verbose, \
     20          ("MFMediaStream=%p (%s), " msg, this, \
     21           this->GetDescriptionName().get(), ##__VA_ARGS__))
     22 
     23 using Microsoft::WRL::ComPtr;
     24 using Microsoft::WRL::MakeAndInitialize;
     25 
     26 /* static */
     27 MFMediaEngineVideoStream* MFMediaEngineVideoStream::Create(
     28    uint64_t aStreamId, const TrackInfo& aInfo, bool aIsEncryptedCustomInit,
     29    MFMediaSource* aParentSource) {
     30  MFMediaEngineVideoStream* stream;
     31  MOZ_ASSERT(aInfo.IsVideo());
     32  if (FAILED(MakeAndInitialize<MFMediaEngineVideoStream>(
     33          &stream, aStreamId, aInfo, aIsEncryptedCustomInit, aParentSource))) {
     34    return nullptr;
     35  }
     36  stream->mStreamType =
     37      GetStreamTypeFromMimeType(aInfo.GetAsVideoInfo()->mMimeType);
     38  MOZ_ASSERT(StreamTypeIsVideo(stream->mStreamType));
     39  stream->mHasReceivedInitialCreateDecoderConfig = false;
     40  stream->SetDCompSurfaceHandle(INVALID_HANDLE_VALUE, gfx::IntSize{});
     41  return stream;
     42 }
     43 
     44 void MFMediaEngineVideoStream::SetKnowsCompositor(
     45    layers::KnowsCompositor* aKnowsCompositor) {
     46  ComPtr<MFMediaEngineVideoStream> self = this;
     47  (void)mTaskQueue->Dispatch(NS_NewRunnableFunction(
     48      "MFMediaEngineStream::SetKnowsCompositor",
     49      [self, knowCompositor = RefPtr<layers::KnowsCompositor>{aKnowsCompositor},
     50       this]() {
     51        mKnowsCompositor = knowCompositor;
     52        LOG("Set SetKnowsCompositor=%p", mKnowsCompositor.get());
     53        ResolvePendingPromisesIfNeeded();
     54      }));
     55 }
     56 
     57 void MFMediaEngineVideoStream::SetDCompSurfaceHandle(HANDLE aDCompSurfaceHandle,
     58                                                     gfx::IntSize aDisplay) {
     59  ComPtr<MFMediaEngineVideoStream> self = this;
     60  (void)mTaskQueue->Dispatch(NS_NewRunnableFunction(
     61      "MFMediaEngineStream::SetDCompSurfaceHandle",
     62      [self, aDCompSurfaceHandle, aDisplay, this]() {
     63        if (mDCompSurfaceHandle == aDCompSurfaceHandle) {
     64          return;
     65        }
     66        mDCompSurfaceHandle = aDCompSurfaceHandle;
     67        mNeedRecreateImage = true;
     68        {
     69          MutexAutoLock lock(mMutex);
     70          if (aDCompSurfaceHandle != INVALID_HANDLE_VALUE &&
     71              aDisplay != mDisplay) {
     72            LOG("Update display [%dx%d] -> [%dx%d]", mDisplay.Width(),
     73                mDisplay.Height(), aDisplay.Width(), aDisplay.Height());
     74            mDisplay = aDisplay;
     75          }
     76        }
     77        LOG("Set DCompSurfaceHandle, handle=%p", mDCompSurfaceHandle);
     78        ResolvePendingPromisesIfNeeded();
     79      }));
     80 }
     81 
     82 HRESULT MFMediaEngineVideoStream::CreateMediaType(const TrackInfo& aInfo,
     83                                                  IMFMediaType** aMediaType) {
     84  auto& videoInfo = *aInfo.GetAsVideoInfo();
     85  mIsEncrypted = videoInfo.mCrypto.IsEncrypted();
     86 
     87  GUID subType = VideoMimeTypeToMediaFoundationSubtype(videoInfo.mMimeType);
     88  NS_ENSURE_TRUE(subType != GUID_NULL, MF_E_TOPO_CODEC_NOT_FOUND);
     89 
     90  // https://docs.microsoft.com/en-us/windows/win32/medfound/media-type-attributes
     91  ComPtr<IMFMediaType> mediaType;
     92  RETURN_IF_FAILED(wmf::MFCreateMediaType(&mediaType));
     93  RETURN_IF_FAILED(mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video));
     94  RETURN_IF_FAILED(mediaType->SetGUID(MF_MT_SUBTYPE, subType));
     95 
     96  const auto& image = videoInfo.mImage;
     97  UINT32 imageWidth = image.Width();
     98  UINT32 imageHeight = image.Height();
     99  RETURN_IF_FAILED(MFSetAttributeSize(mediaType.Get(), MF_MT_FRAME_SIZE,
    100                                      imageWidth, imageHeight));
    101 
    102  UINT32 displayWidth = videoInfo.mDisplay.Width();
    103  UINT32 displayHeight = videoInfo.mDisplay.Height();
    104  {
    105    MutexAutoLock lock(mMutex);
    106    mDisplay = videoInfo.mDisplay;
    107  }
    108  // PAR = DAR / SAR = (DW / DH) / (SW / SH) = (DW * SH) / (DH * SW)
    109  RETURN_IF_FAILED(MFSetAttributeRatio(
    110      mediaType.Get(), MF_MT_PIXEL_ASPECT_RATIO, displayWidth * imageHeight,
    111      displayHeight * imageWidth));
    112 
    113  // https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/ns-mfobjects-mfoffset
    114  // The value of the MFOffset number is value + (fract / 65536.0f).
    115  static const auto ToMFOffset = [](float aValue) {
    116    MFOffset offset;
    117    offset.value = static_cast<short>(aValue);
    118    offset.fract = static_cast<WORD>(65536 * (aValue - offset.value));
    119    return offset;
    120  };
    121  MFVideoArea area;
    122  area.OffsetX = ToMFOffset(videoInfo.ImageRect().x);
    123  area.OffsetY = ToMFOffset(videoInfo.ImageRect().y);
    124  area.Area = {(LONG)imageWidth, (LONG)imageHeight};
    125  RETURN_IF_FAILED(mediaType->SetBlob(MF_MT_GEOMETRIC_APERTURE, (UINT8*)&area,
    126                                      sizeof(area)));
    127 
    128  // https://docs.microsoft.com/en-us/windows/win32/api/mfapi/ne-mfapi-mfvideorotationformat
    129  static const auto ToMFVideoRotationFormat = [](VideoRotation aRotation) {
    130    using Rotation = VideoRotation;
    131    switch (aRotation) {
    132      case Rotation::kDegree_0:
    133        return MFVideoRotationFormat_0;
    134      case Rotation::kDegree_90:
    135        return MFVideoRotationFormat_90;
    136      case Rotation::kDegree_180:
    137        return MFVideoRotationFormat_180;
    138      default:
    139        MOZ_ASSERT(aRotation == Rotation::kDegree_270);
    140        return MFVideoRotationFormat_270;
    141    }
    142  };
    143  const auto rotation = ToMFVideoRotationFormat(videoInfo.mRotation);
    144  RETURN_IF_FAILED(mediaType->SetUINT32(MF_MT_VIDEO_ROTATION, rotation));
    145 
    146  static const auto ToMFVideoTransFunc =
    147      [](const Maybe<gfx::YUVColorSpace>& aColorSpace) {
    148        using YUVColorSpace = gfx::YUVColorSpace;
    149        if (!aColorSpace) {
    150          return MFVideoTransFunc_Unknown;
    151        }
    152        // https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/ne-mfobjects-mfvideotransferfunction
    153        switch (*aColorSpace) {
    154          case YUVColorSpace::BT601:
    155          case YUVColorSpace::BT709:
    156            return MFVideoTransFunc_709;
    157          case YUVColorSpace::BT2020:
    158            return MFVideoTransFunc_2020;
    159          case YUVColorSpace::Identity:
    160            return MFVideoTransFunc_sRGB;
    161          default:
    162            return MFVideoTransFunc_Unknown;
    163        }
    164      };
    165  const auto transFunc = ToMFVideoTransFunc(videoInfo.mColorSpace);
    166  RETURN_IF_FAILED(mediaType->SetUINT32(MF_MT_TRANSFER_FUNCTION, transFunc));
    167 
    168  static const auto ToMFVideoPrimaries =
    169      [](const Maybe<gfx::YUVColorSpace>& aColorSpace) {
    170        using YUVColorSpace = gfx::YUVColorSpace;
    171        if (!aColorSpace) {
    172          return MFVideoPrimaries_Unknown;
    173        }
    174        // https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/ne-mfobjects-mfvideoprimaries
    175        switch (*aColorSpace) {
    176          case YUVColorSpace::BT601:
    177            return MFVideoPrimaries_Unknown;
    178          case YUVColorSpace::BT709:
    179            return MFVideoPrimaries_BT709;
    180          case YUVColorSpace::BT2020:
    181            return MFVideoPrimaries_BT2020;
    182          case YUVColorSpace::Identity:
    183            return MFVideoPrimaries_BT709;
    184          default:
    185            return MFVideoPrimaries_Unknown;
    186        }
    187      };
    188  const auto videoPrimaries = ToMFVideoPrimaries(videoInfo.mColorSpace);
    189  RETURN_IF_FAILED(mediaType->SetUINT32(MF_MT_VIDEO_PRIMARIES, videoPrimaries));
    190 
    191  LOG("Created video type, subtype=%s, image=[%ux%u], display=[%ux%u], "
    192      "rotation=%s, tranFuns=%s, primaries=%s, encrypted=%d",
    193      GUIDToStr(subType), imageWidth, imageHeight, displayWidth, displayHeight,
    194      MFVideoRotationFormatToStr(rotation),
    195      MFVideoTransferFunctionToStr(transFunc),
    196      MFVideoPrimariesToStr(videoPrimaries), mIsEncrypted);
    197  if (IsEncrypted()) {
    198    ComPtr<IMFMediaType> protectedMediaType;
    199    RETURN_IF_FAILED(wmf::MFWrapMediaType(mediaType.Get(),
    200                                          MFMediaType_Protected, subType,
    201                                          protectedMediaType.GetAddressOf()));
    202    LOG("Wrap MFMediaType_Video into MFMediaType_Protected");
    203    *aMediaType = protectedMediaType.Detach();
    204  } else {
    205    *aMediaType = mediaType.Detach();
    206  }
    207  return S_OK;
    208 }
    209 
    210 bool MFMediaEngineVideoStream::HasEnoughRawData() const {
    211  // If more than this much raw video is queued, we'll hold off request more
    212  // video.
    213  return mRawDataQueueForFeedingEngine.PreciseDuration() >=
    214         StaticPrefs::media_wmf_media_engine_raw_data_threshold_video();
    215 }
    216 
    217 bool MFMediaEngineVideoStream::IsDCompImageReady() {
    218  AssertOnTaskQueue();
    219  if (!mDCompSurfaceHandle || mDCompSurfaceHandle == INVALID_HANDLE_VALUE) {
    220    LOGV("Can't create image without a valid dcomp surface handle");
    221    return false;
    222  }
    223 
    224  if (!mKnowsCompositor) {
    225    LOGV("Can't create image without the knows compositor");
    226    return false;
    227  }
    228 
    229  if (!mDcompSurfaceImage || mNeedRecreateImage) {
    230    MutexAutoLock lock(mMutex);
    231    // DirectComposition only supports RGBA. We use DXGI_FORMAT_B8G8R8A8_UNORM
    232    // as a default because we can't know what format the dcomp surface is.
    233    // https://docs.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositionsurfacefactory-createsurface
    234    mDcompSurfaceImage = new layers::DcompSurfaceImage(
    235        mDCompSurfaceHandle, mDisplay, gfx::SurfaceFormat::B8G8R8A8,
    236        mKnowsCompositor);
    237    mNeedRecreateImage = false;
    238    LOG("Created dcomp surface image, handle=%p, size=[%u,%u]",
    239        mDCompSurfaceHandle, mDisplay.Width(), mDisplay.Height());
    240  }
    241  return true;
    242 }
    243 
    244 RefPtr<MediaDataDecoder::DecodePromise> MFMediaEngineVideoStream::OutputData(
    245    RefPtr<MediaRawData> aSample) {
    246  if (IsShutdown()) {
    247    return MediaDataDecoder::DecodePromise::CreateAndReject(
    248        MediaResult(NS_ERROR_FAILURE,
    249                    RESULT_DETAIL("MFMediaEngineStream is shutdown")),
    250        __func__);
    251  }
    252  AssertOnTaskQueue();
    253  NotifyNewData(aSample);
    254  MediaDataDecoder::DecodedData outputs;
    255  if (RefPtr<MediaData> outputData = OutputDataInternal()) {
    256    outputs.AppendElement(outputData);
    257    LOGV("Output data [%" PRId64 ",%" PRId64 "]",
    258         outputData->mTime.ToMicroseconds(),
    259         outputData->GetEndTime().ToMicroseconds());
    260  }
    261  if (ShouldDelayVideoDecodeBeforeDcompReady()) {
    262    LOG("Dcomp isn't ready and we already have enough video data. We will send "
    263        "them back together at one when Dcomp is ready");
    264    return mVideoDecodeBeforeDcompPromise.Ensure(__func__);
    265  }
    266  return MediaDataDecoder::DecodePromise::CreateAndResolve(std::move(outputs),
    267                                                           __func__);
    268 }
    269 
    270 already_AddRefed<MediaData> MFMediaEngineVideoStream::OutputDataInternal() {
    271  AssertOnTaskQueue();
    272  if (mRawDataQueueForGeneratingOutput.GetSize() == 0 || !IsDCompImageReady()) {
    273    return nullptr;
    274  }
    275  RefPtr<MediaRawData> sample = mRawDataQueueForGeneratingOutput.PopFront();
    276  RefPtr<VideoData> output;
    277  {
    278    MutexAutoLock lock(mMutex);
    279    output = VideoData::CreateFromImage(
    280        mDisplay, sample->mOffset, sample->mTime, sample->mDuration,
    281        mDcompSurfaceImage, sample->mKeyframe, sample->mTimecode);
    282  }
    283  return output.forget();
    284 }
    285 
    286 RefPtr<MediaDataDecoder::DecodePromise> MFMediaEngineVideoStream::Drain() {
    287  AssertOnTaskQueue();
    288  MediaDataDecoder::DecodedData outputs;
    289  if (!IsDCompImageReady()) {
    290    LOGV("Waiting for dcomp image for draining");
    291    // A workaround for a special case where we have sent all input data to the
    292    // media engine, and waiting for an output. Sometime media engine would
    293    // never return the first frame to us, unless we notify it the end event,
    294    // which happens on the case where the video only contains one frame. If we
    295    // don't send end event to the media engine, the drain promise would be
    296    // pending forever.
    297    if (!mSampleRequestTokens.empty() &&
    298        mRawDataQueueForFeedingEngine.GetSize() == 0) {
    299      NotifyEndEvent();
    300    }
    301    return mPendingDrainPromise.Ensure(__func__);
    302  }
    303  return MFMediaEngineStream::Drain();
    304 }
    305 
    306 RefPtr<MediaDataDecoder::FlushPromise> MFMediaEngineVideoStream::Flush() {
    307  AssertOnTaskQueue();
    308  auto promise = MFMediaEngineStream::Flush();
    309  mPendingDrainPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
    310  mVideoDecodeBeforeDcompPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED,
    311                                                __func__);
    312  return promise;
    313 }
    314 
    315 void MFMediaEngineVideoStream::ResolvePendingPromisesIfNeeded() {
    316  AssertOnTaskQueue();
    317  if (!IsDCompImageReady()) {
    318    return;
    319  }
    320 
    321  // Resolve decoding promise first, then drain promise
    322  if (!mVideoDecodeBeforeDcompPromise.IsEmpty()) {
    323    MediaDataDecoder::DecodedData outputs;
    324    while (RefPtr<MediaData> outputData = OutputDataInternal()) {
    325      outputs.AppendElement(outputData);
    326      LOGV("Output data [%" PRId64 ",%" PRId64 "]",
    327           outputData->mTime.ToMicroseconds(),
    328           outputData->GetEndTime().ToMicroseconds());
    329    }
    330    mVideoDecodeBeforeDcompPromise.Resolve(std::move(outputs), __func__);
    331    LOG("Resolved video decode before Dcomp promise");
    332  }
    333 
    334  // This drain promise could return no data, if all data has been processed in
    335  // the decoding promise.
    336  if (!mPendingDrainPromise.IsEmpty()) {
    337    MediaDataDecoder::DecodedData outputs;
    338    while (RefPtr<MediaData> outputData = OutputDataInternal()) {
    339      outputs.AppendElement(outputData);
    340      LOGV("Output data [%" PRId64 ",%" PRId64 "]",
    341           outputData->mTime.ToMicroseconds(),
    342           outputData->GetEndTime().ToMicroseconds());
    343    }
    344    mPendingDrainPromise.Resolve(std::move(outputs), __func__);
    345    LOG("Resolved pending drain promise");
    346  }
    347 }
    348 
    349 MediaDataDecoder::ConversionRequired MFMediaEngineVideoStream::NeedsConversion()
    350    const {
    351  return mStreamType == WMFStreamType::H264 ||
    352                 mStreamType == WMFStreamType::HEVC
    353             ? MediaDataDecoder::ConversionRequired::kNeedAnnexB
    354             : MediaDataDecoder::ConversionRequired::kNeedNone;
    355 }
    356 
    357 void MFMediaEngineVideoStream::SetConfig(const TrackInfo& aConfig) {
    358  MOZ_ASSERT(aConfig.IsVideo());
    359  ComPtr<MFMediaEngineStream> self = this;
    360  (void)mTaskQueue->Dispatch(
    361      NS_NewRunnableFunction("MFMediaEngineStream::SetConfig",
    362                             [self, info = *aConfig.GetAsVideoInfo(), this]() {
    363                               if (mHasReceivedInitialCreateDecoderConfig) {
    364                                 // Here indicating a new config for video,
    365                                 // which is triggered by the media change
    366                                 // monitor, so we need to update the config.
    367                                 UpdateConfig(info);
    368                               }
    369                               mHasReceivedInitialCreateDecoderConfig = true;
    370                             }));
    371 }
    372 
    373 void MFMediaEngineVideoStream::UpdateConfig(const VideoInfo& aInfo) {
    374  AssertOnTaskQueue();
    375  // Disable explicit format change event for H264/HEVC to allow switching to
    376  // the new stream without a full re-create, which will be much faster. This is
    377  // also due to the fact that the MFT decoder can handle some format changes
    378  // without a format change event. For format changes that the MFT decoder
    379  // cannot support (e.g. codec change), the playback will fail later with
    380  // MF_E_INVALIDMEDIATYPE (0xC00D36B4).
    381  if (mStreamType == WMFStreamType::H264 ||
    382      mStreamType == WMFStreamType::HEVC) {
    383    return;
    384  }
    385 
    386  LOG("Video config changed, will update stream descriptor");
    387  PROFILER_MARKER_TEXT("VideoConfigChange", MEDIA_PLAYBACK, {},
    388                       nsPrintfCString("stream=%s, id=%" PRIu64,
    389                                       GetDescriptionName().get(), mStreamId));
    390  ComPtr<IMFMediaType> mediaType;
    391  RETURN_VOID_IF_FAILED(CreateMediaType(aInfo, mediaType.GetAddressOf()));
    392  RETURN_VOID_IF_FAILED(GenerateStreamDescriptor(mediaType));
    393  RETURN_VOID_IF_FAILED(mMediaEventQueue->QueueEventParamUnk(
    394      MEStreamFormatChanged, GUID_NULL, S_OK, mediaType.Get()));
    395 }
    396 
    397 void MFMediaEngineVideoStream::ShutdownCleanUpOnTaskQueue() {
    398  AssertOnTaskQueue();
    399  mPendingDrainPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
    400  mVideoDecodeBeforeDcompPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED,
    401                                                __func__);
    402 }
    403 
    404 void MFMediaEngineVideoStream::SendRequestSampleEvent(bool aIsEnough) {
    405  AssertOnTaskQueue();
    406  MFMediaEngineStream::SendRequestSampleEvent(aIsEnough);
    407  // We need more data to be sent in, we should resolve the promise to allow
    408  // more input data to be sent.
    409  if (!aIsEnough && !mVideoDecodeBeforeDcompPromise.IsEmpty()) {
    410    LOG("Resolved pending input promise to allow more input be sent in");
    411    mVideoDecodeBeforeDcompPromise.Resolve(MediaDataDecoder::DecodedData{},
    412                                           __func__);
    413  }
    414 }
    415 
    416 bool MFMediaEngineVideoStream::IsEnded() const {
    417  AssertOnTaskQueue();
    418  // If a video only contains one frame, the media engine won't return a decoded
    419  // frame before we tell it the track is already ended. However, due to the
    420  // constraint of our media pipeline, the format reader won't notify EOS until
    421  // the draining finishes, which causes a deadlock. Therefore, we would
    422  // consider having pending drain promise as a sign of EOS as well, in order to
    423  // get the decoded frame and revolve the drain promise.
    424  return (mReceivedEOS || !mPendingDrainPromise.IsEmpty()) &&
    425         mRawDataQueueForFeedingEngine.GetSize() == 0;
    426 }
    427 
    428 bool MFMediaEngineVideoStream::IsEncrypted() const {
    429  return mIsEncrypted || mIsEncryptedCustomInit;
    430 }
    431 
    432 bool MFMediaEngineVideoStream::ShouldDelayVideoDecodeBeforeDcompReady() {
    433  return HasEnoughRawData() && !IsDCompImageReady();
    434 }
    435 
    436 nsCString MFMediaEngineVideoStream::GetCodecName() const {
    437  switch (mStreamType) {
    438    case WMFStreamType::H264:
    439      return "h264"_ns;
    440    case WMFStreamType::VP8:
    441      return "vp8"_ns;
    442    case WMFStreamType::VP9:
    443      return "vp9"_ns;
    444    case WMFStreamType::AV1:
    445      return "av1"_ns;
    446    case WMFStreamType::HEVC:
    447      return "hevc"_ns;
    448    default:
    449      return "unknown"_ns;
    450  };
    451 }
    452 
    453 #undef LOG
    454 #undef LOGV
    455 
    456 }  // namespace mozilla