blob: bf7f3e591e3cd6077e4039e7e0b8a08f44d1c4ea [file] [log] [blame]
// 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 <fcntl.h>
#include <fuchsia/hardware/audio/llcpp/fidl.h>
#include <inttypes.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fdio.h>
#include <lib/zx/channel.h>
#include <lib/zx/clock.h>
#include <lib/zx/handle.h>
#include <lib/zx/vmar.h>
#include <lib/zx/vmo.h>
#include <stdio.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/device/audio.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include <limits>
#include <audio-proto-utils/format-utils.h>
#include <audio-utils/audio-device-stream.h>
#include <audio-utils/audio-input.h>
#include <audio-utils/audio-output.h>
#include <fbl/algorithm.h>
#include <fbl/auto_call.h>
#include <fbl/unique_fd.h>
namespace audio {
namespace utils {
AudioDeviceStream::AudioDeviceStream(StreamDirection direction, uint32_t dev_id)
: direction_(direction) {
snprintf(name_, sizeof(name_), "/dev/class/audio-%s-2/%03u",
direction == StreamDirection::kInput ? "input" : "output", dev_id);
}
AudioDeviceStream::AudioDeviceStream(StreamDirection direction, const char* dev_path)
: direction_(direction) {
strncpy(name_, dev_path, sizeof(name_) - 1);
name_[sizeof(name_) - 1] = 0;
}
AudioDeviceStream::~AudioDeviceStream() { Close(); }
zx_status_t AudioDeviceStream::Open() {
if (stream_ch_ != ZX_HANDLE_INVALID)
return ZX_ERR_BAD_STATE;
zx::channel local, remote;
auto res = zx::channel::create(0, &local, &remote);
if (res != ZX_OK) {
printf("Failed to create channel (res %d)\n", res);
return res;
}
res = fdio_service_connect(name(), remote.release());
if (res != ZX_OK) {
printf("Failed to obtain channel (res %d)\n", res);
return res;
}
audio_fidl::Device::SyncClient client_wrap(std::move(local));
audio_fidl::Device::ResultOf::GetChannel channel_wrap = client_wrap.GetChannel();
if (!channel_wrap.ok()) {
res = channel_wrap.status();
printf("GetChannel failed with error %s (res %d)\n", channel_wrap.error(), res);
return res;
}
stream_ch_ = std::move(channel_wrap->channel);
return ZX_OK;
}
zx_status_t AudioDeviceStream::GetSupportedFormats(const SupportedFormatsCallback& cb) const {
auto formats =
audio_fidl::StreamConfig::Call::GetSupportedFormats(zx::unowned_channel(stream_ch_));
for (auto& i : formats->supported_formats) {
cb(i);
}
return ZX_OK;
}
zx_status_t AudioDeviceStream::WatchPlugState(
audio_stream_cmd_plug_detect_resp_t* out_state) const {
ZX_DEBUG_ASSERT(out_state != nullptr);
auto prop = audio_fidl::StreamConfig::Call::GetProperties(zx::unowned_channel(stream_ch_));
if (prop.status() != ZX_OK) {
printf("Failed to get properties to watch plug state (res %d)\n", prop.status());
return prop.status();
}
auto state = audio_fidl::StreamConfig::Call::WatchPlugState(zx::unowned_channel(stream_ch_));
if (state.status() != ZX_OK) {
printf("Failed to watch plug state (res %d)\n", state.status());
return state.status();
}
if (prop->properties.plug_detect_capabilities() ==
audio_fidl::PlugDetectCapabilities::CAN_ASYNC_NOTIFY) {
out_state->plug_state_time = state->plug_state.plug_state_time();
out_state->flags = state->plug_state.plugged() ? AUDIO_PDNF_PLUGGED : 0;
out_state->flags |= AUDIO_PDNF_CAN_NOTIFY;
} else {
out_state->flags = AUDIO_PDNF_PLUGGED;
out_state->flags |= AUDIO_PDNF_HARDWIRED;
}
return ZX_OK;
}
zx_status_t AudioDeviceStream::SetMute(bool mute) {
muted_ = mute;
return SetGainParams();
}
zx_status_t AudioDeviceStream::SetAgc(bool enabled) {
agc_enabled_ = enabled;
return SetGainParams();
}
zx_status_t AudioDeviceStream::SetGain(float gain) {
gain_ = gain;
return SetGainParams();
}
zx_status_t AudioDeviceStream::SetGainParams() {
auto builder = audio_fidl::GainState::UnownedBuilder();
fidl::aligned<bool> muted = muted_;
fidl::aligned<bool> agc_enabled = agc_enabled_;
fidl::aligned<float> gain = gain_;
builder.set_muted(fidl::unowned_ptr(&muted));
builder.set_agc_enabled(fidl::unowned_ptr(&agc_enabled));
builder.set_gain_db(fidl::unowned_ptr(&gain));
audio_fidl::StreamConfig::Call::SetGain(zx::unowned_channel(stream_ch_), builder.build());
return ZX_OK;
}
zx_status_t AudioDeviceStream::WatchGain(audio_stream_cmd_get_gain_resp_t* out_gain) const {
auto prop = audio_fidl::StreamConfig::Call::GetProperties(zx::unowned_channel(stream_ch_));
if (prop.status() == ZX_OK) {
out_gain->min_gain = prop->properties.min_gain_db();
out_gain->max_gain = prop->properties.max_gain_db();
out_gain->gain_step = prop->properties.gain_step_db();
} else {
printf("Failed to get properties to watch gainstate (res %d)\n", prop.status());
return prop.status();
}
auto gain_state = audio_fidl::StreamConfig::Call::WatchGainState(zx::unowned_channel(stream_ch_));
if (gain_state.status() == ZX_OK) {
out_gain->cur_gain = gain_state->gain_state.gain_db();
out_gain->can_mute = gain_state->gain_state.has_muted();
if (gain_state->gain_state.has_muted()) {
out_gain->cur_mute = gain_state->gain_state.muted();
}
out_gain->can_agc = gain_state->gain_state.has_agc_enabled();
if (gain_state->gain_state.has_agc_enabled()) {
out_gain->cur_mute = gain_state->gain_state.agc_enabled();
}
} else {
printf("Failed to watch gain state (res %d)\n", gain_state.status());
return gain_state.status();
}
return ZX_OK;
}
zx_status_t AudioDeviceStream::GetUniqueId(audio_stream_cmd_get_unique_id_resp_t* out_id) const {
if (out_id == nullptr)
return ZX_ERR_INVALID_ARGS;
auto result = audio_fidl::StreamConfig::Call::GetProperties(zx::unowned_channel(stream_ch_));
size_t size = (result->properties.unique_id().size() > sizeof(out_id->unique_id))
? sizeof(out_id->unique_id)
: result->properties.unique_id().size();
memcpy(out_id->unique_id.data, result->properties.unique_id().data(), size);
return ZX_OK;
}
zx_status_t AudioDeviceStream::GetString(audio_stream_string_id_t id,
audio_stream_cmd_get_string_resp_t* out_str) const {
if (out_str == nullptr)
return ZX_ERR_INVALID_ARGS;
auto result = audio_fidl::StreamConfig::Call::GetProperties(zx::unowned_channel(stream_ch_));
switch (id) {
case AUDIO_STREAM_STR_ID_MANUFACTURER:
out_str->strlen = static_cast<uint32_t>(result->properties.manufacturer().size());
memcpy(out_str->str, result->properties.manufacturer().data(), out_str->strlen);
break;
case AUDIO_STREAM_STR_ID_PRODUCT:
out_str->strlen = static_cast<uint32_t>(result->properties.product().size());
memcpy(out_str->str, result->properties.product().data(), out_str->strlen);
break;
default:
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
zx_status_t AudioDeviceStream::PlugMonitor(float duration, PlugMonitorCallback* monitor) {
const double duration_ns = static_cast<double>(duration) * ZX_SEC(1);
const zx_time_t deadline = zx_deadline_after(static_cast<zx_duration_t>(duration_ns));
zx_time_t last_plug_time = zx::clock::get_monotonic().get();
while (true) {
// TODO(andresoportus): Currently if no plug state changes occur, we wait forever.
// Once LLCPP supports async clients, stop monitoring even when there is no plug state change.
audio_stream_cmd_plug_detect_resp_t out_state = {};
auto status = WatchPlugState(&out_state);
if (status != ZX_OK) {
printf("Failed to watch plug state (res %d)\n", status);
return status;
}
zx_time_t plug_time = out_state.plug_state_time;
printf("Plug State now : %s (%.3lf sec since last change).\n",
out_state.flags & AUDIO_PDNF_PLUGGED ? "plugged" : "unplugged",
static_cast<double>(zx_time_sub_time(plug_time, last_plug_time)) /
static_cast<double>(zx::sec(1).get()));
if (out_state.flags & AUDIO_PDNF_HARDWIRED) {
printf("Stream reports that it is hardwired, Monitoring is not possible.\n");
return ZX_OK;
}
if (monitor) {
return (*monitor)(out_state.flags & AUDIO_PDNF_PLUGGED, plug_time);
}
if (zx::clock::get_monotonic().get() > deadline) {
break;
}
}
printf("Monitoring finished.\n");
return ZX_OK;
}
zx_status_t AudioDeviceStream::SetFormat(uint32_t frames_per_second, uint16_t channels,
uint64_t channels_to_use_bitmask,
audio_sample_format_t sample_format) {
if ((stream_ch_ == ZX_HANDLE_INVALID) || (rb_ch_ != ZX_HANDLE_INVALID))
return ZX_ERR_BAD_STATE;
auto sizes = audio::utils::GetSampleSizes(sample_format);
sample_size_ = sizes.valid_bits_per_sample;
channel_size_ = 8 * sizes.bytes_per_sample;
if (sample_size_ == 0 || channel_size_ == 0) {
return ZX_ERR_NOT_SUPPORTED;
}
channel_cnt_ = channels;
frame_sz_ = channels * channel_size_ / 8;
frame_rate_ = frames_per_second;
sample_format_ = sample_format;
zx::channel local, remote;
auto status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
return status;
}
audio_fidl::PcmFormat pcm_format = {};
pcm_format.number_of_channels = static_cast<uint8_t>(channel_cnt_);
pcm_format.channels_to_use_bitmask = channels_to_use_bitmask;
pcm_format.sample_format = audio_fidl::SampleFormat::PCM_SIGNED;
pcm_format.frame_rate = frames_per_second;
pcm_format.bytes_per_sample = channel_size_ / 8;
pcm_format.valid_bits_per_sample = sample_size_;
auto builder = audio_fidl::Format::UnownedBuilder();
builder.set_pcm_format(fidl::unowned_ptr(&pcm_format));
audio_fidl::StreamConfig::Call::CreateRingBuffer(zx::unowned_channel(stream_ch_), builder.build(),
std::move(remote));
rb_ch_ = std::move(local);
return ZX_OK;
}
zx_status_t AudioDeviceStream::GetBuffer(uint32_t frames, uint32_t irqs_per_ring) {
zx_status_t res;
if (!frames)
return ZX_ERR_INVALID_ARGS;
if (!rb_ch_.is_valid() || rb_vmo_.is_valid() || !frame_sz_) {
return ZX_ERR_BAD_STATE;
}
// Stash the FIFO depth, in case users need to know it.
{
auto result = audio_fidl::RingBuffer::Call::GetProperties(zx::unowned_channel(rb_ch_));
if (result.status() != ZX_OK) {
return ZX_ERR_BAD_STATE;
}
fifo_depth_ = result->properties.fifo_depth();
external_delay_nsec_ = result->properties.external_delay();
}
// Get a VMO representing the ring buffer we will share with the audio driver.
// fast_capture_notifications not supported (set to an invalid channel).
// fast_playback_notifications not supported (set to an invalid channel).
auto result =
audio_fidl::RingBuffer::Call::GetVmo(zx::unowned_channel(rb_ch_), frames, irqs_per_ring);
uint64_t rb_sz = static_cast<uint64_t>(result->result.response().num_frames) * frame_sz_;
rb_vmo_ = std::move(result->result.mutable_response().ring_buffer);
// We have the buffer, fetch the underlying size of the VMO (a rounded up
// multiple of pages) and sanity check it against the effective size the
// driver reported.
uint64_t rb_page_sz;
res = rb_vmo_.get_size(&rb_page_sz);
if (res != ZX_OK) {
printf("Failed to fetch ring buffer VMO size (res %d)\n", res);
return res;
}
if ((rb_sz > std::numeric_limits<decltype(rb_sz_)>::max()) || (rb_sz > rb_page_sz)) {
printf(
"Bad ring buffer size returned by audio driver! "
"(kernel size = %lu driver size = %lu)\n",
rb_page_sz, rb_sz);
return ZX_ERR_INVALID_ARGS;
}
rb_sz_ = static_cast<decltype(rb_sz_)>(rb_sz);
// Map the VMO into our address space
// TODO(johngro) : How do I specify the cache policy for this mapping?
uint32_t flags = input() ? ZX_VM_PERM_READ : ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
res = zx::vmar::root_self()->map(flags, 0u, rb_vmo_, 0u, rb_sz_,
reinterpret_cast<uintptr_t*>(&rb_virt_));
if (res != ZX_OK) {
printf("Failed to map ring buffer VMO (res %d)\n", res);
return res;
}
// Success! If this is an output device, zero out the buffer and we are done.
if (!input()) {
memset(rb_virt_, 0, rb_sz_);
}
return ZX_OK;
}
zx_status_t AudioDeviceStream::StartRingBuffer() {
if (rb_ch_ == ZX_HANDLE_INVALID)
return ZX_ERR_BAD_STATE;
auto result = audio_fidl::RingBuffer::Call::Start(zx::unowned_channel(rb_ch_));
start_time_ = result->start_time;
return result.status();
}
zx_status_t AudioDeviceStream::StopRingBuffer() {
if (rb_ch_ == ZX_HANDLE_INVALID)
return ZX_ERR_BAD_STATE;
start_time_ = 0;
auto result = audio_fidl::RingBuffer::Call::Stop(zx::unowned_channel(rb_ch_));
return result.status();
}
void AudioDeviceStream::ResetRingBuffer() {
if (rb_virt_ != nullptr) {
ZX_DEBUG_ASSERT(rb_sz_ != 0);
zx::vmar::root_self()->unmap(reinterpret_cast<uintptr_t>(rb_virt_), rb_sz_);
}
rb_ch_.reset();
rb_vmo_.reset();
rb_sz_ = 0;
rb_virt_ = nullptr;
}
void AudioDeviceStream::Close() {
ResetRingBuffer();
stream_ch_.reset();
}
bool AudioDeviceStream::IsChannelConnected(const zx::channel& ch) {
if (!ch.is_valid())
return false;
zx_signals_t junk;
return ch.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time(), &junk) != ZX_ERR_TIMED_OUT;
}
} // namespace utils
} // namespace audio