// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "media/base/win/mf_helpers.h"
#include <initguid.h>
#include <d3d11.h>
#include <d3d11_4.h>
#include <ks.h>
#include <ksmedia.h>
#include <mfapi.h>
#include <mferror.h>
#include <mfidl.h>
#include <mmreg.h>
#include <wrl.h>
#include "base/check_op.h"
#include "base/win/scoped_co_mem.h"
#include "base/win/windows_version.h"
#include "media/base/audio_codecs.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/channel_layout.h"
#include "media/base/win/mf_helpers.h"
#if BUILDFLAG(ENABLE_PLATFORM_AC4_AUDIO)
#include "media/formats/mp4/ac4.h"
#endif // BUILDFLAG(ENABLE_PLATFORM_AC4_AUDIO)
#include "gpu/ipc/common/dxgi_helpers.h"
#include "media/media_buildflags.h"
#include "third_party/libyuv/include/libyuv.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'))
// ID3D11DeviceChild and ID3D11Device implement SetPrivateData with
// the exact same parameters.
template <typename T>
HRESULT SetDebugNameInternal(T* d3d11_object, const char* debug_string) {
return d3d11_object->SetPrivateData(WKPDID_D3DDebugObjectName,
strlen(debug_string), debug_string);
}
// Given an audio format tag |wave_format|, it returns an audio subtype GUID per
// https://docs.microsoft.com/en-us/windows/win32/medfound/audio-subtype-guids
// |wave_format| must be one of the WAVE_FORMAT_* constants defined in mmreg.h.
GUID MediaFoundationSubTypeFromWaveFormat(uint32_t wave_format) {
GUID format_base = MFAudioFormat_Base;
format_base.Data1 = wave_format;
return format_base;
}
GUID AudioCodecToMediaFoundationSubtype(AudioCodec codec) {
DVLOG(1) << __func__ << ": codec=" << codec;
switch (codec) {
case AudioCodec::kAAC:
return MFAudioFormat_AAC;
case AudioCodec::kMP3:
return MFAudioFormat_MP3;
case AudioCodec::kPCM:
return MFAudioFormat_PCM;
case AudioCodec::kVorbis:
return MFAudioFormat_Vorbis;
case AudioCodec::kFLAC:
return MFAudioFormat_FLAC;
case AudioCodec::kAMR_NB:
return MFAudioFormat_AMR_NB;
case AudioCodec::kAMR_WB:
return MFAudioFormat_AMR_WB;
case AudioCodec::kPCM_MULAW:
return MediaFoundationSubTypeFromWaveFormat(WAVE_FORMAT_MULAW);
case AudioCodec::kGSM_MS:
return MediaFoundationSubTypeFromWaveFormat(WAVE_FORMAT_GSM610);
case AudioCodec::kPCM_S16BE:
return MFAudioFormat_PCM;
case AudioCodec::kPCM_S24BE:
return MFAudioFormat_PCM;
case AudioCodec::kOpus:
return MFAudioFormat_Opus;
case AudioCodec::kEAC3:
return MFAudioFormat_Dolby_DDPlus;
case AudioCodec::kPCM_ALAW:
return MediaFoundationSubTypeFromWaveFormat(WAVE_FORMAT_ALAW);
case AudioCodec::kALAC:
return MFAudioFormat_ALAC;
case AudioCodec::kAC3:
return MFAudioFormat_Dolby_AC3;
#if BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO)
case AudioCodec::kDTS:
case AudioCodec::kDTSE:
return MFAudioFormat_DTS_RAW;
case AudioCodec::kDTSXP2:
return MFAudioFormat_DTS_UHD;
#endif // BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO)
#if BUILDFLAG(ENABLE_PLATFORM_AC4_AUDIO)
case AudioCodec::kAC4:
return MFAudioFormat_Dolby_AC4;
#endif // BUILDFLAG(ENABLE_PLATFORM_AC4_AUDIO)
default:
return GUID_NULL;
}
}
bool IsUncompressedAudio(AudioCodec codec) {
switch (codec) {
case AudioCodec::kPCM:
case AudioCodec::kPCM_S16BE:
case AudioCodec::kPCM_S24BE:
return true;
default:
return false;
}
}
bool AreLowIVBytesZero(const std::string& iv) {
if (iv.length() != 16) {
return false;
}
for (size_t i = 8; i < iv.length(); i++) {
if (iv[i] != '\0') {
return false;
}
}
return true;
}
// Add encryption related attributes to |mf_sample| and update |last_key_id|.
HRESULT AddEncryptAttributes(const DecryptConfig& decrypt_config,
IMFSample* mf_sample,
GUID* last_key_id) {
DVLOG(3) << __func__;
MFSampleEncryptionProtectionScheme mf_protection_scheme;
if (decrypt_config.encryption_scheme() == EncryptionScheme::kCenc) {
mf_protection_scheme = MFSampleEncryptionProtectionScheme::
MF_SAMPLE_ENCRYPTION_PROTECTION_SCHEME_AES_CTR;
} else if (decrypt_config.encryption_scheme() == EncryptionScheme::kCbcs) {
mf_protection_scheme = MFSampleEncryptionProtectionScheme::
MF_SAMPLE_ENCRYPTION_PROTECTION_SCHEME_AES_CBC;
if (decrypt_config.HasPattern()) {
DVLOG(3) << __func__ << ": encryption_pattern="
<< decrypt_config.encryption_pattern().value();
// Invalid if crypt == 0 and skip >= 0.
CHECK(!(decrypt_config.encryption_pattern()->crypt_byte_block() == 0 &&
decrypt_config.encryption_pattern()->skip_byte_block() > 0));
// Crypt and skip byte blocks for the sample-based protection pattern need
// be set if the protection scheme is `cbcs`. No need to set for 10:0,
// 1:0, or 0:0 patterns since Media Foundation Media Engine treats them as
// `cbc1` always but it won't reset IV between subsamples (which means IV
// to be restored to the constant IV after each subsample). Trying to set
// crypt to non-zero and skip to 0 will cause an error:
// - If either of these attributes are not present or have a value of 0,
// the sample is `cbc1`. See
// https://learn.microsoft.com/en-us/windows/win32/medfound/mfsampleextension-encryption-cryptbyteblock
// and
// https://learn.microsoft.com/en-us/windows/win32/medfound/mfsampleextension-encryption-skipbyteblock
// - If `cBlocksStripeEncrypted` is 0, `cBlocksStripeClear` must be also
// 0. See
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/d3d10umddi/ns-d3d10umddi-d3dwddm2_4ddi_video_decoder_buffer_desc
if (decrypt_config.encryption_pattern()->skip_byte_block() > 0) {
RETURN_IF_FAILED(mf_sample->SetUINT32(
MFSampleExtension_Encryption_CryptByteBlock,
decrypt_config.encryption_pattern()->crypt_byte_block()));
RETURN_IF_FAILED(mf_sample->SetUINT32(
MFSampleExtension_Encryption_SkipByteBlock,
decrypt_config.encryption_pattern()->skip_byte_block()));
}
}
} else {
NOTREACHED_IN_MIGRATION() << "Unexpected encryption scheme";
return MF_E_UNEXPECTED;
}
RETURN_IF_FAILED(mf_sample->SetUINT32(
MFSampleExtension_Encryption_ProtectionScheme, mf_protection_scheme));
// KID
// https://matroska.org/technical/specs/index.html#ContentEncKeyID
// For WebM case, key ID size is not specified.
if (decrypt_config.key_id().length() != sizeof(GUID)) {
DLOG(ERROR) << __func__ << ": Unsupported key ID size";
return MF_E_UNEXPECTED;
}
GUID key_id = GetGUIDFromString(decrypt_config.key_id());
RETURN_IF_FAILED(mf_sample->SetGUID(MFSampleExtension_Content_KeyID, key_id));
*last_key_id = key_id;
// IV
size_t iv_length = decrypt_config.iv().length();
DCHECK(iv_length == 16);
// For cases where a 16-byte IV is specified, but the low 8-bytes are all
// 0, ensure that a 8-byte IV is set (this allows HWDRM to work on
// hardware / drivers which don't support CTR decryption with 16-byte IVs)
if (AreLowIVBytesZero(decrypt_config.iv())) {
iv_length = 8;
}
RETURN_IF_FAILED(mf_sample->SetBlob(
MFSampleExtension_Encryption_SampleID,
reinterpret_cast<const uint8_t*>(decrypt_config.iv().c_str()),
iv_length));
// Handle subsample entries.
const auto& subsample_entries = decrypt_config.subsamples();
if (subsample_entries.empty()) {
return S_OK;
}
std::vector<MediaFoundationSubsampleEntry> mf_subsample_entries(
subsample_entries.size());
for (size_t i = 0; i < subsample_entries.size(); i++) {
mf_subsample_entries[i] =
MediaFoundationSubsampleEntry(subsample_entries[i]);
}
const uint32_t mf_sample_entries_size =
sizeof(MediaFoundationSubsampleEntry) * mf_subsample_entries.size();
RETURN_IF_FAILED(mf_sample->SetBlob(
MFSampleExtension_Encryption_SubSample_Mapping,
reinterpret_cast<const uint8_t*>(mf_subsample_entries.data()),
mf_sample_entries_size));
return S_OK;
}
} // namespace
Microsoft::WRL::ComPtr<IMFSample> CreateEmptySampleWithBuffer(
uint32_t buffer_length,
int align) {
CHECK_GT(buffer_length, 0U);
Microsoft::WRL::ComPtr<IMFSample> sample;
HRESULT hr = MFCreateSample(&sample);
RETURN_ON_HR_FAILURE(hr, "MFCreateSample failed",
Microsoft::WRL::ComPtr<IMFSample>());
Microsoft::WRL::ComPtr<IMFMediaBuffer> buffer;
if (align == 0) {
// Note that MFCreateMemoryBuffer is same as MFCreateAlignedMemoryBuffer
// with the align argument being 0.
hr = MFCreateMemoryBuffer(buffer_length, &buffer);
} else {
hr = MFCreateAlignedMemoryBuffer(buffer_length, align - 1, &buffer);
}
RETURN_ON_HR_FAILURE(hr, "Failed to create memory buffer for sample",
Microsoft::WRL::ComPtr<IMFSample>());
hr = sample->AddBuffer(buffer.Get());
RETURN_ON_HR_FAILURE(hr, "Failed to add buffer to sample",
Microsoft::WRL::ComPtr<IMFSample>());
buffer->SetCurrentLength(0);
return sample;
}
MediaBufferScopedPointer::MediaBufferScopedPointer(IMFMediaBuffer* media_buffer)
: media_buffer_(media_buffer),
buffer_(nullptr),
max_length_(0),
current_length_(0) {
HRESULT hr = media_buffer_->Lock(&buffer_, &max_length_, ¤t_length_);
CHECK(SUCCEEDED(hr));
}
MediaBufferScopedPointer::~MediaBufferScopedPointer() {
HRESULT hr = media_buffer_->Unlock();
CHECK(SUCCEEDED(hr));
}
HRESULT CopyCoTaskMemWideString(LPCWSTR in_string, LPWSTR* out_string) {
if (!in_string || !out_string) {
return E_INVALIDARG;
}
size_t size = (wcslen(in_string) + 1) * sizeof(wchar_t);
LPWSTR copy = reinterpret_cast<LPWSTR>(CoTaskMemAlloc(size));
if (!copy)
return E_OUTOFMEMORY;
wcscpy(copy, in_string);
*out_string = copy;
return S_OK;
}
HRESULT SetDebugName(ID3D11DeviceChild* d3d11_device_child,
const char* debug_string) {
return SetDebugNameInternal(d3d11_device_child, debug_string);
}
HRESULT SetDebugName(ID3D11Device* d3d11_device, const char* debug_string) {
return SetDebugNameInternal(d3d11_device, debug_string);
}
ChannelLayout ChannelConfigToChannelLayout(ChannelConfig config) {
switch (config) {
case KSAUDIO_SPEAKER_MONO:
return CHANNEL_LAYOUT_MONO;
case KSAUDIO_SPEAKER_STEREO:
return CHANNEL_LAYOUT_STEREO;
case KSAUDIO_SPEAKER_2POINT1:
return CHANNEL_LAYOUT_2POINT1;
case KSAUDIO_SPEAKER_3POINT0:
return CHANNEL_LAYOUT_SURROUND;
case KSAUDIO_SPEAKER_3POINT1:
return CHANNEL_LAYOUT_3_1;
case KSAUDIO_SPEAKER_QUAD:
return CHANNEL_LAYOUT_QUAD;
case KSAUDIO_SPEAKER_SURROUND:
return CHANNEL_LAYOUT_4_0;
case KSAUDIO_SPEAKER_5POINT0:
return CHANNEL_LAYOUT_5_0;
case KSAUDIO_SPEAKER_5POINT1:
return CHANNEL_LAYOUT_5_1_BACK;
case KSAUDIO_SPEAKER_5POINT1_SURROUND:
return CHANNEL_LAYOUT_5_1;
case KSAUDIO_SPEAKER_7POINT0:
return CHANNEL_LAYOUT_7_0;
case KSAUDIO_SPEAKER_7POINT1:
return CHANNEL_LAYOUT_7_1_WIDE_BACK;
case KSAUDIO_SPEAKER_7POINT1_SURROUND:
return CHANNEL_LAYOUT_7_1;
case KSAUDIO_SPEAKER_DIRECTOUT:
// When specifying the wave format for a direct-out stream, an application
// should set the dwChannelMask member of the WAVEFORMATEXTENSIBLE
// structure to the value KSAUDIO_SPEAKER_DIRECTOUT, which is zero.
// A channel mask of zero indicates that no speaker positions are defined.
// As always, the number of channels in the stream is specified in the
// Format.nChannels member.
return CHANNEL_LAYOUT_DISCRETE;
default:
DVLOG(2) << "Unsupported channel configuration: " << config;
return CHANNEL_LAYOUT_UNSUPPORTED;
}
}
// GUID is little endian. The byte array in network order is big endian.
std::vector<uint8_t> ByteArrayFromGUID(REFGUID guid) {
std::vector<uint8_t> byte_array(sizeof(GUID));
GUID* reversed_guid = reinterpret_cast<GUID*>(byte_array.data());
*reversed_guid = guid;
reversed_guid->Data1 = _byteswap_ulong(guid.Data1);
reversed_guid->Data2 = _byteswap_ushort(guid.Data2);
reversed_guid->Data3 = _byteswap_ushort(guid.Data3);
// Data4 is already a byte array so no need to byte swap.
return byte_array;
}
// |guid_string| is a binary serialization of a GUID in network byte order
// format.
GUID GetGUIDFromString(const std::string& guid_string) {
DCHECK_EQ(guid_string.length(), sizeof(GUID));
GUID reversed_guid =
*(reinterpret_cast<UNALIGNED const GUID*>(guid_string.c_str()));
reversed_guid.Data1 = _byteswap_ulong(reversed_guid.Data1);
reversed_guid.Data2 = _byteswap_ushort(reversed_guid.Data2);
reversed_guid.Data3 = _byteswap_ushort(reversed_guid.Data3);
// Data4 is already a byte array so no need to byte swap.
return reversed_guid;
}
std::string GetStringFromGUID(REFGUID guid) {
GUID guid_tmp = guid;
guid_tmp.Data1 = _byteswap_ulong(guid_tmp.Data1);
guid_tmp.Data2 = _byteswap_ushort(guid_tmp.Data2);
guid_tmp.Data3 = _byteswap_ushort(guid_tmp.Data3);
return std::string(reinterpret_cast<char*>(&guid_tmp), sizeof(GUID));
}
// Given an AudioDecoderConfig, get its corresponding IMFMediaType format.
// Note:
// IMFMediaType is derived from IMFAttributes and hence all the of information
// in a media type is store as attributes.
// https://docs.microsoft.com/en-us/windows/win32/medfound/media-type-attributes
// has a list of media type attributes.
HRESULT GetDefaultAudioType(const AudioDecoderConfig decoder_config,
IMFMediaType** media_type_out) {
DVLOG(1) << __func__;
ComPtr<IMFMediaType> media_type;
RETURN_IF_FAILED(MFCreateMediaType(&media_type));
RETURN_IF_FAILED(media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio));
GUID mf_subtype = AudioCodecToMediaFoundationSubtype(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));
bool uncompressed = IsUncompressedAudio(decoder_config.codec());
if (uncompressed) {
RETURN_IF_FAILED(media_type->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, 1));
} else {
RETURN_IF_FAILED(media_type->SetUINT32(MF_MT_COMPRESSED, 1));
}
int channels = decoder_config.channels();
if (channels > 0) {
RETURN_IF_FAILED(media_type->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, channels));
}
int samples_per_second = decoder_config.samples_per_second();
if (samples_per_second > 0) {
RETURN_IF_FAILED(media_type->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND,
samples_per_second));
}
int bits_per_sample = decoder_config.bytes_per_channel() * 8;
if (bits_per_sample > 0) {
RETURN_IF_FAILED(
media_type->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bits_per_sample));
}
if (uncompressed) {
unsigned long block_alignment = decoder_config.bytes_per_frame();
if (block_alignment > 0) {
RETURN_IF_FAILED(
media_type->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, block_alignment));
}
unsigned long average_bps = samples_per_second * block_alignment;
if (average_bps > 0) {
RETURN_IF_FAILED(
media_type->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, average_bps));
}
}
*media_type_out = media_type.Detach();
return S_OK;
}
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
HRESULT GetAacAudioType(const AudioDecoderConfig& decoder_config,
IMFMediaType** media_type_out) {
DVLOG(1) << __func__;
ComPtr<IMFMediaType> media_type;
RETURN_IF_FAILED(GetDefaultAudioType(decoder_config, &media_type));
// On Windows `extra_data` is not populated for AAC in `decoder_config`. Use
// `aac_extra_data` instead. See crbug.com/1245123.
const auto& extra_data = decoder_config.aac_extra_data();
size_t wave_format_size = sizeof(HEAACWAVEINFO) + extra_data.size();
std::vector<uint8_t> wave_format_buffer(wave_format_size);
HEAACWAVEINFO* aac_wave_format =
reinterpret_cast<HEAACWAVEINFO*>(wave_format_buffer.data());
aac_wave_format->wfx.wFormatTag = WAVE_FORMAT_MPEG_HEAAC;
aac_wave_format->wfx.nChannels = decoder_config.channels();
aac_wave_format->wfx.wBitsPerSample = decoder_config.bytes_per_channel() * 8;
aac_wave_format->wfx.nSamplesPerSec = decoder_config.samples_per_second();
aac_wave_format->wfx.nAvgBytesPerSec =
decoder_config.samples_per_second() * decoder_config.bytes_per_frame();
aac_wave_format->wfx.nBlockAlign = 1;
size_t extra_size = wave_format_size - sizeof(WAVEFORMATEX);
aac_wave_format->wfx.cbSize = static_cast<WORD>(extra_size);
aac_wave_format->wPayloadType = 0; // RAW AAC
aac_wave_format->wAudioProfileLevelIndication =
0xFE; // no audio profile specified
aac_wave_format->wStructType = 0; // audio specific config follows
aac_wave_format->wReserved1 = 0;
aac_wave_format->dwReserved2 = 0;
if (!extra_data.empty()) {
memcpy(reinterpret_cast<uint8_t*>(aac_wave_format) + sizeof(HEAACWAVEINFO),
extra_data.data(), extra_data.size());
}
RETURN_IF_FAILED(MFInitMediaTypeFromWaveFormatEx(
media_type.Get(), reinterpret_cast<const WAVEFORMATEX*>(aac_wave_format),
wave_format_size));
*media_type_out = media_type.Detach();
return S_OK;
}
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
#if BUILDFLAG(ENABLE_PLATFORM_AC4_AUDIO)
// An attribute defined to indicate if the input audio is already
// previrtualized. Now it is used to indicate if the input stream is a Dolby AC4
// IMS stream. That information will be used by Dolby AC4 MFT to create correct
// output media types.
// GUID: {4EACAB51-FFE5-421A-A2A7-8B7409A1CAC4}
// Type: UINT32(BOOL)
DEFINE_GUID(MF_MT_SPATIAL_AUDIO_IS_PREVIRTUALIZED,
0x4eacab51,
0xffe5,
0x421a,
0xa2,
0xa7,
0x8b,
0x74,
0x09,
0xa1,
0xca,
0xc4);
HRESULT GetAC4AudioType(const AudioDecoderConfig& decoder_config,
IMFMediaType** media_type_out) {
RETURN_IF_FAILED(GetDefaultAudioType(decoder_config, media_type_out));
if (decoder_config.extra_data().size() != sizeof(media::mp4::AC4StreamInfo)) {
return MF_E_INVALIDMEDIATYPE;
}
auto* media_type = *media_type_out;
auto stream_info = *reinterpret_cast<const media::mp4::AC4StreamInfo*>(
decoder_config.extra_data().data());
RETURN_IF_FAILED(
media_type->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, stream_info.channels));
RETURN_IF_FAILED(media_type->SetUINT32(MF_MT_SPATIAL_AUDIO_IS_PREVIRTUALIZED,
stream_info.is_ims));
RETURN_IF_FAILED(media_type->SetUINT32(MF_MT_SPATIAL_AUDIO_DATA_PRESENT,
stream_info.is_ajoc));
return S_OK;
}
#endif // BUILDFLAG(ENABLE_PLATFORM_AC4_AUDIO)
// MFTIME defines units of 100 nanoseconds.
MFTIME TimeDeltaToMfTime(base::TimeDelta time) {
return time.InNanoseconds() / 100;
}
base::TimeDelta MfTimeToTimeDelta(MFTIME mf_time) {
return base::Nanoseconds(mf_time * 100);
}
GUID VideoCodecToMFSubtype(VideoCodec codec, VideoCodecProfile profile) {
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:
if (profile == VideoCodecProfile::DOLBYVISION_PROFILE0 ||
profile == VideoCodecProfile::DOLBYVISION_PROFILE9) {
return MFVideoFormat_H264;
} else {
return MFVideoFormat_HEVC;
}
case VideoCodec::kAV1:
return MFVideoFormat_AV1;
default:
return GUID_NULL;
}
}
GUID VideoPixelFormatToMFSubtype(VideoPixelFormat video_pixel_format) {
switch (video_pixel_format) {
case VideoPixelFormat::PIXEL_FORMAT_I420:
return MFVideoFormat_I420;
case VideoPixelFormat::PIXEL_FORMAT_YV12:
return MFVideoFormat_YV12;
case VideoPixelFormat::PIXEL_FORMAT_NV12:
return MFVideoFormat_NV12;
case VideoPixelFormat::PIXEL_FORMAT_NV21:
return MFVideoFormat_NV21;
case VideoPixelFormat::PIXEL_FORMAT_YUY2:
return MFVideoFormat_YUY2;
case VideoPixelFormat::PIXEL_FORMAT_ARGB:
return MFVideoFormat_ARGB32;
case VideoPixelFormat::PIXEL_FORMAT_XRGB:
return MFVideoFormat_RGB32;
case VideoPixelFormat::PIXEL_FORMAT_RGB24:
return MFVideoFormat_RGB24;
default:
return GUID_NULL;
}
}
MFVideoPrimaries VideoPrimariesToMFVideoPrimaries(
VideoColorSpace::PrimaryID primaries) {
switch (primaries) {
case VideoColorSpace::PrimaryID::BT709:
return MFVideoPrimaries_BT709;
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::BT2020:
return MFVideoPrimaries_BT2020;
case VideoColorSpace::PrimaryID::EBU_3213_E:
return MFVideoPrimaries_EBU3213;
default:
return MFVideoPrimaries_Unknown;
}
}
HRESULT GenerateSampleFromDecoderBuffer(
const scoped_refptr<DecoderBuffer>& buffer,
IMFSample** sample_out,
GUID* last_key_id,
TransformSampleCB transform_sample_cb) {
DVLOG(3) << __func__;
ComPtr<IMFSample> mf_sample;
RETURN_IF_FAILED(MFCreateSample(&mf_sample));
if (buffer->is_key_frame()) {
RETURN_IF_FAILED(mf_sample->SetUINT32(MFSampleExtension_CleanPoint, 1));
}
DVLOG(3) << __func__ << ": buffer->duration()=" << buffer->duration()
<< ", buffer->timestamp()=" << buffer->timestamp();
MFTIME sample_duration = TimeDeltaToMfTime(buffer->duration());
RETURN_IF_FAILED(mf_sample->SetSampleDuration(sample_duration));
MFTIME sample_time = TimeDeltaToMfTime(buffer->timestamp());
RETURN_IF_FAILED(mf_sample->SetSampleTime(sample_time));
ComPtr<IMFMediaBuffer> mf_buffer;
size_t data_size = buffer->size();
RETURN_IF_FAILED(MFCreateMemoryBuffer(buffer->size(), &mf_buffer));
BYTE* mf_buffer_data = nullptr;
DWORD max_length = 0;
RETURN_IF_FAILED(mf_buffer->Lock(&mf_buffer_data, &max_length, 0));
memcpy(mf_buffer_data, buffer->data(), data_size);
RETURN_IF_FAILED(mf_buffer->SetCurrentLength(data_size));
RETURN_IF_FAILED(mf_buffer->Unlock());
RETURN_IF_FAILED(mf_sample->AddBuffer(mf_buffer.Get()));
if (buffer->decrypt_config()) {
RETURN_IF_FAILED(AddEncryptAttributes(*(buffer->decrypt_config()),
mf_sample.Get(), last_key_id));
}
if (transform_sample_cb) {
RETURN_IF_FAILED(std::move(transform_sample_cb).Run(mf_sample));
}
*sample_out = mf_sample.Detach();
return S_OK;
}
HRESULT CreateDecryptConfigFromSample(
IMFSample* mf_sample,
const GUID& key_id,
std::unique_ptr<DecryptConfig>* decrypt_config) {
DVLOG(3) << __func__;
CHECK(mf_sample);
EncryptionScheme encryption_scheme = EncryptionScheme::kUnencrypted;
UINT32 mf_protection_scheme = 0;
RETURN_IF_FAILED(mf_sample->GetUINT32(
MFSampleExtension_Encryption_ProtectionScheme, &mf_protection_scheme));
switch (mf_protection_scheme) {
case MFSampleEncryptionProtectionScheme::
MF_SAMPLE_ENCRYPTION_PROTECTION_SCHEME_AES_CTR:
encryption_scheme = EncryptionScheme::kCenc;
break;
case MFSampleEncryptionProtectionScheme::
MF_SAMPLE_ENCRYPTION_PROTECTION_SCHEME_AES_CBC:
encryption_scheme = EncryptionScheme::kCbcs;
break;
case MFSampleEncryptionProtectionScheme::
MF_SAMPLE_ENCRYPTION_PROTECTION_SCHEME_NONE:
DLOG(ERROR) << __func__
<< ": Unexpected encryption scheme: mf_protection_scheme="
<< mf_protection_scheme;
return MF_E_UNEXPECTED;
}
// IV
UINT32 iv_length = 0;
base::win::ScopedCoMem<BYTE> iv;
RETURN_IF_FAILED(mf_sample->GetBlobSize(MFSampleExtension_Encryption_SampleID,
&iv_length));
if (iv_length == 8) {
// A 8-byte IV is set (this allows hardware decryption to work on hardware
// / drivers which don't support CTR decryption with 16-byte IVs). For
// DecryptBuffer, a 16-byte IV should be specified, but ensure the low
// 8-bytes are all 0.
iv_length = 16;
}
iv.Reset(reinterpret_cast<BYTE*>(CoTaskMemAlloc(iv_length * sizeof(BYTE))));
if (!iv) {
return E_OUTOFMEMORY;
}
ZeroMemory(iv, iv_length * sizeof(BYTE));
RETURN_IF_FAILED(mf_sample->GetBlob(MFSampleExtension_Encryption_SampleID, iv,
iv_length, nullptr));
// Subsample entries
std::vector<media::SubsampleEntry> subsamples;
base::win::ScopedCoMem<MediaFoundationSubsampleEntry> subsample_mappings;
uint32_t subsample_mappings_size = 0;
// If `MFSampleExtension_Encryption_SubSample_Mapping` attribute doesn't
// exist, we should not fail the call. i.e., Encrypted audio content.
if (SUCCEEDED(mf_sample->GetAllocatedBlob(
MFSampleExtension_Encryption_SubSample_Mapping,
reinterpret_cast<uint8_t**>(&subsample_mappings),
&subsample_mappings_size))) {
if (subsample_mappings_size >= sizeof(MediaFoundationSubsampleEntry)) {
uint32_t subsample_count =
subsample_mappings_size / sizeof(MediaFoundationSubsampleEntry);
for (uint32_t i = 0; i < subsample_count; ++i) {
DVLOG(3) << __func__ << ": subsample_mappings[" << i
<< "].clear_bytes=" << subsample_mappings[i].clear_bytes
<< ", cipher_bytes=" << subsample_mappings[i].cipher_bytes;
subsamples.emplace_back(subsample_mappings[i].clear_bytes,
subsample_mappings[i].cipher_bytes);
}
}
}
// Key ID
const auto key_id_string = GetStringFromGUID(key_id);
const auto iv_string = std::string(iv.get(), iv.get() + iv_length);
DVLOG(3) << __func__ << ": key_id_string=" << key_id_string
<< ", iv_string=" << iv_string
<< ", iv_string.size()=" << iv_string.size();
if (encryption_scheme == EncryptionScheme::kCenc) {
*decrypt_config =
DecryptConfig::CreateCencConfig(key_id_string, iv_string, subsamples);
} else {
EncryptionPattern encryption_pattern;
// Try to get crypt and skip byte blocks for pattern encryption. Use the
// values only if both `MFSampleExtension_Encryption_CryptByteBlock` and
// `MFSampleExtension_Encryption_SkipByteBlock` are present. Otherwise,
// assume both are zeros.
UINT32 crypt_byte_block = 0;
UINT32 skip_byte_block = 0;
if (SUCCEEDED(mf_sample->GetUINT32(
MFSampleExtension_Encryption_CryptByteBlock, &crypt_byte_block)) &&
SUCCEEDED(mf_sample->GetUINT32(
MFSampleExtension_Encryption_SkipByteBlock, &skip_byte_block))) {
encryption_pattern = EncryptionPattern(crypt_byte_block, skip_byte_block);
}
DVLOG(3) << __func__ << ": encryption_pattern=" << encryption_pattern;
*decrypt_config = DecryptConfig::CreateCbcsConfig(
key_id_string, iv_string, subsamples, encryption_pattern);
}
return S_OK;
}
HRESULT GenerateSampleFromVideoFrame(
const VideoFrame* frame,
DXGIDeviceManager* dxgi_device_manager,
bool use_dxgi_buffer,
Microsoft::WRL::ComPtr<ID3D11Texture2D>* staging_texture,
DWORD buffer_alignment,
IMFSample** sample_out) {
constexpr size_t kOneMicrosecondInMFSampleTimeUnits = 10;
HRESULT hr;
Microsoft::WRL::ComPtr<IMFSample> sample;
hr = MFCreateSample(&sample);
RETURN_ON_HR_FAILURE(hr, "Failed to create sample", hr);
if (frame->storage_type() ==
VideoFrame::StorageType::STORAGE_GPU_MEMORY_BUFFER &&
dxgi_device_manager != nullptr) {
gfx::GpuMemoryBufferHandle buffer_handle =
frame->GetGpuMemoryBufferHandle();
if (buffer_handle.is_null()) {
LOG(ERROR) << "Failed to get GMB for input frame";
return MF_E_INVALID_STREAM_DATA;
}
CHECK_EQ(buffer_handle.type, gfx::GpuMemoryBufferType::DXGI_SHARED_HANDLE);
auto d3d_device = dxgi_device_manager->GetDevice();
if (!d3d_device) {
LOG(ERROR) << "Failed to get device from MF DXGI device manager";
return E_HANDLE;
}
Microsoft::WRL::ComPtr<ID3D11Device1> device1;
hr = d3d_device.As(&device1);
RETURN_ON_HR_FAILURE(hr, "Failed to query ID3D11Device1", hr);
Microsoft::WRL::ComPtr<ID3D11Texture2D> input_texture;
hr = device1->OpenSharedResource1(buffer_handle.dxgi_handle.Get(),
IID_PPV_ARGS(&input_texture));
RETURN_ON_HR_FAILURE(hr, "Failed to open shared GMB D3D texture", hr);
if (use_dxgi_buffer) {
Microsoft::WRL::ComPtr<IMFMediaBuffer> mf_buffer;
hr = MFCreateDXGISurfaceBuffer(__uuidof(ID3D11Texture2D),
input_texture.Get(), 0, FALSE, &mf_buffer);
RETURN_ON_HR_FAILURE(hr, "Failed to create MF DXGI surface buffer", hr);
DWORD buffer_length = 0;
hr = mf_buffer->GetMaxLength(&buffer_length);
RETURN_ON_HR_FAILURE(hr, "Failed to get max buffer length", hr);
hr = mf_buffer->SetCurrentLength(buffer_length);
RETURN_ON_HR_FAILURE(hr, "Failed to set current buffer length", hr);
RETURN_ON_HR_FAILURE(hr, "Failed to create sample", hr);
hr = sample->AddBuffer(mf_buffer.Get());
RETURN_ON_HR_FAILURE(hr, "Failed to add buffer to sample", hr);
hr = sample->SetSampleTime(frame->timestamp().InMicroseconds() *
kOneMicrosecondInMFSampleTimeUnits);
RETURN_ON_HR_FAILURE(hr, "Failed to set sample timestamp", hr);
} else {
Microsoft::WRL::ComPtr<IMFMediaBuffer> input_buffer;
size_t allocation_size =
VideoFrame::AllocationSize(frame->format(), frame->coded_size());
hr = MFCreateAlignedMemoryBuffer(
allocation_size,
buffer_alignment == 0 ? buffer_alignment : buffer_alignment - 1,
&input_buffer);
RETURN_ON_HR_FAILURE(
hr, "Failed to create memory buffer for input sample", hr);
MediaBufferScopedPointer scoped_buffer(input_buffer.Get());
bool copy_succeeded = gpu::CopyD3D11TexToMem(
input_texture.Get(), scoped_buffer.get(), scoped_buffer.max_length(),
d3d_device.Get(), staging_texture);
if (!copy_succeeded) {
LOG(ERROR) << "Failed to copy sample to memory.";
return E_FAIL;
}
size_t copied_bytes = frame->visible_rect().width() *
frame->visible_rect().height() * 3 / 2;
hr = input_buffer->SetCurrentLength(copied_bytes);
RETURN_ON_HR_FAILURE(hr, "Failed to set current buffer length", hr);
hr = sample->AddBuffer(input_buffer.Get());
RETURN_ON_HR_FAILURE(hr, "Failed to add buffer to sample", hr);
}
} else if (frame->HasTextures()) {
// TODO:Handle non-GMB textures. This needs access to SharedImageManager.
// See crbug.com/40162806
return E_UNEXPECTED;
} else {
size_t allocation_size = VideoFrame::AllocationSize(
frame->format(), frame->visible_rect().size());
Microsoft::WRL::ComPtr<IMFMediaBuffer> input_buffer;
hr = MFCreateAlignedMemoryBuffer(
allocation_size,
buffer_alignment == 0 ? buffer_alignment : buffer_alignment - 1,
&input_buffer);
RETURN_ON_HR_FAILURE(hr, "Failed to create memory buffer", hr);
MediaBufferScopedPointer scoped_buffer(input_buffer.Get());
size_t buffer_offset = 0;
for (size_t i = 0; i < VideoFrame::NumPlanes(frame->format()); i++) {
// |width| in libyuv::CopyPlane() is in bytes, not pixels.
gfx::Size plane_size = VideoFrame::PlaneSize(
frame->format(), i, frame->visible_rect().size());
libyuv::CopyPlane(frame->visible_data(i),
frame->layout().planes()[i].stride,
scoped_buffer.get() + buffer_offset,
frame->layout().planes()[i].stride, plane_size.width(),
plane_size.height());
buffer_offset +=
plane_size.height() *
VideoFrame::RowBytes(i, frame->format(), plane_size.width());
}
hr = input_buffer->SetCurrentLength(allocation_size);
RETURN_ON_HR_FAILURE(hr, "Failed to set current buffer length", hr);
hr = sample->AddBuffer(input_buffer.Get());
RETURN_ON_HR_FAILURE(hr, "Failed to add buffer to sample", hr);
}
hr = sample->SetSampleTime(frame->timestamp().InMicroseconds() *
kOneMicrosecondInMFSampleTimeUnits);
RETURN_ON_HR_FAILURE(hr, "Failed to set sample timestamp", hr);
*sample_out = sample.Detach();
return S_OK;
}
} // namespace media