blob: 0180a2b9065350151ff84bfd6cba28ce9999d5c8 [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_renderer.h"
#include <fuchsia/media/audio/cpp/fidl.h>
#include <lib/fit/defer.h>
#include <lib/syslog/cpp/macros.h>
#include <string>
#include "fuchsia/media/cpp/fidl.h"
#include "src/media/audio/audio_core/audio_admin.h"
#include "src/media/audio/audio_core/logging_flags.h"
#include "src/media/audio/audio_core/reporter.h"
#include "src/media/audio/audio_core/stream_usage.h"
#include "src/media/audio/audio_core/stream_volume_manager.h"
#include "src/media/audio/lib/clock/utils.h"
namespace media::audio {
namespace {
// Constants used when using dropout checks
constexpr bool kEnableDropoutChecks = false;
constexpr bool kDisplayPacketOnDropout = false;
// Dropout checkers are currently limited to float32 data only.
constexpr fuchsia::media::AudioSampleFormat kDropoutChecksFormat =
fuchsia::media::AudioSampleFormat::FLOAT;
// Only enable the dropout checks if the renderer also fits these other dimensions.
constexpr int32_t kDropoutChecksChannelCount = 2;
constexpr int32_t kDropoutChecksFrameRate = 44100;
// Const values used by PowerChecker to analyze the RMS power of incoming packets.
// Adjust the window size and min RMS level as needed, for the test content being used.
// Set channel count, frame rate and sample_format so that only the client of interest is analyzed.
// Current values were used for a stereo float 44.1k source stream containing ampl-0.5 white noise.
constexpr int64_t kRmsWindowInFrames = 512;
constexpr double kRmsLevelMin = 0.065; // 0.16;
// With the controlled content (sine|const|ramp|noise at full-scale amplitude) that is
// commonly used with this dropout checker, consecutive silent frames should not occur.
constexpr int64_t kConsecutiveSilenceFramesAllowed = 1;
} // namespace
AudioRenderer::AudioRenderer(
fidl::InterfaceRequest<fuchsia::media::AudioRenderer> audio_renderer_request, Context* context)
: BaseRenderer(std::move(audio_renderer_request), context),
mix_profile_period_(context->process_config().mix_profile_config().period) {
context->volume_manager().AddStream(this);
reporter().SetUsage(RenderUsageFromFidlRenderUsage(usage_));
if constexpr (kLogRendererCtorDtorCalls) {
FX_LOGS(INFO) << __FUNCTION__ << " (" << this << ") *****";
}
}
AudioRenderer::~AudioRenderer() {
if constexpr (kLogRendererCtorDtorCalls) {
FX_LOGS(INFO) << __FUNCTION__ << " (" << this
<< ") usage:" << RenderUsageToString(RenderUsageFromFidlRenderUsage(usage_))
<< " *****";
}
// We (not ~BaseRenderer) must call this, because our ReportStop is gone when parent dtor runs
ReportStopIfStarted();
context().volume_manager().RemoveStream(this);
}
void AudioRenderer::OnLinkAdded() {
// With a link, our Mixer and Gain objects have been created, so we can set initial gain levels.
context().volume_manager().NotifyStreamChanged(this);
BaseRenderer::OnLinkAdded();
}
void AudioRenderer::ReportStart() {
BaseRenderer::ReportStart();
context().audio_admin().UpdateRendererState(RenderUsageFromFidlRenderUsage(usage_), true, this);
}
void AudioRenderer::ReportStop() {
BaseRenderer::ReportStop();
context().audio_admin().UpdateRendererState(RenderUsageFromFidlRenderUsage(usage_), false, this);
}
void AudioRenderer::SetUsage(fuchsia::media::AudioRenderUsage usage) {
TRACE_DURATION("audio", "AudioRenderer::SetUsage");
if (format_) {
FX_LOGS(WARNING) << "SetUsage called after SetPcmStreamType.";
context().route_graph().RemoveRenderer(*this);
return;
}
reporter().SetUsage(RenderUsageFromFidlRenderUsage(usage));
if constexpr (kLogAudioRendererSetUsageCalls) {
FX_LOGS(INFO) << __FUNCTION__ << " (" << this << ") changed from "
<< RenderUsageToString(RenderUsageFromFidlRenderUsage(usage_)) << " to "
<< RenderUsageToString(RenderUsageFromFidlRenderUsage(usage)) << " *****";
}
usage_ = usage;
}
// If received clock is null, use our adjustable clock. Else, use this new clock. Fail/disconnect,
// if the client-submitted clock has insufficient rights. Strip off other rights such as WRITE.
void AudioRenderer::SetReferenceClock(zx::clock ref_clock) {
TRACE_DURATION("audio", "AudioRenderer::SetReferenceClock");
auto cleanup = fit::defer([this]() { context().route_graph().RemoveRenderer(*this); });
// Lock after storing |cleanup| to ensure |mutex_| is released upon function return, rather than
// |cleanup| completion.
std::lock_guard<std::mutex> lock(mutex_);
// We cannot change the reference clock, once it is set. Also, calling `SetPcmStreamType` will
// automatically sets the default reference clock, if one has not been explicitly set.
if (reference_clock_is_set_) {
FX_LOGS(WARNING) << "Attempted to change reference clock after setting it.";
return;
}
zx_status_t status;
if (ref_clock.is_valid()) {
status = SetCustomReferenceClock(std::move(ref_clock));
} else {
status = SetAdjustableReferenceClock();
}
if (status != ZX_OK) {
return;
}
reference_clock_is_set_ = true;
cleanup.cancel();
}
void AudioRenderer::SetPcmStreamType(fuchsia::media::AudioStreamType stream_type) {
TRACE_DURATION("audio", "AudioRenderer::SetPcmStreamType");
std::lock_guard<std::mutex> lock(mutex_);
auto cleanup = fit::defer([this]() { context().route_graph().RemoveRenderer(*this); });
// We cannot change the format while we are currently operational
if (IsOperating()) {
FX_LOGS(WARNING) << "Attempted to set format while in operational mode.";
return;
}
auto format_result = Format::Create(stream_type);
if (format_result.is_error()) {
FX_LOGS(WARNING) << "AudioRenderer: PcmStreamType is invalid";
return;
}
format_ = {format_result.take_value()};
// Only create a PowerChecker if enabled, and if the renderer fits our specifications
if constexpr (kEnableDropoutChecks) {
if (format_->sample_format() == kDropoutChecksFormat &&
format_->frames_per_second() == kDropoutChecksFrameRate &&
format_->channels() == kDropoutChecksChannelCount) {
std::ostringstream out;
out << "AudioRenderer(" << this << ")";
power_checker_ = std::make_unique<PowerChecker>(kRmsWindowInFrames, format_->channels(),
kRmsLevelMin, out.str());
silence_checker_ = std::make_unique<SilenceChecker>(kConsecutiveSilenceFramesAllowed,
format_->channels(), out.str());
}
}
reporter().SetFormat(*format_);
context().route_graph().SetRendererRoutingProfile(
*this, {.routable = true, .usage = StreamUsage::WithRenderUsage(usage_)});
// Once we route the renderer, we accept the default reference clock if one hasn't yet been set.
reference_clock_is_set_ = true;
// Things went well, cancel the cleanup hook. If our config had been validated previously, it will
// have to be revalidated as we move into the operational phase of our life.
InvalidateConfiguration();
cleanup.cancel();
}
void AudioRenderer::SerializeWithPause(fit::closure callback) {
if (pause_ramp_state_) {
pause_ramp_state_->queued.push_back(std::move(callback));
} else {
callback();
}
}
void AudioRenderer::AddPayloadBufferInternal(uint32_t id, zx::vmo payload_buffer) {
SerializeWithPause([this, id, payload_buffer = std::move(payload_buffer)]() mutable {
BaseRenderer::AddPayloadBufferInternal(id, std::move(payload_buffer));
});
}
void AudioRenderer::RemovePayloadBufferInternal(uint32_t id) {
SerializeWithPause([this, id]() mutable { BaseRenderer::RemovePayloadBufferInternal(id); });
}
// Analyze a packet for dropouts; return true if no dropouts. Because it is called only when
// debugging specific conditions/content, this function assumes that the packet format is FLOAT.
bool AudioRenderer::AnalyzePacket(fuchsia::media::StreamPacket packet) {
auto payload_buffer = payload_buffers().find(packet.payload_buffer_id)->second;
auto packet_data = reinterpret_cast<const float*>(
reinterpret_cast<uint8_t*>(payload_buffer->start()) + packet.payload_offset);
int64_t frame_count = packet.payload_size / format()->bytes_per_frame();
auto frame_start = frames_received();
auto rms_check =
power_checker_ ? power_checker_->Check(packet_data, frame_start, frame_count, true) : true;
auto silence_check = silence_checker_
? silence_checker_->Check(packet_data, frame_start, frame_count, true)
: true;
// If packet fails either check, display its metadata. Limit logging to avoid log storms.
if (!rms_check || !silence_check) {
FX_LOGS_FIRST_N(INFO, 200) << "********** Dropout detected (rms_check "
<< (rms_check ? "pass" : "FAIL") << ", consec_silence_check "
<< (silence_check ? "pass" : "FAIL")
<< ") in packet payload_buffer_id " << packet.payload_buffer_id
<< ", offset " << packet.payload_offset << " (bytes), size "
<< packet.payload_size << " (bytes), frames 0 to " << frame_count - 1
<< ", pts "
<< (packet.pts == fuchsia::media::NO_TIMESTAMP
? "NO_TIMESTAMP"
: std::to_string(packet.pts).c_str())
<< " **********";
// If the debug flag is enabled, also display the packet's entire set of data values.
if constexpr (kDisplayPacketOnDropout) {
std::ostringstream out;
for (auto i = 0; i < frame_count; ++i) {
out << " [" << std::setw(3) << i << "]";
auto sample_index = i * format()->channels();
for (auto chan = 0; chan < format()->channels(); ++chan) {
out << std::setprecision(6) << std::fixed << std::setw(10)
<< packet_data[sample_index + chan];
}
if ((i + 1) % (8 / format()->channels()) == 0 || (i + 1) == frame_count) {
// To limit this to approx. the same interval as the logging above, we assume 10-msec
// packets at the specified stereo-44.1k format.
FX_LOGS_FIRST_N(INFO, 22050) << out.str();
out.str(std::string()); // clear the stream's contents to prep for for the next line
}
}
}
}
return rms_check && silence_check;
}
void AudioRenderer::SendPacketInternal(fuchsia::media::StreamPacket packet,
SendPacketCallback callback) {
SerializeWithPause([this, packet, callback = std::move(callback)]() mutable {
BaseRenderer::SendPacketInternal(packet, std::move(callback));
if constexpr (kEnableDropoutChecks) {
if (power_checker_ || silence_checker_) {
AnalyzePacket(packet);
}
}
});
}
void AudioRenderer::DiscardAllPacketsInternal(DiscardAllPacketsCallback callback) {
SerializeWithPause([this, callback = std::move(callback)]() mutable {
BaseRenderer::DiscardAllPacketsInternal(std::move(callback));
});
}
void AudioRenderer::EnableMinLeadTimeEventsInternal(bool enabled) {
SerializeWithPause(
[this, enabled]() mutable { BaseRenderer::EnableMinLeadTimeEventsInternal(enabled); });
}
void AudioRenderer::GetMinLeadTimeInternal(GetMinLeadTimeCallback callback) {
SerializeWithPause([this, callback = std::move(callback)]() mutable {
BaseRenderer::GetMinLeadTimeInternal(std::move(callback));
});
}
// To eliminate audible pops from discontinuity-on-immediate-start, ramp up from a very low level.
constexpr bool kEnableRampUpOnPlay = true;
constexpr float kInitialRampUpGainDb = -120.0f;
constexpr zx::duration kRampUpOnPlayDuration = zx::msec(5);
// To eliminate audible pops from discontinuity-on-pause, first ramp down to silence, then pause.
constexpr bool kEnableRampDownOnPause = true;
constexpr float kFinalRampDownGainDb = -120.0f;
constexpr zx::duration kRampDownOnPauseDuration = zx::msec(5);
void AudioRenderer::PlayInternal(zx::time reference_time, zx::time media_time,
PlayCallback callback) {
if constexpr (kLogRendererPlayCalls) {
FX_LOGS(INFO) << "Renderer(" << this << ") Play(ref time "
<< (reference_time.get() == fuchsia::media::NO_TIMESTAMP
? "NO_TIMESTAMP"
: std::to_string(reference_time.get()))
<< ", media time "
<< (media_time.get() == fuchsia::media::NO_TIMESTAMP
? "NO_TIMESTAMP"
: std::to_string(media_time.get()))
<< ")";
}
if constexpr (kEnableRampDownOnPause) {
// Allow Play() to interrupt a pending Pause(). This reduces the chance of underflow when
// the client calls Play() with a reference_time very close to now -- if we instead wait
// for the Pause() to complete before calling Play(), we delay starting the Play(), which
// may move the clock past reference_time.
if (pause_ramp_state_) {
FinishPauseRamp(pause_ramp_state_);
}
}
if constexpr (kEnableRampUpOnPlay) {
// As a workaround until time-stamped Play/Pause/Gain commands, start a ramp-up then call Play.
// Set gain to silent, before starting the ramp-up to current val.
PostStreamGainMute({
.gain_db = kInitialRampUpGainDb,
.ramp = GainRamp{.end_gain_db = 0.0f, .duration = kRampUpOnPlayDuration},
.control = StreamGainCommand::Control::ADJUSTMENT,
});
}
BaseRenderer::PlayInternal(reference_time, media_time, std::move(callback));
}
void AudioRenderer::PauseInternal(PauseCallback callback) {
if constexpr (kLogRendererPauseCalls) {
FX_LOGS(INFO) << "Renderer(" << this << ") Pause";
}
if constexpr (!kEnableRampDownOnPause) {
BaseRenderer::PauseInternal(std::move(callback));
return;
}
// If already pausing, just queue this callback to be run when the pause ramp completes.
// There cannot be an intervening Play() because Play() always interrupts the pause ramp.
if (pause_ramp_state_) {
if (callback) {
pause_ramp_state_->callbacks.push_back(std::move(callback));
}
return;
}
// As a short-term workaround until time-stamped Play/Pause/Gain commands are in place, start the
// ramp-down immediately, and post a delayed task for the actual Pause.
// On receiving the Pause callback, restore stream gain to its original value.
pause_ramp_state_ = std::make_shared<PauseRampState>();
if (callback) {
pause_ramp_state_->callbacks.push_back(std::move(callback));
}
// Callback to tear down pause_ramp_state_ when the ramp completes.
// We add a shared self-reference in case the renderer is unbound before this callback runs.
auto finish_pause_ramp = [this, self = shared_from_this(), state = pause_ramp_state_]() mutable {
FinishPauseRamp(state);
};
// Don't call SetGainInternal/SetGainWithRampInternal to avoid gain notifications.
PostStreamGainMute({
.ramp = GainRamp{.end_gain_db = kFinalRampDownGainDb, .duration = kRampDownOnPauseDuration},
.control = StreamGainCommand::Control::ADJUSTMENT,
});
// Before restoring the original gain, wait for a mix to reflect the rampdown. Gain is calculated
// at the start of each mix. Unless we wait for the next one, our SetGain cancels any ongoing
// rampdowns, leading to the discontinuities these ramp-downs intend to eliminate.
const zx::duration delay = mix_profile_period_ + kRampDownOnPauseDuration;
context().threading_model().FidlDomain().PostDelayedTask(std::move(finish_pause_ramp), delay);
}
void AudioRenderer::FinishPauseRamp(std::shared_ptr<PauseRampState> expected_state) {
TRACE_DURATION("audio", "AudioRenderer::FinishPauseRamp");
FX_CHECK(expected_state);
// Skip if the callback was already invoked. This can happen if our pause ramp was
// interrupted by a call to Play(). We use a shared pointer to avoid ABA problems
// when the ramp is interrupted by a Play() followed by another Pause().
if (pause_ramp_state_ != expected_state) {
return;
}
BaseRenderer::PauseInternal([this](int64_t ref_time, int64_t media_time) mutable {
FX_CHECK(pause_ramp_state_);
// Run all pending callbacks.
for (auto& f : pause_ramp_state_->callbacks) {
f(ref_time, media_time);
}
for (auto& f : pause_ramp_state_->queued) {
f();
}
pause_ramp_state_ = nullptr;
});
}
void AudioRenderer::BindGainControl(
fidl::InterfaceRequest<fuchsia::media::audio::GainControl> request) {
TRACE_DURATION("audio", "AudioRenderer::BindGainControl");
gain_control_bindings_.AddBinding(GainControlBinding::Create(this), std::move(request));
}
fuchsia::media::Usage AudioRenderer::GetStreamUsage() const {
return fuchsia::media::Usage::WithRenderUsage(fidl::Clone(usage_));
}
// Set a change to the usage volume+gain_adjustment
void AudioRenderer::RealizeVolume(VolumeCommand volume_command) {
context().link_matrix().ForEachDestLink(
*this, [this, volume_command](LinkMatrix::LinkHandle link) {
FX_CHECK(link.mix_domain) << "Renderer dest link should have a defined mix_domain";
float gain_db = link.loudness_transform->Evaluate<2>({
VolumeValue{volume_command.volume},
GainDbFsValue{volume_command.gain_db_adjustment},
});
if constexpr (kLogRenderUsageVolumeGainActions) {
// TODO(fxbug.dev/51049) Swap this logging for inspect or other real-time gain observation
FX_LOGS(INFO) << static_cast<const void*>(this) << " (gain "
<< &(link.mixer->bookkeeping().gain) << ", mixer "
<< static_cast<const void*>(link.mixer.get()) << ") "
<< StreamUsage::WithRenderUsage(usage_).ToString() << " dest_gain("
<< (volume_command.ramp.has_value() ? "ramping to " : "") << gain_db
<< "db) = Vol(" << volume_command.volume << ") + GainAdjustment("
<< volume_command.gain_db_adjustment << "db)";
}
link.mix_domain->PostTask([link, volume_command, gain_db, reporter = &reporter()]() {
auto& gain = link.mixer->bookkeeping().gain;
// Stop any in-progress ramping; use this new ramp or gain_db instead
if (volume_command.ramp.has_value()) {
gain.SetDestGainWithRamp(gain_db, volume_command.ramp->duration,
volume_command.ramp->ramp_type);
} else {
gain.SetDestGain(gain_db);
}
reporter->SetFinalGain(link.mixer->bookkeeping().gain.GetUnadjustedGainDb());
});
});
}
void AudioRenderer::PostStreamGainMute(StreamGainCommand gain_command) {
context().link_matrix().ForEachDestLink(*this, [this, gain_command](
LinkMatrix::LinkHandle link) mutable {
FX_CHECK(link.mix_domain) << "Renderer dest link should have a defined mix_domain";
if constexpr (kLogRendererSetGainMuteRampActions) {
// TODO(fxbug.dev/51049) Swap this logging for inspect or other real-time gain observation
std::stringstream stream;
stream << static_cast<const void*>(this) << " (gain " << &(link.mixer->bookkeeping().gain)
<< ", mixer " << static_cast<const void*>(link.mixer.get()) << ") stream ("
<< (gain_command.control == StreamGainCommand::Control::ADJUSTMENT ? "adjustment"
: "source")
<< ") Gain: ";
std::string log_string = stream.str();
if (gain_command.mute.has_value()) {
FX_CHECK(gain_command.control == StreamGainCommand::Control::SOURCE);
FX_LOGS(INFO) << log_string << "setting mute to "
<< (gain_command.mute.value() ? "TRUE" : "FALSE");
}
if (gain_command.gain_db.has_value()) {
FX_LOGS(INFO) << log_string << "setting gain to " << gain_command.gain_db.value() << " db";
}
if (gain_command.ramp.has_value()) {
FX_LOGS(INFO) << log_string << "ramping gain to " << gain_command.ramp->end_gain_db
<< " db, over " << gain_command.ramp->duration.to_usecs() << " usec";
}
}
link.mix_domain->PostTask([link, gain_command, reporter = &reporter()]() mutable {
auto& gain = link.mixer->bookkeeping().gain;
switch (gain_command.control) {
case StreamGainCommand::Control::ADJUSTMENT:
if (gain_command.gain_db.has_value()) {
gain.SetGainAdjustment(gain_command.gain_db.value());
}
if (gain_command.ramp.has_value()) {
gain.SetGainAdjustmentWithRamp(gain_command.ramp->end_gain_db,
gain_command.ramp->duration,
gain_command.ramp->ramp_type);
}
break;
case StreamGainCommand::Control::SOURCE:
if (gain_command.mute.has_value()) {
gain.SetSourceMute(gain_command.mute.value());
}
if (gain_command.gain_db.has_value()) {
gain.SetSourceGain(gain_command.gain_db.value());
}
if (gain_command.ramp.has_value()) {
gain.SetSourceGainWithRamp(gain_command.ramp->end_gain_db, gain_command.ramp->duration,
gain_command.ramp->ramp_type);
}
break;
}
// Potentially post this as a delayed task instead, if there is a ramp.
auto final_unadjusted_gain_db = gain.GetUnadjustedGainDb();
reporter->SetFinalGain(final_unadjusted_gain_db);
});
});
}
// Set the stream gain, in each Renderer -> Output audio path. The Gain object contains multiple
// stages. In playback, renderer gain is pre-mix and hence is "source" gain; the usage gain (or
// output gain, if the mixer topology is single-tier) is "dest" gain.
void AudioRenderer::SetGain(float gain_db) {
SerializeWithPause(std::bind(&AudioRenderer::SetGainInternal, this, gain_db));
}
void AudioRenderer::SetGainInternal(float gain_db) {
TRACE_DURATION("audio", "AudioRenderer::SetGain");
if constexpr (kLogRendererSetGainMuteRampCalls) {
FX_LOGS(INFO) << __FUNCTION__ << "(" << gain_db << " dB)";
}
// Before setting stream_gain_db_, always perform this range check.
if (gain_db > fuchsia::media::audio::MAX_GAIN_DB ||
gain_db < fuchsia::media::audio::MUTED_GAIN_DB || isnan(gain_db)) {
FX_LOGS(WARNING) << "SetGain(" << gain_db << " dB) out of range.";
context().route_graph().RemoveRenderer(*this);
return;
}
PostStreamGainMute({.gain_db = gain_db});
stream_gain_db_ = gain_db;
reporter().SetGain(gain_db);
NotifyGainMuteChanged();
}
// Set a stream gain ramp, in each Renderer -> Output audio path. Renderer gain is pre-mix and
// hence is the Source component in the Gain object.
void AudioRenderer::SetGainWithRamp(float gain_db, int64_t duration_ns,
fuchsia::media::audio::RampType ramp_type) {
SerializeWithPause(
std::bind(&AudioRenderer::SetGainWithRampInternal, this, gain_db, duration_ns, ramp_type));
}
void AudioRenderer::SetGainWithRampInternal(float gain_db, int64_t duration_ns,
fuchsia::media::audio::RampType ramp_type) {
TRACE_DURATION("audio", "AudioRenderer::SetGainWithRamp");
if constexpr (kLogRendererSetGainMuteRampCalls) {
FX_LOGS(INFO) << __FUNCTION__ << "(to " << gain_db << " dB over " << duration_ns / 1000
<< " usec)";
}
if (duration_ns <= 0) {
FX_LOGS(WARNING) << "SetGainWithRamp ramp duration (" << duration_ns
<< " nsec) is non-positive; calling SetGain(" << gain_db << ") instead.";
SetGainInternal(gain_db);
return;
}
if (gain_db > fuchsia::media::audio::MAX_GAIN_DB ||
gain_db < fuchsia::media::audio::MUTED_GAIN_DB || isnan(gain_db)) {
FX_LOGS(WARNING) << "SetGainWithRamp(" << gain_db << " dB) out of range.";
context().route_graph().RemoveRenderer(*this);
return;
}
zx::duration duration = zx::nsec(duration_ns);
PostStreamGainMute({.ramp = GainRamp{gain_db, duration, ramp_type}});
stream_gain_db_ = gain_db;
reporter().SetGainWithRamp(gain_db, duration, ramp_type);
// TODO(mpuryear): implement GainControl notifications for gain ramps.
}
// Set a stream mute, in each Renderer -> Output audio path.
void AudioRenderer::SetMute(bool mute) {
SerializeWithPause(std::bind(&AudioRenderer::SetMuteInternal, this, mute));
}
void AudioRenderer::SetMuteInternal(bool mute) {
TRACE_DURATION("audio", "AudioRenderer::SetMute");
if constexpr (kLogRendererSetGainMuteRampCalls) {
FX_LOGS(INFO) << __FUNCTION__ << "(" << mute << ")";
}
// Only do the work if the request represents a change in state.
if (mute_ == mute) {
return;
}
PostStreamGainMute({.mute = mute});
mute_ = mute;
reporter().SetMute(mute);
NotifyGainMuteChanged();
}
void AudioRenderer::NotifyGainMuteChanged() {
TRACE_DURATION("audio", "AudioRenderer::NotifyGainMuteChanged");
std::lock_guard<std::mutex> lock(mutex_);
if (notified_gain_db_ == stream_gain_db_ && notified_mute_ == mute_) {
return;
}
notified_gain_db_ = stream_gain_db_;
notified_mute_ = mute_;
// TODO(mpuryear): consider whether GainControl events should be disable-able, like MinLeadTime.
FX_LOGS(DEBUG) << " (" << notified_gain_db_.value() << " dB, mute: " << notified_mute_.value()
<< ")";
for (auto& gain_binding : gain_control_bindings_.bindings()) {
gain_binding->events().OnGainMuteChanged(notified_gain_db_.value(), notified_mute_.value());
}
}
// Implementation of the GainControl FIDL interface. Just forward to AudioRenderer
void AudioRenderer::GainControlBinding::SetGain(float gain_db) {
TRACE_DURATION("audio", "AudioRenderer::SetGain");
owner_->SetGain(gain_db);
}
void AudioRenderer::GainControlBinding::SetGainWithRamp(float gain_db, int64_t duration_ns,
fuchsia::media::audio::RampType ramp_type) {
TRACE_DURATION("audio", "AudioRenderer::SetSourceGainWithRamp");
owner_->SetGainWithRamp(gain_db, duration_ns, ramp_type);
}
void AudioRenderer::GainControlBinding::SetMute(bool mute) {
TRACE_DURATION("audio", "AudioRenderer::SetMute");
owner_->SetMute(mute);
}
} // namespace media::audio