| // Copyright (c) 2013 The Chromium OS 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 <gtest/gtest.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| extern "C" { |
| // For audio_thread_log.h use. |
| struct audio_thread_event_log* atlog; |
| int atlog_rw_shm_fd; |
| int atlog_ro_shm_fd; |
| #include "audio_thread_log.h" |
| #include "cras_audio_area.h" |
| #include "cras_iodev.h" |
| #include "cras_iodev_list.h" |
| #include "cras_loopback_iodev.h" |
| #include "cras_shm.h" |
| #include "cras_types.h" |
| #include "dev_stream.h" |
| #include "utlist.h" |
| } |
| |
| namespace { |
| |
| static const unsigned int kBufferFrames = 16384; |
| static const unsigned int kFrameBytes = 4; |
| static const unsigned int kBufferSize = kBufferFrames * kFrameBytes; |
| |
| static struct timespec time_now; |
| static cras_audio_area* mock_audio_area; |
| static loopback_hook_data_t loop_hook; |
| static struct cras_iodev* enabled_dev; |
| static unsigned int cras_iodev_list_add_input_called; |
| static unsigned int cras_iodev_list_rm_input_called; |
| static unsigned int cras_iodev_list_set_device_enabled_callback_called; |
| static device_enabled_callback_t device_enabled_callback_cb; |
| static device_disabled_callback_t device_disabled_callback_cb; |
| static void* device_enabled_callback_cb_data; |
| static int cras_iodev_list_register_loopback_called; |
| static int cras_iodev_list_unregister_loopback_called; |
| |
| static char* atlog_name; |
| |
| class LoopBackTestSuite : public testing::Test { |
| protected: |
| virtual void SetUp() { |
| mock_audio_area = (cras_audio_area*)calloc( |
| 1, sizeof(*mock_audio_area) + sizeof(cras_channel_area) * 2); |
| for (unsigned int i = 0; i < kBufferSize; i++) { |
| buf_[i] = rand(); |
| } |
| fmt_.frame_rate = 48000; |
| fmt_.num_channels = 2; |
| fmt_.format = SND_PCM_FORMAT_S16_LE; |
| |
| loop_in_ = loopback_iodev_create(LOOPBACK_POST_MIX_PRE_DSP); |
| EXPECT_EQ(1, cras_iodev_list_add_input_called); |
| loop_in_->format = &fmt_; |
| |
| loop_hook = NULL; |
| cras_iodev_list_add_input_called = 0; |
| cras_iodev_list_rm_input_called = 0; |
| cras_iodev_list_set_device_enabled_callback_called = 0; |
| cras_iodev_list_register_loopback_called = 0; |
| cras_iodev_list_unregister_loopback_called = 0; |
| |
| ASSERT_FALSE(asprintf(&atlog_name, "/ATlog-%d", getpid()) < 0); |
| /* To avoid un-used variable warning. */ |
| atlog_rw_shm_fd = atlog_ro_shm_fd = -1; |
| atlog = audio_thread_event_log_init(atlog_name); |
| } |
| |
| virtual void TearDown() { |
| loopback_iodev_destroy(loop_in_); |
| EXPECT_EQ(1, cras_iodev_list_rm_input_called); |
| EXPECT_EQ(NULL, device_enabled_callback_cb); |
| EXPECT_EQ(NULL, device_disabled_callback_cb); |
| free(mock_audio_area); |
| audio_thread_event_log_deinit(atlog, atlog_name); |
| free(atlog_name); |
| } |
| |
| uint8_t buf_[kBufferSize]; |
| struct cras_audio_format fmt_; |
| struct cras_iodev* loop_in_; |
| }; |
| |
| TEST_F(LoopBackTestSuite, InstallLoopHook) { |
| struct cras_iodev iodev; |
| struct timespec tstamp; |
| |
| iodev.direction = CRAS_STREAM_OUTPUT; |
| iodev.format = &fmt_; |
| iodev.streams = NULL; |
| iodev.info.idx = 123; |
| enabled_dev = &iodev; |
| |
| // Open loopback devices. |
| EXPECT_EQ(0, loop_in_->configure_dev(loop_in_)); |
| EXPECT_EQ(1, cras_iodev_list_set_device_enabled_callback_called); |
| EXPECT_EQ(1, cras_iodev_list_register_loopback_called); |
| |
| // Signal an output device is enabled. |
| device_enabled_callback_cb(&iodev, device_enabled_callback_cb_data); |
| |
| // Expect that a hook was added to the iodev |
| EXPECT_EQ(2, cras_iodev_list_register_loopback_called); |
| ASSERT_NE(reinterpret_cast<loopback_hook_data_t>(NULL), loop_hook); |
| |
| // Check zero frames queued. |
| EXPECT_EQ(0, loop_in_->frames_queued(loop_in_, &tstamp)); |
| |
| device_disabled_callback_cb(&iodev, device_enabled_callback_cb_data); |
| EXPECT_EQ(1, cras_iodev_list_unregister_loopback_called); |
| EXPECT_EQ(3, cras_iodev_list_register_loopback_called); |
| |
| enabled_dev->info.idx = 456; |
| device_enabled_callback_cb(&iodev, device_enabled_callback_cb_data); |
| EXPECT_EQ(4, cras_iodev_list_register_loopback_called); |
| |
| // Close loopback devices. |
| EXPECT_EQ(0, loop_in_->close_dev(loop_in_)); |
| EXPECT_EQ(2, cras_iodev_list_unregister_loopback_called); |
| EXPECT_EQ(2, cras_iodev_list_set_device_enabled_callback_called); |
| } |
| |
| TEST_F(LoopBackTestSuite, SelectDevFromAToB) { |
| struct cras_iodev iodev1, iodev2; |
| |
| iodev1.direction = CRAS_STREAM_OUTPUT; |
| iodev2.direction = CRAS_STREAM_OUTPUT; |
| enabled_dev = &iodev1; |
| |
| enabled_dev->info.idx = 111; |
| EXPECT_EQ(0, loop_in_->configure_dev(loop_in_)); |
| EXPECT_EQ(1, cras_iodev_list_set_device_enabled_callback_called); |
| EXPECT_EQ(1, cras_iodev_list_register_loopback_called); |
| |
| /* Not the current sender being disabled, assert unregister not called. */ |
| iodev2.info.idx = 222; |
| device_disabled_callback_cb(&iodev2, device_enabled_callback_cb_data); |
| EXPECT_EQ(0, cras_iodev_list_unregister_loopback_called); |
| EXPECT_EQ(1, cras_iodev_list_register_loopback_called); |
| |
| enabled_dev = &iodev2; |
| device_disabled_callback_cb(&iodev1, device_enabled_callback_cb_data); |
| EXPECT_EQ(1, cras_iodev_list_unregister_loopback_called); |
| EXPECT_EQ(2, cras_iodev_list_register_loopback_called); |
| |
| EXPECT_EQ(0, loop_in_->close_dev(loop_in_)); |
| } |
| |
| // Test how loopback works if there isn't any output devices open. |
| TEST_F(LoopBackTestSuite, OpenIdleSystem) { |
| cras_audio_area* area; |
| unsigned int nread = 1024; |
| struct timespec tstamp; |
| int rc; |
| |
| // No active output device. |
| enabled_dev = NULL; |
| time_now.tv_sec = 100; |
| time_now.tv_nsec = 0; |
| |
| EXPECT_EQ(0, loop_in_->configure_dev(loop_in_)); |
| EXPECT_EQ(1, cras_iodev_list_set_device_enabled_callback_called); |
| |
| // Should be 480 samples after 480/frame rate seconds |
| time_now.tv_nsec += 480 * 1e9 / 48000; |
| EXPECT_EQ(480, loop_in_->frames_queued(loop_in_, &tstamp)); |
| |
| // Verify frames from loopback record. |
| loop_in_->get_buffer(loop_in_, &area, &nread); |
| EXPECT_EQ(480, nread); |
| memset(buf_, 0, nread * kFrameBytes); |
| rc = memcmp(area->channels[0].buf, buf_, nread * kFrameBytes); |
| EXPECT_EQ(0, rc); |
| loop_in_->put_buffer(loop_in_, nread); |
| |
| // Check zero frames queued. |
| EXPECT_EQ(0, loop_in_->frames_queued(loop_in_, &tstamp)); |
| |
| EXPECT_EQ(0, loop_in_->close_dev(loop_in_)); |
| } |
| |
| TEST_F(LoopBackTestSuite, SimpleLoopback) { |
| cras_audio_area* area; |
| unsigned int nframes = 1024; |
| unsigned int nread = 1024; |
| int rc; |
| struct cras_iodev iodev; |
| struct dev_stream stream; |
| struct timespec tstamp; |
| |
| iodev.streams = &stream; |
| enabled_dev = &iodev; |
| |
| loop_in_->configure_dev(loop_in_); |
| ASSERT_NE(reinterpret_cast<void*>(NULL), loop_hook); |
| |
| // Loopback callback for the hook. |
| loop_hook(buf_, nframes, &fmt_, loop_in_); |
| |
| // Verify frames from loopback record. |
| loop_in_->get_buffer(loop_in_, &area, &nread); |
| EXPECT_EQ(nframes, nread); |
| rc = memcmp(area->channels[0].buf, buf_, nframes * kFrameBytes); |
| EXPECT_EQ(0, rc); |
| loop_in_->put_buffer(loop_in_, nread); |
| |
| // Check zero frames queued. |
| EXPECT_EQ(0, loop_in_->frames_queued(loop_in_, &tstamp)); |
| |
| EXPECT_EQ(0, loop_in_->close_dev(loop_in_)); |
| } |
| |
| // TODO(chinyue): Test closing last iodev while streaming loopback data. |
| |
| /* Stubs */ |
| extern "C" { |
| |
| void cras_audio_area_config_buf_pointers(struct cras_audio_area* area, |
| const struct cras_audio_format* fmt, |
| uint8_t* base_buffer) { |
| mock_audio_area->channels[0].buf = base_buffer; |
| } |
| |
| void cras_iodev_free_audio_area(struct cras_iodev* iodev) {} |
| |
| void cras_iodev_free_format(struct cras_iodev* iodev) {} |
| |
| void cras_iodev_init_audio_area(struct cras_iodev* iodev, int num_channels) { |
| iodev->area = mock_audio_area; |
| } |
| |
| void cras_iodev_add_node(struct cras_iodev* iodev, struct cras_ionode* node) { |
| DL_APPEND(iodev->nodes, node); |
| } |
| |
| void cras_iodev_set_active_node(struct cras_iodev* iodev, |
| struct cras_ionode* node) {} |
| void cras_iodev_list_register_loopback(enum CRAS_LOOPBACK_TYPE loopback_type, |
| unsigned int output_dev_idx, |
| loopback_hook_data_t hook_data, |
| loopback_hook_control_t hook_start, |
| unsigned int loopback_dev_idx) { |
| cras_iodev_list_register_loopback_called++; |
| loop_hook = hook_data; |
| } |
| |
| void cras_iodev_list_unregister_loopback(enum CRAS_LOOPBACK_TYPE loopback_type, |
| unsigned int output_dev_idx, |
| unsigned int loopback_dev_idx) { |
| cras_iodev_list_unregister_loopback_called++; |
| } |
| |
| int cras_iodev_list_add_input(struct cras_iodev* input) { |
| cras_iodev_list_add_input_called++; |
| return 0; |
| } |
| |
| int cras_iodev_list_rm_input(struct cras_iodev* input) { |
| cras_iodev_list_rm_input_called++; |
| return 0; |
| } |
| |
| int cras_iodev_list_set_device_enabled_callback( |
| device_enabled_callback_t enabled_cb, |
| device_disabled_callback_t disabled_cb, |
| void* cb_data) { |
| cras_iodev_list_set_device_enabled_callback_called++; |
| device_enabled_callback_cb = enabled_cb; |
| device_disabled_callback_cb = disabled_cb; |
| device_enabled_callback_cb_data = cb_data; |
| return 0; |
| } |
| |
| int clock_gettime(clockid_t clk_id, struct timespec* tp) { |
| *tp = time_now; |
| return 0; |
| } |
| |
| struct cras_iodev* cras_iodev_list_get_first_enabled_iodev( |
| enum CRAS_STREAM_DIRECTION direction) { |
| return enabled_dev; |
| } |
| |
| } // extern "C" |
| |
| } // namespace |
| |
| int main(int argc, char** argv) { |
| ::testing::InitGoogleTest(&argc, argv); |
| return RUN_ALL_TESTS(); |
| } |