| // Copyright 2017 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_device.h" |
| |
| #include <lib/fit/bridge.h> |
| #include <lib/trace/event.h> |
| |
| #include "src/media/audio/audio_core/audio_device_manager.h" |
| #include "src/media/audio/audio_core/audio_driver.h" |
| #include "src/media/audio/audio_core/audio_output.h" |
| #include "src/media/audio/audio_core/utils.h" |
| |
| namespace media::audio { |
| namespace { |
| |
| constexpr float kDefaultDeviceGain = 0.; |
| |
| } // namespace |
| |
| // static |
| std::string AudioDevice::UniqueIdToString(const audio_stream_unique_id_t& id) { |
| static_assert(sizeof(id.data) == 16, "Unexpected unique ID size"); |
| char buf[(sizeof(id.data) * 2) + 1]; |
| |
| const auto& d = id.data; |
| snprintf(buf, sizeof(buf), "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", |
| d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], d[11], d[12], d[13], |
| d[14], d[15]); |
| return std::string(buf, sizeof(buf) - 1); |
| } |
| |
| // static |
| fit::result<audio_stream_unique_id_t> AudioDevice::UniqueIdFromString(const std::string& id) { |
| if (id.size() != 32) { |
| return fit::error(); |
| } |
| |
| audio_stream_unique_id_t unique_id; |
| auto& d = unique_id.data; |
| const auto captured = |
| sscanf(id.c_str(), |
| "%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx", |
| &d[0], &d[1], &d[2], &d[3], &d[4], &d[5], &d[6], &d[7], &d[8], &d[9], &d[10], &d[11], |
| &d[12], &d[13], &d[14], &d[15]); |
| if (captured != 16) { |
| return fit::error(); |
| } |
| |
| return fit::ok(unique_id); |
| } |
| |
| // Simple accessor here (not in .h) because of forward-declaration issues with AudioDriver |
| AudioClock& AudioDevice::reference_clock() { |
| FX_DCHECK(driver_); |
| return driver_->reference_clock(); |
| } |
| |
| const DeviceConfig::DeviceProfile& AudioDevice::profile() const { |
| if (is_output()) { |
| if (!driver_) { |
| return config_.default_output_device_profile(); |
| } |
| const auto& device_id = driver_->persistent_unique_id(); |
| return config_.output_device_profile(device_id); |
| } else { |
| if (!driver_) { |
| return config_.default_input_device_profile(); |
| } |
| const auto& device_id = driver_->persistent_unique_id(); |
| return config_.input_device_profile(device_id); |
| } |
| } |
| |
| AudioDevice::AudioDevice(AudioObject::Type type, const std::string& name, |
| ThreadingModel* threading_model, DeviceRegistry* registry, |
| LinkMatrix* link_matrix, std::unique_ptr<AudioDriver> driver) |
| : AudioObject(type), |
| name_(name), |
| device_registry_(*registry), |
| threading_model_(*threading_model), |
| mix_domain_(threading_model->AcquireMixDomain(type == Type::Input ? "input-device" |
| : "output-device")), |
| driver_(std::move(driver)), |
| link_matrix_(*link_matrix) { |
| FX_DCHECK(registry); |
| FX_DCHECK((type == Type::Input) || (type == Type::Output)); |
| FX_DCHECK(link_matrix); |
| } |
| |
| AudioDevice::~AudioDevice() = default; |
| |
| std::optional<Format> AudioDevice::format() const { |
| auto _driver = driver(); |
| if (!_driver) { |
| return std::nullopt; |
| } |
| return _driver->GetFormat(); |
| } |
| |
| void AudioDevice::Wakeup() { |
| TRACE_DURATION("audio", "AudioDevice::Wakeup"); |
| mix_wakeup_.Signal(); |
| } |
| |
| uint64_t AudioDevice::token() const { |
| return driver_ ? driver_->stream_channel_koid() : ZX_KOID_INVALID; |
| } |
| |
| // Change a device's gain, propagating the change to the affected links. |
| void AudioDevice::SetGainInfo(const fuchsia::media::AudioGainInfo& info, |
| fuchsia::media::AudioGainValidFlags set_flags) { |
| TRACE_DURATION("audio", "AudioDevice::SetGainInfo"); |
| // Limit the request to what the hardware can support |
| fuchsia::media::AudioGainInfo limited = info; |
| ApplyGainLimits(&limited, set_flags); |
| |
| // For outputs, change the gain of all links where it is the destination. |
| if (is_output()) { |
| link_matrix_.ForEachSourceLink(*this, [&limited](LinkMatrix::LinkHandle link) { |
| if (link.object->type() == AudioObject::Type::AudioRenderer) { |
| const auto muted = (limited.flags & fuchsia::media::AudioGainInfoFlags::MUTE) == |
| fuchsia::media::AudioGainInfoFlags::MUTE; |
| if (muted) { |
| FX_LOGS(WARNING) << "Destination device is muted"; |
| } else { |
| // TODO(fxbug.dev/51049) Logging should be removed upon creation of inspect tool or other |
| // real-time method for gain observation |
| FX_LOGS(INFO) << "Destination device gain=" << limited.gain_db; |
| } |
| link.mixer->bookkeeping().gain.SetDestGain(muted ? fuchsia::media::audio::MUTED_GAIN_DB |
| : limited.gain_db); |
| } |
| }); |
| } else { |
| // For inputs, change the gain of all links where it is the source. |
| FX_DCHECK(is_input()); |
| link_matrix_.ForEachDestLink(*this, [&limited](LinkMatrix::LinkHandle link) { |
| if (link.object->type() == AudioObject::Type::AudioCapturer) { |
| const auto muted = (limited.flags & fuchsia::media::AudioGainInfoFlags::MUTE) == |
| fuchsia::media::AudioGainInfoFlags::MUTE; |
| if (muted) { |
| FX_LOGS(WARNING) << "Source device is muted"; |
| } else { |
| // TODO(fxbug.dev/51049) Logging should be removed upon creation of inspect tool or other |
| // real-time method for gain observation |
| FX_LOGS(INFO) << "Source device gain=" << limited.gain_db; |
| } |
| link.mixer->bookkeeping().gain.SetSourceGain(muted ? fuchsia::media::audio::MUTED_GAIN_DB |
| : limited.gain_db); |
| } |
| }); |
| } |
| |
| FX_DCHECK(device_settings_ != nullptr); |
| if (device_settings_->SetGainInfo(limited, set_flags)) { |
| Wakeup(); |
| } |
| } |
| |
| zx_status_t AudioDevice::Init() { |
| TRACE_DURATION("audio", "AudioDevice::Init"); |
| WakeupEvent::ProcessHandler process_handler( |
| [weak_output = weak_from_this()](WakeupEvent* event) -> zx_status_t { |
| auto output = weak_output.lock(); |
| if (output) { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(token, &output->mix_domain()); |
| output->OnWakeup(); |
| } |
| return ZX_OK; |
| }); |
| |
| zx_status_t res = mix_wakeup_.Activate(mix_domain_->dispatcher(), std::move(process_handler)); |
| if (res != ZX_OK) { |
| FX_PLOGS(ERROR, res) << "Failed to activate wakeup event for AudioDevice"; |
| return res; |
| } |
| |
| return ZX_OK; |
| } |
| |
| void AudioDevice::Cleanup() { |
| TRACE_DURATION("audio", "AudioDevice::Cleanup"); |
| mix_wakeup_.Deactivate(); |
| // ThrottleOutput devices have no driver, so check for that. |
| if (driver_ != nullptr) { |
| // Instruct the driver to release all its resources (channels, timer). |
| driver_->Cleanup(); |
| } |
| mix_domain_ = nullptr; |
| } |
| |
| void AudioDevice::ActivateSelf() { |
| TRACE_DURATION("audio", "AudioDevice::ActivateSelf"); |
| // If we aren't shutting down, tell DeviceManager we are ready for work. |
| if (!is_shutting_down()) { |
| // Create default settings. The device manager will restore these settings |
| // from persistent storage for us when it gets our activation message. |
| FX_DCHECK(device_settings_ == nullptr); |
| FX_DCHECK(driver() != nullptr); |
| |
| HwGainState gain_state = driver()->hw_gain_state(); |
| |
| // We disregard the device's gain at the time of connection and set it to 0, |
| // pending restoration of device_settings. |
| gain_state.cur_gain = kDefaultDeviceGain; |
| |
| const auto id = driver()->persistent_unique_id(); |
| |
| device_settings_ = fbl::MakeRefCounted<AudioDeviceSettings>(id, gain_state, is_input()); |
| |
| // Now poke our manager. |
| threading_model().FidlDomain().PostTask( |
| [self = shared_from_this()]() { self->device_registry().ActivateDevice(std::move(self)); }); |
| } |
| } |
| |
| void AudioDevice::ShutdownSelf() { |
| TRACE_DURATION("audio", "AudioDevice::ShutdownSelf"); |
| // If we are not already in the process of shutting down, send a message to |
| // the main message loop telling it to complete the shutdown process. |
| if (!is_shutting_down()) { |
| shutting_down_.store(true); |
| |
| threading_model().FidlDomain().PostTask( |
| [self = shared_from_this()]() { self->device_registry().RemoveDevice(self); }); |
| } |
| } |
| |
| fit::promise<void, zx_status_t> AudioDevice::Startup() { |
| TRACE_DURATION("audio", "AudioDevice::Startup"); |
| fit::bridge<void, zx_status_t> bridge; |
| mix_domain_->PostTask( |
| [self = shared_from_this(), completer = std::move(bridge.completer)]() mutable { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(token, &self->mix_domain()); |
| zx_status_t res = self->Init(); |
| if (res != ZX_OK) { |
| self->Cleanup(); |
| completer.complete_error(res); |
| return; |
| } |
| self->OnWakeup(); |
| completer.complete_ok(); |
| }); |
| return bridge.consumer.promise(); |
| } |
| |
| fit::promise<void> AudioDevice::Shutdown() { |
| TRACE_DURATION("audio", "AudioDevice::Shutdown"); |
| // The only reason we have this flag is to make sure that Shutdown is idempotent. |
| if (shut_down_) { |
| return fit::make_ok_promise(); |
| } |
| shut_down_ = true; |
| |
| // Give our derived class, and our driver, a chance to clean up resources. |
| fit::bridge<void> bridge; |
| mix_domain_->PostTask( |
| [self = shared_from_this(), completer = std::move(bridge.completer)]() mutable { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(token, &self->mix_domain()); |
| self->Cleanup(); |
| completer.complete_ok(); |
| }); |
| return bridge.consumer.promise(); |
| } |
| |
| bool AudioDevice::UpdatePlugState(bool plugged, zx::time plug_time) { |
| TRACE_DURATION("audio", "AudioDevice::UpdatePlugState"); |
| if ((plugged != plugged_) && (plug_time >= plug_time_)) { |
| plugged_ = plugged; |
| plug_time_ = plug_time; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void AudioDevice::UpdateRoutableState(bool routable) { |
| TRACE_INSTANT("audio", "AudioDevice::UpdateRoutableState", TRACE_SCOPE_PROCESS, "Routable", |
| routable); |
| routable_ = routable; |
| } |
| |
| const std::shared_ptr<ReadableRingBuffer>& AudioDevice::driver_readable_ring_buffer() const { |
| return driver_->readable_ring_buffer(); |
| }; |
| |
| const std::shared_ptr<WritableRingBuffer>& AudioDevice::driver_writable_ring_buffer() const { |
| return driver_->writable_ring_buffer(); |
| }; |
| |
| const TimelineFunction& AudioDevice::driver_ref_time_to_frac_presentation_frame() const { |
| return driver()->ref_time_to_frac_presentation_frame(); |
| } |
| |
| const TimelineFunction& AudioDevice::driver_ref_time_to_frac_safe_read_or_write_frame() const { |
| return driver()->ref_time_to_frac_safe_read_or_write_frame(); |
| } |
| |
| fuchsia::media::AudioDeviceInfo AudioDevice::GetDeviceInfo() const { |
| TRACE_DURATION("audio", "AudioDevice::GetDeviceInfo"); |
| |
| FX_DCHECK(device_settings_); |
| |
| return { |
| .name = driver()->manufacturer_name() + ' ' + driver()->product_name(), |
| .unique_id = UniqueIdToString(driver()->persistent_unique_id()), |
| .token_id = token(), |
| .is_input = is_input(), |
| .gain_info = device_settings_->GetGainInfo(), |
| .is_default = false, |
| }; |
| } |
| |
| } // namespace media::audio |