| // Copyright (c) 2012 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 "cras_dsp_pipeline.h" |
| |
| #include <gtest/gtest.h> |
| |
| #include "cras_config.h" |
| #include "cras_dsp_module.h" |
| |
| #define MAX_MODULES 10 |
| #define MAX_MOCK_PORTS 30 |
| #define FILENAME_TEMPLATE "DspIniTest.XXXXXX" |
| |
| static void fill_test_data(int16_t* data, size_t size) { |
| for (size_t i = 0; i < size; i++) |
| data[i] = i; |
| } |
| |
| static void verify_processed_data(int16_t* data, size_t size, int times) { |
| /* Each time the audio data flow through the mock plugin, the data |
| * will be multiplied by 2 in module->run() below, so if there are n |
| * plugins, the data will be multiplied by (1 << n). */ |
| int multiples = (1 << times); |
| for (size_t i = 0; i < size; i++) { |
| EXPECT_EQ(i * multiples, data[i]); |
| if ((int16_t)i * multiples != data[i]) |
| return; |
| } |
| } |
| |
| struct data { |
| const char* title; |
| int nr_ports; |
| port_direction port_dir[MAX_MOCK_PORTS]; |
| int nr_in_audio; |
| int nr_in_control; |
| int nr_out_audio; |
| int nr_out_control; |
| int in_audio[MAX_MOCK_PORTS]; |
| int in_control[MAX_MOCK_PORTS]; |
| int out_audio[MAX_MOCK_PORTS]; |
| int out_control[MAX_MOCK_PORTS]; |
| int properties; |
| |
| int instantiate_called; |
| int sample_rate; |
| |
| int connect_port_called[MAX_MOCK_PORTS]; |
| float* data_location[MAX_MOCK_PORTS]; |
| |
| int run_called; |
| float input[MAX_MOCK_PORTS]; |
| float output[MAX_MOCK_PORTS]; |
| |
| int sample_count; |
| |
| int get_delay_called; |
| int deinstantiate_called; |
| int free_module_called; |
| int get_properties_called; |
| }; |
| |
| static int instantiate(struct dsp_module* module, unsigned long sample_rate) { |
| struct data* data = (struct data*)module->data; |
| data->instantiate_called++; |
| data->sample_rate = sample_rate; |
| return 0; |
| } |
| |
| static void connect_port(struct dsp_module* module, |
| unsigned long port, |
| float* data_location) { |
| struct data* data = (struct data*)module->data; |
| data->connect_port_called[port]++; |
| data->data_location[port] = data_location; |
| } |
| |
| static int get_delay(struct dsp_module* module) { |
| struct data* data = (struct data*)module->data; |
| data->get_delay_called++; |
| |
| /* If the module title is "mN", then use N as the delay. */ |
| int delay = 0; |
| sscanf(data->title, "m%d", &delay); |
| return delay; |
| } |
| |
| static void run(struct dsp_module* module, unsigned long sample_count) { |
| struct data* data = (struct data*)module->data; |
| data->run_called++; |
| data->sample_count = sample_count; |
| |
| for (int i = 0; i < data->nr_ports; i++) { |
| if (data->port_dir[i] == PORT_INPUT) |
| data->input[i] = *data->data_location[i]; |
| } |
| |
| /* copy the control port data */ |
| for (int i = 0; i < std::min(data->nr_in_control, data->nr_out_control); |
| i++) { |
| int from = data->in_control[i]; |
| int to = data->out_control[i]; |
| data->data_location[to][0] = data->data_location[from][0]; |
| } |
| |
| /* multiply the audio port data by 2 */ |
| for (int i = 0; i < std::min(data->nr_in_audio, data->nr_out_audio); i++) { |
| int from = data->in_audio[i]; |
| int to = data->out_audio[i]; |
| for (unsigned int j = 0; j < sample_count; j++) |
| data->data_location[to][j] = data->data_location[from][j] * 2; |
| } |
| } |
| |
| static void deinstantiate(struct dsp_module* module) { |
| struct data* data = (struct data*)module->data; |
| data->deinstantiate_called++; |
| } |
| |
| static void free_module(struct dsp_module* module) { |
| struct data* data = (struct data*)module->data; |
| data->free_module_called++; |
| } |
| |
| static void really_free_module(struct dsp_module* module) { |
| struct data* data = (struct data*)module->data; |
| free(data); |
| free(module); |
| } |
| |
| static int get_properties(struct dsp_module* module) { |
| struct data* data = (struct data*)module->data; |
| data->get_properties_called++; |
| return data->properties; |
| } |
| static void dump(struct dsp_module* module, struct dumper* d) {} |
| |
| static struct dsp_module* create_mock_module(struct plugin* plugin) { |
| struct data* data; |
| struct dsp_module* module; |
| |
| data = (struct data*)calloc(1, sizeof(struct data)); |
| data->title = plugin->title; |
| data->nr_ports = ARRAY_COUNT(&plugin->ports); |
| for (int i = 0; i < data->nr_ports; i++) { |
| struct port* port = ARRAY_ELEMENT(&plugin->ports, i); |
| data->port_dir[i] = port->direction; |
| |
| if (port->direction == PORT_INPUT) { |
| if (port->type == PORT_AUDIO) |
| data->in_audio[data->nr_in_audio++] = i; |
| else |
| data->in_control[data->nr_in_control++] = i; |
| } else { |
| if (port->type == PORT_AUDIO) |
| data->out_audio[data->nr_out_audio++] = i; |
| else |
| data->out_control[data->nr_out_control++] = i; |
| } |
| } |
| if (strcmp(plugin->label, "inplace_broken") == 0) { |
| data->properties = MODULE_INPLACE_BROKEN; |
| } else { |
| data->properties = 0; |
| } |
| |
| module = (struct dsp_module*)calloc(1, sizeof(struct dsp_module)); |
| module->data = data; |
| module->instantiate = &instantiate; |
| module->connect_port = &connect_port; |
| module->get_delay = &get_delay; |
| module->run = &run; |
| module->deinstantiate = &deinstantiate; |
| module->free_module = &free_module; |
| module->get_properties = &get_properties; |
| module->dump = &dump; |
| return module; |
| } |
| |
| static struct dsp_module* modules[MAX_MODULES]; |
| static struct dsp_module* cras_dsp_module_set_sink_ext_module_val; |
| static int num_modules; |
| static struct dsp_module* find_module(const char* name) { |
| for (int i = 0; i < num_modules; i++) { |
| struct data* data = (struct data*)modules[i]->data; |
| if (strcmp(name, data->title) == 0) |
| return modules[i]; |
| } |
| return NULL; |
| } |
| |
| extern "C" { |
| struct dsp_module* cras_dsp_module_load_ladspa(struct plugin* plugin) { |
| return NULL; |
| } |
| struct dsp_module* cras_dsp_module_load_builtin(struct plugin* plugin) { |
| struct dsp_module* module = create_mock_module(plugin); |
| modules[num_modules++] = module; |
| return module; |
| } |
| void cras_dsp_module_set_sink_ext_module(struct dsp_module* module, |
| struct ext_dsp_module* ext_module) { |
| cras_dsp_module_set_sink_ext_module_val = module; |
| } |
| } |
| |
| namespace { |
| |
| class DspPipelineTestSuite : public testing::Test { |
| protected: |
| virtual void SetUp() { |
| num_modules = 0; |
| strcpy(filename, FILENAME_TEMPLATE); |
| int fd = mkstemp(filename); |
| fp = fdopen(fd, "w"); |
| } |
| |
| virtual void TearDown() { |
| CloseFile(); |
| unlink(filename); |
| } |
| |
| virtual void CloseFile() { |
| if (fp) { |
| fclose(fp); |
| fp = NULL; |
| } |
| } |
| |
| char filename[sizeof(FILENAME_TEMPLATE) + 1]; |
| FILE* fp; |
| struct ext_dsp_module ext_mod; |
| }; |
| |
| TEST_F(DspPipelineTestSuite, Simple) { |
| const char* content = |
| "[M1]\n" |
| "library=builtin\n" |
| "label=source\n" |
| "purpose=capture\n" |
| "output_0={audio}\n" |
| "output_1=<control>\n" |
| "input_2=3.0\n" |
| "[M2]\n" |
| "library=builtin\n" |
| "label=sink\n" |
| "purpose=capture\n" |
| "input_0=<control>\n" |
| "input_1={audio}\n" |
| "\n"; |
| fprintf(fp, "%s", content); |
| CloseFile(); |
| |
| struct cras_expr_env env = CRAS_EXPR_ENV_INIT; |
| struct ini* ini = cras_dsp_ini_create(filename); |
| ASSERT_TRUE(ini); |
| struct pipeline* p = cras_dsp_pipeline_create(ini, &env, "capture"); |
| ASSERT_TRUE(p); |
| ASSERT_EQ(0, cras_dsp_pipeline_load(p)); |
| |
| ASSERT_EQ(2, num_modules); |
| struct dsp_module* m1 = find_module("m1"); |
| struct dsp_module* m2 = find_module("m2"); |
| ASSERT_TRUE(m1); |
| ASSERT_TRUE(m2); |
| |
| ASSERT_EQ(1, cras_dsp_pipeline_get_num_input_channels(p)); |
| ASSERT_EQ(0, cras_dsp_pipeline_instantiate(p, 48000)); |
| |
| struct data* d1 = (struct data*)m1->data; |
| struct data* d2 = (struct data*)m2->data; |
| |
| /* check m1 */ |
| ASSERT_STREQ("m1", d1->title); |
| ASSERT_EQ(3, d1->nr_ports); |
| ASSERT_EQ(PORT_OUTPUT, d1->port_dir[0]); |
| ASSERT_EQ(PORT_OUTPUT, d1->port_dir[1]); |
| ASSERT_EQ(PORT_INPUT, d1->port_dir[2]); |
| ASSERT_EQ(1, d1->instantiate_called); |
| ASSERT_EQ(1, d1->get_delay_called); |
| ASSERT_EQ(48000, d1->sample_rate); |
| ASSERT_EQ(1, d1->connect_port_called[0]); |
| ASSERT_EQ(1, d1->connect_port_called[1]); |
| ASSERT_EQ(1, d1->connect_port_called[2]); |
| ASSERT_TRUE(d1->data_location[0]); |
| ASSERT_TRUE(d1->data_location[1]); |
| ASSERT_TRUE(d1->data_location[2]); |
| ASSERT_EQ(0, d1->run_called); |
| ASSERT_EQ(0, d1->deinstantiate_called); |
| ASSERT_EQ(0, d1->free_module_called); |
| ASSERT_EQ(1, d1->get_properties_called); |
| |
| /* check m2 */ |
| ASSERT_STREQ("m2", d2->title); |
| ASSERT_EQ(2, d2->nr_ports); |
| ASSERT_EQ(PORT_INPUT, d2->port_dir[0]); |
| ASSERT_EQ(PORT_INPUT, d2->port_dir[1]); |
| ASSERT_EQ(1, d2->instantiate_called); |
| ASSERT_EQ(1, d2->get_delay_called); |
| ASSERT_EQ(48000, d2->sample_rate); |
| ASSERT_EQ(1, d2->connect_port_called[0]); |
| ASSERT_EQ(1, d2->connect_port_called[1]); |
| ASSERT_TRUE(d2->data_location[0]); |
| ASSERT_TRUE(d2->data_location[1]); |
| ASSERT_EQ(0, d2->run_called); |
| ASSERT_EQ(0, d2->deinstantiate_called); |
| ASSERT_EQ(0, d2->free_module_called); |
| ASSERT_EQ(1, d2->get_properties_called); |
| |
| /* check the buffer is shared */ |
| ASSERT_EQ(d1->data_location[0], d2->data_location[1]); |
| ASSERT_EQ(d1->data_location[1], d2->data_location[0]); |
| ASSERT_EQ(1, cras_dsp_pipeline_get_peak_audio_buffers(p)); |
| |
| d1->data_location[0][0] = 100; |
| cras_dsp_pipeline_run(p, DSP_BUFFER_SIZE); |
| ASSERT_EQ(1, d1->run_called); |
| ASSERT_EQ(1, d2->run_called); |
| ASSERT_EQ(3, d1->input[2]); |
| ASSERT_EQ(3, d2->input[0]); |
| ASSERT_EQ(100, d2->input[1]); |
| |
| d1->data_location[0][0] = 1000; |
| cras_dsp_pipeline_run(p, DSP_BUFFER_SIZE); |
| ASSERT_EQ(2, d1->run_called); |
| ASSERT_EQ(2, d2->run_called); |
| ASSERT_EQ(3, d1->input[2]); |
| ASSERT_EQ(3, d2->input[0]); |
| ASSERT_EQ(1000, d2->input[1]); |
| |
| /* Expect the sink module "m2" is set. */ |
| cras_dsp_pipeline_set_sink_ext_module(p, &ext_mod); |
| struct data* d = (struct data*)cras_dsp_module_set_sink_ext_module_val->data; |
| ASSERT_STREQ("m2", d->title); |
| |
| cras_dsp_pipeline_deinstantiate(p); |
| ASSERT_EQ(1, d1->deinstantiate_called); |
| ASSERT_EQ(1, d2->deinstantiate_called); |
| |
| cras_dsp_pipeline_free(p); |
| ASSERT_EQ(1, d1->free_module_called); |
| ASSERT_EQ(1, d2->free_module_called); |
| |
| cras_dsp_ini_free(ini); |
| cras_expr_env_free(&env); |
| |
| really_free_module(m1); |
| really_free_module(m2); |
| } |
| |
| TEST_F(DspPipelineTestSuite, Complex) { |
| /* |
| * / --(b)-- 2 --(c)-- \ |
| * 0 ==(a0, a1)== 1 4 ==(f0,f1)== 5 |
| * \ --(d)-- 3 --(e)-- / |
| * |
| * |
| * --(g)-- 6 --(h)-- |
| */ |
| |
| const char* content = |
| "[M6]\n" |
| "library=builtin\n" |
| "label=foo\n" |
| "input_0={g}\n" |
| "output_1={h}\n" |
| "[M5]\n" |
| "library=builtin\n" |
| "label=sink\n" |
| "purpose=playback\n" |
| "input_0={f0}\n" |
| "input_1={f1}\n" |
| "[M4]\n" |
| "library=builtin\n" |
| "label=foo\n" |
| "disable=(equal? output_device \"HDMI\")\n" |
| "input_0=3.14\n" |
| "input_1={c}\n" |
| "output_2={f0}\n" |
| "input_3={e}\n" |
| "output_4={f1}\n" |
| "[M3]\n" |
| "library=builtin\n" |
| "label=foo\n" |
| "input_0={d}\n" |
| "output_1={e}\n" |
| "[M2]\n" |
| "library=builtin\n" |
| "label=inplace_broken\n" |
| "input_0={b}\n" |
| "output_1={c}\n" |
| "[M1]\n" |
| "library=builtin\n" |
| "label=foo\n" |
| "disable=(equal? output_device \"USB\")\n" |
| "input_0={a0}\n" |
| "input_1={a1}\n" |
| "output_2={b}\n" |
| "output_3={d}\n" |
| "[M0]\n" |
| "library=builtin\n" |
| "label=source\n" |
| "purpose=playback\n" |
| "output_0={a0}\n" |
| "output_1={a1}\n"; |
| fprintf(fp, "%s", content); |
| CloseFile(); |
| |
| struct cras_expr_env env = CRAS_EXPR_ENV_INIT; |
| cras_expr_env_install_builtins(&env); |
| cras_expr_env_set_variable_string(&env, "output_device", "HDMI"); |
| cras_expr_env_set_variable_boolean(&env, "swap_lr_disabled", 1); |
| |
| struct ini* ini = cras_dsp_ini_create(filename); |
| ASSERT_TRUE(ini); |
| struct pipeline* p = cras_dsp_pipeline_create(ini, &env, "playback"); |
| ASSERT_TRUE(p); |
| ASSERT_EQ(0, cras_dsp_pipeline_load(p)); |
| |
| ASSERT_EQ(5, num_modules); /* one not connected, one disabled */ |
| struct dsp_module* m0 = find_module("m0"); |
| struct dsp_module* m1 = find_module("m1"); |
| struct dsp_module* m2 = find_module("m2"); |
| struct dsp_module* m3 = find_module("m3"); |
| struct dsp_module* m5 = find_module("m5"); |
| |
| ASSERT_TRUE(m0); |
| ASSERT_TRUE(m1); |
| ASSERT_TRUE(m2); |
| ASSERT_TRUE(m3); |
| ASSERT_FALSE(find_module("m4")); |
| ASSERT_TRUE(m5); |
| ASSERT_FALSE(find_module("m6")); |
| |
| ASSERT_EQ(2, cras_dsp_pipeline_get_num_input_channels(p)); |
| ASSERT_EQ(0, cras_dsp_pipeline_instantiate(p, 48000)); |
| |
| struct data* d0 = (struct data*)m0->data; |
| struct data* d1 = (struct data*)m1->data; |
| struct data* d2 = (struct data*)m2->data; |
| struct data* d3 = (struct data*)m3->data; |
| struct data* d5 = (struct data*)m5->data; |
| |
| /* |
| * / --(b)-- 2 --(c)-- \ |
| * 0 ==(a0, a1)== 1 4 ==(f0,f1)== 5 |
| * \ --(d)-- 3 --(e)-- / |
| * |
| * |
| * --(g)-- 6 --(h)-- |
| */ |
| |
| ASSERT_EQ(d0->data_location[0], d1->data_location[0]); |
| ASSERT_EQ(d0->data_location[1], d1->data_location[1]); |
| ASSERT_EQ(d1->data_location[2], d2->data_location[0]); |
| ASSERT_EQ(d1->data_location[3], d3->data_location[0]); |
| ASSERT_NE(d2->data_location[0], d2->data_location[1]); /* inplace-broken */ |
| ASSERT_EQ(d2->data_location[1], d5->data_location[0]); /* m4 is disabled */ |
| ASSERT_EQ(d3->data_location[1], d5->data_location[1]); |
| |
| /* need 3 buffers because m2 has inplace-broken flag */ |
| ASSERT_EQ(3, cras_dsp_pipeline_get_peak_audio_buffers(p)); |
| |
| int16_t* samples = new int16_t[DSP_BUFFER_SIZE]; |
| fill_test_data(samples, DSP_BUFFER_SIZE); |
| cras_dsp_pipeline_apply(p, (uint8_t*)samples, SND_PCM_FORMAT_S16_LE, 100); |
| /* the data flow through 2 plugins because m4 is disabled. */ |
| verify_processed_data(samples, 100, 2); |
| delete[] samples; |
| |
| ASSERT_EQ(1, d1->run_called); |
| ASSERT_EQ(1, d3->run_called); |
| |
| /* check m5 */ |
| ASSERT_EQ(1, d5->run_called); |
| ASSERT_EQ(100, d5->sample_count); |
| |
| /* Expect the sink module "m5" is set. */ |
| cras_dsp_pipeline_set_sink_ext_module(p, &ext_mod); |
| struct data* d = (struct data*)cras_dsp_module_set_sink_ext_module_val->data; |
| ASSERT_STREQ("m5", d->title); |
| |
| /* re-instantiate */ |
| ASSERT_EQ(1, d5->instantiate_called); |
| ASSERT_EQ(1, d5->get_delay_called); |
| ASSERT_EQ(1 + 3 + 5, cras_dsp_pipeline_get_delay(p)); |
| |
| cras_dsp_pipeline_deinstantiate(p); |
| cras_dsp_pipeline_instantiate(p, 44100); |
| |
| ASSERT_EQ(1, d5->deinstantiate_called); |
| ASSERT_EQ(2, d5->instantiate_called); |
| ASSERT_EQ(2, d5->get_delay_called); |
| ASSERT_EQ(1 + 3 + 5, cras_dsp_pipeline_get_delay(p)); |
| ASSERT_EQ(0, d5->free_module_called); |
| ASSERT_EQ(44100, d5->sample_rate); |
| ASSERT_EQ(2, d5->connect_port_called[0]); |
| ASSERT_EQ(2, d5->connect_port_called[1]); |
| |
| cras_dsp_pipeline_free(p); |
| cras_dsp_ini_free(ini); |
| cras_expr_env_free(&env); |
| |
| really_free_module(m0); |
| really_free_module(m1); |
| really_free_module(m2); |
| really_free_module(m3); |
| really_free_module(m5); |
| } |
| |
| } // namespace |
| |
| int main(int argc, char** argv) { |
| ::testing::InitGoogleTest(&argc, argv); |
| return RUN_ALL_TESTS(); |
| } |