blob: 6d623bf3d8d5a4042cf7fbfd652b549d68431185 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/renderers/win/media_foundation_video_stream.h"
#include <initguid.h> // NOLINT(build/include_order)
#include <mferror.h> // NOLINT(build/include_order)
#include <wrl.h> // NOLINT(build/include_order)
#include "base/metrics/histogram_functions.h"
#include "base/numerics/safe_conversions.h"
#include "media/base/video_codecs.h"
#include "media/base/video_decoder_config.h"
#include "media/base/win/mf_helpers.h"
namespace media {
using Microsoft::WRL::ComPtr;
using Microsoft::WRL::MakeAndInitialize;
namespace {
// This is supported by Media Foundation.
DEFINE_MEDIATYPE_GUID(MFVideoFormat_THEORA, FCC('theo'))
GUID VideoCodecToMFSubtype(VideoCodec codec) {
switch (codec) {
case VideoCodec::kH264:
return MFVideoFormat_H264;
case VideoCodec::kVC1:
return MFVideoFormat_WVC1;
case VideoCodec::kMPEG2:
return MFVideoFormat_MPEG2;
case VideoCodec::kMPEG4:
return MFVideoFormat_MP4V;
case VideoCodec::kTheora:
return MFVideoFormat_THEORA;
case VideoCodec::kVP8:
return MFVideoFormat_VP80;
case VideoCodec::kVP9:
return MFVideoFormat_VP90;
case VideoCodec::kHEVC:
return MFVideoFormat_HEVC;
case VideoCodec::kDolbyVision:
// TODO(frankli): DolbyVision also supports H264 when the profile ID is 9
// (DOLBYVISION_PROFILE9). Will it be fine to use HEVC?
return MFVideoFormat_HEVC;
case VideoCodec::kAV1:
return MFVideoFormat_AV1;
default:
return GUID_NULL;
}
}
MFVideoRotationFormat VideoRotationToMF(VideoRotation rotation) {
DVLOG(2) << __func__ << ": rotation=" << rotation;
switch (rotation) {
case VideoRotation::VIDEO_ROTATION_0:
return MFVideoRotationFormat_0;
case VideoRotation::VIDEO_ROTATION_90:
return MFVideoRotationFormat_90;
case VideoRotation::VIDEO_ROTATION_180:
return MFVideoRotationFormat_180;
case VideoRotation::VIDEO_ROTATION_270:
return MFVideoRotationFormat_270;
}
}
MFVideoPrimaries VideoPrimariesToMF(
media::VideoColorSpace::PrimaryID primary_id) {
DVLOG(2) << __func__ << ": primary_id=" << static_cast<int>(primary_id);
switch (primary_id) {
case VideoColorSpace::PrimaryID::INVALID:
return MFVideoPrimaries_Unknown;
case VideoColorSpace::PrimaryID::BT709:
return MFVideoPrimaries_BT709;
case VideoColorSpace::PrimaryID::UNSPECIFIED:
return MFVideoPrimaries_Unknown;
case VideoColorSpace::PrimaryID::BT470M:
return MFVideoPrimaries_BT470_2_SysM;
case VideoColorSpace::PrimaryID::BT470BG:
return MFVideoPrimaries_BT470_2_SysBG;
case VideoColorSpace::PrimaryID::SMPTE170M:
return MFVideoPrimaries_SMPTE170M;
case VideoColorSpace::PrimaryID::SMPTE240M:
return MFVideoPrimaries_SMPTE240M;
case VideoColorSpace::PrimaryID::FILM:
return MFVideoPrimaries_Unknown;
case VideoColorSpace::PrimaryID::BT2020:
return MFVideoPrimaries_BT2020;
case VideoColorSpace::PrimaryID::SMPTEST428_1:
return MFVideoPrimaries_Unknown;
case VideoColorSpace::PrimaryID::SMPTEST431_2:
return MFVideoPrimaries_Unknown;
case VideoColorSpace::PrimaryID::SMPTEST432_1:
return MFVideoPrimaries_Unknown;
case VideoColorSpace::PrimaryID::EBU_3213_E:
return MFVideoPrimaries_EBU3213;
default:
DLOG(ERROR) << "VideoPrimariesToMF failed due to invalid Video Primary.";
}
return MFVideoPrimaries_Unknown;
}
MFVideoTransferFunction VideoTransferFunctionToMF(
media::VideoColorSpace::TransferID transfer_id) {
DVLOG(2) << __func__ << ": transfer_id=" << static_cast<int>(transfer_id);
switch (transfer_id) {
case VideoColorSpace::TransferID::INVALID:
return MFVideoTransFunc_Unknown;
case VideoColorSpace::TransferID::BT709:
return MFVideoTransFunc_709;
case VideoColorSpace::TransferID::UNSPECIFIED:
return MFVideoTransFunc_Unknown;
case VideoColorSpace::TransferID::GAMMA22:
return MFVideoTransFunc_22;
case VideoColorSpace::TransferID::GAMMA28:
return MFVideoTransFunc_28;
case VideoColorSpace::TransferID::SMPTE170M:
return MFVideoTransFunc_709;
case VideoColorSpace::TransferID::SMPTE240M:
return MFVideoTransFunc_240M;
case VideoColorSpace::TransferID::LINEAR:
return MFVideoTransFunc_10;
case VideoColorSpace::TransferID::LOG:
return MFVideoTransFunc_Log_100;
case VideoColorSpace::TransferID::LOG_SQRT:
return MFVideoTransFunc_Unknown;
case VideoColorSpace::TransferID::IEC61966_2_4:
return MFVideoTransFunc_Unknown;
case VideoColorSpace::TransferID::BT1361_ECG:
return MFVideoTransFunc_Unknown;
case VideoColorSpace::TransferID::IEC61966_2_1:
return MFVideoTransFunc_sRGB;
case VideoColorSpace::TransferID::BT2020_10:
return MFVideoTransFunc_2020;
case VideoColorSpace::TransferID::BT2020_12:
return MFVideoTransFunc_2020;
case VideoColorSpace::TransferID::SMPTEST2084:
return MFVideoTransFunc_2084;
case VideoColorSpace::TransferID::SMPTEST428_1:
return MFVideoTransFunc_Unknown;
case VideoColorSpace::TransferID::ARIB_STD_B67:
return MFVideoTransFunc_HLG;
default:
DLOG(ERROR) << "VideoTransferFunctionToMF failed due to invalid Transfer "
"Function.";
}
return MFVideoTransFunc_Unknown;
}
//
// https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/ns-mfobjects-mfoffset
// The value of the MFOffset number is value + (fract / 65536.0f).
MFOffset MakeOffset(float value) {
MFOffset offset;
offset.value = base::checked_cast<short>(value);
offset.fract = base::checked_cast<WORD>(65536 * (value - offset.value));
return offset;
}
// TODO(frankli): investigate if VideoCodecProfile is needed by MF.
HRESULT GetVideoType(const VideoDecoderConfig& decoder_config,
IMFMediaType** media_type_out) {
ComPtr<IMFMediaType> media_type;
RETURN_IF_FAILED(MFCreateMediaType(&media_type));
RETURN_IF_FAILED(media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video));
GUID mf_subtype = VideoCodecToMFSubtype(decoder_config.codec());
if (mf_subtype == GUID_NULL) {
DLOG(ERROR) << "Unsupported codec type: " << decoder_config.codec();
return MF_E_TOPO_CODEC_NOT_FOUND;
}
RETURN_IF_FAILED(media_type->SetGUID(MF_MT_SUBTYPE, mf_subtype));
UINT32 width = decoder_config.visible_rect().width();
UINT32 height = decoder_config.visible_rect().height();
RETURN_IF_FAILED(
MFSetAttributeSize(media_type.Get(), MF_MT_FRAME_SIZE, width, height));
UINT32 natural_width = decoder_config.natural_size().width();
UINT32 natural_height = decoder_config.natural_size().height();
RETURN_IF_FAILED(
MFSetAttributeRatio(media_type.Get(), MF_MT_PIXEL_ASPECT_RATIO,
height * natural_width, width * natural_height));
MFVideoArea area;
area.OffsetX =
MakeOffset(static_cast<float>(decoder_config.visible_rect().x()));
area.OffsetY =
MakeOffset(static_cast<float>(decoder_config.visible_rect().y()));
area.Area = decoder_config.natural_size().ToSIZE();
RETURN_IF_FAILED(media_type->SetBlob(MF_MT_GEOMETRIC_APERTURE, (UINT8*)&area,
sizeof(area)));
if (decoder_config.video_transformation().rotation !=
VideoRotation::VIDEO_ROTATION_0) {
MFVideoRotationFormat mf_rotation =
VideoRotationToMF(decoder_config.video_transformation().rotation);
RETURN_IF_FAILED(media_type->SetUINT32(MF_MT_VIDEO_ROTATION, mf_rotation));
}
MFVideoTransferFunction mf_transfer_function =
VideoTransferFunctionToMF(decoder_config.color_space_info().transfer);
RETURN_IF_FAILED(
media_type->SetUINT32(MF_MT_TRANSFER_FUNCTION, mf_transfer_function));
MFVideoPrimaries mf_video_primary =
VideoPrimariesToMF(decoder_config.color_space_info().primaries);
RETURN_IF_FAILED(
media_type->SetUINT32(MF_MT_VIDEO_PRIMARIES, mf_video_primary));
base::UmaHistogramEnumeration(
"Media.MediaFoundation.VideoColorSpace.TransferID",
decoder_config.color_space_info().transfer);
base::UmaHistogramEnumeration(
"Media.MediaFoundation.VideoColorSpace.PrimaryID",
decoder_config.color_space_info().primaries);
*media_type_out = media_type.Detach();
return S_OK;
}
} // namespace
/*static*/
HRESULT MediaFoundationVideoStream::Create(
int stream_id,
IMFMediaSource* parent_source,
DemuxerStream* demuxer_stream,
std::unique_ptr<MediaLog> media_log,
MediaFoundationStreamWrapper** stream_out) {
DVLOG(1) << __func__ << ": stream_id=" << stream_id;
ComPtr<IMFMediaStream> video_stream;
VideoCodec codec = demuxer_stream->video_decoder_config().codec();
switch (codec) {
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
case VideoCodec::kH264:
RETURN_IF_FAILED(MakeAndInitialize<MediaFoundationH264VideoStream>(
&video_stream, stream_id, parent_source, demuxer_stream,
std::move(media_log)));
break;
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
case VideoCodec::kHEVC:
#endif
#if BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
case VideoCodec::kDolbyVision:
#endif
#if BUILDFLAG(ENABLE_PLATFORM_HEVC) || BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
RETURN_IF_FAILED(MakeAndInitialize<MediaFoundationHEVCVideoStream>(
&video_stream, stream_id, parent_source, demuxer_stream,
std::move(media_log)));
break;
#endif // BUILDFLAG(ENABLE_PLATFORM_HEVC) ||
// BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
default:
RETURN_IF_FAILED(MakeAndInitialize<MediaFoundationVideoStream>(
&video_stream, stream_id, parent_source, demuxer_stream,
std::move(media_log)));
break;
}
*stream_out =
static_cast<MediaFoundationStreamWrapper*>(video_stream.Detach());
return S_OK;
}
bool MediaFoundationVideoStream::IsEncrypted() const {
VideoDecoderConfig decoder_config = demuxer_stream_->video_decoder_config();
return decoder_config.is_encrypted();
}
HRESULT MediaFoundationVideoStream::GetMediaType(
IMFMediaType** media_type_out) {
VideoDecoderConfig decoder_config = demuxer_stream_->video_decoder_config();
return GetVideoType(decoder_config, media_type_out);
}
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
HRESULT MediaFoundationH264VideoStream::GetMediaType(
IMFMediaType** media_type_out) {
VideoDecoderConfig decoder_config = demuxer_stream_->video_decoder_config();
RETURN_IF_FAILED(GetVideoType(decoder_config, media_type_out));
// Enable conversion to Annex-B
demuxer_stream_->EnableBitstreamConverter();
return S_OK;
}
bool MediaFoundationH264VideoStream::AreFormatChangesEnabled() {
// Disable explicit format change event for H264 to allow switching to the
// new stream without a full re-create, which will be much faster. This is
// also due to the fact that the MFT decoder can handle some format changes
// without a format change event. For format changes that the MFT decoder
// cannot support (e.g. codec change), the playback will fail later with
// MF_E_INVALIDMEDIATYPE (0xC00D36B4).
return false;
}
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
bool MediaFoundationHEVCVideoStream::AreFormatChangesEnabled() {
// Disable explicit format change event for HEVC to allow switching to the
// new stream without a full re-create, which will be much faster. This is
// also due to the fact that the MFT decoder can handle some format changes
// without a format change event. For format changes that the MFT decoder
// cannot support (e.g. codec change), the playback will fail later with
// MF_E_INVALIDMEDIATYPE (0xC00D36B4).
return false;
}
#endif // BUILDFLAG(ENABLE_PLATFORM_HEVC)
} // namespace media