| // 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/audio_core/stream_usage.h" |
| #include "src/media/audio/lib/clock/clone_mono.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(); |
| } |
| } |
| reporter().SetUsage(CaptureUsageFromFidlCaptureUsage(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)); |
| |
| // Once we route the capturer, we accept the default reference clock if one hasn't yet been set. |
| if (routable) { |
| reference_clock_is_set_ = true; |
| } |
| } |
| |
| void AudioCapturer::OnLinkAdded() { |
| BaseCapturer::OnLinkAdded(); |
| if (!loopback_) { |
| context().volume_manager().NotifyStreamChanged(this); |
| } |
| } |
| |
| constexpr auto kRequiredClockRights = ZX_RIGHT_DUPLICATE | ZX_RIGHT_TRANSFER | ZX_RIGHT_READ; |
| // 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 AudioCapturer::SetReferenceClock(zx::clock raw_clock) { |
| TRACE_DURATION("audio", "AudioCapturer::SetReferenceClock"); |
| AUDIO_LOG_OBJ(DEBUG, this); |
| |
| auto cleanup = fit::defer([this]() { BeginShutdown(); }); |
| |
| // We cannot change the reference clock, once set. Also, once the capturer is routed to a device |
| // (which occurs upon AddPayloadBuffer), we set the default clock if one has not yet been set. |
| if (reference_clock_is_set_) { |
| FX_LOGS(WARNING) << "Cannot change reference clock once it is set!"; |
| return; |
| } |
| |
| zx_status_t status; |
| if (raw_clock.is_valid()) { |
| // If raw_clock doesn't have DUPLICATE or READ or TRANSFER rights, return (i.e. shutdown). |
| status = raw_clock.replace(kRequiredClockRights, &raw_clock); |
| if (status != ZX_OK) { |
| FX_PLOGS(WARNING, status) << "Could not set rights on client-submitted reference clock"; |
| return; |
| } |
| SetClock(AudioClock::ClientFixed(std::move(raw_clock))); |
| } else { |
| // To achieve "no-SRC", we will rate-adjust this clock to match the device clock. |
| SetClock(AudioClock::ClientAdjustable(audio::clock::AdjustableCloneOfMonotonic())); |
| } |
| |
| reference_clock_is_set_ = true; |
| |
| 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(WARNING) << "Cannot change format after payload buffer has been added" |
| << "(state = " << static_cast<uint32_t>(state) << ")"; |
| return; |
| } |
| |
| auto format_result = Format::Create(stream_type); |
| if (format_result.is_error()) { |
| FX_LOGS(WARNING) << "AudioCapturer: PcmStreamType is invalid"; |
| return; |
| } |
| |
| // 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(); |
| reporter().SetUsage(CaptureUsageFromFidlCaptureUsage(usage)); |
| 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_packets()) { |
| 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) << "Capturer gain ramping is not implemented"; |
| } |
| |
| 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()}, |
| }); |
| // TODO(fxbug.dev/51049) Logging should be removed upon creation of inspect tool or other |
| // real-time method for gain observation |
| if (gain_db != 0.0) { |
| FX_LOGS(INFO) << this << " " << StreamUsage::WithCaptureUsage(usage_).ToString() |
| << " Gain(" << gain_db << "db) = " |
| << "Vol(" << volume_command.volume << ") + GainAdjustment(" |
| << volume_command.gain_db_adjustment << "db) + StreamGain(" |
| << stream_gain_db_ << "db)"; |
| } |
| |
| mix_domain().PostTask( |
| [link, gain_db]() { 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(WARNING) << "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; |
| } |
| |
| reporter().SetGain(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; |
| } |
| |
| reporter().SetMute(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 |