blob: ad81920d094b01c2c38d675f9e4eb3198032ca39 [file] [log] [blame]
// Copyright 2020 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_capturer.h"
#include <lib/fit/defer.h>
#include "src/media/audio/audio_core/audio_admin.h"
#include "src/media/audio/audio_core/reporter.h"
#include "src/media/audio/lib/clock/utils.h"
#include "src/media/audio/lib/logging/logging.h"
namespace media::audio {
namespace {
constexpr float kInitialCaptureGainDb = Gain::kUnityGainDb;
}
AudioCapturer::AudioCapturer(fuchsia::media::AudioCapturerConfiguration configuration,
std::optional<Format> format,
fidl::InterfaceRequest<fuchsia::media::AudioCapturer> request,
Context* context)
: BaseCapturer(std::move(format), std::move(request), context),
loopback_(configuration.is_loopback()),
mute_(false),
stream_gain_db_(kInitialCaptureGainDb) {
FX_DCHECK(context);
if (!loopback_) {
context->volume_manager().AddStream(this);
if (configuration.input().has_usage()) {
usage_ = configuration.input().usage();
}
}
}
AudioCapturer::~AudioCapturer() {
if (!loopback_) {
context().volume_manager().RemoveStream(this);
}
}
void AudioCapturer::ReportStart() {
BaseCapturer::ReportStart();
if (!loopback_) {
context().audio_admin().UpdateCapturerState(usage_, true, this);
}
}
void AudioCapturer::ReportStop() {
BaseCapturer::ReportStop();
if (!loopback_) {
context().audio_admin().UpdateCapturerState(usage_, false, this);
}
}
void AudioCapturer::OnStateChanged(State old_state, State new_state) {
BaseCapturer::OnStateChanged(old_state, new_state);
if (!loopback_ && new_state == State::OperatingSync) {
context().volume_manager().NotifyStreamChanged(this);
}
}
void AudioCapturer::SetRoutingProfile(bool routable) {
auto profile =
RoutingProfile{.routable = routable, .usage = StreamUsage::WithCaptureUsage(capture_usage())};
context().route_graph().SetCapturerRoutingProfile(*this, std::move(profile));
}
void AudioCapturer::OnLinkAdded() {
BaseCapturer::OnLinkAdded();
if (!loopback_) {
context().volume_manager().NotifyStreamChanged(this);
}
}
// If received clock is null, duplicate and use the optimal clock. Else, use this new clock as-is.
void AudioCapturer::SetReferenceClock(zx::clock ref_clock) {
TRACE_DURATION("audio", "AudioCapturer::SetReferenceClock");
AUD_VLOG_OBJ(TRACE, this);
auto cleanup = fit::defer([this]() { BeginShutdown(); });
if (!ref_clock.is_valid()) {
auto status = DuplicateClock(optimal_clock(), &ref_clock);
FX_DCHECK(status == ZX_OK) << "Could not duplicate the optimal clock";
}
set_reference_clock(std::move(ref_clock));
cleanup.cancel();
}
void AudioCapturer::SetPcmStreamType(fuchsia::media::AudioStreamType stream_type) {
TRACE_DURATION("audio", "AudioCapturer::SetPcmStreamType");
// If something goes wrong, hang up the phone and shutdown.
auto cleanup = fit::defer([this]() { BeginShutdown(); });
// If our shared buffer has been assigned, we are operating and our mode can no longer be changed.
State state = capture_state();
if (state != State::WaitingForVmo) {
FX_LOGS(ERROR) << "Cannot change capture mode while operating!"
<< "(state = " << static_cast<uint32_t>(state) << ")";
return;
}
auto format_result = Format::Create(stream_type);
if (format_result.is_error()) {
FX_LOGS(ERROR) << "AudioCapturer: PcmStreamType is invalid";
return;
}
REPORT(SettingCapturerStreamType(*this, stream_type));
// Success, record our new format.
UpdateFormat(format_result.take_value());
cleanup.cancel();
}
void AudioCapturer::BindGainControl(
fidl::InterfaceRequest<fuchsia::media::audio::GainControl> request) {
TRACE_DURATION("audio", "AudioCapturer::BindGainControl");
gain_control_bindings_.AddBinding(this, std::move(request));
}
void AudioCapturer::SetUsage(fuchsia::media::AudioCaptureUsage usage) {
TRACE_DURATION("audio", "AudioCapturer::SetUsage");
if (usage == usage_) {
return;
}
if (loopback_) {
FX_LOGS(WARNING) << "SetUsage on loopback capturer is not allowed";
return;
}
ReportStop();
usage_ = usage;
context().volume_manager().NotifyStreamChanged(this);
State state = capture_state();
SetRoutingProfile(StateIsRoutable(state));
if (state == State::OperatingAsync) {
ReportStart();
}
if (state == State::OperatingSync) {
if (has_pending_capture_buffers()) {
ReportStart();
}
}
}
bool AudioCapturer::GetStreamMute() const { return mute_; }
fuchsia::media::Usage AudioCapturer::GetStreamUsage() const {
// We should only be calling these from the StreamVolumeManager. We don't register LOOPBACK
// capturers with the StreamVolumeManager since those capturers do not have a compatible
// usage.
FX_DCHECK(!loopback_);
fuchsia::media::Usage usage;
usage.set_capture_usage(usage_);
return usage;
}
void AudioCapturer::RealizeVolume(VolumeCommand volume_command) {
if (volume_command.ramp.has_value()) {
FX_LOGS(WARNING)
<< "Requested ramp of capturer; ramping for destination gains is unimplemented.";
}
context().link_matrix().ForEachSourceLink(*this,
[this, &volume_command](LinkMatrix::LinkHandle link) {
float gain_db = link.loudness_transform->Evaluate<3>({
VolumeValue{volume_command.volume},
GainDbFsValue{volume_command.gain_db_adjustment},
GainDbFsValue{stream_gain_db_.load()},
});
link.mixer->bookkeeping().gain.SetDestGain(gain_db);
});
}
void AudioCapturer::SetGain(float gain_db) {
TRACE_DURATION("audio", "AudioCapturer::SetGain");
// Before setting stream_gain_db_, we should always perform this range check.
if ((gain_db < fuchsia::media::audio::MUTED_GAIN_DB) ||
(gain_db > fuchsia::media::audio::MAX_GAIN_DB) || isnan(gain_db)) {
FX_LOGS(ERROR) << "SetGain(" << gain_db << " dB) out of range.";
BeginShutdown();
return;
}
// If the incoming SetGain request represents no change, we're done
// (once we add gain ramping, this type of check isn't workable).
if (stream_gain_db_ == gain_db) {
return;
}
REPORT(SettingCapturerGain(*this, gain_db));
stream_gain_db_.store(gain_db);
if (!loopback_) {
context().volume_manager().NotifyStreamChanged(this);
}
NotifyGainMuteChanged();
}
void AudioCapturer::SetMute(bool mute) {
TRACE_DURATION("audio", "AudioCapturer::SetMute");
// If the incoming SetMute request represents no change, we're done.
if (mute_ == mute) {
return;
}
REPORT(SettingCapturerMute(*this, mute));
mute_ = mute;
if (!loopback_) {
context().volume_manager().NotifyStreamChanged(this);
}
NotifyGainMuteChanged();
}
void AudioCapturer::NotifyGainMuteChanged() {
TRACE_DURATION("audio", "AudioCapturer::NotifyGainMuteChanged");
// Consider making these events disable-able like MinLeadTime.
for (auto& gain_binding : gain_control_bindings_.bindings()) {
gain_binding->events().OnGainMuteChanged(stream_gain_db_, mute_);
}
}
} // namespace media::audio