blob: a6b2cc8279aafb138d18b643e4f16b582d938fa7 [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_source_wrapper.h"
#include <mferror.h>
#include "media/base/audio_decoder_config.h"
#include "media/base/demuxer_stream.h"
#include "media/base/media_log.h"
#include "media/base/video_decoder_config.h"
#include "media/base/win/mf_helpers.h"
namespace media {
using Microsoft::WRL::ComPtr;
MediaFoundationSourceWrapper::MediaFoundationSourceWrapper() = default;
MediaFoundationSourceWrapper::~MediaFoundationSourceWrapper() {
DVLOG_FUNC(1);
if (!cdm_proxy_)
return;
// Notify |cdm_proxy_| of last Key IDs.
for (uint32_t stream_id = 0; stream_id < StreamCount(); stream_id++) {
HRESULT hr = cdm_proxy_->SetLastKeyId(
stream_id, media_streams_[stream_id]->GetLastKeyId());
DLOG_IF(ERROR, FAILED(hr))
<< "Failed to notify CDM proxy of last Key IDs: " << PrintHr(hr);
}
}
HRESULT MediaFoundationSourceWrapper::RuntimeClassInitialize(
MediaResource* media_resource,
MediaLog* media_log,
scoped_refptr<base::SequencedTaskRunner> task_runner) {
DVLOG_FUNC(1);
if (media_resource->GetType() != MediaResource::Type::STREAM) {
DLOG(ERROR) << "MediaResource is not of Type STREAM";
return E_INVALIDARG;
}
task_runner_ = task_runner;
auto demuxer_streams = media_resource->GetAllStreams();
int stream_id = 0;
for (DemuxerStream* demuxer_stream : demuxer_streams) {
ComPtr<MediaFoundationStreamWrapper> mf_stream;
RETURN_IF_FAILED(MediaFoundationStreamWrapper::Create(
stream_id++, this, demuxer_stream, media_log->Clone(), task_runner,
&mf_stream));
media_streams_.push_back(mf_stream);
}
RETURN_IF_FAILED(MFCreateEventQueue(&mf_media_event_queue_));
return S_OK;
}
void MediaFoundationSourceWrapper::DetachResource() {
DVLOG_FUNC(1);
for (auto stream : media_streams_) {
stream->DetachDemuxerStream();
}
}
HRESULT MediaFoundationSourceWrapper::GetCharacteristics(
DWORD* characteristics) {
DVLOG_FUNC(3);
if (state_ == State::kShutdown) {
DLOG(ERROR) << __func__ << ": MF_E_SHUTDOWN";
return MF_E_SHUTDOWN;
}
*characteristics = MFMEDIASOURCE_CAN_SEEK;
return S_OK;
}
HRESULT MediaFoundationSourceWrapper::SelectDefaultStreams(
const DWORD stream_desc_count,
IMFPresentationDescriptor* presentation_descriptor) {
bool audio_stream_selected = false;
bool video_stream_selected = false;
for (DWORD idx = 0; idx < stream_desc_count; idx++) {
ComPtr<IMFStreamDescriptor> stream_descriptor;
BOOL selected;
RETURN_IF_FAILED(presentation_descriptor->GetStreamDescriptorByIndex(
idx, &selected, &stream_descriptor));
if (selected)
continue;
DWORD stream_id;
RETURN_IF_FAILED(stream_descriptor->GetStreamIdentifier(&stream_id));
if (media_streams_[stream_id]->StreamType() == DemuxerStream::Type::AUDIO &&
!audio_stream_selected) {
audio_stream_selected = true;
RETURN_IF_FAILED(presentation_descriptor->SelectStream(idx));
} else if (media_streams_[stream_id]->StreamType() ==
DemuxerStream::Type::VIDEO &&
!video_stream_selected) {
video_stream_selected = true;
RETURN_IF_FAILED(presentation_descriptor->SelectStream(idx));
}
}
return S_OK;
}
HRESULT MediaFoundationSourceWrapper::CreatePresentationDescriptor(
IMFPresentationDescriptor** presentation_descriptor_out) {
DVLOG_FUNC(2);
if (state_ == State::kShutdown) {
DLOG(ERROR) << __func__ << ": MF_E_SHUTDOWN";
return MF_E_SHUTDOWN;
}
ComPtr<IMFPresentationDescriptor> presentation_descriptor;
std::vector<ComPtr<IMFStreamDescriptor>> stream_descriptors;
for (auto stream : media_streams_) {
ComPtr<IMFStreamDescriptor> stream_descriptor;
RETURN_IF_FAILED(stream->GetStreamDescriptor(&stream_descriptor));
stream_descriptors.push_back(stream_descriptor);
}
const DWORD stream_desc_count = static_cast<DWORD>(stream_descriptors.size());
RETURN_IF_FAILED(MFCreatePresentationDescriptor(
stream_desc_count,
reinterpret_cast<IMFStreamDescriptor**>(stream_descriptors.data()),
&presentation_descriptor));
RETURN_IF_FAILED(
SelectDefaultStreams(stream_desc_count, presentation_descriptor.Get()));
*presentation_descriptor_out = presentation_descriptor.Detach();
return S_OK;
}
// https://docs.microsoft.com/en-us/windows/win32/api/mfidl/nf-mfidl-imfmediasource-start
HRESULT MediaFoundationSourceWrapper::Start(
IMFPresentationDescriptor* presentation_descriptor,
const GUID* guid_time_format,
const PROPVARIANT* start_position) {
DVLOG_FUNC(2);
if (state_ == State::kShutdown) {
DLOG(ERROR) << __func__ << ": MF_E_SHUTDOWN";
return MF_E_SHUTDOWN;
}
bool seeked = false;
bool started = false;
// - If the previous state was stopped, the source sends an MESourceStarted
// event.
// - If the previous state was started or paused and the starting position is
// the current position(VT_EMPTY), the source sends an MESourceStarted event.
// - If the previous state was started or paused, and a new starting position
// is specified, the source sends an MESourceSeeked event.
if (state_ == State::kInitialized || state_ == State::kStopped) {
started = true;
} else if (state_ == State::kPaused || state_ == State::kStarted) {
if (start_position->vt == VT_EMPTY)
started = true;
else
seeked = true;
}
// - For each new stream, the source sends an MENewStream event. This event is
// sent for the first Start call in which the stream appears. The event data
// is a pointer to the stream's IMFMediaStream interface.
// - For each updated stream, the source sends an MEUpdatedStream event. A
// stream is updated if the stream already existed when Start was called (for
// example, if the application seeks during playback).
DWORD stream_desc_count = 0;
RETURN_IF_FAILED(
presentation_descriptor->GetStreamDescriptorCount(&stream_desc_count));
for (DWORD i = 0; i < stream_desc_count; i++) {
ComPtr<IMFStreamDescriptor> stream_descriptor;
BOOL selected;
RETURN_IF_FAILED(presentation_descriptor->GetStreamDescriptorByIndex(
i, &selected, &stream_descriptor));
DWORD stream_id;
RETURN_IF_FAILED(stream_descriptor->GetStreamIdentifier(&stream_id));
if (stream_id >= media_streams_.size()) {
DLOG(ERROR) << "Unexpected stream id in descriptor: " << stream_id;
continue;
}
ComPtr<MediaFoundationStreamWrapper> stream = media_streams_[stream_id];
stream->SetFlushed(false);
if (selected) {
MediaEventType event_type = MENewStream;
if (stream->IsSelected()) {
event_type = MEUpdatedStream;
}
ComPtr<IUnknown> unknown_stream;
RETURN_IF_FAILED(stream.As(&unknown_stream));
RETURN_IF_FAILED(mf_media_event_queue_->QueueEventParamUnk(
event_type, GUID_NULL, S_OK, unknown_stream.Get()));
// - If the source sends an MESourceStarted event, each media stream
// sends an MEStreamStarted event. If the source sends an MESourceSeeked
// event, each stream sends an MEStreamSeeked event.
if (started) {
DCHECK(!seeked);
RETURN_IF_FAILED(stream->QueueStartedEvent(start_position));
} else if (seeked) {
RETURN_IF_FAILED(stream->QueueSeekedEvent(start_position));
}
}
stream->SetSelected(selected);
}
if (started) {
DCHECK(!seeked);
ComPtr<IMFMediaEvent> media_event;
RETURN_IF_FAILED(MFCreateMediaEvent(MESourceStarted, GUID_NULL, S_OK,
start_position, &media_event));
RETURN_IF_FAILED(mf_media_event_queue_->QueueEvent(media_event.Get()));
} else if (seeked) {
RETURN_IF_FAILED(
QueueEvent(MESourceSeeked, GUID_NULL, S_OK, start_position));
}
state_ = State::kStarted;
presentation_ended_ = false;
return S_OK;
}
HRESULT MediaFoundationSourceWrapper::Stop() {
DVLOG_FUNC(2);
if (state_ == State::kShutdown) {
DLOG(ERROR) << __func__ << ": MF_E_SHUTDOWN";
return MF_E_SHUTDOWN;
}
RETURN_IF_FAILED(QueueEvent(MESourceStopped, GUID_NULL, S_OK, nullptr));
for (auto stream : media_streams_) {
if (stream->IsSelected()) {
stream->QueueStoppedEvent();
}
}
state_ = State::kStopped;
return S_OK;
}
HRESULT MediaFoundationSourceWrapper::Pause() {
DVLOG_FUNC(2);
if (state_ == State::kShutdown) {
DLOG(ERROR) << __func__ << ": MF_E_SHUTDOWN";
return MF_E_SHUTDOWN;
}
if (state_ != State::kStarted) {
DLOG(ERROR) << __func__ << ": MF_E_INVALID_STATE_TRANSITION";
return MF_E_INVALID_STATE_TRANSITION;
}
RETURN_IF_FAILED(QueueEvent(MESourcePaused, GUID_NULL, S_OK, nullptr));
for (auto stream : media_streams_) {
if (stream->IsSelected()) {
stream->QueuePausedEvent();
}
}
state_ = State::kPaused;
return S_OK;
}
// After this method is called, methods on the media source and all of its
// media streams return MF_E_SHUTDOWN (except for IUnknown methods).
HRESULT MediaFoundationSourceWrapper::Shutdown() {
DVLOG_FUNC(1);
for (auto stream : media_streams_) {
stream->DetachParent();
}
state_ = State::kShutdown;
return S_OK;
}
// TODO(frankli): In MediaFoundationSourceWrapper::XxxEvent methods below,
// investigate why MediaFoundationSourceWrapper is not being destructed
// when we return MF_E_SHUTDOWN if |state_| is State::kShutdown.
// https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/nf-mfobjects-imfmediaeventgenerator-queueevent
// indicates the object can return MF_E_SHUTDOWN.
//
HRESULT MediaFoundationSourceWrapper::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 MediaFoundationSourceWrapper::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 MediaFoundationSourceWrapper::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 MediaFoundationSourceWrapper::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 MediaFoundationSourceWrapper::GetInputTrustAuthority(
DWORD stream_id,
REFIID riid,
IUnknown** object_out) {
DVLOG_FUNC(1);
if (stream_id >= StreamCount())
return E_INVALIDARG;
if (!cdm_proxy_) {
DVLOG_FUNC(1) << "MF_E_NOT_PROTECTED";
return MF_E_NOT_PROTECTED;
}
if (!media_streams_[stream_id]->IsEncrypted()) {
DVLOG_FUNC(1) << "Unprotected stream; stream_id=" << stream_id;
return MF_E_NOT_PROTECTED;
}
// Use |nullptr| for content init_data and |0| for its size.
RETURN_IF_FAILED(cdm_proxy_->GetInputTrustAuthority(
stream_id, StreamCount(), nullptr, 0, riid, object_out));
return S_OK;
}
HRESULT MediaFoundationSourceWrapper::GetService(REFGUID guid_service,
REFIID riid,
LPVOID* result) {
DVLOG_FUNC(3);
DCHECK(result);
if (!IsEqualGUID(guid_service, MF_RATE_CONTROL_SERVICE))
return MF_E_UNSUPPORTED_SERVICE;
return QueryInterface(riid, result);
}
HRESULT MediaFoundationSourceWrapper::GetSlowestRate(MFRATE_DIRECTION direction,
BOOL supports_thinning,
float* rate) {
DVLOG_FUNC(3);
DCHECK(rate);
if (direction == MFRATE_REVERSE) {
return MF_E_REVERSE_UNSUPPORTED;
}
*rate = 0.0f;
return state_ == State::kShutdown ? MF_E_SHUTDOWN : S_OK;
}
HRESULT MediaFoundationSourceWrapper::GetFastestRate(MFRATE_DIRECTION direction,
BOOL supports_thinning,
float* rate) {
DVLOG_FUNC(3);
DCHECK(rate);
if (direction == MFRATE_REVERSE) {
return MF_E_REVERSE_UNSUPPORTED;
}
*rate = 0.0f;
HRESULT hr = MF_E_SHUTDOWN;
if (state_ != State::kShutdown) {
*rate = 8.0f;
hr = S_OK;
}
return hr;
}
HRESULT MediaFoundationSourceWrapper::IsRateSupported(BOOL supports_thinning,
float new_rate,
float* supported_rate) {
DVLOG_FUNC(2) << "new_rate=" << new_rate;
if (state_ == State::kShutdown)
return MF_E_SHUTDOWN;
if (supported_rate)
*supported_rate = 0.0f;
MFRATE_DIRECTION direction =
(new_rate >= 0) ? MFRATE_FORWARD : MFRATE_REVERSE;
float fastest_rate = 0.0f;
float slowest_rate = 0.0f;
GetFastestRate(direction, supports_thinning, &fastest_rate);
GetSlowestRate(direction, supports_thinning, &slowest_rate);
HRESULT hr;
if (supports_thinning) {
// We do not support thinning right now. If Thin is supported, this
// MediaSource is expected to drop all samples except ones marked as
// MFSampleExtension_CleanPoint
hr = MF_E_THINNING_UNSUPPORTED;
} else if (new_rate < slowest_rate) {
hr = MF_E_REVERSE_UNSUPPORTED;
} else if (new_rate > fastest_rate) {
hr = MF_E_UNSUPPORTED_RATE;
} else {
// Supported
hr = S_OK;
if (supported_rate) {
*supported_rate = new_rate;
}
}
DVLOG_FUNC(2) << PrintHr(hr);
return hr;
}
HRESULT MediaFoundationSourceWrapper::SetRate(BOOL supports_thinning,
float rate) {
DVLOG_FUNC(2);
if (state_ == State::kShutdown)
return MF_E_SHUTDOWN;
HRESULT hr = IsRateSupported(supports_thinning, rate, &current_rate_);
if (SUCCEEDED(hr)) {
PROPVARIANT varRate;
varRate.vt = VT_R4;
varRate.fltVal = current_rate_;
hr = QueueEvent(MESourceRateChanged, GUID_NULL, S_OK, &varRate);
}
return hr;
}
HRESULT MediaFoundationSourceWrapper::GetRate(BOOL* supports_thinning,
float* rate) {
DVLOG_FUNC(2);
*supports_thinning = FALSE;
*rate = 0.0f;
HRESULT hr = MF_E_SHUTDOWN;
if (state_ != State::kShutdown) {
hr = S_OK;
*rate = current_rate_;
}
return hr;
}
uint32_t MediaFoundationSourceWrapper::StreamCount() const {
return media_streams_.size();
}
void MediaFoundationSourceWrapper::CheckForEndOfPresentation() {
DVLOG_FUNC(2);
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (presentation_ended_) {
return;
}
bool presentation_ended = true;
for (auto mf_stream : media_streams_) {
if (!mf_stream->HasEnded()) {
presentation_ended = false;
break;
}
}
presentation_ended_ = presentation_ended;
if (presentation_ended_) {
HRESULT hr = QueueEvent(MEEndOfPresentation, GUID_NULL, S_OK, nullptr);
DLOG_IF(ERROR, FAILED(hr))
<< "Failed to notify end of presentation: " << PrintHr(hr);
}
}
bool MediaFoundationSourceWrapper::HasEncryptedStream() const {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
for (auto mf_stream : media_streams_) {
if (mf_stream->IsEncrypted()) {
return true;
}
}
return false;
}
void MediaFoundationSourceWrapper::SetCdmProxy(
scoped_refptr<MediaFoundationCdmProxy> cdm_proxy) {
DVLOG_FUNC(2);
DCHECK(task_runner_->RunsTasksInCurrentSequence());
// cdm_proxy_ should never change.
DCHECK(!cdm_proxy_);
cdm_proxy_ = std::move(cdm_proxy);
HRESULT hr = cdm_proxy_->RefreshTrustedInput();
DLOG_IF(ERROR, FAILED(hr))
<< "Failed to refresh trusted input: " << PrintHr(hr);
}
bool MediaFoundationSourceWrapper::SetVideoStreamEnabled(bool enabled) {
DVLOG_FUNC(2) << "enabled=" << enabled;
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (enabled == video_stream_enabled_)
return false;
video_stream_enabled_ = enabled;
for (auto stream : media_streams_) {
if (stream->IsSelected() &&
stream->StreamType() == DemuxerStream::Type::VIDEO) {
stream->SetEnabled(enabled);
if (enabled && stream->HasEnded()) {
// Need to restart (pause+play) if we're re-enabling a video stream
// which has previously reached EOS.
return true;
}
}
}
return false;
}
void MediaFoundationSourceWrapper::FlushStreams() {
DVLOG_FUNC(2);
DCHECK(task_runner_->RunsTasksInCurrentSequence());
for (auto stream : media_streams_) {
stream->SetFlushed(true);
}
}
} // namespace media