#include "media/audio/audio_output_device.h"
#include <stddef.h>
#include <stdint.h>
#include <cmath>
#include <memory>
#include <utility>
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_restrictions.h"
#include "base/timer/timer.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/audio_output_device_thread_callback.h"
#include "media/base/limits.h"
namespace media {
AudioOutputDevice::AudioOutputDevice(
std::unique_ptr<AudioOutputIPC> ipc,
const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner,
const AudioSinkParameters& sink_params,
base::TimeDelta authorization_timeout)
: io_task_runner_(io_task_runner),
callback_(nullptr),
ipc_(std::move(ipc)),
state_(IDLE),
session_id_(sink_params.session_id),
device_id_(sink_params.device_id),
stopping_hack_(false),
did_receive_auth_(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED),
output_params_(AudioParameters::UnavailableDeviceParams()),
device_status_(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL),
auth_timeout_(authorization_timeout) {
DCHECK(ipc_);
DCHECK(io_task_runner_);
}
void AudioOutputDevice::Initialize(const AudioParameters& params,
RenderCallback* callback) {
CHECK(params.IsValid());
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&AudioOutputDevice::InitializeOnIOThread, this, params,
base::UnsafeDangling(callback)));
}
void AudioOutputDevice::InitializeOnIOThread(
const AudioParameters& params,
MayBeDangling<RenderCallback> callback) {
DCHECK(!callback_) << "Calling Initialize() twice?";
DCHECK(params.IsValid());
DVLOG(1) << __func__ << ": " << params.AsHumanReadableString();
audio_parameters_ = params;
base::AutoLock auto_lock(audio_thread_lock_);
if (!stopping_hack_)
callback_ = callback;
}
AudioOutputDevice::~AudioOutputDevice() {
{
base::AutoLock auto_lock(device_info_lock_);
if (pending_device_info_cb_) {
std::move(pending_device_info_cb_)
.Run(OutputDeviceInfo(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL));
}
}
#if DCHECK_IS_ON()
DCHECK(audio_thread_lock_.Try());
DCHECK_EQ(state_, IDLE);
DCHECK(!audio_thread_);
DCHECK(!audio_callback_);
DCHECK(!stopping_hack_);
audio_thread_lock_.Release();
#endif
}
void AudioOutputDevice::RequestDeviceAuthorization() {
TRACE_EVENT0("audio", "AudioOutputDevice::RequestDeviceAuthorization");
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&AudioOutputDevice::RequestDeviceAuthorizationOnIOThread,
this));
}
void AudioOutputDevice::Start() {
TRACE_EVENT0("audio", "AudioOutputDevice::Start");
io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&AudioOutputDevice::CreateStreamOnIOThread, this));
}
void AudioOutputDevice::Stop() {
TRACE_EVENT0("audio", "AudioOutputDevice::Stop");
{
base::ScopedAllowBaseSyncPrimitives allow;
base::AutoLock auto_lock(audio_thread_lock_);
audio_thread_.reset();
stopping_hack_ = true;
}
io_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&AudioOutputDevice::ShutDownOnIOThread, this));
}
void AudioOutputDevice::Play() {
TRACE_EVENT0("audio", "AudioOutputDevice::Play");
io_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&AudioOutputDevice::PlayOnIOThread, this));
}
void AudioOutputDevice::Pause() {
TRACE_EVENT0("audio", "AudioOutputDevice::Pause");
io_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&AudioOutputDevice::PauseOnIOThread, this));
}
void AudioOutputDevice::Flush() {
TRACE_EVENT0("audio", "AudioOutputDevice::Flush");
io_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&AudioOutputDevice::FlushOnIOThread, this));
}
bool AudioOutputDevice::SetVolume(double volume) {
TRACE_EVENT1("audio", "AudioOutputDevice::Pause", "volume", volume);
if (volume < 0 || volume > 1.0)
return false;
return io_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&AudioOutputDevice::SetVolumeOnIOThread, this, volume));
}
OutputDeviceInfo AudioOutputDevice::GetOutputDeviceInfo() {
TRACE_EVENT0("audio", "AudioOutputDevice::GetOutputDeviceInfo");
DCHECK(!io_task_runner_->BelongsToCurrentThread());
did_receive_auth_.Wait();
return GetOutputDeviceInfo_Signaled();
}
void AudioOutputDevice::GetOutputDeviceInfoAsync(OutputDeviceInfoCB info_cb) {
{
base::AutoLock auto_lock(device_info_lock_);
if (!did_receive_auth_.IsSignaled()) {
DCHECK(!pending_device_info_cb_);
pending_device_info_cb_ =
base::BindPostTaskToCurrentDefault(std::move(info_cb));
return;
}
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(info_cb), GetOutputDeviceInfo_Signaled()));
}
bool AudioOutputDevice::IsOptimizedForHardwareParameters() {
return true;
}
bool AudioOutputDevice::CurrentThreadIsRenderingThread() {
return audio_callback_->CurrentThreadIsAudioDeviceThread();
}
void AudioOutputDevice::RequestDeviceAuthorizationOnIOThread() {
DCHECK(io_task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, IDLE);
state_ = AUTHORIZATION_REQUESTED;
ipc_->RequestDeviceAuthorization(this, session_id_, device_id_);
if (auth_timeout_.is_positive()) {
auth_timeout_action_ = std::make_unique<base::OneShotTimer>();
auth_timeout_action_->Start(
FROM_HERE, auth_timeout_,
base::BindOnce(&AudioOutputDevice::OnDeviceAuthorized, this,
OUTPUT_DEVICE_STATUS_ERROR_TIMED_OUT, AudioParameters(),
std::string()));
}
}
void AudioOutputDevice::CreateStreamOnIOThread() {
TRACE_EVENT0("audio", "AudioOutputDevice::Create");
DCHECK(io_task_runner_->BelongsToCurrentThread());
#if DCHECK_IS_ON()
{
base::AutoLock auto_lock(audio_thread_lock_);
if (!stopping_hack_)
DCHECK(callback_) << "Initialize hasn't been called";
}
#endif
DCHECK_NE(state_, STREAM_CREATION_REQUESTED);
if (!ipc_) {
NotifyRenderCallbackOfError();
return;
}
if (state_ == IDLE && !(did_receive_auth_.IsSignaled() && device_id_.empty()))
RequestDeviceAuthorizationOnIOThread();
ipc_->CreateStream(this, audio_parameters_);
ipc_->PlayStream();
state_ = STREAM_CREATION_REQUESTED;
}
void AudioOutputDevice::PlayOnIOThread() {
DCHECK(io_task_runner_->BelongsToCurrentThread());
if (audio_callback_)
audio_callback_->InitializePlayStartTime();
if (ipc_)
ipc_->PlayStream();
}
void AudioOutputDevice::PauseOnIOThread() {
DCHECK(io_task_runner_->BelongsToCurrentThread());
if (ipc_)
ipc_->PauseStream();
}
void AudioOutputDevice::FlushOnIOThread() {
DCHECK(io_task_runner_->BelongsToCurrentThread());
if (ipc_)
ipc_->FlushStream();
}
void AudioOutputDevice::ShutDownOnIOThread() {
DCHECK(io_task_runner_->BelongsToCurrentThread());
if (ipc_)
ipc_->CloseStream();
state_ = IDLE;
auth_timeout_action_.reset();
UMA_HISTOGRAM_ENUMERATION("Media.Audio.Render.StreamCallbackError2",
had_error_);
had_error_ = kNoError;
base::AutoLock auto_lock_(audio_thread_lock_);
base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_thread_join;
audio_thread_.reset();
audio_callback_.reset();
stopping_hack_ = false;
}
void AudioOutputDevice::SetVolumeOnIOThread(double volume) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
if (ipc_)
ipc_->SetVolume(volume);
}
void AudioOutputDevice::OnError() {
TRACE_EVENT0("audio", "AudioOutputDevice::OnError");
DCHECK(io_task_runner_->BelongsToCurrentThread());
if (state_ == IDLE)
return;
NotifyRenderCallbackOfError();
}
void AudioOutputDevice::OnDeviceAuthorized(
OutputDeviceStatus device_status,
const AudioParameters& output_params,
const std::string& matched_device_id) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
auth_timeout_action_.reset();
if (!ipc_)
return;
UMA_HISTOGRAM_BOOLEAN("Media.Audio.Render.OutputDeviceAuthorizationTimedOut",
device_status == OUTPUT_DEVICE_STATUS_ERROR_TIMED_OUT);
LOG_IF(WARNING, device_status == OUTPUT_DEVICE_STATUS_ERROR_TIMED_OUT)
<< "Output device authorization timed out";
if (!did_receive_auth_.IsSignaled()) {
device_status_ = device_status;
UMA_HISTOGRAM_ENUMERATION("Media.Audio.Render.OutputDeviceStatus",
device_status, OUTPUT_DEVICE_STATUS_MAX + 1);
}
if (device_status == OUTPUT_DEVICE_STATUS_OK) {
TRACE_EVENT0("audio", "AudioOutputDevice authorized");
if (!did_receive_auth_.IsSignaled()) {
output_params_ = output_params;
DCHECK(AudioDeviceDescription::UseSessionIdToSelectDevice(session_id_,
device_id_) ||
matched_device_id_.empty());
matched_device_id_ = matched_device_id;
DVLOG(1) << "AudioOutputDevice authorized, session_id: " << session_id_
<< ", device_id: " << device_id_
<< ", matched_device_id: " << matched_device_id_;
OnAuthSignal();
}
} else {
TRACE_EVENT1("audio", "AudioOutputDevice not authorized", "auth status",
device_status_);
ipc_->CloseStream();
OnIPCClosed();
NotifyRenderCallbackOfError();
}
}
void AudioOutputDevice::OnStreamCreated(
base::UnsafeSharedMemoryRegion shared_memory_region,
base::SyncSocket::ScopedHandle socket_handle,
bool playing_automatically) {
TRACE_EVENT0("audio", "AudioOutputDevice::OnStreamCreated");
DCHECK(io_task_runner_->BelongsToCurrentThread());
DCHECK(shared_memory_region.IsValid());
#if BUILDFLAG(IS_WIN)
DCHECK(socket_handle.is_valid());
#else
DCHECK(socket_handle.is_valid());
#endif
DCHECK_GT(shared_memory_region.GetSize(), 0u);
if (state_ != STREAM_CREATION_REQUESTED)
return;
{
base::AutoLock auto_lock(audio_thread_lock_);
if (stopping_hack_)
return;
DCHECK(!audio_thread_);
DCHECK(!audio_callback_);
audio_callback_ = std::make_unique<AudioOutputDeviceThreadCallback>(
audio_parameters_, std::move(shared_memory_region), callback_);
if (playing_automatically)
audio_callback_->InitializePlayStartTime();
audio_thread_ = std::make_unique<AudioDeviceThread>(
audio_callback_.get(), std::move(socket_handle), "AudioOutputDevice",
base::ThreadType::kRealtimeAudio);
}
}
void AudioOutputDevice::OnIPCClosed() {
TRACE_EVENT0("audio", "AudioOutputDevice::OnIPCClosed");
DCHECK(io_task_runner_->BelongsToCurrentThread());
ipc_.reset();
state_ = IDLE;
OnAuthSignal();
}
OutputDeviceInfo AudioOutputDevice::GetOutputDeviceInfo_Signaled() {
DCHECK(did_receive_auth_.IsSignaled());
OutputDeviceInfo info(AudioDeviceDescription::UseSessionIdToSelectDevice(
session_id_, device_id_)
? matched_device_id_
: device_id_,
device_status_, output_params_);
TRACE_EVENT1("audio", "AudioOutputDevice::GetOutputDeviceInfo_Signaled",
"info", info.AsHumanReadableString());
return info;
}
void AudioOutputDevice::OnAuthSignal() {
DCHECK(io_task_runner_->BelongsToCurrentThread());
base::AutoLock auto_lock(device_info_lock_);
did_receive_auth_.Signal();
if (pending_device_info_cb_)
std::move(pending_device_info_cb_).Run(GetOutputDeviceInfo_Signaled());
}
void AudioOutputDevice::NotifyRenderCallbackOfError() {
TRACE_EVENT0("audio", "AudioOutputDevice::NotifyRenderCallbackOfError");
DCHECK(io_task_runner_->BelongsToCurrentThread());
base::AutoLock auto_lock(audio_thread_lock_);
if (callback_ && !stopping_hack_) {
if (audio_callback_)
had_error_ = kErrorDuringRendering;
else
had_error_ = kErrorDuringCreation;
callback_->OnRenderError();
}
}
}