| // 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/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 <algorithm> |
| #include <limits> |
| |
| #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 { |
| |
| template <typename ReqType, typename RespType> |
| zx_status_t DoCallImpl(const zx::channel& channel, const ReqType& req, RespType* resp, |
| zx::handle* resp_handle_out, uint32_t* resp_len_out = nullptr) { |
| zx_channel_call_args_t args; |
| |
| ZX_DEBUG_ASSERT((resp_handle_out == nullptr) || !resp_handle_out->is_valid()); |
| |
| args.wr_bytes = const_cast<ReqType*>(&req); |
| args.wr_num_bytes = sizeof(ReqType); |
| args.wr_handles = nullptr; |
| args.wr_num_handles = 0; |
| args.rd_bytes = resp; |
| args.rd_num_bytes = sizeof(RespType); |
| args.rd_handles = resp_handle_out ? resp_handle_out->reset_and_get_address() : nullptr; |
| args.rd_num_handles = resp_handle_out ? 1 : 0; |
| |
| uint32_t bytes, handles; |
| |
| zx_status_t status = channel.call(0, zx::time(ZX_TIME_INFINITE), &args, &bytes, &handles); |
| if (status != ZX_OK) { |
| printf("Cmd failure (cmd %04x, res %d)\n", req.hdr.cmd, status); |
| return status; |
| } |
| |
| // If the caller wants to know the size of the response length, let them |
| // check to make sure it is consistent with what they expect. Otherwise, |
| // make sure that the number of bytes we got back matches the size of the |
| // response structure. |
| if (resp_len_out != nullptr) { |
| *resp_len_out = bytes; |
| } else if (bytes != sizeof(RespType)) { |
| printf("Unexpected response size (got %u, expected %zu)\n", bytes, sizeof(RespType)); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| return ZX_OK; |
| } |
| |
| template <typename ReqType, typename RespType> |
| zx_status_t DoCall(const zx::channel& channel, const ReqType& req, RespType* resp, |
| zx::handle* resp_handle_out = nullptr) { |
| zx_status_t res = DoCallImpl(channel, req, resp, resp_handle_out); |
| return (res != ZX_OK) ? res : resp->result; |
| } |
| |
| template <typename ReqType, typename RespType> |
| zx_status_t DoNoFailCall(const zx::channel& channel, const ReqType& req, RespType* resp, |
| zx::handle* resp_handle_out = nullptr) { |
| return DoCallImpl(channel, req, resp, resp_handle_out); |
| } |
| |
| AudioDeviceStream::AudioDeviceStream(StreamDirection direction, uint32_t dev_id) |
| : direction_(direction) { |
| snprintf(name_, sizeof(name_), "/dev/class/audio-%s/%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); |
| } |
| res = fdio_service_connect(name(), remote.release()); |
| using Device = ::llcpp::fuchsia::hardware::audio::Device; |
| Device::SyncClient client_wrap(std::move(local)); |
| Device::ResultOf::GetChannel channel_wrap = client_wrap.GetChannel(); |
| stream_ch_ = std::move(channel_wrap->channel); |
| |
| return res; |
| } |
| |
| zx_status_t AudioDeviceStream::GetSupportedFormats( |
| fbl::Vector<audio_stream_format_range_t>* out_formats) const { |
| constexpr uint32_t MIN_RESP_SIZE = offsetof(audio_stream_cmd_get_formats_resp_t, format_ranges); |
| audio_stream_cmd_get_formats_req req; |
| audio_stream_cmd_get_formats_resp resp; |
| uint32_t rxed; |
| zx_status_t res; |
| |
| if (out_formats == nullptr) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| req.hdr.cmd = AUDIO_STREAM_CMD_GET_FORMATS; |
| req.hdr.transaction_id = 1; |
| res = DoCallImpl(stream_ch_, req, &resp, nullptr, &rxed); |
| if ((res != ZX_OK) || (rxed < MIN_RESP_SIZE)) { |
| printf("Failed to fetch initial suppored format list chunk (res %d, rxed %u)\n", res, rxed); |
| return res; |
| } |
| |
| uint32_t expected_formats = resp.format_range_count; |
| if (!expected_formats) |
| return ZX_OK; |
| |
| out_formats->reset(); |
| fbl::AllocChecker ac; |
| out_formats->reserve(expected_formats, &ac); |
| if (!ac.check()) { |
| printf("Failed to allocated %u entries for format ranges\n", expected_formats); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| zx_txid_t txid = resp.hdr.transaction_id; |
| uint32_t processed_formats = 0; |
| while (true) { |
| if (resp.hdr.cmd != AUDIO_STREAM_CMD_GET_FORMATS) { |
| printf( |
| "Unexpected response command while fetching formats " |
| "(expected 0x%08x, got 0x%08x)\n", |
| AUDIO_STREAM_CMD_GET_FORMATS, resp.hdr.cmd); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| if (resp.hdr.transaction_id != txid) { |
| printf( |
| "Unexpected response transaction id while fetching formats " |
| "(expected 0x%08x, got 0x%08x)\n", |
| txid, resp.hdr.transaction_id); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| if (resp.first_format_range_ndx != processed_formats) { |
| printf("Bad format index while fetching formats (expected %u, got %hu)\n", processed_formats, |
| resp.first_format_range_ndx); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| uint32_t todo = std::min(static_cast<uint32_t>(expected_formats - processed_formats), |
| AUDIO_STREAM_CMD_GET_FORMATS_MAX_RANGES_PER_RESPONSE); |
| size_t min_size = MIN_RESP_SIZE + (todo * sizeof(audio_stream_format_range_t)); |
| if (rxed < min_size) { |
| printf("Short response while fetching formats (%u < %zu)\n", rxed, min_size); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| for (uint16_t i = 0; i < todo; ++i) { |
| out_formats->push_back(resp.format_ranges[i]); |
| } |
| |
| processed_formats += todo; |
| if (processed_formats == expected_formats) |
| break; |
| |
| zx_signals_t pending_sig; |
| res = stream_ch_.wait_one(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, |
| zx::time(ZX_TIME_INFINITE), &pending_sig); |
| if (res != ZX_OK) { |
| printf("Failed to wait for next response after processing %u/%u formats (res %d)\n", |
| processed_formats, expected_formats, res); |
| return res; |
| } |
| |
| res = stream_ch_.read(0u, &resp, nullptr, sizeof(resp), 0, &rxed, nullptr); |
| if (res != ZX_OK) { |
| printf("Failed to read next response after processing %u/%u formats (res %d)\n", |
| processed_formats, expected_formats, res); |
| return res; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t AudioDeviceStream::GetPlugState(audio_stream_cmd_plug_detect_resp_t* out_state, |
| bool enable_notify) const { |
| ZX_DEBUG_ASSERT(out_state != nullptr); |
| audio_stream_cmd_plug_detect_req req; |
| |
| req.hdr.cmd = AUDIO_STREAM_CMD_PLUG_DETECT; |
| req.hdr.transaction_id = 1; |
| req.flags = enable_notify ? AUDIO_PDF_ENABLE_NOTIFICATIONS : AUDIO_PDF_NONE; |
| |
| zx_status_t res = DoNoFailCall(stream_ch_, req, out_state); |
| if (res != ZX_OK) |
| printf("Failed to fetch plug detect information! (res %d)\n", res); |
| |
| return res; |
| } |
| |
| void AudioDeviceStream::DisablePlugNotifications() { |
| audio_stream_cmd_plug_detect_req req; |
| |
| req.hdr.cmd = static_cast<audio_cmd_t>(AUDIO_STREAM_CMD_PLUG_DETECT | AUDIO_FLAG_NO_ACK); |
| req.hdr.transaction_id = 1; |
| req.flags = AUDIO_PDF_DISABLE_NOTIFICATIONS; |
| |
| stream_ch_.write(0, &req, sizeof(req), nullptr, 0); |
| } |
| |
| zx_status_t AudioDeviceStream::SetMute(bool mute) { |
| audio_stream_cmd_set_gain_req req; |
| audio_stream_cmd_set_gain_resp resp; |
| |
| req.hdr.cmd = AUDIO_STREAM_CMD_SET_GAIN; |
| req.hdr.transaction_id = 1; |
| req.flags = mute ? static_cast<audio_set_gain_flags_t>(AUDIO_SGF_MUTE_VALID | AUDIO_SGF_MUTE) |
| : AUDIO_SGF_MUTE_VALID; |
| |
| zx_status_t res = DoCall(stream_ch_, req, &resp); |
| if (res != ZX_OK) |
| printf("Failed to %smute stream! (res %d)\n", mute ? "" : "un", res); |
| else |
| printf("Stream is now %smuted\n", mute ? "" : "un"); |
| |
| return res; |
| } |
| |
| zx_status_t AudioDeviceStream::SetAgc(bool enabled) { |
| audio_stream_cmd_set_gain_req req; |
| audio_stream_cmd_set_gain_resp resp; |
| |
| req.hdr.cmd = AUDIO_STREAM_CMD_SET_GAIN; |
| req.hdr.transaction_id = 1; |
| req.flags = enabled ? static_cast<audio_set_gain_flags_t>(AUDIO_SGF_AGC_VALID | AUDIO_SGF_AGC) |
| : AUDIO_SGF_AGC_VALID; |
| |
| zx_status_t res = DoCall(stream_ch_, req, &resp); |
| if (res != ZX_OK) |
| printf("Failed to %sable AGC for stream! (res %d)\n", enabled ? "en" : "dis", res); |
| else |
| printf("Stream AGC is now %sabled\n", enabled ? "en" : "dis"); |
| |
| return res; |
| } |
| |
| zx_status_t AudioDeviceStream::SetGain(float gain) { |
| audio_stream_cmd_set_gain_req req; |
| audio_stream_cmd_set_gain_resp resp; |
| |
| req.hdr.cmd = AUDIO_STREAM_CMD_SET_GAIN; |
| req.hdr.transaction_id = 1; |
| req.flags = AUDIO_SGF_GAIN_VALID; |
| req.gain = gain; |
| |
| zx_status_t res = DoCall(stream_ch_, req, &resp); |
| if (res != ZX_OK) { |
| printf("Failed to set gain to %.2f dB! (res %d)\n", gain, res); |
| } else { |
| printf("Gain is now %.2f dB. Stream is %smuted.\n", resp.cur_gain, resp.cur_mute ? "" : "un"); |
| } |
| |
| return res; |
| } |
| |
| zx_status_t AudioDeviceStream::GetGain(audio_stream_cmd_get_gain_resp_t* out_gain) const { |
| if (out_gain == nullptr) |
| return ZX_ERR_INVALID_ARGS; |
| |
| audio_stream_cmd_get_gain_req req; |
| req.hdr.cmd = AUDIO_STREAM_CMD_GET_GAIN; |
| req.hdr.transaction_id = 1; |
| |
| return DoNoFailCall(stream_ch_, req, out_gain); |
| } |
| |
| 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; |
| |
| audio_stream_cmd_get_unique_id_req req; |
| req.hdr.cmd = AUDIO_STREAM_CMD_GET_UNIQUE_ID; |
| req.hdr.transaction_id = 1; |
| |
| return DoNoFailCall(stream_ch_, req, out_id); |
| } |
| |
| 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; |
| |
| audio_stream_cmd_get_string_req req; |
| req.hdr.cmd = AUDIO_STREAM_CMD_GET_STRING; |
| req.hdr.transaction_id = 1; |
| req.id = id; |
| |
| return DoNoFailCall(stream_ch_, req, out_str); |
| } |
| |
| zx_status_t AudioDeviceStream::GetClockDomain( |
| audio_stream_cmd_get_clock_domain_resp_t* out_domain) const { |
| if (out_domain == nullptr) |
| return ZX_ERR_INVALID_ARGS; |
| |
| audio_stream_cmd_get_clock_domain_req req; |
| req.hdr.cmd = AUDIO_STREAM_CMD_GET_CLOCK_DOMAIN; |
| req.hdr.transaction_id = 1; |
| |
| return DoNoFailCall(stream_ch_, req, out_domain); |
| } |
| |
| 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)); |
| audio_stream_cmd_plug_detect_resp resp; |
| zx_status_t res = GetPlugState(&resp, true); |
| if (res != ZX_OK) |
| return res; |
| |
| zx_time_t last_plug_time = resp.plug_state_time; |
| bool last_plug_state = (resp.flags & AUDIO_PDNF_PLUGGED); |
| printf("Initial plug state is : %s.\n", last_plug_state ? "plugged" : "unplugged"); |
| |
| if (resp.flags & AUDIO_PDNF_HARDWIRED) { |
| printf("Stream reports that it is hardwired, Monitoring is not possible.\n"); |
| return ZX_OK; |
| } |
| |
| auto ReportPlugState = [&last_plug_time, &last_plug_state, &monitor]( |
| bool plug_state, zx_time_t plug_time) -> bool { |
| printf("Plug State now : %s (%.3lf sec since last change).\n", |
| plug_state ? "plugged" : "unplugged", |
| static_cast<double>(zx_time_sub_time(plug_time, last_plug_time)) / |
| static_cast<double>(ZX_SEC(1))); |
| |
| last_plug_state = plug_state; |
| last_plug_time = plug_time; |
| if (monitor) { |
| return (*monitor)(plug_state, plug_time); |
| } |
| return true; // Continue monitoring. |
| }; |
| |
| if (resp.flags & AUDIO_PDNF_CAN_NOTIFY) { |
| printf("Stream is capable of async notification. Monitoring for %.2f seconds\n", duration); |
| |
| auto cleanup = fbl::MakeAutoCall([this]() { DisablePlugNotifications(); }); |
| while (true) { |
| zx_signals_t pending; |
| res = stream_ch_.wait_one(ZX_CHANNEL_PEER_CLOSED | ZX_CHANNEL_READABLE, zx::time(deadline), |
| &pending); |
| |
| if ((res != ZX_OK) || (pending & ZX_CHANNEL_PEER_CLOSED)) { |
| if (res != ZX_ERR_TIMED_OUT) |
| printf("Error while waiting for plug notification (res %d)\n", res); |
| |
| if (pending & ZX_CHANNEL_PEER_CLOSED) |
| printf("Peer closed while waiting for plug notification\n"); |
| |
| break; |
| } |
| |
| ZX_DEBUG_ASSERT(pending & ZX_CHANNEL_READABLE); |
| |
| audio_stream_plug_detect_notify_t state; |
| uint32_t bytes_read; |
| res = stream_ch_.read(0, &state, nullptr, sizeof(state), 0, &bytes_read, nullptr); |
| if (res != ZX_OK) { |
| printf("Read failure while waiting for plug notification (res %d)\n", res); |
| break; |
| } |
| |
| if ((bytes_read != sizeof(state)) || (state.hdr.cmd != AUDIO_STREAM_PLUG_DETECT_NOTIFY)) { |
| printf( |
| "Size/type mismatch while waiting for plug notification. " |
| "Got (%u/%u) Expected (%zu/%u)\n", |
| bytes_read, state.hdr.cmd, sizeof(state), AUDIO_STREAM_PLUG_DETECT_NOTIFY); |
| break; |
| } |
| |
| bool plug_state = (state.flags & AUDIO_PDNF_PLUGGED); |
| if (!ReportPlugState(plug_state, state.plug_state_time)) { |
| printf("Monitoring finished after callback reported.\n"); |
| return ZX_OK; |
| } |
| } |
| } else { |
| printf("Stream is not capable of async notification. Polling for %.2f seconds\n", duration); |
| |
| while (true) { |
| zx_time_t now = zx_clock_get_monotonic(); |
| if (now >= deadline) |
| break; |
| |
| zx_time_t next_wake = std::min(deadline, zx_time_add_duration(now, ZX_MSEC(100u))); |
| |
| zx_signals_t sigs; |
| zx_status_t res = stream_ch_.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time(next_wake), &sigs); |
| |
| if ((res != ZX_OK) && (res != ZX_ERR_TIMED_OUT)) { |
| printf("Error waiting on stream channel (res %d)\n", res); |
| break; |
| } |
| |
| if (sigs & ZX_CHANNEL_PEER_CLOSED) { |
| printf("Peer closed connection while polling plug state\n"); |
| break; |
| } |
| |
| res = GetPlugState(&resp, true); |
| if (res != ZX_OK) { |
| printf("Failed to poll plug state (res %d)\n", res); |
| break; |
| } |
| |
| bool plug_state = (resp.flags & AUDIO_PDNF_PLUGGED); |
| if (plug_state != last_plug_state) { |
| if (!ReportPlugState(resp.flags, resp.plug_state_time)) { |
| printf("Monitoring finished after callback reported.\n"); |
| return ZX_OK; |
| } |
| } |
| } |
| } |
| |
| printf("Monitoring finished.\n"); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t AudioDeviceStream::SetFormat(uint32_t frames_per_second, uint16_t channels, |
| audio_sample_format_t sample_format) { |
| if ((stream_ch_ == ZX_HANDLE_INVALID) || (rb_ch_ != ZX_HANDLE_INVALID)) |
| return ZX_ERR_BAD_STATE; |
| |
| auto noflag_format = |
| static_cast<audio_sample_format_t>((sample_format & ~AUDIO_SAMPLE_FORMAT_FLAG_MASK)); |
| |
| switch (noflag_format) { |
| case AUDIO_SAMPLE_FORMAT_8BIT: |
| sample_size_ = 1; |
| break; |
| case AUDIO_SAMPLE_FORMAT_16BIT: |
| sample_size_ = 2; |
| break; |
| case AUDIO_SAMPLE_FORMAT_24BIT_PACKED: |
| sample_size_ = 3; |
| break; |
| case AUDIO_SAMPLE_FORMAT_20BIT_IN32: |
| case AUDIO_SAMPLE_FORMAT_24BIT_IN32: |
| case AUDIO_SAMPLE_FORMAT_32BIT: |
| case AUDIO_SAMPLE_FORMAT_32BIT_FLOAT: |
| sample_size_ = 4; |
| break; |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| channel_cnt_ = channels; |
| frame_sz_ = channels * sample_size_; |
| frame_rate_ = frames_per_second; |
| sample_format_ = sample_format; |
| |
| audio_stream_cmd_set_format_req_t req; |
| audio_stream_cmd_set_format_resp_t resp; |
| req.hdr.cmd = AUDIO_STREAM_CMD_SET_FORMAT; |
| req.hdr.transaction_id = 1; |
| req.frames_per_second = frames_per_second; |
| req.channels = channels; |
| req.sample_format = sample_format; |
| |
| zx::handle tmp; |
| zx_status_t res = DoCall(stream_ch_, req, &resp, &tmp); |
| if (res != ZX_OK) { |
| printf("Failed to set format %uHz %hu-Ch fmt 0x%x (res %d)\n", frames_per_second, channels, |
| sample_format, res); |
| } |
| |
| external_delay_nsec_ = resp.external_delay_nsec; |
| |
| // TODO(johngro) : Verify the type of this handle before transferring it to |
| // our ring buffer channel handle. |
| rb_ch_.reset(tmp.release()); |
| |
| return res; |
| } |
| |
| 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. |
| { |
| audio_rb_cmd_get_fifo_depth_req_t req; |
| audio_rb_cmd_get_fifo_depth_resp_t resp; |
| |
| req.hdr.cmd = AUDIO_RB_CMD_GET_FIFO_DEPTH; |
| req.hdr.transaction_id = 1; |
| res = DoCall(rb_ch_, req, &resp); |
| if (res != ZX_OK) { |
| printf("Failed to fetch fifo depth (res %d)\n", res); |
| return res; |
| } |
| |
| fifo_depth_ = resp.fifo_depth; |
| } |
| |
| uint64_t rb_sz; |
| { |
| // Get a VMO representing the ring buffer we will share with the audio driver. |
| audio_rb_cmd_get_buffer_req_t req; |
| audio_rb_cmd_get_buffer_resp_t resp; |
| |
| req.hdr.cmd = AUDIO_RB_CMD_GET_BUFFER; |
| req.hdr.transaction_id = 1; |
| req.min_ring_buffer_frames = frames; |
| req.notifications_per_ring = irqs_per_ring; |
| |
| zx::handle tmp; |
| res = DoCall(rb_ch_, req, &resp, &tmp); |
| |
| if ((res == ZX_OK) && (resp.result != ZX_OK)) |
| res = resp.result; |
| |
| if (res != ZX_OK) { |
| printf("Failed to get driver ring buffer VMO (res %d)\n", res); |
| return res; |
| } |
| |
| rb_sz = static_cast<uint64_t>(resp.num_ring_buffer_frames) * frame_sz_; |
| |
| // TODO(johngro) : Verify the type of this handle before transferring it to our VMO handle. |
| rb_vmo_.reset(tmp.release()); |
| } |
| |
| // 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; |
| |
| audio_rb_cmd_start_req_t req; |
| audio_rb_cmd_start_resp_t resp; |
| |
| req.hdr.cmd = AUDIO_RB_CMD_START; |
| req.hdr.transaction_id = 1; |
| |
| zx_status_t res = DoCall(rb_ch_, req, &resp); |
| |
| if (res == ZX_OK) { |
| start_time_ = resp.start_time; |
| } |
| |
| return res; |
| } |
| |
| zx_status_t AudioDeviceStream::StopRingBuffer() { |
| if (rb_ch_ == ZX_HANDLE_INVALID) |
| return ZX_ERR_BAD_STATE; |
| |
| start_time_ = 0; |
| |
| audio_rb_cmd_stop_req_t req; |
| audio_rb_cmd_stop_resp_t resp; |
| |
| req.hdr.cmd = AUDIO_RB_CMD_STOP; |
| req.hdr.transaction_id = 1; |
| |
| return DoCall(rb_ch_, req, &resp); |
| } |
| |
| 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 |