| // Copyright 2019 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/drivers/test/audio_driver_test.h" |
| |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <fuchsia/hardware/audio/cpp/fidl.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fzl/vmo-mapper.h> |
| #include <lib/zx/vmo.h> |
| |
| #include <algorithm> |
| #include <cstring> |
| |
| #include "src/media/audio/lib/logging/logging.h" |
| |
| namespace media::audio::test { |
| |
| static const struct { |
| const char* path; |
| DeviceType device_type; |
| } AUDIO_DEVNODES[] = { |
| {.path = "/dev/class/audio-input", .device_type = DeviceType::Input}, |
| {.path = "/dev/class/audio-output", .device_type = DeviceType::Output}, |
| }; |
| |
| // static |
| bool AudioDriverTest::no_input_devices_found_ = false; |
| bool AudioDriverTest::no_output_devices_found_ = false; |
| |
| // static |
| void AudioDriverTest::SetUpTestSuite() { |
| // For verbose logging, set to -media::audio::TRACE or -media::audio::SPEW |
| #ifdef NDEBUG |
| Logging::Init(FX_LOG_WARNING, {"audio_driver_test"}); |
| #else |
| Logging::Init(FX_LOG_INFO, {"audio_driver_test"}); |
| #endif |
| } |
| |
| // static |
| std::atomic_uint32_t AudioDriverTest::unique_transaction_id_ = 0; |
| |
| // static |
| zx_txid_t AudioDriverTest::NextTransactionId() { |
| uint32_t trans_id = ++unique_transaction_id_; |
| if (trans_id == AUDIO_INVALID_TRANSACTION_ID) { |
| trans_id = ++unique_transaction_id_; |
| } |
| return trans_id; |
| } |
| |
| void AudioDriverTest::SetUp() { |
| TestFixture::SetUp(); |
| |
| stream_channel_.reset(); |
| } |
| |
| void AudioDriverTest::TearDown() { |
| ring_buffer_transceiver_.Close(); |
| stream_transceiver_.Close(); |
| |
| watchers_.clear(); |
| |
| TestFixture::TearDown(); |
| } |
| |
| bool AudioDriverTest::WaitForDevice(DeviceType device_type) { |
| if (device_type == DeviceType::Input && AudioDriverTest::no_input_devices_found_) { |
| return false; |
| } |
| if (device_type == DeviceType::Output && AudioDriverTest::no_output_devices_found_) { |
| return false; |
| } |
| |
| device_type_ = device_type; |
| bool enumeration_done = false; |
| |
| // Set up the watchers, etc. If any fail, automatically stop monitoring all device sources. |
| for (const auto& devnode : AUDIO_DEVNODES) { |
| if (device_type != devnode.device_type) { |
| continue; |
| } |
| |
| auto watcher = fsl::DeviceWatcher::CreateWithIdleCallback( |
| devnode.path, |
| [this, device_type](int dir_fd, const std::string& filename) { |
| AUD_VLOG(TRACE) << "'" << filename << "' dir_fd " << dir_fd; |
| this->AddDevice(dir_fd, filename, device_type); |
| }, |
| [&enumeration_done]() { enumeration_done = true; }); |
| |
| if (watcher == nullptr) { |
| EXPECT_FALSE(watcher == nullptr) |
| << "AudioDriverTest failed to create DeviceWatcher for '" << devnode.path << "'."; |
| watchers_.clear(); |
| return false; |
| } |
| watchers_.emplace_back(std::move(watcher)); |
| } |
| // |
| // ... or ... |
| // |
| // Receive a call to AddDeviceByChannel(std::move(stream_channel), name, device_type); |
| // |
| |
| RunLoopUntil([&enumeration_done]() { return enumeration_done; }); |
| |
| // If we timed out waiting for devices, this target may not have any. Don't waste further time. |
| if (!stream_channel_ready_) { |
| if (device_type == DeviceType::Input) { |
| AudioDriverTest::no_input_devices_found_ = true; |
| } else { |
| AudioDriverTest::no_output_devices_found_ = true; |
| } |
| FX_LOGS(WARNING) << "*** No audio " << ((device_type == DeviceType::Input) ? "input" : "output") |
| << " devices detected on this target. ***"; |
| return false; |
| } |
| |
| // ASSERT that we can communicate with the driver at all. |
| EXPECT_TRUE(stream_channel_.is_valid()); |
| EXPECT_TRUE(stream_channel_ready_); |
| |
| zx_status_t status = stream_transceiver_.Init( |
| std::move(stream_channel_), fit::bind_member(this, &AudioDriverTest::OnInboundStreamMessage), |
| ErrorHandler()); |
| EXPECT_EQ(ZX_OK, status); |
| |
| return stream_channel_ready_ && (status == ZX_OK); |
| } |
| |
| void AudioDriverTest::AddDevice(int dir_fd, const std::string& name, DeviceType device_type) { |
| // TODO(mpuryear): on systems with more than one audio device of a given type, test them all. |
| if (stream_channel_ready_) { |
| FX_LOGS(WARNING) << "More than one device detected. For now, we need to ignore it."; |
| return; |
| } |
| |
| // Open the device node. |
| fbl::unique_fd dev_node(openat(dir_fd, name.c_str(), O_RDONLY)); |
| if (!dev_node.is_valid()) { |
| FX_LOGS(ERROR) << "AudioDriverTest failed to open device node at \"" << name << "\". (" |
| << strerror(errno) << " : " << errno << ")"; |
| FAIL(); |
| } |
| |
| // Obtain the FDIO device channel, wrap it in a sync proxy, use that to get the stream channel. |
| zx::channel dev_channel; |
| zx_status_t status = |
| fdio_get_service_handle(dev_node.release(), dev_channel.reset_and_get_address()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to obtain FDIO service channel to audio " |
| << ((device_type == DeviceType::Input) ? "input" : "output"); |
| FAIL(); |
| } |
| |
| // Obtain the stream channel |
| auto dev = |
| fidl::InterfaceHandle<fuchsia::hardware::audio::Device>(std::move(dev_channel)).BindSync(); |
| fidl::InterfaceRequest<fuchsia::hardware::audio::StreamConfig> intf_req = {}; |
| |
| status = dev->GetChannel(&intf_req); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to open channel to audio " |
| << ((device_type == DeviceType::Input) ? "input" : "output"); |
| FAIL(); |
| } |
| stream_channel_ = intf_req.TakeChannel(); |
| |
| AUD_VLOG(TRACE) << "Successfully opened devnode '" << name << "' for audio " |
| << ((device_type == DeviceType::Input) ? "input" : "output"); |
| stream_channel_ready_ = true; |
| } |
| |
| // Stream channel requests |
| // |
| // Request the driver's unique ID. |
| // TODO(mpuryear): ensure that this differs between input and output. |
| void AudioDriverTest::RequestUniqueId() { |
| if (error_occurred_) { |
| return; |
| } |
| |
| MessageTransceiver::Message request_message; |
| auto& request = request_message.ResizeBytesAs<audio_stream_cmd_get_unique_id_req_t>(); |
| unique_id_transaction_id_ = NextTransactionId(); |
| request.hdr.transaction_id = unique_id_transaction_id_; |
| request.hdr.cmd = AUDIO_STREAM_CMD_GET_UNIQUE_ID; |
| |
| EXPECT_EQ(ZX_OK, stream_transceiver_.SendMessage(request_message)); |
| |
| RunLoopUntil([this]() { return received_get_unique_id_; }); |
| } |
| |
| // Request that the driver return its manufacturer string. |
| void AudioDriverTest::RequestManufacturerString() { |
| if (error_occurred_) { |
| return; |
| } |
| |
| MessageTransceiver::Message request_message; |
| auto& request = request_message.ResizeBytesAs<audio_stream_cmd_get_string_req_t>(); |
| manufacturer_string_transaction_id_ = NextTransactionId(); |
| request.hdr.transaction_id = manufacturer_string_transaction_id_; |
| request.hdr.cmd = AUDIO_STREAM_CMD_GET_STRING; |
| |
| request.id = AUDIO_STREAM_STR_ID_MANUFACTURER; |
| |
| EXPECT_EQ(ZX_OK, stream_transceiver_.SendMessage(request_message)); |
| |
| // This command can return an error, so we check for error_occurred_ as well |
| RunLoopUntil([this]() { return received_get_string_manufacturer_ || error_occurred_; }); |
| } |
| |
| // Request that the driver return its product string. |
| void AudioDriverTest::RequestProductString() { |
| if (error_occurred_) { |
| return; |
| } |
| |
| MessageTransceiver::Message request_message; |
| auto& request = request_message.ResizeBytesAs<audio_stream_cmd_get_string_req_t>(); |
| product_string_transaction_id_ = NextTransactionId(); |
| request.hdr.transaction_id = product_string_transaction_id_; |
| request.hdr.cmd = AUDIO_STREAM_CMD_GET_STRING; |
| |
| request.id = AUDIO_STREAM_STR_ID_PRODUCT; |
| |
| EXPECT_EQ(ZX_OK, stream_transceiver_.SendMessage(request_message)); |
| |
| // This command can return an error, so we check for error_occurred_ as well |
| RunLoopUntil([this]() { return received_get_string_product_ || error_occurred_; }); |
| } |
| |
| // Request that the driver return its clock domain. |
| void AudioDriverTest::RequestClockDomain() { |
| if (error_occurred_) { |
| return; |
| } |
| |
| MessageTransceiver::Message request_message; |
| auto& request = request_message.ResizeBytesAs<audio_stream_cmd_get_clock_domain_req_t>(); |
| get_clock_domain_transaction_id_ = NextTransactionId(); |
| request.hdr.transaction_id = get_clock_domain_transaction_id_; |
| request.hdr.cmd = AUDIO_STREAM_CMD_GET_CLOCK_DOMAIN; |
| |
| EXPECT_EQ(ZX_OK, stream_transceiver_.SendMessage(request_message)); |
| |
| RunLoopUntil([this]() { return received_get_clock_domain_; }); |
| } |
| |
| // Request that the driver return its gain capabilities and current state. |
| void AudioDriverTest::RequestGain() { |
| if (error_occurred_) { |
| return; |
| } |
| |
| MessageTransceiver::Message request_message; |
| auto& request = request_message.ResizeBytesAs<audio_stream_cmd_get_gain_req_t>(); |
| get_gain_transaction_id_ = NextTransactionId(); |
| request.hdr.transaction_id = get_gain_transaction_id_; |
| request.hdr.cmd = AUDIO_STREAM_CMD_GET_GAIN; |
| |
| EXPECT_EQ(ZX_OK, stream_transceiver_.SendMessage(request_message)); |
| |
| RunLoopUntil([this]() { return received_get_gain_; }); |
| } |
| |
| // Determine an appropriate gain state to request, then call other method to request to the |
| // driver. This method assumes that the driver has already successfully responded to a GetGain |
| // request. |
| void AudioDriverTest::RequestSetGain() { |
| if (error_occurred_) { |
| return; |
| } |
| |
| ASSERT_TRUE(received_get_gain_); |
| |
| if (max_gain_ == min_gain_) { |
| FX_LOGS(WARNING) << "*** Audio " << ((device_type_ == DeviceType::Input) ? "input" : "output") |
| << " has fixed gain (" << cur_gain_ << " dB). Skipping SetGain test. ***"; |
| return; |
| } |
| |
| set_gain_ = min_gain_; |
| if (cur_gain_ == min_gain_) { |
| set_gain_ += gain_step_; |
| } |
| |
| audio_set_gain_flags_t flags = AUDIO_SGF_GAIN_VALID; |
| if (can_mute_) { |
| flags |= AUDIO_SGF_MUTE_VALID; |
| if (!cur_mute_) { |
| flags |= AUDIO_SGF_MUTE; |
| } |
| } |
| if (can_agc_) { |
| flags |= AUDIO_SGF_AGC_VALID; |
| if (!cur_agc_) { |
| flags |= AUDIO_SGF_AGC; |
| } |
| } |
| RequestSetGain(flags, set_gain_); |
| } |
| |
| // Request that the driver set its gain state to the specified gain_db and flags. |
| // This method assumes that the driver has already successfully responded to a GetGain request. |
| void AudioDriverTest::RequestSetGain(audio_set_gain_flags_t flags, float gain_db) { |
| if (error_occurred_) { |
| return; |
| } |
| |
| ASSERT_TRUE(received_get_gain_); |
| |
| MessageTransceiver::Message request_message; |
| auto& request = request_message.ResizeBytesAs<audio_stream_cmd_set_gain_req_t>(); |
| set_gain_transaction_id_ = NextTransactionId(); |
| request.hdr.transaction_id = set_gain_transaction_id_; |
| request.hdr.cmd = AUDIO_STREAM_CMD_SET_GAIN; |
| |
| request.flags = flags; |
| request.gain = gain_db; |
| set_mute_ = (flags & AUDIO_SGF_MUTE_VALID) ? (flags & AUDIO_SGF_MUTE) : cur_mute_; |
| set_agc_ = (flags & AUDIO_SGF_AGC_VALID) ? (flags & AUDIO_SGF_AGC) : cur_agc_; |
| set_gain_ = (flags & AUDIO_SGF_GAIN_VALID) ? gain_db : cur_gain_; |
| |
| EXPECT_EQ(ZX_OK, stream_transceiver_.SendMessage(request_message)); |
| |
| // This command can return an error, so we check for error_occurred_ as well |
| RunLoopUntil([this]() { return received_set_gain_ || error_occurred_; }); |
| } |
| |
| // Request that the driver return the format ranges that it supports. |
| void AudioDriverTest::RequestFormats() { |
| if (error_occurred_) { |
| return; |
| } |
| |
| MessageTransceiver::Message request_message; |
| auto& request = request_message.ResizeBytesAs<audio_stream_cmd_get_formats_req_t>(); |
| get_formats_transaction_id_ = NextTransactionId(); |
| request.hdr.transaction_id = get_formats_transaction_id_; |
| request.hdr.cmd = AUDIO_STREAM_CMD_GET_FORMATS; |
| |
| EXPECT_EQ(ZX_OK, stream_transceiver_.SendMessage(request_message)); |
| |
| RunLoopUntil([this]() { return received_get_formats_; }); |
| } |
| |
| // For the channelization and sample_format that we've set, determine the size of each frame. |
| // This method assumes that the driver has already successfully responded to a SetFormat request. |
| void AudioDriverTest::CalculateFrameSize() { |
| if (error_occurred_ || !received_set_format_) { |
| return; |
| } |
| |
| switch (sample_format_) { |
| case AUDIO_SAMPLE_FORMAT_8BIT: |
| frame_size_ = num_channels_; |
| break; |
| |
| case AUDIO_SAMPLE_FORMAT_16BIT: |
| frame_size_ = num_channels_ * sizeof(int16_t); |
| break; |
| |
| case AUDIO_SAMPLE_FORMAT_20BIT_PACKED: |
| frame_size_ = (num_channels_ * 5 + 3) / 4; |
| break; |
| |
| case AUDIO_SAMPLE_FORMAT_24BIT_PACKED: |
| frame_size_ = num_channels_ * 3; |
| break; |
| |
| case AUDIO_SAMPLE_FORMAT_20BIT_IN32: |
| case AUDIO_SAMPLE_FORMAT_24BIT_IN32: |
| case AUDIO_SAMPLE_FORMAT_32BIT: |
| frame_size_ = num_channels_ * sizeof(int32_t); |
| break; |
| |
| case AUDIO_SAMPLE_FORMAT_32BIT_FLOAT: |
| frame_size_ = num_channels_ * sizeof(float); |
| break; |
| |
| case AUDIO_SAMPLE_FORMAT_BITSTREAM: |
| default: |
| ASSERT_TRUE(false) << "Unknown sample_format_ " << std::hex << sample_format_; |
| frame_size_ = 0; |
| break; |
| } |
| } |
| |
| void AudioDriverTest::SelectFirstFormat() { |
| if (received_get_formats_) { |
| // strip off the UNSIGNED and INVERT_ENDIAN bits... |
| auto first_range = format_ranges_.front(); |
| auto first_format = first_range.sample_formats & ~AUDIO_SAMPLE_FORMAT_FLAG_MASK; |
| ASSERT_NE(first_format, 0u); |
| |
| // just keep the lowest sample format bit. |
| audio_sample_format_t bit = 1; |
| while ((first_format & bit) == 0) { |
| bit <<= 1; |
| } |
| first_format &= bit; |
| |
| frame_rate_ = first_range.min_frames_per_second; |
| sample_format_ = first_format; |
| num_channels_ = first_range.min_channels; |
| } |
| } |
| |
| void AudioDriverTest::SelectLastFormat() { |
| if (received_get_formats_) { |
| // strip off the UNSIGNED and INVERT_ENDIAN bits... |
| auto last_range = format_ranges_.back(); |
| auto last_format = last_range.sample_formats & ~AUDIO_SAMPLE_FORMAT_FLAG_MASK; |
| ASSERT_NE(last_format, 0u); |
| |
| // and just keep the highest remaining sample format bit. |
| while (last_format & (last_format - 1)) { |
| last_format &= (last_format - 1); |
| } |
| |
| frame_rate_ = last_range.max_frames_per_second; |
| sample_format_ = last_format; |
| num_channels_ = last_range.max_channels; |
| } |
| } |
| |
| // Request that driver set format to the lowest rate/channelization of the first range reported. |
| // This method assumes that the driver has already successfully responded to a GetFormats request. |
| void AudioDriverTest::RequestSetFormatMin() { |
| if (error_occurred_) { |
| return; |
| } |
| |
| ASSERT_TRUE(received_get_formats_); |
| ASSERT_GT(format_ranges_.size(), 0u); |
| |
| SelectFirstFormat(); |
| |
| MessageTransceiver::Message request_message; |
| auto& request = request_message.ResizeBytesAs<audio_stream_cmd_set_format_req_t>(); |
| set_format_transaction_id_ = NextTransactionId(); |
| request.hdr.transaction_id = set_format_transaction_id_; |
| request.hdr.cmd = AUDIO_STREAM_CMD_SET_FORMAT; |
| |
| request.frames_per_second = frame_rate_; |
| request.sample_format = sample_format_; |
| request.channels = num_channels_; |
| |
| EXPECT_EQ(ZX_OK, stream_transceiver_.SendMessage(request_message)); |
| |
| // This command can return an error, so we check for error_occurred_ as well |
| RunLoopUntil( |
| [this]() { return (received_set_format_ && ring_buffer_channel_ready_) || error_occurred_; }); |
| CalculateFrameSize(); |
| } |
| |
| // Request that driver set format to the highest rate/channelization of the final range reported. |
| // This method assumes that the driver has already successfully responded to a GetFormats request. |
| void AudioDriverTest::RequestSetFormatMax() { |
| if (error_occurred_) { |
| return; |
| } |
| |
| ASSERT_TRUE(received_get_formats_); |
| ASSERT_GT(format_ranges_.size(), 0u); |
| |
| SelectLastFormat(); |
| |
| MessageTransceiver::Message request_message; |
| auto& request = request_message.ResizeBytesAs<audio_stream_cmd_set_format_req_t>(); |
| set_format_transaction_id_ = NextTransactionId(); |
| request.hdr.transaction_id = set_format_transaction_id_; |
| request.hdr.cmd = AUDIO_STREAM_CMD_SET_FORMAT; |
| |
| request.frames_per_second = frame_rate_; |
| request.sample_format = sample_format_; |
| request.channels = num_channels_; |
| |
| EXPECT_EQ(ZX_OK, stream_transceiver_.SendMessage(request_message)); |
| |
| // This command can return an error, so we check for error_occurred_ as well |
| RunLoopUntil( |
| [this]() { return (received_set_format_ && ring_buffer_channel_ready_) || error_occurred_; }); |
| CalculateFrameSize(); |
| } |
| |
| // Request that driver retrieve the current plug detection state and capabilities. |
| void AudioDriverTest::RequestPlugDetect() { |
| if (error_occurred_) { |
| return; |
| } |
| |
| MessageTransceiver::Message request_message; |
| auto& request = request_message.ResizeBytesAs<audio_stream_cmd_plug_detect_req_t>(); |
| plug_detect_transaction_id_ = NextTransactionId(); |
| request.hdr.transaction_id = plug_detect_transaction_id_; |
| request.hdr.cmd = AUDIO_STREAM_CMD_PLUG_DETECT; |
| |
| request.flags = AUDIO_PDF_ENABLE_NOTIFICATIONS; |
| should_plug_notify_ = true; |
| |
| EXPECT_EQ(ZX_OK, stream_transceiver_.SendMessage(request_message)); |
| |
| RunLoopUntil([this]() { return received_plug_detect_; }); |
| } |
| |
| // Ring-buffer channel requests |
| // |
| // Request that the driver return the FIFO depth (in bytes), at the currently set format. |
| // This method relies on the ring buffer channel, received with response to a successful |
| // SetFormat. |
| void AudioDriverTest::RequestFifoDepth() { |
| if (error_occurred_) { |
| return; |
| } |
| |
| ASSERT_TRUE(ring_buffer_channel_ready_); |
| |
| MessageTransceiver::Message request_message; |
| auto& request = request_message.ResizeBytesAs<audio_rb_cmd_get_fifo_depth_req_t>(); |
| get_fifo_depth_transaction_id_ = NextTransactionId(); |
| request.hdr.transaction_id = get_fifo_depth_transaction_id_; |
| request.hdr.cmd = AUDIO_RB_CMD_GET_FIFO_DEPTH; |
| |
| EXPECT_EQ(ZX_OK, ring_buffer_transceiver_.SendMessage(request_message)); |
| |
| // This command can return an error, so we check for error_occurred_ as well |
| RunLoopUntil([this]() { return received_get_fifo_depth_ || error_occurred_; }); |
| } |
| |
| // Request that the driver return a VMO handle for the ring buffer, at the currently set format. |
| // This method relies on the ring buffer channel, received with response to a successful |
| // SetFormat. |
| void AudioDriverTest::RequestBuffer(uint32_t min_ring_buffer_frames, |
| uint32_t notifications_per_ring) { |
| if (error_occurred_) { |
| return; |
| } |
| |
| ASSERT_TRUE(ring_buffer_channel_ready_); |
| |
| MessageTransceiver::Message request_message; |
| auto& request = request_message.ResizeBytesAs<audio_rb_cmd_get_buffer_req_t>(); |
| get_buffer_transaction_id_ = NextTransactionId(); |
| request.hdr.transaction_id = get_buffer_transaction_id_; |
| request.hdr.cmd = AUDIO_RB_CMD_GET_BUFFER; |
| |
| request.min_ring_buffer_frames = min_ring_buffer_frames; |
| min_ring_buffer_frames_ = min_ring_buffer_frames; |
| |
| request.notifications_per_ring = notifications_per_ring; |
| notifications_per_ring_ = notifications_per_ring; |
| |
| EXPECT_EQ(ZX_OK, ring_buffer_transceiver_.SendMessage(request_message)); |
| |
| // This command can return an error, so we check for error_occurred_ as well |
| RunLoopUntil([this]() { return received_get_buffer_ || error_occurred_; }); |
| } |
| |
| // Request that the driver start the ring buffer engine, responding with the start_time. |
| // This method assumes that the ring buffer VMO was received in a successful GetBuffer response. |
| void AudioDriverTest::RequestStart() { |
| if (error_occurred_) { |
| return; |
| } |
| |
| ASSERT_TRUE(ring_buffer_ready_); |
| |
| MessageTransceiver::Message request_message; |
| auto& request = request_message.ResizeBytesAs<audio_rb_cmd_start_req_t>(); |
| start_transaction_id_ = NextTransactionId(); |
| request.hdr.transaction_id = start_transaction_id_; |
| request.hdr.cmd = AUDIO_RB_CMD_START; |
| |
| auto send_time = zx::clock::get_monotonic().get(); |
| EXPECT_EQ(ZX_OK, ring_buffer_transceiver_.SendMessage(request_message)); |
| |
| // This command can return an error, so we check for error_occurred_ as well |
| RunLoopUntil([this]() { return received_start_ || error_occurred_; }); |
| |
| EXPECT_GT(start_time_, send_time); |
| // TODO(mpuryear): validate start_time is not too far in the future (it includes FIFO delay). |
| } |
| |
| // Request that the driver stop the ring buffer engine, including quieting position notifications. |
| // This method assumes that the ring buffer engine has previously been successfully started. |
| void AudioDriverTest::RequestStop() { |
| if (error_occurred_) { |
| return; |
| } |
| |
| ASSERT_TRUE(received_start_); |
| |
| MessageTransceiver::Message request_message; |
| auto& request = request_message.ResizeBytesAs<audio_rb_cmd_stop_req_t>(); |
| stop_transaction_id_ = NextTransactionId(); |
| request.hdr.transaction_id = stop_transaction_id_; |
| request.hdr.cmd = AUDIO_RB_CMD_STOP; |
| |
| EXPECT_EQ(ZX_OK, ring_buffer_transceiver_.SendMessage(request_message)); |
| |
| // This command can return an error, so we check for error_occurred_ as well |
| RunLoopUntil([this]() { return received_stop_ || error_occurred_; }); |
| } |
| |
| // Handle an incoming stream channel message (generally a response from a previous request) |
| void AudioDriverTest::OnInboundStreamMessage(MessageTransceiver::Message message) { |
| auto& header = message.BytesAs<audio_cmd_hdr_t>(); |
| switch (header.cmd) { |
| case AUDIO_STREAM_CMD_GET_UNIQUE_ID: |
| HandleGetUniqueIdResponse(message.BytesAs<audio_stream_cmd_get_unique_id_resp_t>()); |
| break; |
| |
| case AUDIO_STREAM_CMD_GET_STRING: |
| HandleGetStringResponse(message.BytesAs<audio_stream_cmd_get_string_resp_t>()); |
| break; |
| |
| case AUDIO_STREAM_CMD_GET_CLOCK_DOMAIN: |
| HandleGetClockDomainResponse(message.BytesAs<audio_stream_cmd_get_clock_domain_resp_t>()); |
| break; |
| |
| case AUDIO_STREAM_CMD_GET_GAIN: |
| HandleGetGainResponse(message.BytesAs<audio_stream_cmd_get_gain_resp_t>()); |
| break; |
| |
| case AUDIO_STREAM_CMD_SET_GAIN: |
| HandleSetGainResponse(message.BytesAs<audio_stream_cmd_set_gain_resp_t>()); |
| break; |
| |
| case AUDIO_STREAM_CMD_GET_FORMATS: |
| HandleGetFormatsResponse(message.BytesAs<audio_stream_cmd_get_formats_resp_t>()); |
| break; |
| |
| case AUDIO_STREAM_CMD_SET_FORMAT: |
| HandleSetFormatResponse(message.BytesAs<audio_stream_cmd_set_format_resp_t>()); |
| // On success, a channel used to control the audio buffer will be returned. |
| ExtractRingBufferChannel(message); |
| break; |
| |
| case AUDIO_STREAM_CMD_PLUG_DETECT: |
| HandlePlugDetectResponse(message.BytesAs<audio_stream_cmd_plug_detect_resp_t>()); |
| break; |
| |
| case AUDIO_STREAM_PLUG_DETECT_NOTIFY: |
| HandlePlugDetectNotify(message.BytesAs<audio_stream_cmd_plug_detect_resp_t>()); |
| break; |
| |
| default: |
| EXPECT_TRUE(false) << "Unrecognized header.cmd value " << header.cmd; |
| break; |
| } |
| } |
| |
| // Validate just the command portion of the response header. |
| bool AudioDriverTest::ValidateResponseCommand(audio_cmd_hdr header, audio_cmd_t expected_command) { |
| EXPECT_EQ(header.cmd, expected_command) << "Unexpected command!"; |
| |
| return (expected_command == header.cmd); |
| } |
| |
| // Validate just the transaction ID portion of the response header. |
| void AudioDriverTest::ValidateResponseTransaction(audio_cmd_hdr header, |
| zx_txid_t expected_transaction_id) { |
| EXPECT_EQ(header.transaction_id, expected_transaction_id) << "Unexpected transaction ID!"; |
| } |
| |
| // Validate the entire response header. |
| bool AudioDriverTest::ValidateResponseHeader(audio_cmd_hdr header, |
| zx_txid_t expected_transaction_id, |
| audio_cmd_t expected_command) { |
| ValidateResponseTransaction(header, expected_transaction_id); |
| return ValidateResponseCommand(header, expected_command); |
| } |
| |
| // Handle a get_unique_id response on the stream channel. |
| void AudioDriverTest::HandleGetUniqueIdResponse( |
| const audio_stream_cmd_get_unique_id_resp_t& response) { |
| if (!ValidateResponseHeader(response.hdr, unique_id_transaction_id_, |
| AUDIO_STREAM_CMD_GET_UNIQUE_ID)) { |
| return; |
| } |
| |
| EXPECT_EQ(sizeof(response.unique_id.data), sizeof(audio_stream_unique_id_t)); |
| memcpy(unique_id_.data(), response.unique_id.data, kUniqueIdLength); |
| |
| char id_buf[2 * kUniqueIdLength + 1]; |
| std::snprintf(id_buf, sizeof(id_buf), |
| "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", unique_id_[0], |
| unique_id_[1], unique_id_[2], unique_id_[3], unique_id_[4], unique_id_[5], |
| unique_id_[6], unique_id_[7], unique_id_[8], unique_id_[9], unique_id_[10], |
| unique_id_[11], unique_id_[12], unique_id_[13], unique_id_[14], unique_id_[15]); |
| AUD_VLOG(TRACE) << "Received unique_id " << id_buf; |
| |
| received_get_unique_id_ = true; |
| } |
| |
| // Handle a get_string response on the stream channel (either mfr or prod). |
| void AudioDriverTest::HandleGetStringResponse(const audio_stream_cmd_get_string_resp_t& response) { |
| if (!ValidateResponseCommand(response.hdr, AUDIO_STREAM_CMD_GET_STRING)) { |
| return; |
| } |
| |
| constexpr auto kMaxStringLength = |
| sizeof(audio_stream_cmd_get_string_resp_t) - sizeof(audio_cmd_hdr_t) - (3 * sizeof(uint32_t)); |
| EXPECT_LE(response.strlen, kMaxStringLength); |
| if (response.result != ZX_OK) { |
| error_occurred_ = true; |
| FAIL(); |
| } |
| |
| auto response_str = reinterpret_cast<const char*>(response.str); |
| if (response.id == AUDIO_STREAM_STR_ID_MANUFACTURER) { |
| ValidateResponseTransaction(response.hdr, manufacturer_string_transaction_id_); |
| |
| manufacturer_ = std::string(response_str, response.strlen); |
| received_get_string_manufacturer_ = true; |
| } else if (response.id == AUDIO_STREAM_STR_ID_PRODUCT) { |
| ValidateResponseTransaction(response.hdr, product_string_transaction_id_); |
| |
| product_ = std::string(response_str, response.strlen); |
| received_get_string_product_ = true; |
| } else { |
| ASSERT_TRUE(false) << "Unrecognized string ID received: " << response.id; |
| } |
| } |
| |
| // Handle a get_clock_domain response on the stream channel. |
| void AudioDriverTest::HandleGetClockDomainResponse( |
| const audio_stream_cmd_get_clock_domain_resp_t& response) { |
| if (!ValidateResponseHeader(response.hdr, get_clock_domain_transaction_id_, |
| AUDIO_STREAM_CMD_GET_CLOCK_DOMAIN)) { |
| return; |
| } |
| |
| EXPECT_NE(response.clock_domain, kInvalidClockDomain); |
| clock_domain_ = response.clock_domain; |
| |
| received_get_clock_domain_ = true; |
| } |
| |
| // Handle a get_gain response on the stream channel. |
| void AudioDriverTest::HandleGetGainResponse(const audio_stream_cmd_get_gain_resp_t& response) { |
| if (!ValidateResponseHeader(response.hdr, get_gain_transaction_id_, AUDIO_STREAM_CMD_GET_GAIN)) { |
| return; |
| } |
| |
| cur_mute_ = response.cur_mute; |
| can_mute_ = response.can_mute; |
| cur_agc_ = response.cur_agc; |
| can_agc_ = response.can_agc; |
| cur_gain_ = response.cur_gain; |
| min_gain_ = response.min_gain; |
| max_gain_ = response.max_gain; |
| gain_step_ = response.gain_step; |
| |
| if (cur_mute_) { |
| EXPECT_TRUE(can_mute_); |
| } |
| if (cur_agc_) { |
| EXPECT_TRUE(can_agc_); |
| } |
| EXPECT_GE(cur_gain_, min_gain_); |
| EXPECT_LE(cur_gain_, max_gain_); |
| if (max_gain_ > min_gain_) { |
| EXPECT_GT(gain_step_, 0.0f); |
| } else { |
| EXPECT_EQ(gain_step_, 0.0f); |
| } |
| |
| received_get_gain_ = true; |
| } |
| |
| // Handle a set_gain response on the stream channel. |
| void AudioDriverTest::HandleSetGainResponse(const audio_stream_cmd_set_gain_resp_t& response) { |
| if (!ValidateResponseHeader(response.hdr, set_gain_transaction_id_, AUDIO_STREAM_CMD_SET_GAIN)) { |
| return; |
| } |
| if (response.result != ZX_OK) { |
| error_occurred_ = true; |
| FAIL(); |
| } |
| |
| cur_mute_ = response.cur_mute; |
| EXPECT_EQ(cur_mute_, set_mute_); |
| if (cur_mute_) { |
| EXPECT_TRUE(can_mute_); |
| } |
| |
| cur_agc_ = response.cur_agc; |
| EXPECT_EQ(cur_agc_, set_agc_); |
| if (cur_agc_) { |
| EXPECT_TRUE(can_agc_); |
| } |
| |
| cur_gain_ = response.cur_gain; |
| EXPECT_EQ(cur_gain_, set_gain_); |
| EXPECT_GE(cur_gain_, min_gain_); |
| EXPECT_LE(cur_gain_, max_gain_); |
| |
| received_set_gain_ = true; |
| } |
| |
| // Handle a get_formats response on the stream channel. This response may be a multi-part. |
| void AudioDriverTest::HandleGetFormatsResponse( |
| const audio_stream_cmd_get_formats_resp_t& response) { |
| if (!ValidateResponseHeader(response.hdr, get_formats_transaction_id_, |
| AUDIO_STREAM_CMD_GET_FORMATS)) { |
| return; |
| } |
| |
| EXPECT_GT(response.format_range_count, 0u); |
| EXPECT_LT(response.first_format_range_ndx, response.format_range_count); |
| EXPECT_EQ(response.first_format_range_ndx, next_format_range_ndx_); |
| |
| if (response.first_format_range_ndx == 0) { |
| get_formats_range_count_ = response.format_range_count; |
| format_ranges_.clear(); |
| } else { |
| EXPECT_EQ(response.format_range_count, get_formats_range_count_) |
| << "Format range count cannot change over multiple get_formats responses"; |
| } |
| auto num_ranges = |
| std::min<uint16_t>(response.format_range_count - response.first_format_range_ndx, |
| AUDIO_STREAM_CMD_GET_FORMATS_MAX_RANGES_PER_RESPONSE); |
| |
| for (auto i = 0; i < num_ranges; ++i) { |
| EXPECT_NE(response.format_ranges[i].sample_formats & ~AUDIO_SAMPLE_FORMAT_FLAG_MASK, 0u); |
| |
| EXPECT_GE(response.format_ranges[i].min_frames_per_second, |
| fuchsia::media::MIN_PCM_FRAMES_PER_SECOND); |
| EXPECT_LE(response.format_ranges[i].max_frames_per_second, |
| fuchsia::media::MAX_PCM_FRAMES_PER_SECOND); |
| EXPECT_LE(response.format_ranges[i].min_frames_per_second, |
| response.format_ranges[i].max_frames_per_second); |
| |
| EXPECT_GE(response.format_ranges[i].min_channels, fuchsia::media::MIN_PCM_CHANNEL_COUNT); |
| EXPECT_LE(response.format_ranges[i].max_channels, fuchsia::media::MAX_PCM_CHANNEL_COUNT); |
| EXPECT_LE(response.format_ranges[i].min_channels, response.format_ranges[i].max_channels); |
| |
| EXPECT_NE(response.format_ranges[i].flags, 0u); |
| |
| format_ranges_.push_back(response.format_ranges[i]); |
| } |
| |
| next_format_range_ndx_ += num_ranges; |
| if (next_format_range_ndx_ == response.format_range_count) { |
| EXPECT_EQ(response.format_range_count, format_ranges_.size()); |
| received_get_formats_ = true; |
| } |
| } |
| |
| // Handle a set_format response on the stream channel. After, we will extract a ring buffer |
| // channel. |
| void AudioDriverTest::HandleSetFormatResponse(const audio_stream_cmd_set_format_resp_t& response) { |
| if (!ValidateResponseHeader(response.hdr, set_format_transaction_id_, |
| AUDIO_STREAM_CMD_SET_FORMAT)) { |
| return; |
| } |
| |
| if (response.result != ZX_OK) { |
| if (response.result == ZX_ERR_ACCESS_DENIED) { |
| AUD_LOG(WARNING) |
| << "ZX_ERR_ACCESS_DENIED: audio_core may already be connected to this device"; |
| } |
| error_occurred_ = true; |
| FAIL(); |
| } |
| |
| external_delay_nsec_ = response.external_delay_nsec; |
| |
| received_set_format_ = true; |
| } |
| |
| // In concert with incoming SetFormat response on stream channel, extract the ring-buffer channel. |
| // With it, initialize the message transceiver that will handle messages to/from this channel. |
| void AudioDriverTest::ExtractRingBufferChannel(MessageTransceiver::Message message) { |
| if (!received_set_format_) { |
| return; |
| } |
| |
| ASSERT_EQ(message.handles_.size(), 1u); |
| |
| EXPECT_EQ( |
| ring_buffer_transceiver_.Init( |
| zx::channel(message.handles_[0]), |
| fit::bind_member(this, &AudioDriverTest::OnInboundRingBufferMessage), ErrorHandler()), |
| ZX_OK); |
| message.handles_.clear(); |
| |
| ring_buffer_channel_ready_ = true; |
| } |
| |
| // Handle plug_detection on the stream channel (shared across response and notification). |
| void AudioDriverTest::HandlePlugDetect(audio_pd_notify_flags_t flags, zx_time_t plug_state_time) { |
| if (received_plug_detect_) { |
| EXPECT_EQ(hardwired_, (flags & AUDIO_PDNF_HARDWIRED)); |
| } |
| hardwired_ = flags & AUDIO_PDNF_HARDWIRED; |
| |
| if (received_plug_detect_) { |
| EXPECT_EQ(can_plug_notify_, (flags & AUDIO_PDNF_CAN_NOTIFY)); |
| } |
| can_plug_notify_ = flags & AUDIO_PDNF_CAN_NOTIFY; |
| plugged_ = flags & AUDIO_PDNF_PLUGGED; |
| |
| plug_state_time_ = plug_state_time; |
| EXPECT_LT(plug_state_time_, zx::clock::get_monotonic().get()); |
| |
| AUD_VLOG(TRACE) << "Plug_state_time: " << plug_state_time; |
| } |
| |
| // Handle a plug_detect response on the stream channel (response solicited by client). |
| void AudioDriverTest::HandlePlugDetectResponse( |
| const audio_stream_cmd_plug_detect_resp_t& response) { |
| if (!ValidateResponseHeader(response.hdr, plug_detect_transaction_id_, |
| AUDIO_STREAM_CMD_PLUG_DETECT)) { |
| return; |
| } |
| |
| HandlePlugDetect(response.flags, response.plug_state_time); |
| received_plug_detect_ = true; |
| } |
| |
| // Handle a plug_detect notification on the stream channel (async message not solicited by |
| // client). |
| void AudioDriverTest::HandlePlugDetectNotify(const audio_stream_cmd_plug_detect_resp_t& notify) { |
| if (!ValidateResponseHeader(notify.hdr, AUDIO_INVALID_TRANSACTION_ID, |
| AUDIO_STREAM_PLUG_DETECT_NOTIFY)) { |
| return; |
| } |
| |
| EXPECT_FALSE(hardwired_); |
| EXPECT_TRUE(can_plug_notify_); |
| EXPECT_TRUE(should_plug_notify_); |
| |
| HandlePlugDetect(notify.flags, notify.plug_state_time); |
| received_plug_detect_notify_ = true; |
| |
| AUD_LOG(ERROR) << "Driver autonomously generated an asynchronous plug detect notification"; |
| } |
| |
| // Handle all incoming response message types, on the ring buffer channel. |
| void AudioDriverTest::OnInboundRingBufferMessage(MessageTransceiver::Message message) { |
| auto& header = message.BytesAs<audio_cmd_hdr_t>(); |
| switch (header.cmd) { |
| case AUDIO_RB_CMD_GET_FIFO_DEPTH: |
| HandleGetFifoDepthResponse(message.BytesAs<audio_rb_cmd_get_fifo_depth_resp_t>()); |
| break; |
| |
| case AUDIO_RB_CMD_GET_BUFFER: |
| HandleGetBufferResponse(message.BytesAs<audio_rb_cmd_get_buffer_resp_t>()); |
| |
| // On success, a VMO for the ring buffer will be returned. |
| ExtractRingBuffer(message); |
| break; |
| |
| case AUDIO_RB_CMD_START: |
| HandleStartResponse(message.BytesAs<audio_rb_cmd_start_resp_t>()); |
| break; |
| |
| case AUDIO_RB_CMD_STOP: |
| HandleStopResponse(message.BytesAs<audio_rb_cmd_stop_resp_t>()); |
| break; |
| |
| case AUDIO_RB_POSITION_NOTIFY: |
| HandlePositionNotify(message.BytesAs<audio_rb_position_notify_t>()); |
| break; |
| |
| default: |
| EXPECT_TRUE(false) << "Unrecognized header.cmd value " << header.cmd; |
| break; |
| } |
| } |
| |
| // Handle a get_fifo_depth response on the ring buffer channel. |
| void AudioDriverTest::HandleGetFifoDepthResponse( |
| const audio_rb_cmd_get_fifo_depth_resp_t& response) { |
| if (!ValidateResponseHeader(response.hdr, get_fifo_depth_transaction_id_, |
| AUDIO_RB_CMD_GET_FIFO_DEPTH)) { |
| return; |
| } |
| |
| if (response.result != ZX_OK) { |
| error_occurred_ = true; |
| FAIL(); |
| } |
| |
| fifo_depth_ = response.fifo_depth; |
| |
| received_get_fifo_depth_ = true; |
| } |
| |
| // Handle a get_buffer response on the ring buffer channel. |
| void AudioDriverTest::HandleGetBufferResponse(const audio_rb_cmd_get_buffer_resp_t& response) { |
| if (!ValidateResponseHeader(response.hdr, get_buffer_transaction_id_, AUDIO_RB_CMD_GET_BUFFER)) { |
| return; |
| } |
| |
| if (response.result != ZX_OK) { |
| error_occurred_ = true; |
| FAIL(); |
| } |
| |
| EXPECT_GE(response.num_ring_buffer_frames, min_ring_buffer_frames_); |
| ring_buffer_frames_ = response.num_ring_buffer_frames; |
| |
| received_get_buffer_ = true; |
| } |
| |
| // Given the GET_BUFFER response message, retrieve the ring buffer VMO handle and save it. |
| void AudioDriverTest::ExtractRingBuffer(MessageTransceiver::Message get_buffer_response) { |
| ASSERT_TRUE(received_get_buffer_); |
| |
| EXPECT_EQ(get_buffer_response.handles_.size(), 1u); |
| zx::vmo ring_buffer_vmo = zx::vmo(get_buffer_response.handles_[0]); |
| get_buffer_response.handles_.clear(); |
| EXPECT_TRUE(ring_buffer_vmo.is_valid()); |
| |
| const zx_vm_option_t option_flags = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE; |
| EXPECT_EQ( |
| ring_buffer_.CreateAndMap(ring_buffer_frames_ * frame_size_, option_flags, nullptr, |
| &ring_buffer_vmo, ZX_RIGHT_READ | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER), |
| ZX_OK); |
| |
| AUD_VLOG(TRACE) << "Mapping size: " << ring_buffer_frames_ * frame_size_; |
| |
| ring_buffer_ready_ = true; |
| } |
| |
| // Handle a start response on the ring buffer channel. |
| void AudioDriverTest::HandleStartResponse(const audio_rb_cmd_start_resp_t& response) { |
| if (!ValidateResponseHeader(response.hdr, start_transaction_id_, AUDIO_RB_CMD_START)) { |
| return; |
| } |
| |
| if (response.result != ZX_OK) { |
| error_occurred_ = true; |
| FAIL(); |
| } |
| |
| ASSERT_GT(response.start_time, 0u); |
| start_time_ = response.start_time; |
| |
| received_start_ = true; |
| } |
| |
| // Handle a stop response on the ring buffer channel. Clear out any previous position notification |
| // count, to enable us to detect whether any were received after the STOP command was processed. |
| void AudioDriverTest::HandleStopResponse(const audio_rb_cmd_stop_resp_t& response) { |
| if (!ValidateResponseHeader(response.hdr, stop_transaction_id_, AUDIO_RB_CMD_STOP)) { |
| return; |
| } |
| |
| if (response.result != ZX_OK) { |
| error_occurred_ = true; |
| FAIL(); |
| } |
| |
| position_notification_count_ = 0; |
| received_stop_ = true; |
| } |
| |
| // Handle a position notification on the ring buffer channel. |
| void AudioDriverTest::HandlePositionNotify(const audio_rb_position_notify_t& notify) { |
| if (!ValidateResponseHeader(notify.hdr, get_position_transaction_id_, AUDIO_RB_POSITION_NOTIFY)) { |
| return; |
| } |
| |
| EXPECT_GT(notifications_per_ring_, 0u); |
| |
| auto now = zx::clock::get_monotonic().get(); |
| EXPECT_LT(start_time_, now); |
| EXPECT_LT(notify.monotonic_time, now); |
| |
| if (position_notification_count_) { |
| EXPECT_GT(notify.monotonic_time, start_time_); |
| EXPECT_GT(notify.monotonic_time, last_monotonic_time_); |
| } else { |
| EXPECT_GE(notify.monotonic_time, start_time_); |
| } |
| |
| last_monotonic_time_ = notify.monotonic_time; |
| ring_buffer_position_ = notify.ring_buffer_pos; |
| EXPECT_LT(ring_buffer_position_, ring_buffer_frames_ * frame_size_); |
| |
| ++position_notification_count_; |
| |
| AUD_VLOG(TRACE) << "Position: " << ring_buffer_position_ |
| << ", notification_count: " << position_notification_count_; |
| } |
| |
| // Wait for the specified number of position notifications, or timeout at 60 seconds. |
| void AudioDriverTest::ExpectPositionNotifyCount(uint32_t count) { |
| if (error_occurred_) { |
| return; |
| } |
| |
| RunLoopUntil([this, count]() { return position_notification_count_ >= count; }); |
| |
| auto timestamp_duration = last_monotonic_time_ - start_time_; |
| auto observed_duration = zx::clock::get_monotonic().get() - start_time_; |
| ASSERT_GE(position_notification_count_, count) << "No position notifications received"; |
| |
| ASSERT_NE(frame_rate_ * notifications_per_ring_, 0u); |
| auto ns_per_notification = |
| (zx::sec(1) * ring_buffer_frames_) / (frame_rate_ * notifications_per_ring_); |
| auto expected_min_time = ns_per_notification.get() * (count - 1); |
| auto expected_time = ns_per_notification.get() * count; |
| auto expected_max_time = ns_per_notification.get() * (count + 2); |
| |
| AUD_VLOG(TRACE) << "Timestamp delta from min/ideal/max: " << std::setw(10) |
| << (expected_min_time - timestamp_duration) << " : " << std::setw(10) |
| << (expected_time - timestamp_duration) << " : " << std::setw(10) |
| << (expected_max_time - timestamp_duration); |
| EXPECT_GE(timestamp_duration, expected_min_time); |
| EXPECT_LT(timestamp_duration, expected_max_time); |
| |
| AUD_VLOG(TRACE) << "Observed delta from min/ideal/max : " << std::setw(10) |
| << (expected_min_time - observed_duration) << " : " << std::setw(10) |
| << (expected_time - observed_duration) << " : " << std::setw(10) |
| << (expected_max_time - observed_duration); |
| EXPECT_GT(observed_duration, expected_min_time); |
| } |
| |
| // After waiting for one second, we should NOT have received any position notifications. |
| void AudioDriverTest::ExpectNoPositionNotifications() { |
| if (error_occurred_) { |
| return; |
| } |
| |
| zx::nanosleep(zx::deadline_after(zx::sec(1))); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(position_notification_count_, 0u); |
| } |
| |
| // |
| // Test cases that target each of the various driver commands |
| // |
| |
| // Stream channel commands |
| // |
| // AUDIO_STREAM_CMD_GET_UNIQUE_ID |
| // For input stream, verify a valid GET_UNIQUE_ID response is successfully received. |
| TEST_F(AudioDriverTest, InputGetUniqueId) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestUniqueId(); |
| } |
| |
| // For output stream, verify a valid GET_UNIQUE_ID response is successfully received. |
| TEST_F(AudioDriverTest, OutputGetUniqueId) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestUniqueId(); |
| } |
| |
| // AUDIO_STREAM_CMD_GET_STRING - Manufacturer |
| // For input stream, verify a valid GET_STRING (MANUFACTURER) response is successfully received. |
| TEST_F(AudioDriverTest, InputGetManufacturer) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestManufacturerString(); |
| } |
| |
| // For output stream, verify a valid GET_STRING (MANUFACTURER) response is successfully received. |
| TEST_F(AudioDriverTest, OutputGetManufacturer) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestManufacturerString(); |
| } |
| |
| // AUDIO_STREAM_CMD_GET_STRING - Product |
| // For input stream, verify a valid GET_STRING (PRODUCT) response is successfully received. |
| TEST_F(AudioDriverTest, InputGetProduct) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestProductString(); |
| } |
| |
| // For output stream, verify a valid GET_STRING (PRODUCT) response is successfully received. |
| TEST_F(AudioDriverTest, OutputGetProduct) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestProductString(); |
| } |
| |
| // AUDIO_STREAM_CMD_GET_CLOCK_DOMAIN |
| // For input stream, verify a valid GET_CLOCK_DOMAIN response is successfully received. |
| TEST_F(AudioDriverTest, InputGetClockDomain) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestClockDomain(); |
| } |
| |
| // For output stream, verify a valid GET_CLOCK_DOMAIN response is successfully received. |
| TEST_F(AudioDriverTest, OutputGetClockDomain) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestClockDomain(); |
| } |
| |
| // AUDIO_STREAM_CMD_GET_GAIN |
| // For input stream, verify a valid GET_GAIN response is successfully received. |
| TEST_F(AudioDriverTest, InputGetGain) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestGain(); |
| } |
| |
| // For output stream, verify a valid GET_GAIN response is successfully received. |
| TEST_F(AudioDriverTest, OutputGetGain) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestGain(); |
| } |
| |
| // AUDIO_STREAM_CMD_SET_GAIN |
| // For input stream, verify a valid SET_GAIN response is successfully received. |
| TEST_F(AudioDriverTest, InputSetGain) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestGain(); |
| RequestSetGain(); |
| } |
| |
| // For output stream, verify a valid SET_GAIN response is successfully received. |
| TEST_F(AudioDriverTest, OutputSetGain) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestGain(); |
| RequestSetGain(); |
| } |
| |
| // AUDIO_STREAM_CMD_GET_FORMATS |
| // For input stream, verify a valid GET_FORMATS response is successfully received. |
| TEST_F(AudioDriverTest, InputGetFormats) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestFormats(); |
| } |
| |
| // For output stream, verify a valid GET_FORMATS response is successfully received. |
| TEST_F(AudioDriverTest, OutputGetFormats) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestFormats(); |
| } |
| |
| // AUDIO_STREAM_CMD_SET_FORMAT |
| // For output stream, verify a valid SET_FORMAT response, for low-bit-rate format -- and that a |
| // valid ring buffer channel is received. |
| TEST_F(AudioDriverTest, InputSetFormatMin) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestFormats(); |
| |
| RequestSetFormatMin(); |
| } |
| |
| // For output stream, verify a valid SET_FORMAT response, for low-bit-rate format -- and that a |
| // valid ring buffer channel is received. |
| TEST_F(AudioDriverTest, OutputSetFormatMin) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestFormats(); |
| |
| RequestSetFormatMin(); |
| } |
| |
| // For input stream, verify a valid SET_FORMAT response, for high-bit-rate format -- and that a |
| // valid ring buffer channel is received. |
| TEST_F(AudioDriverTest, InputSetFormatMax) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestFormats(); |
| |
| RequestSetFormatMax(); |
| } |
| |
| // For output stream, verify a valid SET_FORMAT response, for high-bit-rate format -- and that a |
| // valid ring buffer channel is received. |
| TEST_F(AudioDriverTest, OutputSetFormatMax) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestFormats(); |
| |
| RequestSetFormatMax(); |
| } |
| |
| // AUDIO_STREAM_CMD_PLUG_DETECT |
| // For input stream, verify a valid PLUG_DETECT response is successfully received. |
| TEST_F(AudioDriverTest, InputPlugDetect) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestPlugDetect(); |
| } |
| |
| // For output stream, verify a valid PLUG_DETECT response is successfully received. |
| TEST_F(AudioDriverTest, OutputPlugDetect) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestPlugDetect(); |
| } |
| |
| // AUDIO_STREAM_PLUG_DETECT_NOTIFY is not testable without scriptable PLUG/UNPLUG actions |
| |
| // Ring Buffer channel commands |
| // |
| // AUDIO_RB_CMD_GET_FIFO_DEPTH |
| // For input stream, verify a valid GET_FIFO_DEPTH response is successfully received. |
| TEST_F(AudioDriverTest, InputGetFifoDepth) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMin(); |
| |
| RequestFifoDepth(); |
| } |
| |
| // For output stream, verify a valid GET_FIFO_DEPTH response is successfully received. |
| TEST_F(AudioDriverTest, OutputGetFifoDepth) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMax(); |
| |
| RequestFifoDepth(); |
| } |
| |
| // AUDIO_RB_CMD_GET_BUFFER |
| // For input stream, verify a GET_BUFFER response and ring buffer VMO is successfully received. |
| TEST_F(AudioDriverTest, InputGetBuffer) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMax(); |
| |
| uint32_t frames = 48000; |
| uint32_t notifs = 8; |
| RequestBuffer(frames, notifs); |
| } |
| |
| // For output stream, verify a GET_BUFFER response and ring buffer VMO is successfully received. |
| TEST_F(AudioDriverTest, OutputGetBuffer) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMin(); |
| |
| uint32_t frames = 100; |
| uint32_t notifs = 1; |
| RequestBuffer(frames, notifs); |
| } |
| |
| // AUDIO_RB_CMD_START |
| // For input stream, verify that a valid START response is successfully received. |
| TEST_F(AudioDriverTest, InputStart) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMax(); |
| RequestBuffer(100, 0); |
| |
| RequestStart(); |
| } |
| |
| // For output stream, verify that a valid START response is successfully received. |
| TEST_F(AudioDriverTest, OutputStart) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMin(); |
| RequestBuffer(32000, 0); |
| |
| RequestStart(); |
| } |
| |
| // AUDIO_RB_CMD_STOP |
| // For input stream, verify that a valid STOP response is successfully received. |
| TEST_F(AudioDriverTest, InputStop) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMax(); |
| RequestBuffer(24000, 0); |
| RequestStart(); |
| |
| RequestStop(); |
| } |
| |
| // For output stream, verify that a valid STOP response is successfully received. |
| TEST_F(AudioDriverTest, OutputStop) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMin(); |
| RequestBuffer(100, 0); |
| RequestStart(); |
| |
| RequestStop(); |
| } |
| |
| // AUDIO_RB_POSITION_NOTIFY |
| // For input stream, verify position notifications at fast rate (~180/sec) over approx 100 ms. |
| TEST_F(AudioDriverTest, InputPositionNotifyFast) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMax(); |
| RequestBuffer(8000, 32); |
| RequestStart(); |
| |
| ExpectPositionNotifyCount(16); |
| } |
| |
| // For output stream, verify position notifications at fast rate (~180/sec) over approx 100 ms. |
| TEST_F(AudioDriverTest, OutputPositionNotifyFast) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMax(); |
| RequestBuffer(8000, 32); |
| RequestStart(); |
| |
| ExpectPositionNotifyCount(16); |
| } |
| |
| // For input stream, verify position notifications at slow rate (2/sec) over approx 1 second. |
| TEST_F(AudioDriverTest, InputPositionNotifySlow) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMin(); |
| RequestBuffer(48000, 2); |
| RequestStart(); |
| |
| ExpectPositionNotifyCount(2); |
| } |
| |
| // For output stream, verify position notifications at slow rate (2/sec) over approx 1 second. |
| TEST_F(AudioDriverTest, OutputPositionNotifySlow) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMin(); |
| RequestBuffer(48000, 2); |
| RequestStart(); |
| |
| ExpectPositionNotifyCount(2); |
| } |
| |
| // For input stream, verify that no position notifications arrive if notifications_per_ring is 0. |
| TEST_F(AudioDriverTest, InputPositionNotifyNone) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMax(); |
| RequestBuffer(8000, 0); |
| RequestStart(); |
| |
| ExpectNoPositionNotifications(); |
| } |
| |
| // For output stream, verify that no position notifications arrive if notifications_per_ring is 0. |
| TEST_F(AudioDriverTest, OutputPositionNotifyNone) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMax(); |
| RequestBuffer(8000, 0); |
| RequestStart(); |
| |
| ExpectNoPositionNotifications(); |
| } |
| |
| // For input stream, verify that no position notificatons arrive after STOP. |
| TEST_F(AudioDriverTest, InputNoPositionNotifyAfterStop) { |
| if (!WaitForDevice(DeviceType::Input)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMax(); |
| RequestBuffer(8000, 32); |
| RequestStart(); |
| ExpectPositionNotifyCount(2); |
| RequestStop(); |
| |
| ExpectNoPositionNotifications(); |
| } |
| |
| // For output stream, verify that no position notificatons arrive after STOP. |
| TEST_F(AudioDriverTest, OutputNoPositionNotifyAfterStop) { |
| if (!WaitForDevice(DeviceType::Output)) { |
| return; |
| } |
| |
| RequestFormats(); |
| RequestSetFormatMax(); |
| RequestBuffer(8000, 32); |
| RequestStart(); |
| ExpectPositionNotifyCount(2); |
| RequestStop(); |
| |
| ExpectNoPositionNotifications(); |
| } |
| |
| // For input stream, verify that monotonic_time values are close to NOW, and always increasing. |
| |
| } // namespace media::audio::test |