blob: 65e712fb3a9d4a55ac3a2e5ab1c4453d5223b6d2 [file] [log] [blame]
// Copyright 2018 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_apm_list.h"
#include "cras_audio_area.h"
#include "cras_dsp_pipeline.h"
#include "cras_iodev.h"
#include "cras_iodev_list.h"
#include "cras_types.h"
#include "float_buffer.h"
#include "webrtc_apm.h"
}
#define FILENAME_TEMPLATE "ApmTest.XXXXXX"
namespace {
static void* stream_ptr = reinterpret_cast<void*>(0x123);
static void* dev_ptr = reinterpret_cast<void*>(0x345);
static void* dev_ptr2 = reinterpret_cast<void*>(0x678);
static struct cras_apm_list* list;
static struct cras_audio_area fake_audio_area;
static unsigned int dsp_util_interleave_frames;
static unsigned int webrtc_apm_process_stream_f_called;
static unsigned int webrtc_apm_process_reverse_stream_f_called;
static device_enabled_callback_t device_enabled_callback_val;
static struct ext_dsp_module* ext_dsp_module_value;
static struct cras_ionode fake_node;
static struct cras_iodev fake_iodev;
static int webrtc_apm_create_called;
static bool cras_iodev_is_aec_use_case_ret;
static dictionary* webrtc_apm_create_aec_ini_val = NULL;
static dictionary* webrtc_apm_create_apm_ini_val = NULL;
TEST(ApmList, ApmListCreate) {
list = cras_apm_list_create(stream_ptr, 0);
EXPECT_EQ((void*)NULL, list);
list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
EXPECT_NE((void*)NULL, list);
EXPECT_EQ(APM_ECHO_CANCELLATION, cras_apm_list_get_effects(list));
cras_apm_list_destroy(list);
}
static char* prepare_tempdir() {
char dirname[sizeof(FILENAME_TEMPLATE) + 1];
char filename[64];
char* tempdir;
FILE* fp;
strcpy(dirname, FILENAME_TEMPLATE);
tempdir = mkdtemp(dirname);
snprintf(filename, 64, "%s/apm.ini", tempdir);
fp = fopen(filename, "w");
fprintf(fp, "%s", "[foo]\n");
fclose(fp);
fp = NULL;
snprintf(filename, 64, "%s/aec.ini", tempdir);
fp = fopen(filename, "w");
fprintf(fp, "%s", "[bar]\n");
fclose(fp);
fp = NULL;
return strdup(tempdir);
}
static void delete_tempdir(char* dir) {
char filename[64];
snprintf(filename, 64, "%s/apm.ini", dir);
unlink(filename);
snprintf(filename, 64, "%s/aec.ini", dir);
unlink(filename);
rmdir(dir);
}
static void init_channel_layout(struct cras_audio_format* fmt) {
int i;
for (i = 0; i < CRAS_CH_MAX; i++)
fmt->channel_layout[i] = -1;
}
TEST(ApmList, AddApmInputDevUnuseFirstChannel) {
struct cras_audio_format fmt;
struct cras_audio_format* val;
struct cras_apm* apm;
int ch;
const int num_test_casts = 9;
int test_layouts[num_test_casts][CRAS_CH_MAX] = {
{0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{0, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{2, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{3, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1},
{3, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1}};
int test_num_channels[num_test_casts] = {1, 2, 2, 2, 2, 3, 4, 4, 4};
fmt.frame_rate = 48000;
fmt.format = SND_PCM_FORMAT_S16_LE;
cras_apm_list_init("");
list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
EXPECT_NE((void*)NULL, list);
for (int i = 0; i < num_test_casts; i++) {
fmt.num_channels = test_num_channels[i];
init_channel_layout(&fmt);
for (ch = 0; ch < CRAS_CH_MAX; ch++)
fmt.channel_layout[ch] = test_layouts[i][ch];
/* Input dev is of aec use case. */
apm = cras_apm_list_add_apm(list, dev_ptr, &fmt, 1);
EXPECT_NE((void*)NULL, apm);
/* Assert that the post-processing format never has an unset
* first channel in the layout. */
bool first_channel_found_in_layout = 0;
val = cras_apm_list_get_format(apm);
for (ch = 0; ch < CRAS_CH_MAX; ch++)
if (0 == val->channel_layout[ch])
first_channel_found_in_layout = 1;
EXPECT_EQ(1, first_channel_found_in_layout);
cras_apm_list_remove_apm(list, dev_ptr);
}
cras_apm_list_destroy(list);
cras_apm_list_deinit();
}
TEST(ApmList, AddRemoveApm) {
struct cras_audio_format fmt;
char* dir;
fmt.num_channels = 2;
fmt.frame_rate = 48000;
fmt.format = SND_PCM_FORMAT_S16_LE;
fake_iodev.active_node = &fake_node;
fake_node.type = CRAS_NODE_TYPE_INTERNAL_SPEAKER;
dir = prepare_tempdir();
cras_apm_list_init(dir);
cras_iodev_is_aec_use_case_ret = 1;
list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
EXPECT_NE((void*)NULL, list);
/* Input dev is of aec use case. */
EXPECT_NE((void*)NULL, cras_apm_list_add_apm(list, dev_ptr, &fmt, 1));
EXPECT_NE((void*)NULL, webrtc_apm_create_aec_ini_val);
EXPECT_NE((void*)NULL, webrtc_apm_create_apm_ini_val);
EXPECT_EQ((void*)NULL, cras_apm_list_get_active_apm(stream_ptr, dev_ptr));
cras_apm_list_start_apm(list, dev_ptr);
EXPECT_NE((void*)NULL, cras_apm_list_get_active_apm(stream_ptr, dev_ptr));
EXPECT_EQ((void*)NULL, cras_apm_list_get_active_apm(stream_ptr, dev_ptr2));
/* Input dev is not of aec use case. */
EXPECT_NE((void*)NULL, cras_apm_list_add_apm(list, dev_ptr2, &fmt, 0));
EXPECT_EQ((void*)NULL, webrtc_apm_create_aec_ini_val);
EXPECT_EQ((void*)NULL, webrtc_apm_create_apm_ini_val);
cras_apm_list_start_apm(list, dev_ptr2);
cras_apm_list_stop_apm(list, dev_ptr);
EXPECT_EQ((void*)NULL, cras_apm_list_get_active_apm(stream_ptr, dev_ptr));
EXPECT_NE((void*)NULL, cras_apm_list_get_active_apm(stream_ptr, dev_ptr2));
cras_apm_list_stop_apm(list, dev_ptr2);
cras_apm_list_remove_apm(list, dev_ptr);
cras_apm_list_remove_apm(list, dev_ptr2);
cras_apm_list_destroy(list);
cras_apm_list_deinit();
delete_tempdir(dir);
free(dir);
}
TEST(ApmList, OutputTypeNotAecUseCase) {
struct cras_audio_format fmt;
char* dir;
fmt.num_channels = 2;
fmt.frame_rate = 48000;
fmt.format = SND_PCM_FORMAT_S16_LE;
fake_iodev.active_node = &fake_node;
dir = prepare_tempdir();
cras_apm_list_init(dir);
list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
EXPECT_NE((void*)NULL, list);
/* Output device is of aec use case. */
cras_iodev_is_aec_use_case_ret = 1;
EXPECT_NE((void*)NULL, cras_apm_list_add_apm(list, dev_ptr, &fmt, 1));
EXPECT_NE((void*)NULL, webrtc_apm_create_aec_ini_val);
EXPECT_NE((void*)NULL, webrtc_apm_create_apm_ini_val);
cras_apm_list_remove_apm(list, dev_ptr);
/* Output device is not of aec use case. */
cras_iodev_is_aec_use_case_ret = 0;
EXPECT_NE((void*)NULL, cras_apm_list_add_apm(list, dev_ptr, &fmt, 1));
EXPECT_EQ((void*)NULL, webrtc_apm_create_aec_ini_val);
EXPECT_EQ((void*)NULL, webrtc_apm_create_apm_ini_val);
cras_apm_list_remove_apm(list, dev_ptr);
cras_apm_list_destroy(list);
cras_apm_list_deinit();
delete_tempdir(dir);
free(dir);
}
TEST(ApmList, ApmProcessForwardBuffer) {
struct cras_apm* apm;
struct cras_audio_format fmt;
struct cras_audio_area* area;
struct float_buffer* buf;
fmt.num_channels = 2;
fmt.frame_rate = 48000;
fmt.format = SND_PCM_FORMAT_S16_LE;
init_channel_layout(&fmt);
fmt.channel_layout[CRAS_CH_FL] = 0;
fmt.channel_layout[CRAS_CH_FR] = 1;
cras_apm_list_init("");
list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
EXPECT_NE((void*)NULL, list);
apm = cras_apm_list_add_apm(list, dev_ptr, &fmt, 1);
buf = float_buffer_create(500, 2);
float_buffer_written(buf, 300);
webrtc_apm_process_stream_f_called = 0;
cras_apm_list_process(apm, buf, 0);
EXPECT_EQ(0, webrtc_apm_process_stream_f_called);
area = cras_apm_list_get_processed(apm);
EXPECT_EQ(0, area->frames);
float_buffer_reset(buf);
float_buffer_written(buf, 200);
cras_apm_list_process(apm, buf, 0);
area = cras_apm_list_get_processed(apm);
EXPECT_EQ(1, webrtc_apm_process_stream_f_called);
EXPECT_EQ(480, dsp_util_interleave_frames);
EXPECT_EQ(480, area->frames);
/* Put some processed frames. Another apm_list process will not call
* into webrtc_apm because the processed buffer is not yet empty.
*/
cras_apm_list_put_processed(apm, 200);
float_buffer_reset(buf);
float_buffer_written(buf, 500);
cras_apm_list_process(apm, buf, 0);
EXPECT_EQ(1, webrtc_apm_process_stream_f_called);
/* Put another 280 processed frames, so it's now ready for webrtc_apm
* to process another chunk of 480 frames (10ms) data.
*/
cras_apm_list_put_processed(apm, 280);
cras_apm_list_process(apm, buf, 0);
EXPECT_EQ(2, webrtc_apm_process_stream_f_called);
float_buffer_destroy(&buf);
cras_apm_list_destroy(list);
cras_apm_list_deinit();
}
TEST(ApmList, ApmProcessReverseData) {
struct cras_apm* apm;
struct cras_audio_format fmt;
struct float_buffer* buf;
float* const* rp;
unsigned int nread;
struct cras_iodev fake_iodev;
fmt.num_channels = 2;
fmt.frame_rate = 48000;
fmt.format = SND_PCM_FORMAT_S16_LE;
fake_iodev.direction = CRAS_STREAM_OUTPUT;
device_enabled_callback_val = NULL;
ext_dsp_module_value = NULL;
webrtc_apm_process_reverse_stream_f_called = 0;
cras_apm_list_init("");
EXPECT_NE((void*)NULL, device_enabled_callback_val);
device_enabled_callback_val(&fake_iodev, NULL);
EXPECT_NE((void*)NULL, ext_dsp_module_value);
EXPECT_NE((void*)NULL, ext_dsp_module_value->ports);
buf = float_buffer_create(500, 2);
float_buffer_written(buf, 500);
nread = 500;
rp = float_buffer_read_pointer(buf, 0, &nread);
for (int i = 0; i < buf->num_channels; i++)
ext_dsp_module_value->ports[i] = rp[i];
ext_dsp_module_value->configure(ext_dsp_module_value, 800, 2, 48000);
ext_dsp_module_value->run(ext_dsp_module_value, 500);
EXPECT_EQ(0, webrtc_apm_process_reverse_stream_f_called);
list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
EXPECT_NE((void*)NULL, list);
apm = cras_apm_list_add_apm(list, dev_ptr, &fmt, 1);
cras_apm_list_start_apm(list, dev_ptr);
ext_dsp_module_value->run(ext_dsp_module_value, 250);
EXPECT_EQ(0, webrtc_apm_process_reverse_stream_f_called);
ext_dsp_module_value->run(ext_dsp_module_value, 250);
EXPECT_EQ(1, webrtc_apm_process_reverse_stream_f_called);
float_buffer_destroy(&buf);
cras_apm_list_destroy(list);
cras_apm_list_deinit();
}
TEST(ApmList, StreamAddToAlreadyOpenedDev) {
struct cras_audio_format fmt;
struct cras_apm *apm1, *apm2;
fmt.num_channels = 2;
fmt.frame_rate = 48000;
fmt.format = SND_PCM_FORMAT_S16_LE;
cras_apm_list_init("");
webrtc_apm_create_called = 0;
list = cras_apm_list_create(stream_ptr, APM_ECHO_CANCELLATION);
EXPECT_NE((void*)NULL, list);
apm1 = cras_apm_list_add_apm(list, dev_ptr, &fmt, 1);
EXPECT_EQ(1, webrtc_apm_create_called);
EXPECT_NE((void*)NULL, apm1);
apm2 = cras_apm_list_add_apm(list, dev_ptr, &fmt, 1);
EXPECT_EQ(1, webrtc_apm_create_called);
EXPECT_EQ(apm1, apm2);
cras_apm_list_destroy(list);
cras_apm_list_deinit();
}
extern "C" {
int cras_iodev_list_set_device_enabled_callback(
device_enabled_callback_t enabled_cb,
device_disabled_callback_t disabled_cb,
void* cb_data) {
device_enabled_callback_val = enabled_cb;
return 0;
}
struct cras_iodev* cras_iodev_list_get_first_enabled_iodev(
enum CRAS_STREAM_DIRECTION direction) {
return &fake_iodev;
}
void cras_iodev_set_ext_dsp_module(struct cras_iodev* iodev,
struct ext_dsp_module* ext) {
ext_dsp_module_value = ext;
}
bool cras_iodev_is_aec_use_case(const struct cras_ionode* node) {
return cras_iodev_is_aec_use_case_ret;
}
struct cras_audio_area* cras_audio_area_create(int num_channels) {
return &fake_audio_area;
}
void cras_audio_area_destroy(struct cras_audio_area* area) {}
void cras_audio_area_config_channels(struct cras_audio_area* area,
const struct cras_audio_format* fmt) {}
void cras_audio_area_config_buf_pointers(struct cras_audio_area* area,
const struct cras_audio_format* fmt,
uint8_t* base_buffer) {}
void dsp_util_interleave(float* const* input,
int16_t* output,
int channels,
snd_pcm_format_t format,
int frames) {
dsp_util_interleave_frames = frames;
}
struct aec_config* aec_config_get(const char* device_config_dir) {
return NULL;
}
void aec_config_dump(struct aec_config* config) {}
struct apm_config* apm_config_get(const char* device_config_dir) {
return NULL;
}
void apm_config_dump(struct apm_config* config) {}
webrtc_apm webrtc_apm_create(unsigned int num_channels,
unsigned int frame_rate,
dictionary* aec_ini,
dictionary* apm_ini) {
webrtc_apm_create_called++;
webrtc_apm_create_aec_ini_val = aec_ini;
webrtc_apm_create_apm_ini_val = apm_ini;
return reinterpret_cast<webrtc_apm>(0x11);
}
void webrtc_apm_dump_configs(dictionary* aec_ini, dictionary* apm_ini) {}
void webrtc_apm_destroy(webrtc_apm apm) {
return;
}
int webrtc_apm_process_stream_f(webrtc_apm ptr,
int num_channels,
int rate,
float* const* data) {
webrtc_apm_process_stream_f_called++;
return 0;
}
int webrtc_apm_process_reverse_stream_f(webrtc_apm ptr,
int num_channels,
int rate,
float* const* data) {
webrtc_apm_process_reverse_stream_f_called++;
return 0;
}
int webrtc_apm_aec_dump(webrtc_apm ptr,
void** work_queue,
int start,
FILE* handle) {
return 0;
}
} // extern "C"
} // namespace
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}