| // Copyright 2018 The Chromium Authors |
| // 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/functional/bind.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/trace_event/trace_event.h" |
| #include "content/browser/browser_main_loop.h" |
| #include "content/browser/media/audio_stream_broker_helper.h" |
| #include "content/browser/media/media_internals.h" |
| #include "content/browser/renderer_host/media/media_stream_manager.h" |
| #include "content/browser/renderer_host/media/preferred_audio_output_device_manager.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_device_description.h" |
| #include "media/audio/audio_logging.h" |
| #include "media/mojo/mojom/audio_data_pipe.mojom.h" |
| #include "media/mojo/mojom/audio_output_stream.mojom.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| 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, |
| const GlobalRenderFrameHostToken& main_frame_token, |
| int stream_id, |
| const std::string& output_device_id, |
| const media::AudioParameters& params, |
| const base::UnguessableToken& group_id, |
| DeleterCallback deleter, |
| mojo::PendingRemote<media::mojom::AudioOutputStreamProviderClient> client) |
| : AudioStreamBroker(render_process_id, render_frame_id), |
| main_frame_token_(main_frame_token), |
| output_device_id_(output_device_id), |
| params_(params), |
| group_id_(group_id), |
| deleter_(std::move(deleter)), |
| client_(std::move(client)), |
| observer_(render_process_id, render_frame_id, stream_id), |
| observer_receiver_(&observer_) { |
| DCHECK(client_); |
| DCHECK(deleter_); |
| DCHECK(group_id_); |
| TRACE_EVENT_BEGIN("audio", "AudioOutputStreamBroker", |
| perfetto::Track::FromPointer(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_disconnect_handler( |
| base::BindOnce(&AudioOutputStreamBroker::Cleanup, base::Unretained(this), |
| DisconnectReason::kTerminatedByClient)); |
| |
| NotifyFrameHostOfAudioStreamStarted(render_process_id, render_frame_id, |
| /*is_capturing=*/false); |
| } |
| |
| AudioOutputStreamBroker::~AudioOutputStreamBroker() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_); |
| |
| NotifyFrameHostOfAudioStreamStopped(render_process_id(), render_frame_id(), |
| /*is_capturing=*/false); |
| |
| const StreamBrokerDisconnectReason reason = |
| GetDisconnectReason(disconnect_reason_, AwaitingCreated()); |
| |
| if (AwaitingCreated()) { |
| // End "CreateStream" trace event. |
| TRACE_EVENT_END("audio", perfetto::Track::FromPointer(this), "success", |
| "failed or cancelled"); |
| } |
| |
| if (MediaStreamManager::GetPreferredOutputManagerInstance()) { |
| MediaStreamManager::GetPreferredOutputManagerInstance()->RemoveSwitcher( |
| main_frame_token_, this); |
| } |
| |
| // End "AudioOutputStreamBroker" trace event. |
| TRACE_EVENT_END("audio", perfetto::Track::FromPointer(this), |
| "disconnect reason", static_cast<uint32_t>(reason)); |
| } |
| |
| void AudioOutputStreamBroker::CreateStream( |
| media::mojom::AudioStreamFactory* factory) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_); |
| DCHECK(!observer_receiver_.is_bound()); |
| DCHECK(!device_switch_interface_.is_bound()); |
| TRACE_EVENT_BEGIN("audio", "CreateStream", perfetto::Track::FromPointer(this), |
| "device id", output_device_id_); |
| |
| stream_creation_start_time_ = base::TimeTicks::Now(); |
| |
| // Set up observer ptr. Unretained is safe because |this| owns |
| // |observer_receiver_|. |
| mojo::PendingAssociatedRemote<media::mojom::AudioOutputStreamObserver> |
| observer; |
| observer_receiver_.Bind(observer.InitWithNewEndpointAndPassReceiver()); |
| observer_receiver_.set_disconnect_with_reason_handler(base::BindOnce( |
| &AudioOutputStreamBroker::ObserverBindingLost, base::Unretained(this))); |
| |
| mojo::PendingRemote<media::mojom::AudioOutputStream> stream; |
| auto stream_receiver = stream.InitWithNewPipeAndPassReceiver(); |
| |
| // 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; |
| |
| if (MediaStreamManager::GetPreferredOutputManagerInstance() && |
| media::AudioDeviceDescription::IsDefaultDevice(output_device_id_)) { |
| // Register the device switcher with PreferredAudioOutputDeviceManager. |
| // `output_device_id_` will be updated by the `SwitchAudioOutputDeviceId`, |
| // which is called by the PreferredAudioOutputDeviceManager during |
| // `AddSwitcher()`. |
| MediaStreamManager::GetPreferredOutputManagerInstance()->AddSwitcher( |
| main_frame_token_, this); |
| |
| factory->CreateSwitchableOutputStream( |
| std::move(stream_receiver), |
| device_switch_interface_.BindNewPipeAndPassReceiver(), |
| std::move(observer), |
| MediaInternals::GetInstance()->CreateMojoAudioLog( |
| media::AudioLogFactory::AudioComponent::kAudioOuputController, |
| log_component_id, render_process_id(), render_frame_id()), |
| output_device_id_, params_, group_id_, |
| base::BindOnce(&AudioOutputStreamBroker::StreamCreated, |
| weak_ptr_factory_.GetWeakPtr(), std::move(stream))); |
| } else { |
| factory->CreateOutputStream( |
| std::move(stream_receiver), std::move(observer), |
| MediaInternals::GetInstance()->CreateMojoAudioLog( |
| media::AudioLogFactory::AudioComponent::kAudioOuputController, |
| log_component_id, render_process_id(), render_frame_id()), |
| output_device_id_, params_, group_id_, |
| base::BindOnce(&AudioOutputStreamBroker::StreamCreated, |
| weak_ptr_factory_.GetWeakPtr(), std::move(stream))); |
| } |
| } |
| |
| void AudioOutputStreamBroker::StreamCreated( |
| mojo::PendingRemote<media::mojom::AudioOutputStream> stream, |
| media::mojom::ReadWriteAudioDataPipePtr data_pipe) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_); |
| // End "CreateStream" trace event. |
| TRACE_EVENT_END("audio", perfetto::Track::FromPointer(this), "success", |
| !!data_pipe); |
| 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_INSTANT("audio", "ObserverBindingLost", |
| perfetto::Track::FromPointer(this), "reset reason", |
| reason); |
| if (reason > static_cast<uint32_t>(DisconnectReason::kMaxValue)) { |
| NOTREACHED() << "Invalid reason: " << reason; |
| } |
| |
| DisconnectReason reason_enum = static_cast<DisconnectReason>(reason); |
| |
| // TODO(crbug.com/40551225): 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(); |
| } |
| |
| void AudioOutputStreamBroker::SwitchAudioOutputDeviceId( |
| const std::string& device_id) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_); |
| output_device_id_ = device_id; |
| if (device_switch_interface_.is_bound()) { |
| device_switch_interface_->SwitchAudioOutputDeviceId(output_device_id_); |
| } |
| } |
| |
| } // namespace content |