| // Copyright 2018 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 "content/browser/media/audio_output_stream_broker.h" |
| |
| #include <utility> |
| |
| #include "base/metrics/histogram_macros.h" |
| #include "base/trace_event/trace_event.h" |
| #include "content/browser/media/media_internals.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/media_observer.h" |
| #include "content/public/common/content_client.h" |
| #include "media/audio/audio_logging.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| // Used in Media.Audio.Render.StreamBrokerDisconnectReason2 histogram, matches |
| // StreamBrokerDisconnectReason2 enum. |
| enum class StreamBrokerDisconnectReason { |
| kDefault = 0, |
| kPlatformError, |
| kTerminatedByClient, |
| kTerminatedByClientAwaitingCreated, |
| kStreamCreationFailed, |
| kDocumentDestroyed, |
| kDocumentDestroyedAwaitingCreated, |
| kMaxValue = kDocumentDestroyedAwaitingCreated |
| }; |
| |
| using DisconnectReason = |
| media::mojom::AudioOutputStreamObserver::DisconnectReason; |
| |
| StreamBrokerDisconnectReason GetDisconnectReason(DisconnectReason reason, |
| bool awaiting_created) { |
| switch (reason) { |
| case DisconnectReason::kPlatformError: |
| return StreamBrokerDisconnectReason::kPlatformError; |
| case DisconnectReason::kTerminatedByClient: |
| return awaiting_created |
| ? StreamBrokerDisconnectReason:: |
| kTerminatedByClientAwaitingCreated |
| : StreamBrokerDisconnectReason::kTerminatedByClient; |
| case DisconnectReason::kStreamCreationFailed: |
| return StreamBrokerDisconnectReason::kStreamCreationFailed; |
| case DisconnectReason::kDocumentDestroyed: |
| return awaiting_created |
| ? StreamBrokerDisconnectReason:: |
| kDocumentDestroyedAwaitingCreated |
| : StreamBrokerDisconnectReason::kDocumentDestroyed; |
| case DisconnectReason::kDefault: |
| return StreamBrokerDisconnectReason::kDefault; |
| } |
| } |
| |
| } // namespace |
| |
| AudioOutputStreamBroker::AudioOutputStreamBroker( |
| int render_process_id, |
| int render_frame_id, |
| int stream_id, |
| const std::string& output_device_id, |
| const media::AudioParameters& params, |
| const base::UnguessableToken& group_id, |
| const base::Optional<base::UnguessableToken>& processing_id, |
| DeleterCallback deleter, |
| media::mojom::AudioOutputStreamProviderClientPtr client) |
| : AudioStreamBroker(render_process_id, render_frame_id), |
| output_device_id_(output_device_id), |
| params_(params), |
| group_id_(group_id), |
| processing_id_(processing_id), |
| deleter_(std::move(deleter)), |
| client_(std::move(client)), |
| observer_(render_process_id, render_frame_id, stream_id), |
| observer_binding_(&observer_), |
| weak_ptr_factory_(this) { |
| DCHECK(client_); |
| DCHECK(deleter_); |
| DCHECK(group_id_); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("audio", "AudioOutputStreamBroker", this); |
| |
| MediaObserver* media_observer = |
| GetContentClient()->browser()->GetMediaObserver(); |
| |
| // May be null in unit tests. |
| if (media_observer) |
| media_observer->OnCreatingAudioStream(render_process_id, render_frame_id); |
| |
| // Unretained is safe because |this| owns |client_| |
| client_.set_connection_error_handler( |
| base::BindOnce(&AudioOutputStreamBroker::Cleanup, base::Unretained(this), |
| DisconnectReason::kTerminatedByClient)); |
| } |
| |
| AudioOutputStreamBroker::~AudioOutputStreamBroker() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_); |
| |
| const StreamBrokerDisconnectReason reason = |
| GetDisconnectReason(disconnect_reason_, AwaitingCreated()); |
| |
| if (AwaitingCreated()) { |
| TRACE_EVENT_NESTABLE_ASYNC_END1("audio", "CreateStream", this, "success", |
| "failed or cancelled"); |
| } |
| |
| TRACE_EVENT_NESTABLE_ASYNC_END1("audio", "AudioOutputStreamBroker", this, |
| "disconnect reason", |
| static_cast<uint32_t>(reason)); |
| |
| UMA_HISTOGRAM_ENUMERATION("Media.Audio.Render.StreamBrokerDisconnectReason2", |
| reason); |
| |
| if (AwaitingCreated()) { |
| UMA_HISTOGRAM_TIMES( |
| "Media.Audio.Render.StreamBrokerDocumentDestroyedAwaitingCreatedTime", |
| base::TimeTicks::Now() - stream_creation_start_time_); |
| } |
| } |
| |
| void AudioOutputStreamBroker::CreateStream( |
| audio::mojom::StreamFactory* factory) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_); |
| DCHECK(!observer_binding_.is_bound()); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("audio", "CreateStream", this, "device id", |
| output_device_id_); |
| stream_creation_start_time_ = base::TimeTicks::Now(); |
| |
| // Set up observer ptr. Unretained is safe because |this| owns |
| // |observer_binding_|. |
| media::mojom::AudioOutputStreamObserverAssociatedPtrInfo ptr_info; |
| observer_binding_.Bind(mojo::MakeRequest(&ptr_info)); |
| observer_binding_.set_connection_error_with_reason_handler(base::BindOnce( |
| &AudioOutputStreamBroker::ObserverBindingLost, base::Unretained(this))); |
| |
| media::mojom::AudioOutputStreamPtr stream; |
| media::mojom::AudioOutputStreamRequest stream_request = |
| mojo::MakeRequest(&stream); |
| |
| // Note that the component id for AudioLog is used to differentiate between |
| // several users of the same audio log. Since this audio log is for a single |
| // stream, the component id used doesn't matter. |
| constexpr int log_component_id = 0; |
| factory->CreateOutputStream( |
| std::move(stream_request), std::move(ptr_info), |
| MediaInternals::GetInstance()->CreateMojoAudioLog( |
| media::AudioLogFactory::AudioComponent::AUDIO_OUTPUT_CONTROLLER, |
| log_component_id, render_process_id(), render_frame_id()), |
| output_device_id_, params_, group_id_, processing_id_, |
| base::BindOnce(&AudioOutputStreamBroker::StreamCreated, |
| weak_ptr_factory_.GetWeakPtr(), std::move(stream))); |
| } |
| |
| void AudioOutputStreamBroker::StreamCreated( |
| media::mojom::AudioOutputStreamPtr stream, |
| media::mojom::ReadWriteAudioDataPipePtr data_pipe) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_); |
| TRACE_EVENT_NESTABLE_ASYNC_END1("audio", "CreateStream", this, "success", |
| !!data_pipe); |
| UMA_HISTOGRAM_TIMES("Media.Audio.Render.StreamBrokerStreamCreationTime", |
| base::TimeTicks::Now() - stream_creation_start_time_); |
| stream_creation_start_time_ = base::TimeTicks(); |
| |
| if (!data_pipe) { |
| // Stream creation failed. Signal error. |
| client_.ResetWithReason( |
| static_cast<uint32_t>(DisconnectReason::kPlatformError), std::string()); |
| Cleanup(DisconnectReason::kStreamCreationFailed); |
| return; |
| } |
| |
| client_->Created(std::move(stream), std::move(data_pipe)); |
| } |
| |
| void AudioOutputStreamBroker::ObserverBindingLost( |
| uint32_t reason, |
| const std::string& description) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_); |
| TRACE_EVENT_NESTABLE_ASYNC_INSTANT1("audio", "ObserverBindingLost", this, |
| "reset reason", reason); |
| if (reason > static_cast<uint32_t>(DisconnectReason::kMaxValue)) |
| NOTREACHED() << "Invalid reason: " << reason; |
| |
| DisconnectReason reason_enum = static_cast<DisconnectReason>(reason); |
| |
| // TODO(https://crbug.com/787806): Don't propagate errors if we can retry |
| // instead. |
| client_.ResetWithReason( |
| static_cast<uint32_t>(DisconnectReason::kPlatformError), std::string()); |
| Cleanup((reason_enum == DisconnectReason::kPlatformError && AwaitingCreated()) |
| ? DisconnectReason::kStreamCreationFailed |
| : reason_enum); |
| } |
| |
| void AudioOutputStreamBroker::Cleanup(DisconnectReason reason) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_); |
| DCHECK_EQ(DisconnectReason::kDocumentDestroyed, disconnect_reason_); |
| disconnect_reason_ = reason; |
| std::move(deleter_).Run(this); |
| } |
| |
| bool AudioOutputStreamBroker::AwaitingCreated() const { |
| return stream_creation_start_time_ != base::TimeTicks(); |
| } |
| |
| } // namespace content |