| /* Copyright 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 <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <stdint.h> |
| #include <time.h> |
| |
| using testing::MatchesRegex; |
| using testing::internal::CaptureStdout; |
| using testing::internal::GetCapturedStdout; |
| |
| extern "C" { |
| #include "cras_hfp_info.c" |
| #include "sbc_codec_stub.h" |
| } |
| static struct hfp_info* info; |
| static struct cras_iodev dev; |
| static cras_audio_format format; |
| |
| static int cras_msbc_plc_create_called; |
| static int cras_msbc_plc_handle_good_frames_called; |
| static int cras_msbc_plc_handle_bad_frames_called; |
| |
| static thread_callback thread_cb; |
| static void* cb_data; |
| static timespec ts; |
| |
| void ResetStubData() { |
| sbc_codec_stub_reset(); |
| cras_msbc_plc_create_called = 0; |
| |
| format.format = SND_PCM_FORMAT_S16_LE; |
| format.num_channels = 1; |
| format.frame_rate = 8000; |
| dev.format = &format; |
| } |
| |
| namespace { |
| |
| TEST(HfpInfo, AddRmDev) { |
| ResetStubData(); |
| |
| info = hfp_info_create(); |
| ASSERT_NE(info, (void*)NULL); |
| dev.direction = CRAS_STREAM_OUTPUT; |
| |
| /* Test add dev */ |
| ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format)); |
| ASSERT_TRUE(hfp_info_has_iodev(info)); |
| |
| /* Test remove dev */ |
| ASSERT_EQ(0, hfp_info_rm_iodev(info, dev.direction)); |
| ASSERT_FALSE(hfp_info_has_iodev(info)); |
| |
| hfp_info_destroy(info); |
| } |
| |
| TEST(HfpInfo, AddRmDevInvalid) { |
| ResetStubData(); |
| |
| info = hfp_info_create(); |
| ASSERT_NE(info, (void*)NULL); |
| |
| dev.direction = CRAS_STREAM_OUTPUT; |
| |
| /* Remove an iodev which doesn't exist */ |
| ASSERT_NE(0, hfp_info_rm_iodev(info, dev.direction)); |
| |
| /* Adding an iodev twice returns error code */ |
| ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format)); |
| ASSERT_NE(0, hfp_info_add_iodev(info, dev.direction, dev.format)); |
| |
| hfp_info_destroy(info); |
| } |
| |
| TEST(HfpInfo, AcquirePlaybackBuffer) { |
| unsigned buffer_frames, buffer_frames2, queued; |
| uint8_t* samples; |
| |
| ResetStubData(); |
| |
| info = hfp_info_create(); |
| ASSERT_NE(info, (void*)NULL); |
| |
| hfp_info_start(1, 48, HFP_CODEC_ID_CVSD, info); |
| dev.direction = CRAS_STREAM_OUTPUT; |
| ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format)); |
| |
| buffer_frames = 500; |
| hfp_buf_acquire(info, dev.direction, &samples, &buffer_frames); |
| ASSERT_EQ(500, buffer_frames); |
| |
| hfp_buf_release(info, dev.direction, 500); |
| ASSERT_EQ(500, hfp_buf_queued(info, dev.direction)); |
| |
| /* Assert the amount of frames of available buffer + queued buf is |
| * greater than or equal to the buffer size, 2 bytes per frame |
| */ |
| queued = hfp_buf_queued(info, dev.direction); |
| buffer_frames = 500; |
| hfp_buf_acquire(info, dev.direction, &samples, &buffer_frames); |
| ASSERT_GE(info->playback_buf->used_size / 2, buffer_frames + queued); |
| |
| /* Consume all queued data from read buffer */ |
| buf_increment_read(info->playback_buf, queued * 2); |
| |
| queued = hfp_buf_queued(info, dev.direction); |
| ASSERT_EQ(0, queued); |
| |
| /* Assert consecutive acquire buffer will acquire full used size of buffer */ |
| buffer_frames = 500; |
| hfp_buf_acquire(info, dev.direction, &samples, &buffer_frames); |
| hfp_buf_release(info, dev.direction, buffer_frames); |
| |
| buffer_frames2 = 500; |
| hfp_buf_acquire(info, dev.direction, &samples, &buffer_frames2); |
| hfp_buf_release(info, dev.direction, buffer_frames2); |
| |
| ASSERT_GE(info->playback_buf->used_size / 2, buffer_frames + buffer_frames2); |
| |
| hfp_info_destroy(info); |
| } |
| |
| TEST(HfpInfo, AcquireCaptureBuffer) { |
| unsigned buffer_frames, buffer_frames2; |
| uint8_t* samples; |
| |
| ResetStubData(); |
| |
| info = hfp_info_create(); |
| ASSERT_NE(info, (void*)NULL); |
| |
| hfp_info_start(1, 48, HFP_CODEC_ID_CVSD, info); |
| dev.direction = CRAS_STREAM_INPUT; |
| ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format)); |
| |
| /* Put fake data 100 bytes(50 frames) in capture buf for test */ |
| buf_increment_write(info->capture_buf, 100); |
| |
| /* Assert successfully acquire and release 100 bytes of data */ |
| buffer_frames = 50; |
| hfp_buf_acquire(info, dev.direction, &samples, &buffer_frames); |
| ASSERT_EQ(50, buffer_frames); |
| |
| hfp_buf_release(info, dev.direction, buffer_frames); |
| ASSERT_EQ(0, hfp_buf_queued(info, dev.direction)); |
| |
| /* Push fake data to capture buffer */ |
| buf_increment_write(info->capture_buf, info->capture_buf->used_size - 100); |
| buf_increment_write(info->capture_buf, 100); |
| |
| /* Assert consecutive acquire call will consume the whole buffer */ |
| buffer_frames = 1000; |
| hfp_buf_acquire(info, dev.direction, &samples, &buffer_frames); |
| hfp_buf_release(info, dev.direction, buffer_frames); |
| ASSERT_GE(1000, buffer_frames); |
| |
| buffer_frames2 = 1000; |
| hfp_buf_acquire(info, dev.direction, &samples, &buffer_frames2); |
| hfp_buf_release(info, dev.direction, buffer_frames2); |
| |
| ASSERT_GE(info->capture_buf->used_size / 2, buffer_frames + buffer_frames2); |
| |
| hfp_info_destroy(info); |
| } |
| |
| TEST(HfpInfo, HfpReadWriteFD) { |
| int rc; |
| int sock[2]; |
| uint8_t sample[480]; |
| uint8_t* buf; |
| unsigned buffer_count; |
| |
| ResetStubData(); |
| |
| ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock)); |
| |
| info = hfp_info_create(); |
| ASSERT_NE(info, (void*)NULL); |
| |
| dev.direction = CRAS_STREAM_INPUT; |
| hfp_info_start(sock[1], 48, HFP_CODEC_ID_CVSD, info); |
| ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format)); |
| |
| /* Mock the sco fd and send some fake data */ |
| send(sock[0], sample, 48, 0); |
| |
| rc = hfp_read(info); |
| ASSERT_EQ(48, rc); |
| |
| rc = hfp_buf_queued(info, dev.direction); |
| ASSERT_EQ(48 / 2, rc); |
| |
| /* Fill the write buffer*/ |
| buffer_count = info->capture_buf->used_size; |
| buf = buf_write_pointer_size(info->capture_buf, &buffer_count); |
| buf_increment_write(info->capture_buf, buffer_count); |
| ASSERT_NE((void*)NULL, buf); |
| |
| rc = hfp_read(info); |
| ASSERT_EQ(0, rc); |
| |
| ASSERT_EQ(0, hfp_info_rm_iodev(info, dev.direction)); |
| dev.direction = CRAS_STREAM_OUTPUT; |
| ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format)); |
| |
| /* Initial buffer is empty */ |
| rc = hfp_write(info); |
| ASSERT_EQ(0, rc); |
| |
| buffer_count = 1024; |
| buf = buf_write_pointer_size(info->playback_buf, &buffer_count); |
| buf_increment_write(info->playback_buf, buffer_count); |
| |
| rc = hfp_write(info); |
| ASSERT_EQ(48, rc); |
| |
| rc = recv(sock[0], sample, 48, 0); |
| ASSERT_EQ(48, rc); |
| |
| hfp_info_destroy(info); |
| } |
| |
| TEST(HfpInfo, StartHfpInfo) { |
| int sock[2]; |
| |
| ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock)); |
| |
| info = hfp_info_create(); |
| ASSERT_NE(info, (void*)NULL); |
| |
| hfp_info_start(sock[0], 48, HFP_CODEC_ID_CVSD, info); |
| ASSERT_EQ(1, hfp_info_running(info)); |
| ASSERT_EQ(cb_data, (void*)info); |
| |
| hfp_info_stop(info); |
| ASSERT_EQ(0, hfp_info_running(info)); |
| ASSERT_EQ(NULL, cb_data); |
| |
| hfp_info_destroy(info); |
| } |
| |
| TEST(HfpInfo, StartHfpInfoAndRead) { |
| int rc; |
| int sock[2]; |
| uint8_t sample[480]; |
| |
| ResetStubData(); |
| |
| ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock)); |
| |
| info = hfp_info_create(); |
| ASSERT_NE(info, (void*)NULL); |
| |
| /* Start and send two chunk of fake data */ |
| hfp_info_start(sock[1], 48, HFP_CODEC_ID_CVSD, info); |
| send(sock[0], sample, 48, 0); |
| send(sock[0], sample, 48, 0); |
| |
| /* Trigger thread callback */ |
| thread_cb((struct hfp_info*)cb_data, POLLIN); |
| |
| dev.direction = CRAS_STREAM_INPUT; |
| ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format)); |
| |
| /* Expect no data read, since no idev present at previous thread callback */ |
| rc = hfp_buf_queued(info, dev.direction); |
| ASSERT_EQ(0, rc); |
| |
| /* Trigger thread callback after idev added. */ |
| ts.tv_sec = 0; |
| ts.tv_nsec = 5000000; |
| thread_cb((struct hfp_info*)cb_data, POLLIN); |
| |
| rc = hfp_buf_queued(info, dev.direction); |
| ASSERT_EQ(48 / 2, rc); |
| |
| /* Assert wait time is unchanged. */ |
| ASSERT_EQ(0, ts.tv_sec); |
| ASSERT_EQ(5000000, ts.tv_nsec); |
| |
| hfp_info_stop(info); |
| ASSERT_EQ(0, hfp_info_running(info)); |
| |
| hfp_info_destroy(info); |
| } |
| |
| TEST(HfpInfo, StartHfpInfoAndWrite) { |
| int rc; |
| int sock[2]; |
| uint8_t sample[480]; |
| |
| ResetStubData(); |
| |
| ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock)); |
| |
| info = hfp_info_create(); |
| ASSERT_NE(info, (void*)NULL); |
| |
| hfp_info_start(sock[1], 48, HFP_CODEC_ID_CVSD, info); |
| send(sock[0], sample, 48, 0); |
| send(sock[0], sample, 48, 0); |
| |
| /* Trigger thread callback */ |
| thread_cb((struct hfp_info*)cb_data, POLLIN); |
| |
| /* Without odev in presence, zero packet should be sent. */ |
| rc = recv(sock[0], sample, 48, 0); |
| ASSERT_EQ(48, rc); |
| |
| dev.direction = CRAS_STREAM_OUTPUT; |
| ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format)); |
| |
| /* Assert queued samples unchanged before output device added */ |
| ASSERT_EQ(0, hfp_buf_queued(info, dev.direction)); |
| |
| /* Put some fake data and trigger thread callback again */ |
| buf_increment_write(info->playback_buf, 1008); |
| thread_cb((struct hfp_info*)cb_data, POLLIN); |
| |
| /* Assert some samples written */ |
| rc = recv(sock[0], sample, 48, 0); |
| ASSERT_EQ(48, rc); |
| ASSERT_EQ(480, hfp_buf_queued(info, dev.direction)); |
| |
| hfp_info_stop(info); |
| hfp_info_destroy(info); |
| } |
| |
| void send_mSBC_packet(int fd, unsigned seq, int broken_pkt) { |
| /* The first three bytes of hci_sco_buf are h2 header, frame count and mSBC |
| * sync word. The second octet of H2 header is composed by 4 bits fixed 0x8 |
| * and 4 bits sequence number 0000, 0011, 1100, 1111. |
| */ |
| uint8_t headers[4] = {0x08, 0x38, 0xc8, 0xf8}; |
| uint8_t hci_sco_buf[] = { |
| 0x01, 0x00, 0xAD, 0xad, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x00, 0x77, |
| 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd, 0xb6, 0xdb, |
| 0x77, 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd, 0xb6, |
| 0xdb, 0x77, 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6d, 0xdd, |
| 0xb6, 0xdb, 0x77, 0x6d, 0xb6, 0xdd, 0xdb, 0x6d, 0xb7, 0x76, 0xdb, 0x6c}; |
| struct msghdr msg = {0}; |
| struct iovec iov; |
| struct cmsghdr* cmsg; |
| const unsigned int control_size = CMSG_SPACE(sizeof(int)); |
| char control[control_size] = {0}; |
| uint8_t pkt_status = 0; |
| |
| hci_sco_buf[1] = headers[seq % 4]; |
| |
| /* Assume typical 60 bytes case. */ |
| msg.msg_iov = &iov; |
| msg.msg_iovlen = 1; |
| iov.iov_base = hci_sco_buf; |
| iov.iov_len = 60; |
| msg.msg_control = control; |
| msg.msg_controllen = control_size; |
| |
| if (broken_pkt) |
| pkt_status = 0x11; |
| |
| cmsg = CMSG_FIRSTHDR(&msg); |
| cmsg->cmsg_level = SOL_BLUETOOTH; |
| cmsg->cmsg_type = BT_SCM_PKT_STATUS; |
| cmsg->cmsg_len = CMSG_LEN(sizeof(pkt_status)); |
| memcpy(CMSG_DATA(cmsg), &pkt_status, sizeof(pkt_status)); |
| |
| sendmsg(fd, &msg, 0); |
| } |
| |
| TEST(HfpInfo, StartHfpInfoAndReadMsbc) { |
| int sock[2]; |
| int pkt_count = 0; |
| int rc; |
| uint8_t sample[480]; |
| ResetStubData(); |
| |
| ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock)); |
| |
| set_sbc_codec_decoded_out(MSBC_CODE_SIZE); |
| |
| info = hfp_info_create(); |
| ASSERT_NE(info, (void*)NULL); |
| ASSERT_EQ(0, get_msbc_codec_create_called()); |
| ASSERT_EQ(0, cras_msbc_plc_create_called); |
| |
| /* Start and send an mSBC packets with all zero samples */ |
| hfp_info_start(sock[1], 63, HFP_CODEC_ID_MSBC, info); |
| ASSERT_EQ(2, get_msbc_codec_create_called()); |
| ASSERT_EQ(1, cras_msbc_plc_create_called); |
| send_mSBC_packet(sock[0], pkt_count++, 0); |
| |
| /* Trigger thread callback */ |
| thread_cb((struct hfp_info*)cb_data, POLLIN); |
| |
| /* Expect one empty mSBC packet is send, because no odev in presence. */ |
| rc = recv(sock[0], sample, MSBC_PKT_SIZE, 0); |
| ASSERT_EQ(MSBC_PKT_SIZE, rc); |
| |
| dev.direction = CRAS_STREAM_INPUT; |
| ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format)); |
| |
| /* Expect no data read, since no idev present at previous thread callback */ |
| ASSERT_EQ(0, hfp_buf_queued(info, dev.direction)); |
| |
| send_mSBC_packet(sock[0], pkt_count, 0); |
| |
| /* Trigger thread callback after idev added. */ |
| thread_cb((struct hfp_info*)cb_data, POLLIN); |
| rc = recv(sock[0], sample, MSBC_PKT_SIZE, 0); |
| ASSERT_EQ(MSBC_PKT_SIZE, rc); |
| |
| ASSERT_EQ(pkt_count * MSBC_CODE_SIZE / 2, |
| hfp_buf_queued(info, dev.direction)); |
| ASSERT_EQ(2, cras_msbc_plc_handle_good_frames_called); |
| pkt_count++; |
| /* When the third packet is lost, we should call the handle_bad_packet and |
| * still have right size of samples queued |
| */ |
| pkt_count++; |
| send_mSBC_packet(sock[0], pkt_count, 0); |
| thread_cb((struct hfp_info*)cb_data, POLLIN); |
| rc = recv(sock[0], sample, MSBC_PKT_SIZE, 0); |
| ASSERT_EQ(MSBC_PKT_SIZE, rc); |
| |
| /* Packet 1, 2, 4 are all good frames */ |
| ASSERT_EQ(3, cras_msbc_plc_handle_good_frames_called); |
| ASSERT_EQ(1, cras_msbc_plc_handle_bad_frames_called); |
| ASSERT_EQ(pkt_count * MSBC_CODE_SIZE / 2, |
| hfp_buf_queued(info, dev.direction)); |
| pkt_count++; |
| /* If the erroneous data reporting marks the packet as broken, we should |
| * also call the handle_bad_packet and have the right size of samples queued. |
| */ |
| send_mSBC_packet(sock[0], pkt_count, 1); |
| |
| set_sbc_codec_decoded_fail(1); |
| |
| thread_cb((struct hfp_info*)cb_data, POLLIN); |
| rc = recv(sock[0], sample, MSBC_PKT_SIZE, 0); |
| ASSERT_EQ(MSBC_PKT_SIZE, rc); |
| |
| ASSERT_EQ(3, cras_msbc_plc_handle_good_frames_called); |
| ASSERT_EQ(2, cras_msbc_plc_handle_bad_frames_called); |
| ASSERT_EQ(pkt_count * MSBC_CODE_SIZE / 2, |
| hfp_buf_queued(info, dev.direction)); |
| pkt_count++; |
| /* If we can't decode the packet, we should also call the handle_bad_packet |
| * and have the right size of samples queued |
| */ |
| send_mSBC_packet(sock[0], pkt_count, 0); |
| |
| set_sbc_codec_decoded_fail(1); |
| |
| thread_cb((struct hfp_info*)cb_data, POLLIN); |
| rc = recv(sock[0], sample, MSBC_PKT_SIZE, 0); |
| ASSERT_EQ(MSBC_PKT_SIZE, rc); |
| |
| ASSERT_EQ(3, cras_msbc_plc_handle_good_frames_called); |
| ASSERT_EQ(3, cras_msbc_plc_handle_bad_frames_called); |
| ASSERT_EQ(pkt_count * MSBC_CODE_SIZE / 2, |
| hfp_buf_queued(info, dev.direction)); |
| |
| hfp_info_stop(info); |
| ASSERT_EQ(0, hfp_info_running(info)); |
| |
| hfp_info_destroy(info); |
| } |
| |
| TEST(HfpInfo, StartHfpInfoAndWriteMsbc) { |
| int rc; |
| int sock[2]; |
| uint8_t sample[480]; |
| |
| ResetStubData(); |
| |
| set_sbc_codec_encoded_out(57); |
| ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock)); |
| |
| info = hfp_info_create(); |
| ASSERT_NE(info, (void*)NULL); |
| |
| hfp_info_start(sock[1], 63, HFP_CODEC_ID_MSBC, info); |
| send(sock[0], sample, 63, 0); |
| |
| /* Trigger thread callback */ |
| thread_cb((struct hfp_info*)cb_data, POLLIN); |
| |
| dev.direction = CRAS_STREAM_OUTPUT; |
| ASSERT_EQ(0, hfp_info_add_iodev(info, dev.direction, dev.format)); |
| |
| /* Assert queued samples unchanged before output device added */ |
| ASSERT_EQ(0, hfp_buf_queued(info, dev.direction)); |
| |
| /* Put some fake data and trigger thread callback again */ |
| send(sock[0], sample, 63, 0); |
| buf_increment_write(info->playback_buf, 240); |
| thread_cb((struct hfp_info*)cb_data, POLLIN); |
| |
| /* Assert some samples written */ |
| rc = recv(sock[0], sample, 60, 0); |
| ASSERT_EQ(60, rc); |
| ASSERT_EQ(0, hfp_buf_queued(info, dev.direction)); |
| |
| hfp_info_stop(info); |
| hfp_info_destroy(info); |
| } |
| |
| TEST(HfpInfo, WBSLoggerPacketStatusDumpBinary) { |
| struct packet_status_logger logger; |
| char log_regex[64]; |
| int num_wraps[5] = {0, 0, 0, 1, 1}; |
| int wp[5] = {40, 150, 162, 100, 32}; |
| |
| /* Expect the log line wraps at correct length to avoid feedback redact. */ |
| snprintf(log_regex, 64, "([01D]{%d}\n)*", PACKET_STATUS_LOG_LINE_WRAP); |
| |
| packet_status_logger_init(&logger); |
| logger.size = PACKET_STATUS_LEN_BYTES * 8; |
| for (int i = 0; i < 5; i++) { |
| CaptureStdout(); |
| logger.num_wraps = num_wraps[i]; |
| logger.wp = wp[i]; |
| packet_status_logger_dump_binary(&logger); |
| EXPECT_THAT(GetCapturedStdout(), MatchesRegex(log_regex)); |
| } |
| } |
| |
| } // namespace |
| |
| extern "C" { |
| |
| struct audio_thread* cras_iodev_list_get_audio_thread() { |
| return NULL; |
| } |
| |
| void audio_thread_add_events_callback(int fd, |
| thread_callback cb, |
| void* data, |
| int events) { |
| thread_cb = cb; |
| cb_data = data; |
| return; |
| } |
| |
| int audio_thread_rm_callback_sync(struct audio_thread* thread, int fd) { |
| thread_cb = NULL; |
| cb_data = NULL; |
| return 0; |
| } |
| |
| void audio_thread_rm_callback(int fd) {} |
| |
| struct cras_msbc_plc* cras_msbc_plc_create() { |
| cras_msbc_plc_create_called++; |
| return NULL; |
| } |
| |
| void cras_msbc_plc_destroy(struct cras_msbc_plc* plc) {} |
| |
| int cras_msbc_plc_handle_bad_frames(struct cras_msbc_plc* plc, |
| struct cras_audio_codec* codec, |
| uint8_t* output) { |
| cras_msbc_plc_handle_bad_frames_called++; |
| return MSBC_CODE_SIZE; |
| } |
| |
| int cras_msbc_plc_handle_good_frames(struct cras_msbc_plc* plc, |
| const uint8_t* input, |
| uint8_t* output) { |
| cras_msbc_plc_handle_good_frames_called++; |
| return MSBC_CODE_SIZE; |
| } |
| void packet_status_logger_init(struct packet_status_logger* logger) {} |
| |
| void packet_status_logger_update(struct packet_status_logger* logger, |
| bool val) {} |
| } |
| |
| int main(int argc, char** argv) { |
| ::testing::InitGoogleTest(&argc, argv); |
| return RUN_ALL_TESTS(); |
| } |