blob: 0278f592203b3b7602e02f98aa293317c644f4df [file] [log] [blame]
// Copyright 2016 The Fuchsia 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 "src/media/audio/audio_core/audio_core_impl.h"
#include <fuchsia/scheduler/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include "src/lib/fxl/log_settings.h"
#include "src/media/audio/audio_core/audio_capturer_impl.h"
#include "src/media/audio/audio_core/audio_device_manager.h"
#include "src/media/audio/audio_core/audio_renderer_impl.h"
namespace media::audio {
constexpr float AudioCoreImpl::kMaxSystemAudioGainDb;
AudioCoreImpl::AudioCoreImpl(
std::unique_ptr<sys::ComponentContext> startup_context)
: device_manager_(this), ctx_(std::move(startup_context)) {
fxl::LogSettings settings;
#ifdef NDEBUG
settings.min_log_level = fxl::LOG_WARNING;
#else
settings.min_log_level = fxl::LOG_INFO;
#endif
fxl::SetLogSettings(settings);
// Stash a pointer to our async object.
dispatcher_ = async_get_default_dispatcher();
FXL_DCHECK(dispatcher_);
// TODO(johngro) : See ZX-940
//
// Eliminate this as soon as we have a more official way of
// meeting real-time latency requirements. The main async_t is
// responsible for receiving audio payloads sent by applications, so it has
// real time requirements (just like the mixing threads do). In a perfect
// world, however, we would want to have this task run on a thread which is
// different from the thread which is processing *all* audio service jobs
// (even non-realtime ones). This, however, will take more significant
// restructuring. We will cross that bridge when we have the TBD way to deal
// with realtime requirements in place.
auto profile_provider =
ctx_->svc()->Connect<fuchsia::scheduler::ProfileProvider>();
profile_provider->GetProfile(
24 /* HIGH_PRIORITY in LK */,
"src/media/audio/audio_core/audio_core_impl",
[](zx_status_t fidl_status, zx::profile profile) {
FXL_DCHECK(fidl_status == ZX_OK);
if (fidl_status == ZX_OK) {
zx_status_t status = zx::thread::self()->set_profile(profile, 0);
FXL_DCHECK(status == ZX_OK);
}
});
// Set up our output manager.
zx_status_t res = device_manager_.Init();
// TODO(johngro): Do better at error handling than this weak check.
FXL_DCHECK(res == ZX_OK);
PublishServices();
}
AudioCoreImpl::~AudioCoreImpl() {
Shutdown();
FXL_DCHECK(packet_cleanup_queue_.is_empty());
FXL_DCHECK(flush_cleanup_queue_.is_empty());
}
void AudioCoreImpl::PublishServices() {
ctx_->outgoing()->AddPublicService<fuchsia::media::AudioCore>(
[this](fidl::InterfaceRequest<fuchsia::media::AudioCore> request) {
bindings_.AddBinding(this, std::move(request));
bindings_.bindings().back()->events().SystemGainMuteChanged(
system_gain_db_, system_muted_);
});
// TODO(dalesat): Load the gain/mute values.
ctx_->outgoing()->AddPublicService<fuchsia::media::AudioDeviceEnumerator>(
[this](fidl::InterfaceRequest<fuchsia::media::AudioDeviceEnumerator>
request) {
device_manager_.AddDeviceEnumeratorClient(std::move(request));
});
}
void AudioCoreImpl::Shutdown() {
shutting_down_ = true;
device_manager_.Shutdown();
DoPacketCleanup();
}
void AudioCoreImpl::CreateAudioRenderer(
fidl::InterfaceRequest<fuchsia::media::AudioRenderer>
audio_renderer_request) {
device_manager_.AddAudioRenderer(
AudioRendererImpl::Create(std::move(audio_renderer_request), this));
}
void AudioCoreImpl::CreateAudioCapturer(
bool loopback, fidl::InterfaceRequest<fuchsia::media::AudioCapturer>
audio_capturer_request) {
device_manager_.AddAudioCapturer(AudioCapturerImpl::Create(
std::move(audio_capturer_request), this, loopback));
}
void AudioCoreImpl::SetSystemGain(float gain_db) {
// NAN is undefined and "signless". We cannot simply clamp it into range.
if (isnan(gain_db)) {
FXL_LOG(ERROR) << "Invalid system gain " << gain_db
<< " dB -- making no change";
return;
}
gain_db = std::max(std::min(gain_db, kMaxSystemAudioGainDb),
fuchsia::media::audio::MUTED_GAIN_DB);
if (system_gain_db_ == gain_db) {
// This system gain is the same as the last one we broadcast.
// A device might have received a SetDeviceGain call since we last set this.
// Only update devices that have diverged from the System Gain/Mute values.
device_manager_.OnSystemGain(false);
return;
}
system_gain_db_ = gain_db;
// This will be broadcast to all output devices.
device_manager_.OnSystemGain(true);
NotifyGainMuteChanged();
}
void AudioCoreImpl::SetSystemMute(bool muted) {
if (system_muted_ == muted) {
// A device might have received a SetDeviceMute call since we last set this.
// Only update devices that have diverged from the System Gain/Mute values.
device_manager_.OnSystemGain(false);
return;
}
system_muted_ = muted;
// This will be broadcast to all output devices.
device_manager_.OnSystemGain(true);
NotifyGainMuteChanged();
}
void AudioCoreImpl::NotifyGainMuteChanged() {
for (auto& binding : bindings_.bindings()) {
binding->events().SystemGainMuteChanged(system_gain_db_, system_muted_);
}
}
void AudioCoreImpl::SetRoutingPolicy(
fuchsia::media::AudioOutputRoutingPolicy policy) {
device_manager_.SetRoutingPolicy(policy);
}
void AudioCoreImpl::EnableDeviceSettings(bool enabled) {
device_manager_.EnableDeviceSettings(enabled);
}
void AudioCoreImpl::DoPacketCleanup() {
// In order to minimize the time we spend in the lock we obtain the lock, swap
// the contents of the cleanup queue with a local queue and clear the sched
// flag, and finally unlock clean out the queue (which has the side effect of
// triggering all of the send packet callbacks).
//
// Note: this is only safe because we know that we are executing on a single
// threaded task runner. Without this guarantee, it might be possible call
// the send packet callbacks in a different order than the packets were sent
// in the first place. If the async object for the audio service ever loses
// this serialization guarantee (because it becomes multi-threaded, for
// example) we will need to introduce another lock (different from the cleanup
// lock) in order to keep the cleanup tasks properly ordered while
// guaranteeing minimal contention of the cleanup lock (which is being
// acquired by the high priority mixing threads).
fbl::DoublyLinkedList<std::unique_ptr<AudioPacketRef>> tmp_packet_queue;
fbl::DoublyLinkedList<std::unique_ptr<PendingFlushToken>> tmp_token_queue;
{
std::lock_guard<std::mutex> locker(cleanup_queue_mutex_);
packet_cleanup_queue_.swap(tmp_packet_queue);
flush_cleanup_queue_.swap(tmp_token_queue);
cleanup_scheduled_ = false;
}
// Call the Cleanup method for each of the packets in order, then let the tmp
// queue go out of scope cleaning up all of the packet references.
for (auto& packet_ref : tmp_packet_queue) {
packet_ref.Cleanup();
}
for (auto& token : tmp_token_queue) {
token.Cleanup();
}
}
void AudioCoreImpl::SchedulePacketCleanup(
std::unique_ptr<AudioPacketRef> packet) {
std::lock_guard<std::mutex> locker(cleanup_queue_mutex_);
packet_cleanup_queue_.push_back(std::move(packet));
if (!cleanup_scheduled_ && !shutting_down_) {
FXL_DCHECK(dispatcher_);
async::PostTask(dispatcher_, [this]() { DoPacketCleanup(); });
cleanup_scheduled_ = true;
}
}
void AudioCoreImpl::ScheduleFlushCleanup(
std::unique_ptr<PendingFlushToken> token) {
std::lock_guard<std::mutex> locker(cleanup_queue_mutex_);
flush_cleanup_queue_.push_back(std::move(token));
if (!cleanup_scheduled_ && !shutting_down_) {
FXL_DCHECK(dispatcher_);
async::PostTask(dispatcher_, [this]() { DoPacketCleanup(); });
cleanup_scheduled_ = true;
}
}
} // namespace media::audio