blob: c1103afb1f592c585cfe753d5f212b732b43f24f [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 <gtest/gtest.h>
#include <stdio.h>
extern "C" {
#include "cras_messages.h"
// Include C file to test static functions.
#include "cras_client.c"
}
static const cras_stream_id_t FIRST_STREAM_ID = 1;
static int pthread_create_called;
static int pthread_join_called;
static int pthread_cond_timedwait_called;
static int pthread_cond_timedwait_retval;
static int close_called;
static int sendmsg_called;
static void* mmap_return_value;
static int samples_ready_called;
static int samples_ready_frames_value;
static uint8_t* samples_ready_samples_value;
static int pthread_create_returned_value;
namespace {
void InitStaticVariables() {
pthread_create_called = 0;
pthread_join_called = 0;
pthread_cond_timedwait_called = 0;
pthread_cond_timedwait_retval = 0;
close_called = 0;
sendmsg_called = 0;
pthread_create_returned_value = 0;
mmap_return_value = NULL;
samples_ready_called = 0;
samples_ready_frames_value = 0;
}
class CrasClientTestSuite : public testing::Test {
protected:
struct cras_audio_shm* InitShm() {
struct cras_audio_shm* shm =
static_cast<struct cras_audio_shm*>(calloc(1, sizeof(*shm)));
shm->header =
static_cast<cras_audio_shm_header*>(calloc(1, sizeof(*shm->header)));
cras_shm_set_frame_bytes(shm, 4);
uint32_t used_size = shm_writable_frames_ * 4;
cras_shm_set_used_size(shm, used_size);
shm->samples_info.length = used_size * 2;
memcpy(&shm->header->config, &shm->config, sizeof(shm->config));
return shm;
}
virtual void SetUp() {
shm_writable_frames_ = 100;
InitStaticVariables();
memset(&client_, 0, sizeof(client_));
client_.server_fd_state = CRAS_SOCKET_STATE_CONNECTED;
memset(&stream_, 0, sizeof(stream_));
stream_.id = FIRST_STREAM_ID;
struct cras_stream_params* config =
static_cast<cras_stream_params*>(calloc(1, sizeof(*config)));
config->buffer_frames = 1024;
config->cb_threshold = 512;
stream_.config = config;
}
virtual void TearDown() {
if (stream_.config) {
free(stream_.config);
stream_.config = NULL;
}
if (stream_.shm) {
free(stream_.shm->header);
}
free(stream_.shm);
stream_.shm = NULL;
}
void StreamConnected(CRAS_STREAM_DIRECTION direction);
void StreamConnectedFail(CRAS_STREAM_DIRECTION direction);
struct client_stream stream_;
struct cras_client client_;
int shm_writable_frames_;
};
void set_audio_format(struct cras_audio_format* format,
snd_pcm_format_t pcm_format,
size_t frame_rate,
size_t num_channels) {
format->format = pcm_format;
format->frame_rate = frame_rate;
format->num_channels = num_channels;
for (size_t i = 0; i < CRAS_CH_MAX; ++i)
format->channel_layout[i] = i < num_channels ? i : -1;
}
int capture_samples_ready(cras_client* client,
cras_stream_id_t stream_id,
uint8_t* samples,
size_t frames,
const timespec* sample_ts,
void* arg) {
samples_ready_called++;
samples_ready_samples_value = samples;
samples_ready_frames_value = frames;
return frames;
}
TEST_F(CrasClientTestSuite, HandleCaptureDataReady) {
struct cras_audio_shm* shm;
stream_.direction = CRAS_STREAM_INPUT;
shm_writable_frames_ = 480;
shm = InitShm();
stream_.shm = shm;
stream_.config->buffer_frames = 480;
stream_.config->cb_threshold = 480;
stream_.config->aud_cb = capture_samples_ready;
stream_.config->unified_cb = 0;
shm->header->write_buf_idx = 0;
shm->header->read_buf_idx = 0;
shm->header->write_offset[0] = 480 * 4;
shm->header->read_offset[0] = 0;
/* Normal scenario: read buffer has full of data written,
* handle_capture_data_ready() should consume all 480 frames and move
* read_buf_idx to the next buffer. */
handle_capture_data_ready(&stream_, 480);
EXPECT_EQ(1, samples_ready_called);
EXPECT_EQ(480, samples_ready_frames_value);
EXPECT_EQ(cras_shm_buff_for_idx(shm, 0), samples_ready_samples_value);
EXPECT_EQ(1, shm->header->read_buf_idx);
EXPECT_EQ(0, shm->header->write_offset[0]);
EXPECT_EQ(0, shm->header->read_offset[0]);
/* At the beginning of overrun: handle_capture_data_ready() should not
* proceed to call audio_cb because there's no data captured. */
shm->header->read_buf_idx = 0;
shm->header->write_offset[0] = 0;
shm->header->read_offset[0] = 0;
handle_capture_data_ready(&stream_, 480);
EXPECT_EQ(1, samples_ready_called);
EXPECT_EQ(0, shm->header->read_buf_idx);
/* In the middle of overrun: partially written buffer should trigger
* audio_cb, feed the full-sized read buffer to client. */
shm->header->read_buf_idx = 0;
shm->header->write_offset[0] = 123;
shm->header->read_offset[0] = 0;
handle_capture_data_ready(&stream_, 480);
EXPECT_EQ(1, samples_ready_called);
EXPECT_EQ(0, shm->header->read_buf_idx);
}
void CrasClientTestSuite::StreamConnected(CRAS_STREAM_DIRECTION direction) {
struct cras_client_stream_connected msg;
int shm_fds[2] = {0, 1};
int shm_max_size = 600;
size_t format_bytes;
size_t effects = 123;
struct cras_audio_shm_header* header;
stream_.direction = direction;
set_audio_format(&stream_.config->format, SND_PCM_FORMAT_S16_LE, 48000, 4);
struct cras_audio_format server_format;
set_audio_format(&server_format, SND_PCM_FORMAT_S16_LE, 44100, 2);
// Initialize shm area
format_bytes = cras_get_format_bytes(&server_format);
header = (struct cras_audio_shm_header*)calloc(1, sizeof(*header));
header->config.frame_bytes = format_bytes;
header->config.used_size = shm_writable_frames_ * format_bytes;
mmap_return_value = header;
cras_fill_client_stream_connected(&msg, 0, stream_.id, &server_format,
shm_max_size, effects);
stream_connected(&stream_, &msg, shm_fds, 2);
EXPECT_EQ(CRAS_THREAD_RUNNING, stream_.thread.state);
EXPECT_EQ(header, stream_.shm->header);
}
TEST_F(CrasClientTestSuite, InputStreamConnected) {
StreamConnected(CRAS_STREAM_INPUT);
}
TEST_F(CrasClientTestSuite, OutputStreamConnected) {
StreamConnected(CRAS_STREAM_OUTPUT);
}
void CrasClientTestSuite::StreamConnectedFail(CRAS_STREAM_DIRECTION direction) {
struct cras_client_stream_connected msg;
int shm_fds[2] = {0, 1};
int shm_max_size = 600;
size_t format_bytes;
size_t effects = 123;
struct cras_audio_shm_header header;
int rc;
stream_.direction = direction;
set_audio_format(&stream_.config->format, SND_PCM_FORMAT_S16_LE, 48000, 4);
struct cras_audio_format server_format;
set_audio_format(&server_format, SND_PCM_FORMAT_S16_LE, 44100, 2);
// Thread setup
rc = pipe(stream_.wake_fds);
ASSERT_EQ(0, rc);
stream_.thread.state = CRAS_THREAD_WARMUP;
// Initialize shm area
format_bytes = cras_get_format_bytes(&server_format);
memset(&header, 0, sizeof(header));
header.config.frame_bytes = format_bytes;
header.config.used_size = shm_writable_frames_ * format_bytes;
mmap_return_value = &header;
// Put an error in the message.
cras_fill_client_stream_connected(&msg, 1, stream_.id, &server_format,
shm_max_size, effects);
stream_connected(&stream_, &msg, shm_fds, 2);
EXPECT_EQ(CRAS_THREAD_STOP, stream_.thread.state);
EXPECT_EQ(4, close_called); // close the pipefds and shm_fds
}
TEST_F(CrasClientTestSuite, InputStreamConnectedFail) {
StreamConnectedFail(CRAS_STREAM_INPUT);
}
TEST_F(CrasClientTestSuite, OutputStreamConnectedFail) {
StreamConnectedFail(CRAS_STREAM_OUTPUT);
}
TEST_F(CrasClientTestSuite, AddAndRemoveStream) {
cras_stream_id_t stream_id;
struct cras_disconnect_stream_message msg;
int serv_fds[2];
int rc;
// Dynamically allocate the stream so that it can be freed later.
struct client_stream* stream_ptr =
(struct client_stream*)malloc(sizeof(*stream_ptr));
memcpy(stream_ptr, &stream_, sizeof(client_stream));
stream_ptr->config =
(struct cras_stream_params*)malloc(sizeof(*(stream_ptr->config)));
memcpy(stream_ptr->config, stream_.config, sizeof(*(stream_.config)));
stream_ptr->wake_fds[0] = -1;
stream_ptr->wake_fds[1] = -1;
pthread_cond_timedwait_retval = ETIMEDOUT;
EXPECT_EQ(-ETIMEDOUT, client_thread_add_stream(&client_, stream_ptr,
&stream_id, NO_DEVICE));
EXPECT_EQ(pthread_cond_timedwait_called, 1);
EXPECT_EQ(pthread_join_called, 0);
InitStaticVariables();
EXPECT_EQ(
0, client_thread_add_stream(&client_, stream_ptr, &stream_id, NO_DEVICE));
EXPECT_EQ(&client_, stream_ptr->client);
EXPECT_EQ(stream_id, stream_ptr->id);
EXPECT_EQ(pthread_create_called, 1);
EXPECT_NE(-1, stream_ptr->wake_fds[0]);
EXPECT_NE(-1, stream_ptr->wake_fds[1]);
EXPECT_EQ(1, sendmsg_called); // send connect message to server
EXPECT_EQ(stream_ptr, stream_from_id(&client_, stream_id));
stream_ptr->thread.state = CRAS_THREAD_RUNNING;
rc = pipe(serv_fds);
EXPECT_EQ(0, rc);
client_.server_fd = serv_fds[1];
client_.server_fd_state = CRAS_SOCKET_STATE_CONNECTED;
EXPECT_EQ(0, client_thread_rm_stream(&client_, stream_id));
rc = read(serv_fds[0], &msg, sizeof(msg));
EXPECT_EQ(sizeof(msg), rc);
EXPECT_EQ(stream_id, msg.stream_id);
EXPECT_EQ(1, pthread_join_called);
EXPECT_EQ(NULL, stream_from_id(&client_, stream_id));
}
TEST_F(CrasClientTestSuite, SetOutputStreamVolume) {
cras_stream_id_t stream_id;
client_thread_add_stream(&client_, &stream_, &stream_id, NO_DEVICE);
EXPECT_EQ(&stream_, stream_from_id(&client_, stream_id));
/* Set volume before stream connected. */
client_thread_set_stream_volume(&client_, stream_id, 0.3f);
StreamConnected(CRAS_STREAM_OUTPUT);
EXPECT_EQ(0.3f, cras_shm_get_volume_scaler(stream_.shm));
/* Set volume after stream connected. */
client_thread_set_stream_volume(&client_, stream_id, 0.6f);
EXPECT_EQ(0.6f, cras_shm_get_volume_scaler(stream_.shm));
}
TEST_F(CrasClientTestSuite, SetInputStreamVolume) {
cras_stream_id_t stream_id;
client_thread_add_stream(&client_, &stream_, &stream_id, NO_DEVICE);
EXPECT_EQ(&stream_, stream_from_id(&client_, stream_id));
/* Set volume before stream connected. */
client_thread_set_stream_volume(&client_, stream_id, 0.3f);
StreamConnected(CRAS_STREAM_INPUT);
EXPECT_EQ(0.3f, cras_shm_get_volume_scaler(stream_.shm));
/* Set volume after stream connected. */
client_thread_set_stream_volume(&client_, stream_id, 0.6f);
EXPECT_EQ(0.6f, cras_shm_get_volume_scaler(stream_.shm));
}
TEST(CrasClientTest, InitStreamVolume) {
cras_stream_id_t stream_id;
struct cras_stream_params config;
struct add_stream_command_message cmd_msg;
int rc;
struct cras_client client;
memset(&client, 0, sizeof(client));
memset(&config, 0, sizeof(config));
client.server_fd_state = CRAS_SOCKET_STATE_CONNECTED;
config.aud_cb = reinterpret_cast<cras_playback_cb_t>(0x123);
config.err_cb = reinterpret_cast<cras_error_cb_t>(0x456);
client.thread.state = CRAS_THREAD_RUNNING;
rc = pipe(client.command_reply_fds);
EXPECT_EQ(0, rc);
rc = pipe(client.command_fds);
EXPECT_EQ(0, rc);
rc = write(client.command_reply_fds[1], &rc, sizeof(rc));
cras_client_add_stream(&client, &stream_id, &config);
rc = read(client.command_fds[0], &cmd_msg, sizeof(cmd_msg));
EXPECT_EQ(sizeof(cmd_msg), rc);
EXPECT_NE((void*)NULL, cmd_msg.stream);
EXPECT_EQ(1.0f, cmd_msg.stream->volume_scaler);
if (cmd_msg.stream->config)
free(cmd_msg.stream->config);
free(cmd_msg.stream);
}
} // namespace
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
/* stubs */
extern "C" {
ssize_t sendmsg(int sockfd, const struct msghdr* msg, int flags) {
++sendmsg_called;
return msg->msg_iov->iov_len;
}
int close(int fd) {
++close_called;
return 0;
}
int pthread_create(pthread_t* thread,
const pthread_attr_t* attr,
void* (*start_routine)(void*),
void* arg) {
++pthread_create_called;
return pthread_create_returned_value;
}
int pthread_join(pthread_t thread, void** retval) {
++pthread_join_called;
return 0;
}
int pthread_cond_timedwait(pthread_cond_t* __restrict cond,
pthread_mutex_t* __restrict mutex,
const struct timespec* __restrict timeout) {
++pthread_cond_timedwait_called;
return pthread_cond_timedwait_retval;
}
int clock_gettime(clockid_t clk_id, struct timespec* tp) {
tp->tv_sec = 0;
tp->tv_nsec = 0;
return 0;
}
void* mmap(void* addr,
size_t length,
int prot,
int flags,
int fd,
off_t offset) {
return mmap_return_value;
}
struct cras_audio_format* cras_audio_format_create(snd_pcm_format_t format,
size_t frame_rate,
size_t num_channels) {
return reinterpret_cast<struct cras_audio_format*>(0x123);
}
void cras_audio_format_destroy(struct cras_audio_format* fmt) {}
}