blob: e4b24772c1f25085ea6a916ed0eae757c38c6a92 [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 "services/audio/output_stream.h"
#include <utility>
#include "base/bind.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "media/audio/audio_sync_reader.h"
namespace audio {
const float kSilenceThresholdDBFS = -72.24719896f;
// Desired polling frequency. Note: If this is set too low, short-duration
// "blip" sounds won't be detected. http://crbug.com/339133#c4
const int kPowerMeasurementsPerSecond = 15;
OutputStream::OutputStream(
CreatedCallback created_callback,
DeleteCallback delete_callback,
media::mojom::AudioOutputStreamRequest stream_request,
media::mojom::AudioOutputStreamClientPtr client,
media::mojom::AudioOutputStreamObserverAssociatedPtr observer,
media::mojom::AudioLogPtr log,
media::AudioManager* audio_manager,
const std::string& output_device_id,
const media::AudioParameters& params)
: foreign_socket_(),
created_callback_(std::move(created_callback)),
delete_callback_(std::move(delete_callback)),
binding_(this, std::move(stream_request)),
client_(std::move(client)),
observer_(std::move(observer)),
log_(media::mojom::ThreadSafeAudioLogPtr::Create(std::move(log))),
// Unretained is safe since we own |reader_|
reader_(media::AudioSyncReader::Create(
base::BindRepeating(&media::mojom::AudioLog::OnLogMessage,
base::Unretained(log_->get())),
params,
&foreign_socket_)),
weak_factory_(this) {
DCHECK(audio_manager);
DCHECK(binding_.is_bound());
DCHECK(client_.is_bound());
DCHECK(observer_.is_bound());
DCHECK(created_callback_);
DCHECK(delete_callback_);
// |this| owns these objects, so unretained is safe.
base::RepeatingClosure error_handler =
base::BindRepeating(&OutputStream::OnError, base::Unretained(this));
binding_.set_connection_error_handler(error_handler);
client_.set_connection_error_handler(error_handler);
// We allow the observer to terminate the stream by closing the message pipe.
observer_.set_connection_error_handler(std::move(error_handler));
log_->get()->OnCreated(params, output_device_id);
if (!reader_) {
// Failed to create reader. Since we failed to initialize, don't bind the
// request.
OnError();
return;
}
controller_ = media::AudioOutputController::Create(
audio_manager, this, params, output_device_id, reader_.get());
}
OutputStream::~OutputStream() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
log_->get()->OnClosed();
if (created_callback_) {
// Didn't manage to create the stream. Call the callback anyways as mandated
// by mojo.
std::move(created_callback_).Run(nullptr);
}
if (!controller_) {
// Didn't initialize properly, nothing to clean up.
return;
}
// TODO(803102): remove AudioOutputController::Close() after content/ streams
// are removed, destructor should suffice.
controller_->Close(base::OnceClosure());
}
void OutputStream::Play() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
controller_->Play();
log_->get()->OnStarted();
}
void OutputStream::Pause() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
controller_->Pause();
log_->get()->OnStopped();
}
void OutputStream::SetVolume(double volume) {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
if (volume < 0 || volume > 1) {
mojo::ReportBadMessage("Invalid volume");
OnError();
return;
}
controller_->SetVolume(volume);
log_->get()->OnSetVolume(volume);
}
void OutputStream::OnControllerCreated() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
// TODO(803102): Get rid of the OnControllerCreated event after removing
// content/ streams.
const base::SharedMemory* memory = reader_->shared_memory();
base::SharedMemoryHandle foreign_memory_handle =
base::SharedMemory::DuplicateHandle(memory->handle());
if (!base::SharedMemory::IsHandleValid(foreign_memory_handle)) {
OnError();
return;
}
mojo::ScopedSharedBufferHandle buffer_handle = mojo::WrapSharedMemoryHandle(
foreign_memory_handle, memory->requested_size(),
mojo::UnwrappedSharedMemoryHandleProtection::kReadWrite);
mojo::ScopedHandle socket_handle =
mojo::WrapPlatformFile(foreign_socket_.Release());
DCHECK(buffer_handle.is_valid());
DCHECK(socket_handle.is_valid());
std::move(created_callback_)
.Run(
{base::in_place, std::move(buffer_handle), std::move(socket_handle)});
}
void OutputStream::OnControllerPlaying() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
if (playing_)
return;
playing_ = true;
observer_->DidStartPlaying();
if (media::AudioOutputController::will_monitor_audio_levels()) {
DCHECK(!poll_timer_.IsRunning());
// base::Unretained is safe because |this| owns |poll_timer_|.
poll_timer_.Start(
FROM_HERE,
base::TimeDelta::FromSeconds(1) / kPowerMeasurementsPerSecond,
base::BindRepeating(&OutputStream::PollAudioLevel,
base::Unretained(this)));
return;
}
// In case we don't monitor audio levels, we assume a stream is audible when
// it's playing.
observer_->DidChangeAudibleState(true);
}
void OutputStream::OnControllerPaused() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
if (!playing_)
return;
playing_ = false;
if (media::AudioOutputController::will_monitor_audio_levels()) {
DCHECK(poll_timer_.IsRunning());
poll_timer_.Stop();
}
observer_->DidStopPlaying();
}
void OutputStream::OnControllerError() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
// Only propagate platform errors to the renderer.
client_->OnError();
log_->get()->OnError();
OnError();
}
void OutputStream::OnLog(base::StringPiece message) {
// No sequence check: |log_| is thread-safe.
log_->get()->OnLogMessage(message.as_string());
}
void OutputStream::OnError() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
// Defer callback so we're not destructed while in the constructor.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&OutputStream::CallDeleter, weak_factory_.GetWeakPtr()));
// Ignore any incoming calls.
binding_.Close();
}
void OutputStream::CallDeleter() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
std::move(delete_callback_).Run(this);
}
void OutputStream::PollAudioLevel() {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
bool was_audible = is_audible_;
is_audible_ = IsAudible();
if (is_audible_ != was_audible)
observer_->DidChangeAudibleState(is_audible_);
}
bool OutputStream::IsAudible() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
float power_dbfs = controller_->ReadCurrentPowerAndClip().first;
return power_dbfs >= kSilenceThresholdDBFS;
}
} // namespace audio