| // 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_input.h" |
| |
| #include "src/media/audio/audio_core/audio_device_manager.h" |
| |
| namespace media::audio { |
| |
| constexpr zx_duration_t kMinFenceDistance = ZX_MSEC(200); |
| constexpr zx_duration_t kMaxFenceDistance = kMinFenceDistance + ZX_MSEC(20); |
| |
| // static |
| fbl::RefPtr<AudioInput> AudioInput::Create(zx::channel channel, |
| AudioDeviceManager* manager) { |
| return fbl::AdoptRef(new AudioInput(std::move(channel), manager)); |
| } |
| |
| AudioInput::AudioInput(zx::channel channel, AudioDeviceManager* manager) |
| : AudioDevice(Type::Input, manager), |
| initial_stream_channel_(std::move(channel)) {} |
| |
| zx_status_t AudioInput::Init() { |
| zx_status_t res = AudioDevice::Init(); |
| if (res != ZX_OK) { |
| return res; |
| } |
| |
| res = driver_->Init(std::move(initial_stream_channel_)); |
| if (res == ZX_OK) { |
| state_ = State::Initialized; |
| } |
| |
| return res; |
| } |
| |
| void AudioInput::OnWakeup() { |
| // We were poked. Are we just starting up? |
| if (state_ == State::Initialized) { |
| if (driver_->GetDriverInfo() != ZX_OK) { |
| ShutdownSelf(); |
| } else { |
| state_ = State::FetchingFormats; |
| } |
| return; |
| } |
| |
| UpdateDriverGainState(); |
| } |
| |
| void AudioInput::OnDriverInfoFetched() { |
| state_ = State::Idle; |
| |
| uint32_t pref_fps = 48000; |
| uint32_t pref_chan = 1; |
| fuchsia::media::AudioSampleFormat pref_fmt = |
| fuchsia::media::AudioSampleFormat::SIGNED_16; |
| |
| zx_status_t res = SelectBestFormat(driver_->format_ranges(), &pref_fps, |
| &pref_chan, &pref_fmt); |
| if (res != ZX_OK) { |
| FXL_LOG(ERROR) |
| << "Audio input failed to find any compatible driver formats. Req was " |
| << pref_fps << " Hz " << pref_chan << " channel(s) sample format(0x" |
| << std::hex << static_cast<uint32_t>(pref_fmt) << ")"; |
| ShutdownSelf(); |
| return; |
| } |
| |
| const auto& hw_gain = driver()->hw_gain_state(); |
| if (hw_gain.min_gain > hw_gain.max_gain) { |
| FXL_LOG(ERROR) << "Audio input has invalid gain limits [" |
| << hw_gain.min_gain << ", " << hw_gain.max_gain << "]."; |
| ShutdownSelf(); |
| return; |
| } |
| |
| // TODO(mpuryear): Save to the hub the configured format for this input. |
| |
| // Send config request; recompute distance between start|end sampling fences. |
| driver_->Configure(pref_fps, pref_chan, pref_fmt, kMaxFenceDistance); |
| |
| int64_t dist = TimelineRate(pref_fps, ZX_SEC(1)).Scale(kMinFenceDistance); |
| FXL_DCHECK(dist < std::numeric_limits<uint32_t>::max()); |
| driver_->SetEndFenceToStartFenceFrames(static_cast<uint32_t>(dist)); |
| |
| // Tell AudioDeviceManager it can add us to the set of active audio devices. |
| ActivateSelf(); |
| } |
| |
| void AudioInput::OnDriverConfigComplete() { |
| driver_->SetPlugDetectEnabled(true); |
| } |
| |
| void AudioInput::OnDriverStartComplete() { |
| // If we were unplugged while starting, stop now. |
| if (!driver_->plugged()) { |
| driver_->Stop(); |
| } |
| } |
| |
| void AudioInput::OnDriverStopComplete() { |
| // If we were plugged while stopping, start now. |
| if (driver_->plugged()) { |
| driver_->Start(); |
| } |
| } |
| |
| void AudioInput::OnDriverPlugStateChange(bool plugged, zx_time_t plug_time) { |
| if (plugged && (driver_->state() == AudioDriver::State::Configured)) { |
| driver_->Start(); |
| } else if (!plugged && (driver_->state() == AudioDriver::State::Started)) { |
| driver_->Stop(); |
| } |
| |
| // Reflect this message to the AudioDeviceManager so it can deal with the |
| // routing consequences of the plug state change. |
| manager_->ScheduleMainThreadTask([manager = manager_, |
| output = fbl::WrapRefPtr(this), plugged, |
| plug_time]() { |
| manager->HandlePlugStateChange(std::move(output), plugged, plug_time); |
| }); |
| } |
| |
| void AudioInput::ApplyGainLimits(::fuchsia::media::AudioGainInfo* in_out_info, |
| uint32_t set_flags) { |
| // By the time anyone is calling "ApplyGainLimits", we need to have our basic |
| // audio gain control capabilities established. |
| ZX_DEBUG_ASSERT(driver()->state() != AudioDriver::State::Uninitialized); |
| ZX_DEBUG_ASSERT(driver()->state() != AudioDriver::State::MissingDriverInfo); |
| |
| const auto& caps = driver()->hw_gain_state(); |
| |
| // If someone is trying to enable mute, but our hardware does not support |
| // enabling mute, clear the flag. |
| // |
| // TODO(johngro): It should always be possible to mute. We should maintain a |
| // SW flag for implementing mute in case the hardware cannot. |
| if (!caps.can_mute) { |
| in_out_info->flags &= ~(::fuchsia::media::AudioGainInfoFlag_Mute); |
| } |
| |
| // Don't allow AGC unless HW supports it. |
| if (!caps.can_agc) { |
| in_out_info->flags &= ~(::fuchsia::media::AudioGainInfoFlag_AgcEnabled); |
| } |
| |
| // If the user is attempting to set gain, enforce the gain limits. |
| if (set_flags & fuchsia::media::SetAudioGainFlag_GainValid) { |
| // This should have been enforced in OnDriverInfoFetched. |
| FXL_DCHECK(caps.min_gain <= caps.max_gain); |
| |
| // If the hardware has not supplied a valid gain step size, or an |
| // ridiculously small step size, just apply a clamp based on min/max. |
| constexpr float kStepSizeLimit = 1e-6; |
| if (caps.gain_step <= kStepSizeLimit) { |
| in_out_info->gain_db = |
| fbl::clamp(in_out_info->gain_db, caps.min_gain, caps.max_gain); |
| } else { |
| auto min_steps = static_cast<int32_t>(caps.min_gain / caps.gain_step); |
| auto max_steps = static_cast<int32_t>(caps.max_gain / caps.gain_step); |
| int32_t steps = fbl::clamp( |
| static_cast<int32_t>(in_out_info->gain_db / caps.gain_step), |
| min_steps, max_steps); |
| in_out_info->gain_db = static_cast<float>(steps) * caps.gain_step; |
| } |
| } |
| } |
| |
| void AudioInput::UpdateDriverGainState() { |
| if ((state_ != State::Idle) || (device_settings_ == nullptr)) { |
| return; |
| } |
| |
| AudioDeviceSettings::GainState state; |
| audio_set_gain_flags_t dirty_flags = |
| device_settings_->SnapshotGainState(&state); |
| if (!dirty_flags) { |
| return; |
| } |
| |
| driver()->SendSetGain(state, dirty_flags); |
| } |
| |
| } // namespace media::audio |