blob: a1c650a3a3081696e8c7aa0b86131390ba0202a9 [file] [log] [blame]
// 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/renderer_host/media/render_frame_audio_output_stream_factory.h"
#include <cstdint>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/task/post_task.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/unguessable_token.h"
#include "content/browser/media/forwarding_audio_stream_factory.h"
#include "content/browser/renderer_host/media/audio_output_authorization_handler.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "media/base/output_device_info.h"
#include "media/mojo/interfaces/audio_output_stream.mojom.h"
#include "mojo/public/cpp/bindings/binding.h"
namespace content {
class RenderFrameAudioOutputStreamFactory::Core final
: public mojom::RendererAudioOutputStreamFactory {
public:
Core(RenderFrameHost* frame,
media::AudioSystem* audio_system,
MediaStreamManager* media_stream_manager,
mojom::RendererAudioOutputStreamFactoryRequest request);
~Core() final = default;
void Init(mojom::RendererAudioOutputStreamFactoryRequest request);
size_t current_number_of_providers_for_testing() {
return stream_providers_.size();
}
private:
// This class implements media::mojom::AudioOutputStreamProvider for a single
// streams and cleans itself up (using the |owner| pointer) when done.
class ProviderImpl final : public media::mojom::AudioOutputStreamProvider {
public:
ProviderImpl(media::mojom::AudioOutputStreamProviderRequest request,
RenderFrameAudioOutputStreamFactory::Core* owner,
const std::string& device_id)
: owner_(owner),
device_id_(device_id),
binding_(this, std::move(request)) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Unretained is safe since |this| owns |binding_|.
binding_.set_connection_error_handler(
base::BindOnce(&ProviderImpl::Done, base::Unretained(this)));
}
~ProviderImpl() final { DCHECK_CURRENTLY_ON(BrowserThread::IO); }
void Acquire(
const media::AudioParameters& params,
media::mojom::AudioOutputStreamProviderClientPtr provider_client,
const base::Optional<base::UnguessableToken>& processing_id) final {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
TRACE_EVENT1("audio",
"RenderFrameAudioOutputStreamFactory::ProviderImpl::Acquire",
"raw device id", device_id_);
base::WeakPtr<ForwardingAudioStreamFactory::Core> factory =
owner_->forwarding_factory_;
if (factory) {
factory->CreateOutputStream(owner_->process_id_, owner_->frame_id_,
device_id_, params, processing_id,
std::move(provider_client));
}
// Since the stream creation has been propagated, |this| is no longer
// needed.
Done();
}
void Done() { owner_->DeleteProvider(this); }
private:
RenderFrameAudioOutputStreamFactory::Core* const owner_;
const std::string device_id_;
mojo::Binding<media::mojom::AudioOutputStreamProvider> binding_;
DISALLOW_COPY_AND_ASSIGN(ProviderImpl);
};
using OutputStreamProviderSet =
base::flat_set<std::unique_ptr<media::mojom::AudioOutputStreamProvider>,
base::UniquePtrComparator>;
// mojom::RendererAudioOutputStreamFactory implementation.
void RequestDeviceAuthorization(
media::mojom::AudioOutputStreamProviderRequest provider_request,
int32_t session_id,
const std::string& device_id,
RequestDeviceAuthorizationCallback callback) final;
// Here, the |raw_device_id| is used to create the stream, and
// |device_id_for_renderer| is nonempty in the case when the renderer
// requested a device using a |session_id|, to let it know which device was
// chosen. This id is hashed.
void AuthorizationCompleted(
base::TimeTicks auth_start_time,
media::mojom::AudioOutputStreamProviderRequest request,
RequestDeviceAuthorizationCallback callback,
media::OutputDeviceStatus status,
const media::AudioParameters& params,
const std::string& raw_device_id,
const std::string& device_id_for_renderer);
void DeleteProvider(media::mojom::AudioOutputStreamProvider* stream_provider);
const int process_id_;
const int frame_id_;
AudioOutputAuthorizationHandler authorization_handler_;
mojo::Binding<mojom::RendererAudioOutputStreamFactory> binding_;
// Always null-check this weak pointer before dereferencing it.
base::WeakPtr<ForwardingAudioStreamFactory::Core> forwarding_factory_;
// The OutputStreamProviders for authorized streams are kept here while
// waiting for the renderer to finish creating the stream, and destructed
// afterwards.
OutputStreamProviderSet stream_providers_;
// Weak pointers are used to cancel device authorizations that are in flight
// while |this| is destructed.
base::WeakPtrFactory<Core> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(Core);
};
RenderFrameAudioOutputStreamFactory::RenderFrameAudioOutputStreamFactory(
RenderFrameHost* frame,
media::AudioSystem* audio_system,
MediaStreamManager* media_stream_manager,
mojom::RendererAudioOutputStreamFactoryRequest request)
: core_(new Core(frame,
audio_system,
media_stream_manager,
std::move(request))) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
RenderFrameAudioOutputStreamFactory::~RenderFrameAudioOutputStreamFactory() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Ensure |core_| is deleted on the right thread. DeleteOnIOThread isn't used
// as it doesn't post in case it is already executed on the right thread. That
// causes issues in unit tests where the UI thread and the IO thread are the
// same.
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce([](std::unique_ptr<Core>) {}, std::move(core_)));
}
size_t
RenderFrameAudioOutputStreamFactory::CurrentNumberOfProvidersForTesting() {
return core_->current_number_of_providers_for_testing();
}
RenderFrameAudioOutputStreamFactory::Core::Core(
RenderFrameHost* frame,
media::AudioSystem* audio_system,
MediaStreamManager* media_stream_manager,
mojom::RendererAudioOutputStreamFactoryRequest request)
: process_id_(frame->GetProcess()->GetID()),
frame_id_(frame->GetRoutingID()),
authorization_handler_(audio_system, media_stream_manager, process_id_),
binding_(this),
weak_ptr_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ForwardingAudioStreamFactory::Core* tmp_factory =
ForwardingAudioStreamFactory::CoreForFrame(frame);
if (!tmp_factory) {
// The only case when we not have a forwarding factory at this point is when
// the frame belongs to an interstitial. Interstitials don't need audio, so
// it's fine to drop the request.
return;
}
forwarding_factory_ = tmp_factory->AsWeakPtr();
// Unretained is safe since the destruction of |this| is posted to the IO
// thread.
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&Core::Init, base::Unretained(this), std::move(request)));
}
void RenderFrameAudioOutputStreamFactory::Core::Init(
mojom::RendererAudioOutputStreamFactoryRequest request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
binding_.Bind(std::move(request));
}
void RenderFrameAudioOutputStreamFactory::Core::RequestDeviceAuthorization(
media::mojom::AudioOutputStreamProviderRequest provider_request,
int32_t session_id,
const std::string& device_id,
RequestDeviceAuthorizationCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
TRACE_EVENT2(
"audio",
"RenderFrameAudioOutputStreamFactory::RequestDeviceAuthorization",
"device id", device_id, "session_id", session_id);
const base::TimeTicks auth_start_time = base::TimeTicks::Now();
AudioOutputAuthorizationHandler::AuthorizationCompletedCallback
completed_callback = base::BindOnce(
&RenderFrameAudioOutputStreamFactory::Core::AuthorizationCompleted,
weak_ptr_factory_.GetWeakPtr(), auth_start_time,
std::move(provider_request), std::move(callback));
authorization_handler_.RequestDeviceAuthorization(
frame_id_, session_id, device_id, std::move(completed_callback));
}
void RenderFrameAudioOutputStreamFactory::Core::AuthorizationCompleted(
base::TimeTicks auth_start_time,
media::mojom::AudioOutputStreamProviderRequest request,
RequestDeviceAuthorizationCallback callback,
media::OutputDeviceStatus status,
const media::AudioParameters& params,
const std::string& raw_device_id,
const std::string& device_id_for_renderer) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
TRACE_EVENT2("audio",
"RenderFrameAudioOutputStreamFactory::AuthorizationCompleted",
"raw device id", raw_device_id, "status", status);
AudioOutputAuthorizationHandler::UMALogDeviceAuthorizationTime(
auth_start_time);
// If |status| is not OK, this call will be considered as an error signal by
// the renderer.
std::move(callback).Run(status, params, device_id_for_renderer);
if (status == media::OUTPUT_DEVICE_STATUS_OK) {
stream_providers_.insert(std::make_unique<ProviderImpl>(
std::move(request), this, std::move(raw_device_id)));
}
}
void RenderFrameAudioOutputStreamFactory::Core::DeleteProvider(
media::mojom::AudioOutputStreamProvider* stream_provider) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
size_t deleted = stream_providers_.erase(stream_provider);
DCHECK_EQ(1u, deleted);
}
} // namespace content