1 // Rather simplistic benchmark of AudioMixer. Sets up a simple mapping
2 // with the default settings, feeds some white noise to the inputs and
3 // runs a while. Useful for e.g. profiling.
6 #include <bmusb/bmusb.h>
15 #include "audio_mixer.h"
18 #include "input_mapping.h"
19 #include "resampling_queue.h"
22 #define NUM_BENCHMARK_CARDS 4
23 #define NUM_WARMUP_FRAMES 100
24 #define NUM_BENCHMARK_FRAMES 1000
25 #define NUM_TEST_FRAMES 10
26 #define NUM_CHANNELS 8
27 #define NUM_SAMPLES 1024
30 using namespace std::chrono;
32 // 16-bit samples, white noise at full volume.
33 uint8_t samples16[(NUM_SAMPLES * NUM_CHANNELS + 1024) * sizeof(uint16_t)];
35 // 24-bit samples, white noise at low volume (-48 dB).
36 uint8_t samples24[(NUM_SAMPLES * NUM_CHANNELS + 1024) * 3];
38 static uint32_t seed = 1234;
40 // We use our own instead of rand() to get deterministic behavior.
41 // Quality doesn't really matter much.
44 seed = seed * 1103515245u + 12345u;
53 void callback(float level_lufs, float peak_db,
54 std::vector<AudioMixer::BusLevel> bus_levels,
55 float global_level_lufs, float range_low_lufs, float range_high_lufs,
56 float final_makeup_gain_db,
62 vector<float> process_frame(unsigned frame_num, AudioMixer *mixer)
64 duration<int64_t, ratio<NUM_SAMPLES, OUTPUT_FREQUENCY>> frame_duration(frame_num);
65 steady_clock::time_point ts = steady_clock::time_point::min() +
66 duration_cast<steady_clock::duration>(frame_duration);
69 for (unsigned card_index = 0; card_index < NUM_BENCHMARK_CARDS; ++card_index) {
70 bmusb::AudioFormat audio_format;
71 audio_format.bits_per_sample = card_index == 3 ? 24 : 16;
72 audio_format.num_channels = NUM_CHANNELS;
74 unsigned num_samples = NUM_SAMPLES + (lcgrand() % 9) - 5;
75 bool ok = mixer->add_audio(DeviceSpec{InputSourceType::CAPTURE_CARD, card_index},
76 card_index == 3 ? samples24 : samples16, num_samples, audio_format,
77 NUM_SAMPLES * TIMEBASE / OUTPUT_FREQUENCY, ts);
81 return mixer->get_output(ts, NUM_SAMPLES, ResamplingQueue::ADJUST_RATE);
84 void init_mapping(AudioMixer *mixer)
88 InputMapping::Bus bus1;
89 bus1.device = DeviceSpec{InputSourceType::CAPTURE_CARD, 0};
90 bus1.source_channel[0] = 0;
91 bus1.source_channel[1] = 1;
92 mapping.buses.push_back(bus1);
94 InputMapping::Bus bus2;
95 bus2.device = DeviceSpec{InputSourceType::CAPTURE_CARD, 3};
96 bus2.source_channel[0] = 6;
97 bus2.source_channel[1] = 4;
98 mapping.buses.push_back(bus2);
100 mixer->set_input_mapping(mapping);
103 void do_test(const char *filename)
105 AudioMixer mixer(NUM_BENCHMARK_CARDS);
106 mixer.set_audio_level_callback(callback);
107 init_mapping(&mixer);
111 vector<float> output;
112 for (unsigned i = 0; i < NUM_TEST_FRAMES; ++i) {
113 vector<float> frame_output = process_frame(i, &mixer);
114 output.insert(output.end(), frame_output.begin(), frame_output.end());
117 FILE *fp = fopen(filename, "rb");
119 fprintf(stderr, "%s not found, writing new reference.\n", filename);
120 fp = fopen(filename, "wb");
121 fwrite(&output[0], output.size() * sizeof(float), 1, fp);
127 ref.resize(output.size());
128 fread(&ref[0], output.size() * sizeof(float), 1, fp);
131 float max_err = 0.0f, sum_sq_err = 0.0f;
132 for (unsigned i = 0; i < output.size(); ++i) {
133 float err = output[i] - ref[i];
134 max_err = max(max_err, fabs(err));
135 sum_sq_err += err * err;
138 printf("Largest error: %.6f (%+.1f dB)\n", max_err, to_db(max_err));
139 printf("RMS error: %+.1f dB\n", to_db(sqrt(sum_sq_err) / output.size()));
144 AudioMixer mixer(NUM_BENCHMARK_CARDS);
145 mixer.set_audio_level_callback(callback);
146 init_mapping(&mixer);
148 size_t out_samples = 0;
152 steady_clock::time_point start, end;
153 for (unsigned i = 0; i < NUM_WARMUP_FRAMES + NUM_BENCHMARK_FRAMES; ++i) {
154 if (i == NUM_WARMUP_FRAMES) {
155 start = steady_clock::now();
157 vector<float> output = process_frame(i, &mixer);
158 if (i >= NUM_WARMUP_FRAMES) {
159 out_samples += output.size();
162 end = steady_clock::now();
164 double elapsed = duration<double>(end - start).count();
165 double simulated = double(out_samples) / (OUTPUT_FREQUENCY * 2);
166 printf("%ld samples produced in %.1f ms (%.1f%% CPU, %.1fx realtime).\n",
167 out_samples, elapsed * 1e3, 100.0 * elapsed / simulated, simulated / elapsed);
170 int main(int argc, char **argv)
172 for (unsigned i = 0; i < NUM_SAMPLES * NUM_CHANNELS + 1024; ++i) {
173 samples16[i * 2] = lcgrand() & 0xff;
174 samples16[i * 2 + 1] = lcgrand() & 0xff;
176 samples24[i * 3] = lcgrand() & 0xff;
177 samples24[i * 3 + 1] = lcgrand() & 0xff;
178 samples24[i * 3 + 2] = 0;