blob: 7efec37da336389f6ff36e186704f9ee919b5f67 [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 "garnet/bin/media/audio_server/audio_server_impl.h"
#include <fs/service.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include "garnet/bin/media/audio_server/audio_capturer_impl.h"
#include "garnet/bin/media/audio_server/audio_device_manager.h"
#include "garnet/bin/media/audio_server/audio_renderer1_impl.h"
#include "garnet/bin/media/audio_server/audio_renderer2_impl.h"
namespace media {
namespace audio {
AudioServerImpl::AudioServerImpl() : device_manager_(this) {
// Stash a pointer to our async object.
async_ = async_get_default();
FXL_DCHECK(async_);
// TODO(johngro) : See MG-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 server 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.
async::PostTask(
async_, []() { zx_thread_set_priority(24 /* HIGH_PRIORITY in LK */); });
// 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);
// Wait for 50 mSec before we export our services and start to process client
// requests. This will give the device manager layer time to discover the
// AudioInputs and AudioOutputs which are already connected to the system.
//
// TODO(johngro): With some more major surgery, we could rework the device
// manager so that we wait until we are certain that we have discovered and
// probed the capabilities of all of the pre-existing inputs and outputs
// before proceeding. See MTWN-118
async::PostDelayedTask(async_, [this]() { PublishServices(); }, zx::msec(50));
}
AudioServerImpl::~AudioServerImpl() {
Shutdown();
FXL_DCHECK(packet_cleanup_queue_.is_empty());
FXL_DCHECK(flush_cleanup_queue_.is_empty());
}
void AudioServerImpl::PublishServices() {
auto audio_service =
fbl::AdoptRef(new fs::Service([this](zx::channel ch) -> zx_status_t {
bindings_.AddBinding(
this, fidl::InterfaceRequest<fuchsia::media::Audio>(std::move(ch)));
bindings_.bindings().back()->events().SystemGainMuteChanged(
system_gain_db_, system_muted_);
return ZX_OK;
}));
outgoing_.public_dir()->AddEntry(fuchsia::media::Audio::Name_,
std::move(audio_service));
// TODO(dalesat): Load the gain/mute values.
auto audio_device_enumerator_service =
fbl::AdoptRef(new fs::Service([this](zx::channel ch) -> zx_status_t {
device_manager_.AddDeviceEnumeratorClient(std::move(ch));
return ZX_OK;
}));
outgoing_.public_dir()->AddEntry(fuchsia::media::AudioDeviceEnumerator::Name_,
std::move(audio_device_enumerator_service));
outgoing_.ServeFromStartupInfo();
}
void AudioServerImpl::Shutdown() {
shutting_down_ = true;
device_manager_.Shutdown();
DoPacketCleanup();
}
void AudioServerImpl::CreateRenderer(
fidl::InterfaceRequest<fuchsia::media::AudioRenderer> audio_renderer,
fidl::InterfaceRequest<fuchsia::media::MediaRenderer> media_renderer) {
device_manager_.AddRenderer(AudioRenderer1Impl::Create(
std::move(audio_renderer), std::move(media_renderer), this));
}
void AudioServerImpl::CreateRendererV2(
fidl::InterfaceRequest<fuchsia::media::AudioRenderer2> audio_renderer) {
device_manager_.AddRenderer(
AudioRenderer2Impl::Create(std::move(audio_renderer), this));
}
void AudioServerImpl::CreateCapturer(
fidl::InterfaceRequest<fuchsia::media::AudioCapturer>
audio_capturer_request,
bool loopback) {
device_manager_.AddCapturer(AudioCapturerImpl::Create(
std::move(audio_capturer_request), this, loopback));
}
void AudioServerImpl::SetSystemGain(float db_gain) {
db_gain = std::max(std::min(db_gain, kMaxSystemAudioGain),
fuchsia::media::kMutedGain);
if (system_gain_db_ == db_gain) {
return;
}
if (db_gain == fuchsia::media::kMutedGain) {
// System audio gain is being set to |kMutedGain|. This implicitly mutes
// system audio.
system_muted_ = true;
} else if (system_gain_db_ == fuchsia::media::kMutedGain) {
// System audio was muted, because gain was set to |kMutedGain|. We're
// raising the gain now, so we unmute.
system_muted_ = false;
}
system_gain_db_ = db_gain;
device_manager_.OnSystemGainChanged();
NotifyGainMuteChanged();
}
void AudioServerImpl::SetSystemMute(bool muted) {
if (system_gain_db_ == fuchsia::media::kMutedGain) {
// Keep audio muted if system audio gain is set to |kMutedGain|.
muted = true;
}
if (system_muted_ == muted) {
return;
}
system_muted_ = muted;
device_manager_.OnSystemGainChanged();
NotifyGainMuteChanged();
}
void AudioServerImpl::NotifyGainMuteChanged() {
for (auto& binding : bindings_.bindings()) {
binding->events().SystemGainMuteChanged(system_gain_db_, system_muted_);
}
// TODO(dalesat): Save the gain/mute values.
}
void AudioServerImpl::SetRoutingPolicy(
fuchsia::media::AudioOutputRoutingPolicy policy) {
device_manager_.SetRoutingPolicy(policy);
}
void AudioServerImpl::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 server 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<fbl::unique_ptr<AudioPacketRef>> tmp_packet_queue;
fbl::DoublyLinkedList<fbl::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 AudioServerImpl::SchedulePacketCleanup(
fbl::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(async_);
async::PostTask(async_, [this]() { DoPacketCleanup(); });
cleanup_scheduled_ = true;
}
}
void AudioServerImpl::ScheduleFlushCleanup(
fbl::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(async_);
async::PostTask(async_, [this]() { DoPacketCleanup(); });
cleanup_scheduled_ = true;
}
}
} // namespace audio
} // namespace media