blob: 88f058d5cc6d5748a07bb1387a7567e364c25037 [file] [log] [blame]
// 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 <stdio.h>
#include <stdint.h>
#include <gtest/gtest.h>
extern "C" {
#include "a2dp-codecs.h"
#include "cras_audio_area.h"
#include "audio_thread.h"
#include "audio_thread_log.h"
#include "cras_bt_transport.h"
#include "cras_iodev.h"
#include "cras_a2dp_iodev.h"
}
#define FAKE_OBJECT_PATH "/fake/obj/path"
#define MAX_A2DP_ENCODE_CALLS 2
#define MAX_A2DP_WRITE_CALLS 4
static struct cras_bt_transport *fake_transport;
static cras_audio_format format;
static size_t cras_bt_device_append_iodev_called;
static size_t cras_bt_device_rm_iodev_called;
static size_t cras_iodev_add_node_called;
static size_t cras_iodev_rm_node_called;
static size_t cras_iodev_set_active_node_called;
static size_t cras_bt_transport_acquire_called;
static size_t cras_bt_transport_configuration_called;
static size_t cras_bt_transport_release_called;
static size_t init_a2dp_called;
static int init_a2dp_return_val;
static size_t destroy_a2dp_called;
static size_t drain_a2dp_called;
static size_t a2dp_block_size_called;
static size_t a2dp_queued_frames_val;
static size_t cras_iodev_free_format_called;
static size_t cras_iodev_free_resources_called;
static int pcm_buf_size_val[MAX_A2DP_ENCODE_CALLS];
static unsigned int a2dp_encode_processed_bytes_val[MAX_A2DP_ENCODE_CALLS];
static unsigned int a2dp_encode_index;
static int a2dp_write_return_val[MAX_A2DP_WRITE_CALLS];
static unsigned int a2dp_write_index;
static cras_audio_area *dummy_audio_area;
static thread_callback write_callback;
static void *write_callback_data;
static const char *fake_device_name = "fake device name";
static const char *cras_bt_device_name_ret;
static unsigned int cras_bt_transport_write_mtu_ret;
void ResetStubData() {
cras_bt_device_append_iodev_called = 0;
cras_bt_device_rm_iodev_called = 0;
cras_iodev_add_node_called = 0;
cras_iodev_rm_node_called = 0;
cras_iodev_set_active_node_called = 0;
cras_bt_transport_acquire_called = 0;
cras_bt_transport_configuration_called = 0;
cras_bt_transport_release_called = 0;
init_a2dp_called = 0;
init_a2dp_return_val = 0;
destroy_a2dp_called = 0;
drain_a2dp_called = 0;
a2dp_block_size_called = 0;
a2dp_queued_frames_val = 0;
cras_iodev_free_format_called = 0;
cras_iodev_free_resources_called = 0;
memset(a2dp_encode_processed_bytes_val, 0,
sizeof(a2dp_encode_processed_bytes_val));
a2dp_encode_index = 0;
a2dp_write_index = 0;
cras_bt_transport_write_mtu_ret = 800;
fake_transport = reinterpret_cast<struct cras_bt_transport *>(0x123);
if (!dummy_audio_area) {
dummy_audio_area = (cras_audio_area*)calloc(1,
sizeof(*dummy_audio_area) + sizeof(cras_channel_area) * 2);
}
write_callback = NULL;
}
int iodev_set_format(struct cras_iodev *iodev,
struct cras_audio_format *fmt)
{
fmt->format = SND_PCM_FORMAT_S16_LE;
fmt->num_channels = 2;
fmt->frame_rate = 44100;
iodev->format = fmt;
return 0;
}
namespace {
static struct timespec time_now;
TEST(A2dpIoInit, InitializeA2dpIodev) {
struct cras_iodev *iodev;
atlog = (audio_thread_event_log *)calloc(1, sizeof(audio_thread_event_log));
ResetStubData();
cras_bt_device_name_ret = NULL;
iodev = a2dp_iodev_create(fake_transport, NULL);
ASSERT_NE(iodev, (void *)NULL);
ASSERT_EQ(iodev->direction, CRAS_STREAM_OUTPUT);
ASSERT_EQ(1, cras_bt_transport_configuration_called);
ASSERT_EQ(1, init_a2dp_called);
ASSERT_EQ(1, cras_bt_device_append_iodev_called);
ASSERT_EQ(1, cras_iodev_add_node_called);
ASSERT_EQ(1, cras_iodev_set_active_node_called);
/* Assert iodev name matches the object path when bt device doesn't
* have its readable name populated. */
ASSERT_STREQ(FAKE_OBJECT_PATH, iodev->info.name);
a2dp_iodev_destroy(iodev);
ASSERT_EQ(1, cras_bt_device_rm_iodev_called);
ASSERT_EQ(1, cras_iodev_rm_node_called);
ASSERT_EQ(1, destroy_a2dp_called);
ASSERT_EQ(1, cras_iodev_free_resources_called);
cras_bt_device_name_ret = fake_device_name;
/* Assert iodev name matches the bt device's name */
iodev = a2dp_iodev_create(fake_transport, NULL);
ASSERT_STREQ(fake_device_name, iodev->info.name);
a2dp_iodev_destroy(iodev);
}
TEST(A2dpIoInit, InitializeFail) {
struct cras_iodev *iodev;
ResetStubData();
init_a2dp_return_val = -1;
iodev = a2dp_iodev_create(fake_transport, NULL);
ASSERT_EQ(iodev, (void *)NULL);
ASSERT_EQ(1, cras_bt_transport_configuration_called);
ASSERT_EQ(1, init_a2dp_called);
ASSERT_EQ(0, cras_bt_device_append_iodev_called);
ASSERT_EQ(0, cras_iodev_add_node_called);
ASSERT_EQ(0, cras_iodev_set_active_node_called);
ASSERT_EQ(0, cras_iodev_rm_node_called);
}
TEST(A2dpIoInit, OpenIodev) {
struct cras_iodev *iodev;
ResetStubData();
iodev = a2dp_iodev_create(fake_transport, NULL);
iodev_set_format(iodev, &format);
iodev->open_dev(iodev);
ASSERT_EQ(1, cras_bt_transport_acquire_called);
iodev->close_dev(iodev);
ASSERT_EQ(1, cras_bt_transport_release_called);
ASSERT_EQ(1, drain_a2dp_called);
ASSERT_EQ(1, cras_iodev_free_format_called);
a2dp_iodev_destroy(iodev);
}
TEST(A2dpIoInit, GetPutBuffer) {
struct cras_iodev *iodev;
struct cras_audio_area *area1, *area2, *area3;
uint8_t *area1_buf;
unsigned frames;
ResetStubData();
iodev = a2dp_iodev_create(fake_transport, NULL);
iodev_set_format(iodev, &format);
iodev->open_dev(iodev);
ASSERT_NE(write_callback, (void *)NULL);
frames = 256;
iodev->get_buffer(iodev, &area1, &frames);
ASSERT_EQ(256, frames);
ASSERT_EQ(256, area1->frames);
area1_buf = area1->channels[0].buf;
/* Test 100 frames(400 bytes) put and all processed. */
a2dp_encode_processed_bytes_val[0] = 4096 * 4;
a2dp_encode_processed_bytes_val[1] = 400;
a2dp_write_index = 0;
a2dp_write_return_val[0] = -EAGAIN;
a2dp_write_return_val[1] = 400;
iodev->put_buffer(iodev, 100);
write_callback(write_callback_data);
// Start with 4k frames.
EXPECT_EQ(4096, pcm_buf_size_val[0]);
EXPECT_EQ(400, pcm_buf_size_val[1]);
iodev->get_buffer(iodev, &area2, &frames);
ASSERT_EQ(256, frames);
ASSERT_EQ(256, area2->frames);
/* Assert buf2 points to the same position as buf1 */
ASSERT_EQ(400, area2->channels[0].buf - area1_buf);
/* Test 100 frames(400 bytes) put, only 360 bytes processed,
* 40 bytes left in pcm buffer.
*/
a2dp_encode_index = 0;
a2dp_encode_processed_bytes_val[0] = 360;
a2dp_encode_processed_bytes_val[1] = 0;
a2dp_write_index = 0;
a2dp_write_return_val[0] = 360;
a2dp_write_return_val[1] = 0;
iodev->put_buffer(iodev, 100);
write_callback(write_callback_data);
EXPECT_EQ(400, pcm_buf_size_val[0]);
ASSERT_EQ(40, pcm_buf_size_val[1]);
iodev->get_buffer(iodev, &area3, &frames);
/* Existing buffer not completed processed, assert new buffer starts from
* current write pointer.
*/
ASSERT_EQ(256, frames);
EXPECT_EQ(800, area3->channels[0].buf - area1_buf);
a2dp_iodev_destroy(iodev);
}
TEST(A2dpIoInif, FramesQueued) {
struct cras_iodev *iodev;
struct cras_audio_area *area;
unsigned frames;
ResetStubData();
iodev = a2dp_iodev_create(fake_transport, NULL);
iodev_set_format(iodev, &format);
time_now.tv_sec = 0;
time_now.tv_nsec = 0;
iodev->open_dev(iodev);
ASSERT_NE(write_callback, (void *)NULL);
frames = 256;
iodev->get_buffer(iodev, &area, &frames);
ASSERT_EQ(256, frames);
ASSERT_EQ(256, area->frames);
/* Put 100 frames, proccessed 400 bytes to a2dp buffer.
* Assume 200 bytes written out, queued 50 frames in a2dp buffer.
*/
a2dp_encode_processed_bytes_val[0] = 400;
a2dp_encode_processed_bytes_val[1] = 0;
a2dp_write_return_val[0] = 200;
a2dp_write_return_val[1] = -EAGAIN;
a2dp_queued_frames_val = 50;
time_now.tv_sec = 0;
time_now.tv_nsec = 1000000;
iodev->put_buffer(iodev, 300);
write_callback(write_callback_data);
EXPECT_EQ(350, iodev->frames_queued(iodev));
/* After writing another 200 frames, check for correct buffer level. */
time_now.tv_sec = 0;
time_now.tv_nsec = 2000000;
a2dp_encode_index = 0;
a2dp_write_index = 0;
a2dp_encode_processed_bytes_val[0] = 800;
write_callback(write_callback_data);
/* 1000000 nsec has passed, estimated queued frames adjusted by 44 */
EXPECT_EQ(256, iodev->frames_queued(iodev));
EXPECT_EQ(1200, pcm_buf_size_val[0]);
EXPECT_EQ(400, pcm_buf_size_val[1]);
/* Queued frames and new put buffer are all written */
a2dp_encode_processed_bytes_val[0] = 400;
a2dp_encode_processed_bytes_val[1] = 0;
a2dp_encode_index = 0;
a2dp_write_return_val[0] = 400;
a2dp_write_return_val[1] = -EAGAIN;
a2dp_write_index = 0;
/* Add wnother 200 samples, get back to the original level. */
time_now.tv_sec = 0;
time_now.tv_nsec = 50000000;
a2dp_encode_processed_bytes_val[0] = 600;
iodev->put_buffer(iodev, 200);
EXPECT_EQ(1200, pcm_buf_size_val[0]);
EXPECT_EQ(200, iodev->frames_queued(iodev));
}
} // namespace
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
extern "C" {
int cras_bt_transport_configuration(const struct cras_bt_transport *transport,
void *configuration, int len)
{
cras_bt_transport_configuration_called++;
return 0;
}
int cras_bt_transport_acquire(struct cras_bt_transport *transport)
{
cras_bt_transport_acquire_called++;
return 0;
}
int cras_bt_transport_release(struct cras_bt_transport *transport)
{
cras_bt_transport_release_called++;
return 0;
}
int cras_bt_transport_fd(const struct cras_bt_transport *transport)
{
return 0;
}
const char *cras_bt_transport_object_path(
const struct cras_bt_transport *transport)
{
return FAKE_OBJECT_PATH;
}
uint16_t cras_bt_transport_write_mtu(const struct cras_bt_transport *transport)
{
return cras_bt_transport_write_mtu_ret;
}
void cras_iodev_free_format(struct cras_iodev *iodev)
{
cras_iodev_free_format_called++;
}
void cras_iodev_free_resources(struct cras_iodev *iodev)
{
cras_iodev_free_resources_called++;
}
// Cras iodev
void cras_iodev_add_node(struct cras_iodev *iodev, struct cras_ionode *node)
{
cras_iodev_add_node_called++;
iodev->nodes = node;
}
void cras_iodev_rm_node(struct cras_iodev *iodev, struct cras_ionode *node)
{
cras_iodev_rm_node_called++;
iodev->nodes = NULL;
}
void cras_iodev_set_active_node(struct cras_iodev *iodev,
struct cras_ionode *node)
{
cras_iodev_set_active_node_called++;
iodev->active_node = node;
}
// From cras_bt_transport
struct cras_bt_device *cras_bt_transport_device(
const struct cras_bt_transport *transport)
{
return reinterpret_cast<struct cras_bt_device *>(0x456);;
}
enum cras_bt_device_profile cras_bt_transport_profile(
const struct cras_bt_transport *transport)
{
return CRAS_BT_DEVICE_PROFILE_A2DP_SOURCE;
}
// From cras_bt_device
const char *cras_bt_device_name(const struct cras_bt_device *device)
{
return cras_bt_device_name_ret;
}
const char *cras_bt_device_object_path(const struct cras_bt_device *device) {
return "/org/bluez/hci0/dev_1A_2B_3C_4D_5E_6F";
}
void cras_bt_device_append_iodev(struct cras_bt_device *device,
struct cras_iodev *iodev,
enum cras_bt_device_profile profile)
{
cras_bt_device_append_iodev_called++;
}
void cras_bt_device_rm_iodev(struct cras_bt_device *device,
struct cras_iodev *iodev)
{
cras_bt_device_rm_iodev_called++;
}
int init_a2dp(struct a2dp_info *a2dp, a2dp_sbc_t *sbc)
{
init_a2dp_called++;
return init_a2dp_return_val;
}
void destroy_a2dp(struct a2dp_info *a2dp)
{
destroy_a2dp_called++;
}
int a2dp_codesize(struct a2dp_info *a2dp)
{
return 512;
}
int a2dp_block_size(struct a2dp_info *a2dp, int encoded_bytes)
{
a2dp_block_size_called++;
// Assumes a2dp block size is 1:1 before/after encode.
return encoded_bytes;
}
int a2dp_queued_frames(struct a2dp_info *a2dp)
{
return a2dp_queued_frames_val;
}
void a2dp_drain(struct a2dp_info *a2dp)
{
drain_a2dp_called++;
}
int a2dp_encode(struct a2dp_info *a2dp, const void *pcm_buf, int pcm_buf_size,
int format_bytes, size_t link_mtu) {
unsigned int processed;
if (a2dp_encode_index == MAX_A2DP_ENCODE_CALLS)
return 0;
processed = a2dp_encode_processed_bytes_val[a2dp_encode_index];
pcm_buf_size_val[a2dp_encode_index] = pcm_buf_size;
a2dp_encode_index++;
return processed;
}
int a2dp_write(struct a2dp_info *a2dp, int stream_fd, size_t link_mtu) {
return a2dp_write_return_val[a2dp_write_index++];;
}
int clock_gettime(clockid_t clk_id, struct timespec *tp) {
*tp = time_now;
return 0;
}
void cras_iodev_init_audio_area(struct cras_iodev *iodev,
int num_channels) {
iodev->area = dummy_audio_area;
}
void cras_iodev_free_audio_area(struct cras_iodev *iodev) {
}
void cras_audio_area_config_buf_pointers(struct cras_audio_area *area,
const struct cras_audio_format *fmt,
uint8_t *base_buffer)
{
dummy_audio_area->channels[0].buf = base_buffer;
}
// From audio_thread
struct audio_thread_event_log *atlog;
void audio_thread_add_write_callback(int fd, thread_callback cb, void *data) {
write_callback = cb;
write_callback_data = data;
}
void audio_thread_rm_callback(int fd) {
}
void audio_thread_enable_callback(int fd, int enabled) {
}
}