blob: 0d7783695af041c6818ede6629f99abaa9f2a118 [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_stream_wrapper.h"
#include <mferror.h>
#include "base/bind.h"
#include "media/base/video_codecs.h"
#include "media/base/win/mf_helpers.h"
#include "media/renderers/win/media_foundation_audio_stream.h"
#include "media/renderers/win/media_foundation_source_wrapper.h"
#include "media/renderers/win/media_foundation_video_stream.h"
namespace media {
using Microsoft::WRL::ComPtr;
namespace {
// |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 guid = *(reinterpret_cast<UNALIGNED const GUID*>(guid_string.c_str()));
guid.Data1 = _byteswap_ulong(guid.Data1);
guid.Data2 = _byteswap_ushort(guid.Data2);
guid.Data3 = _byteswap_ushort(guid.Data3);
return guid;
}
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;
} else {
NOTREACHED() << "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;
}
// MFTIME defines units of 100 nanoseconds.
MFTIME TimeDeltaToMfTime(base::TimeDelta time) {
return time.InNanoseconds() / 100;
}
} // namespace
MediaFoundationStreamWrapper::MediaFoundationStreamWrapper() = default;
MediaFoundationStreamWrapper::~MediaFoundationStreamWrapper() = default;
/*static*/
HRESULT MediaFoundationStreamWrapper::Create(
int stream_id,
IMFMediaSource* parent_source,
DemuxerStream* demuxer_stream,
std::unique_ptr<MediaLog> media_log,
scoped_refptr<base::SequencedTaskRunner> task_runner,
MediaFoundationStreamWrapper** stream_out) {
DVLOG(1) << __func__ << ": stream_id=" << stream_id;
ComPtr<MediaFoundationStreamWrapper> stream;
switch (demuxer_stream->type()) {
case DemuxerStream::Type::VIDEO:
RETURN_IF_FAILED(MediaFoundationVideoStream::Create(
stream_id, parent_source, demuxer_stream, std::move(media_log),
&stream));
break;
case DemuxerStream::Type::AUDIO:
RETURN_IF_FAILED(MediaFoundationAudioStream::Create(
stream_id, parent_source, demuxer_stream, std::move(media_log),
&stream));
break;
default:
DLOG(ERROR) << "Unsupported demuxer stream type: "
<< demuxer_stream->type();
return E_INVALIDARG;
}
stream->SetTaskRunner(std::move(task_runner));
*stream_out = stream.Detach();
return S_OK;
}
HRESULT MediaFoundationStreamWrapper::RuntimeClassInitialize(
int stream_id,
IMFMediaSource* parent_source,
DemuxerStream* demuxer_stream,
std::unique_ptr<MediaLog> media_log) {
{
base::AutoLock auto_lock(lock_);
parent_source_ = parent_source;
}
demuxer_stream_ = demuxer_stream;
stream_id_ = stream_id;
stream_type_ = demuxer_stream_->type();
DVLOG_FUNC(1) << "stream_id=" << stream_id
<< ", stream_type=" << DemuxerStream::GetTypeName(stream_type_);
media_log_ = std::move(media_log);
RETURN_IF_FAILED(GenerateStreamDescriptor());
RETURN_IF_FAILED(MFCreateEventQueue(&mf_media_event_queue_));
return S_OK;
}
void MediaFoundationStreamWrapper::SetTaskRunner(
scoped_refptr<base::SequencedTaskRunner> task_runner) {
DVLOG_FUNC(1);
task_runner_ = std::move(task_runner);
}
void MediaFoundationStreamWrapper::DetachParent() {
DVLOG_FUNC(1);
base::AutoLock auto_lock(lock_);
parent_source_ = nullptr;
}
void MediaFoundationStreamWrapper::DetachDemuxerStream() {
DVLOG_FUNC(1);
DCHECK(task_runner_->RunsTasksInCurrentSequence());
demuxer_stream_ = nullptr;
}
void MediaFoundationStreamWrapper::SetSelected(bool selected) {
DVLOG_FUNC(2) << "selected=" << selected;
base::AutoLock auto_lock(lock_);
selected_ = selected;
}
bool MediaFoundationStreamWrapper::IsSelected() {
base::AutoLock auto_lock(lock_);
DVLOG_FUNC(2) << "selected_=" << selected_;
return selected_;
}
bool MediaFoundationStreamWrapper::IsEnabled() {
base::AutoLock auto_lock(lock_);
DVLOG_FUNC(2) << "enabled_=" << enabled_;
return enabled_;
}
void MediaFoundationStreamWrapper::SetEnabled(bool enabled) {
DVLOG_FUNC(2) << "enabled=" << enabled;
{
base::AutoLock auto_lock(lock_);
if (enabled_ == enabled)
return;
enabled_ = enabled;
}
// Restart processing of queued requests when stream is re-enabled.
ProcessRequestsIfPossible();
}
void MediaFoundationStreamWrapper::SetFlushed(bool flushed) {
DVLOG_FUNC(2) << "flushed=" << flushed;
base::AutoLock auto_lock(lock_);
flushed_ = flushed;
if (flushed_) {
while (!post_flush_buffers_.empty()) {
post_flush_buffers_.pop();
}
}
}
bool MediaFoundationStreamWrapper::HasEnded() const {
DVLOG_FUNC(2) << "stream_ended_=" << stream_ended_;
return stream_ended_;
}
HRESULT MediaFoundationStreamWrapper::QueueStartedEvent(
const PROPVARIANT* start_position) {
DVLOG_FUNC(2);
state_ = State::kStarted;
RETURN_IF_FAILED(mf_media_event_queue_->QueueEventParamVar(
MEStreamStarted, GUID_NULL, S_OK, start_position));
return S_OK;
}
HRESULT MediaFoundationStreamWrapper::QueueSeekedEvent(
const PROPVARIANT* start_position) {
DVLOG_FUNC(2);
state_ = State::kStarted;
RETURN_IF_FAILED(mf_media_event_queue_->QueueEventParamVar(
MEStreamSeeked, GUID_NULL, S_OK, start_position));
return S_OK;
}
HRESULT MediaFoundationStreamWrapper::QueueStoppedEvent() {
DVLOG_FUNC(2);
state_ = State::kStopped;
RETURN_IF_FAILED(mf_media_event_queue_->QueueEventParamVar(
MEStreamStopped, GUID_NULL, S_OK, nullptr));
return S_OK;
}
HRESULT MediaFoundationStreamWrapper::QueuePausedEvent() {
DVLOG_FUNC(2);
state_ = State::kPaused;
RETURN_IF_FAILED(mf_media_event_queue_->QueueEventParamVar(
MEStreamPaused, GUID_NULL, S_OK, nullptr));
return S_OK;
}
DemuxerStream::Type MediaFoundationStreamWrapper::StreamType() const {
return stream_type_;
}
void MediaFoundationStreamWrapper::ProcessRequestsIfPossible() {
DVLOG_FUNC(3);
DCHECK(task_runner_->RunsTasksInCurrentSequence());
{
base::AutoLock auto_lock(lock_);
if (state_ == State::kPaused || !enabled_)
return;
if (pending_sample_request_tokens_.empty()) {
return;
}
}
if (ServicePostFlushSampleRequest()) {
// A sample has been consumed from the |post_flush_buffers_|.
return;
}
if (!demuxer_stream_ || pending_stream_read_) {
return;
}
demuxer_stream_->Read(
base::BindOnce(&MediaFoundationStreamWrapper::OnDemuxerStreamRead,
weak_factory_.GetWeakPtr()));
pending_stream_read_ = true;
}
HRESULT MediaFoundationStreamWrapper::ServiceSampleRequest(
IUnknown* token,
DecoderBuffer* buffer) {
DVLOG_FUNC(3);
DCHECK(task_runner_->RunsTasksInCurrentSequence());
lock_.AssertAcquired();
if (buffer->end_of_stream()) {
if (!enabled_) {
DVLOG_FUNC(2) << "Ignoring EOS for disabled stream";
return S_OK;
}
DVLOG_FUNC(2) << "End of stream";
RETURN_IF_FAILED(mf_media_event_queue_->QueueEventParamUnk(
MEEndOfStream, GUID_NULL, S_OK, nullptr));
stream_ended_ = true;
if (parent_source_) {
static_cast<MediaFoundationSourceWrapper*>(parent_source_.Get())
->CheckForEndOfPresentation();
}
} else {
DVLOG_FUNC(3) << "buffer ts=" << buffer->timestamp()
<< ", is_key_frame=" << buffer->is_key_frame();
ComPtr<IMFSample> mf_sample;
RETURN_IF_FAILED(GenerateSampleFromDecoderBuffer(buffer, &mf_sample));
if (token) {
RETURN_IF_FAILED(mf_sample->SetUnknown(MFSampleExtension_Token, token));
}
RETURN_IF_FAILED(mf_media_event_queue_->QueueEventParamUnk(
MEMediaSample, GUID_NULL, S_OK, mf_sample.Get()));
}
return S_OK;
}
bool MediaFoundationStreamWrapper::ServicePostFlushSampleRequest() {
DVLOG_FUNC(3);
DCHECK(task_runner_->RunsTasksInCurrentSequence());
base::AutoLock auto_lock(lock_);
if ((flushed_ && state_ != State::kStarted) || post_flush_buffers_.empty()) {
return false;
}
DCHECK(!pending_sample_request_tokens_.empty());
ComPtr<IUnknown> request_token = pending_sample_request_tokens_.front();
HRESULT hr = ServiceSampleRequest(request_token.Get(),
post_flush_buffers_.front().get());
if (FAILED(hr)) {
DLOG(WARNING) << "Failed to service post flush sample: " << PrintHr(hr);
return false;
}
pending_sample_request_tokens_.pop();
post_flush_buffers_.pop();
return true;
}
HRESULT MediaFoundationStreamWrapper::QueueFormatChangedEvent() {
DVLOG_FUNC(2);
ComPtr<IMFMediaType> media_type;
RETURN_IF_FAILED(GetMediaType(&media_type));
RETURN_IF_FAILED(mf_media_event_queue_->QueueEventParamUnk(
MEStreamFormatChanged, GUID_NULL, S_OK, media_type.Get()));
return S_OK;
}
void MediaFoundationStreamWrapper::OnDemuxerStreamRead(
DemuxerStream::Status status,
scoped_refptr<DecoderBuffer> buffer) {
DVLOG_FUNC(3) << "status=" << status
<< (buffer ? " buffer=" + buffer->AsHumanReadableString(true)
: "");
{
base::AutoLock auto_lock(lock_);
DCHECK(pending_stream_read_);
pending_stream_read_ = false;
ComPtr<IUnknown> token = pending_sample_request_tokens_.front();
HRESULT hr = S_OK;
if (status == DemuxerStream::Status::kOk) {
if (!encryption_type_reported_) {
encryption_type_reported_ = true;
ReportEncryptionType(buffer);
}
// Push |buffer| to process later if needed. Otherwise, process it
// immediately.
if (flushed_ || !post_flush_buffers_.empty()) {
DVLOG_FUNC(3) << "push buffer.";
post_flush_buffers_.push(buffer);
} else {
hr = ServiceSampleRequest(token.Get(), buffer.get());
if (FAILED(hr)) {
DLOG(ERROR) << __func__
<< ": ServiceSampleRequest failed: " << PrintHr(hr);
return;
}
pending_sample_request_tokens_.pop();
}
} else if (status == DemuxerStream::Status::kConfigChanged) {
DVLOG_FUNC(2) << "Stream config changed, AreFormatChangesEnabled="
<< AreFormatChangesEnabled();
if (AreFormatChangesEnabled()) {
hr = QueueFormatChangedEvent();
if (FAILED(hr)) {
DLOG(ERROR) << __func__
<< ": QueueFormatChangedEvent failed: " << PrintHr(hr);
return;
}
} else {
// GetMediaType() calls {audio,video}_decoder_config(), which is
// required by DemuxerStream when kConfigChanged happens.
ComPtr<IMFMediaType> media_type;
hr = GetMediaType(&media_type);
if (FAILED(hr)) {
DLOG(ERROR) << __func__ << ": GetMediaType failed: " << PrintHr(hr);
return;
}
}
} else if (status == DemuxerStream::Status::kError) {
DVLOG_FUNC(2) << "Stream read error";
mf_media_event_queue_->QueueEventParamVar(
MEError, GUID_NULL, MF_E_INVALID_STREAM_DATA, nullptr);
return;
} else if (status == DemuxerStream::Status::kAborted) {
DVLOG_FUNC(2) << "Stream read aborted";
// Continue to ProcessRequestsIfPossible() to satisfy pending sample
// request by issuing DemuxerStream::Read() if necessary.
} else {
NOTREACHED() << "Unexpected demuxer stream status. status=" << status
<< ", this=" << this;
}
}
ProcessRequestsIfPossible();
}
HRESULT MediaFoundationStreamWrapper::GenerateSampleFromDecoderBuffer(
DecoderBuffer* buffer,
IMFSample** sample_out) {
DVLOG_FUNC(3);
ComPtr<IMFSample> mf_sample;
RETURN_IF_FAILED(MFCreateSample(&mf_sample));
if (buffer->is_key_frame()) {
RETURN_IF_FAILED(mf_sample->SetUINT32(MFSampleExtension_CleanPoint, 1));
}
MFTIME sample_time = TimeDeltaToMfTime(buffer->timestamp());
RETURN_IF_FAILED(mf_sample->SetSampleTime(sample_time));
MFTIME sample_duration = TimeDeltaToMfTime(buffer->duration());
RETURN_IF_FAILED(mf_sample->SetSampleDuration(sample_duration));
ComPtr<IMFMediaBuffer> mf_buffer;
size_t data_size = buffer->data_size();
RETURN_IF_FAILED(MFCreateMemoryBuffer(buffer->data_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_));
}
RETURN_IF_FAILED(TransformSample(mf_sample));
*sample_out = mf_sample.Detach();
return S_OK;
}
HRESULT MediaFoundationStreamWrapper::TransformSample(
Microsoft::WRL::ComPtr<IMFSample>& sample) {
DVLOG_FUNC(3);
return S_OK;
}
HRESULT MediaFoundationStreamWrapper::GetMediaSource(
IMFMediaSource** media_source_out) {
DVLOG_FUNC(2);
DCHECK(!task_runner_->RunsTasksInCurrentSequence());
base::AutoLock auto_lock(lock_);
if (!parent_source_) {
DLOG(ERROR) << __func__ << ": MF_E_SHUTDOWN";
return MF_E_SHUTDOWN;
}
RETURN_IF_FAILED(parent_source_.CopyTo(media_source_out));
return S_OK;
}
HRESULT MediaFoundationStreamWrapper::GetStreamDescriptor(
IMFStreamDescriptor** stream_descriptor_out) {
DVLOG_FUNC(2);
if (!mf_stream_descriptor_) {
DLOG(ERROR) << __func__ << ": MF_E_NOT_INITIALIZED";
return MF_E_NOT_INITIALIZED;
}
RETURN_IF_FAILED(mf_stream_descriptor_.CopyTo(stream_descriptor_out));
return S_OK;
}
HRESULT MediaFoundationStreamWrapper::RequestSample(IUnknown* token) {
DVLOG_FUNC(3);
DCHECK(!task_runner_->RunsTasksInCurrentSequence());
base::AutoLock auto_lock(lock_);
// If token is nullptr, we still want to push it to represent a sample
// request from MF.
pending_sample_request_tokens_.push(token);
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&MediaFoundationStreamWrapper::ProcessRequestsIfPossible,
weak_factory_.GetWeakPtr()));
return S_OK;
}
HRESULT MediaFoundationStreamWrapper::GetEvent(DWORD flags,
IMFMediaEvent** event_out) {
DVLOG_FUNC(3);
DCHECK(mf_media_event_queue_);
// Not tracing hr to avoid the noise from MF_E_NO_EVENTS_AVAILABLE.
return mf_media_event_queue_->GetEvent(flags, event_out);
}
HRESULT MediaFoundationStreamWrapper::BeginGetEvent(IMFAsyncCallback* callback,
IUnknown* state) {
DVLOG_FUNC(3);
DCHECK(mf_media_event_queue_);
RETURN_IF_FAILED(mf_media_event_queue_->BeginGetEvent(callback, state));
return S_OK;
}
HRESULT MediaFoundationStreamWrapper::EndGetEvent(IMFAsyncResult* result,
IMFMediaEvent** event_out) {
DVLOG_FUNC(3);
DCHECK(mf_media_event_queue_);
RETURN_IF_FAILED(mf_media_event_queue_->EndGetEvent(result, event_out));
return S_OK;
}
HRESULT MediaFoundationStreamWrapper::QueueEvent(MediaEventType type,
REFGUID extended_type,
HRESULT status,
const PROPVARIANT* value) {
DVLOG_FUNC(3);
DCHECK(mf_media_event_queue_);
RETURN_IF_FAILED(mf_media_event_queue_->QueueEventParamVar(
type, extended_type, status, value));
return S_OK;
}
HRESULT MediaFoundationStreamWrapper::GenerateStreamDescriptor() {
DVLOG_FUNC(2);
ComPtr<IMFMediaType> media_type;
IMFMediaType** mediaTypes = &media_type;
RETURN_IF_FAILED(GetMediaType(&media_type));
RETURN_IF_FAILED(MFCreateStreamDescriptor(stream_id_, 1, mediaTypes,
&mf_stream_descriptor_));
if (IsEncrypted()) {
RETURN_IF_FAILED(mf_stream_descriptor_->SetUINT32(MF_SD_PROTECTED, 1));
}
return S_OK;
}
bool MediaFoundationStreamWrapper::AreFormatChangesEnabled() {
return true;
}
GUID MediaFoundationStreamWrapper::GetLastKeyId() const {
return last_key_id_;
}
void MediaFoundationStreamWrapper::ReportEncryptionType(
const scoped_refptr<DecoderBuffer>& buffer) {
auto encryption_type = EncryptionType::kClear;
if (IsEncrypted()) {
bool is_buffer_encrypted = buffer->decrypt_config();
encryption_type = !is_buffer_encrypted
? EncryptionType::kEncryptedWithClearLead
: EncryptionType::kEncrypted;
}
if (encryption_type == EncryptionType::kEncryptedWithClearLead) {
MEDIA_LOG(INFO, media_log_) << "MediaFoundationStreamWrapper: "
<< DemuxerStream::GetTypeName(stream_type_)
<< " stream is encrypted with clear lead";
}
// TODO(xhwang): Report `encryption_type` to `PipelineStatistics` so it's
// also reported to UKM.
}
} // namespace media