]> git.sesse.net Git - nageru/blob - benchmark_audio_mixer.cpp
Let settings follow buses when editing the mapping.
[nageru] / benchmark_audio_mixer.cpp
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.
4
5 #include <stdio.h>
6 #include <stdint.h>
7 #include <vector>
8 #include <chrono>
9 #include "audio_mixer.h"
10 #include "db.h"
11 #include "timebase.h"
12
13 #define NUM_BENCHMARK_CARDS 4
14 #define NUM_WARMUP_FRAMES 100
15 #define NUM_BENCHMARK_FRAMES 1000
16 #define NUM_TEST_FRAMES 10
17 #define NUM_CHANNELS 8
18 #define NUM_SAMPLES 1024
19
20 using namespace std;
21 using namespace std::chrono;
22
23 // 16-bit samples, white noise at full volume.
24 uint8_t samples16[(NUM_SAMPLES * NUM_CHANNELS + 1024) * sizeof(uint16_t)];
25
26 // 24-bit samples, white noise at low volume (-48 dB).
27 uint8_t samples24[(NUM_SAMPLES * NUM_CHANNELS + 1024) * 3];
28
29 static uint32_t seed = 1234;
30
31 // We use our own instead of rand() to get deterministic behavior.
32 // Quality doesn't really matter much.
33 uint32_t lcgrand()
34 {
35         seed = seed * 1103515245u + 12345u;
36         return seed;
37 }
38
39 void reset_lcgrand()
40 {
41         seed = 1234;
42 }
43
44 void callback(float level_lufs, float peak_db,
45               std::vector<AudioMixer::BusLevel> bus_levels,
46               float global_level_lufs, float range_low_lufs, float range_high_lufs,
47               float final_makeup_gain_db,
48               float correlation)
49 {
50         // Empty.
51 }
52
53 vector<float> process_frame(unsigned frame_num, AudioMixer *mixer)
54 {
55         // Feed the inputs.
56         for (unsigned card_index = 0; card_index < NUM_BENCHMARK_CARDS; ++card_index) {
57                 bmusb::AudioFormat audio_format;
58                 audio_format.bits_per_sample = card_index == 3 ? 24 : 16;
59                 audio_format.num_channels = NUM_CHANNELS;
60                 
61                 unsigned num_samples = NUM_SAMPLES + (lcgrand() % 9) - 5;
62                 bool ok = mixer->add_audio(DeviceSpec{InputSourceType::CAPTURE_CARD, card_index},
63                         card_index == 3 ? samples24 : samples16, num_samples, audio_format,
64                         NUM_SAMPLES * TIMEBASE / OUTPUT_FREQUENCY);
65                 assert(ok);
66         }
67
68         double pts = double(frame_num) * NUM_SAMPLES / OUTPUT_FREQUENCY;
69         return mixer->get_output(pts, NUM_SAMPLES, ResamplingQueue::ADJUST_RATE);
70 }
71
72 void init_mapping(AudioMixer *mixer)
73 {
74         InputMapping mapping;
75
76         InputMapping::Bus bus1;
77         bus1.device = DeviceSpec{InputSourceType::CAPTURE_CARD, 0};
78         bus1.source_channel[0] = 0;
79         bus1.source_channel[1] = 1;
80         mapping.buses.push_back(bus1);
81
82         InputMapping::Bus bus2;
83         bus2.device = DeviceSpec{InputSourceType::CAPTURE_CARD, 3};
84         bus2.source_channel[0] = 6;
85         bus2.source_channel[1] = 4;
86         mapping.buses.push_back(bus2);
87
88         mixer->set_input_mapping(mapping);
89 }
90
91 void do_test(const char *filename)
92 {
93         AudioMixer mixer(NUM_BENCHMARK_CARDS);
94         mixer.set_audio_level_callback(callback);
95         init_mapping(&mixer);
96
97         reset_lcgrand();
98
99         vector<float> output;
100         for (unsigned i = 0; i < NUM_TEST_FRAMES; ++i) {
101                 vector<float> frame_output = process_frame(i, &mixer);
102                 output.insert(output.end(), frame_output.begin(), frame_output.end());
103         }
104
105         FILE *fp = fopen(filename, "rb");
106         if (fp == nullptr) {
107                 fprintf(stderr, "%s not found, writing new reference.\n", filename);
108                 fp = fopen(filename, "wb");
109                 fwrite(&output[0], output.size() * sizeof(float), 1, fp);
110                 fclose(fp);
111                 return;
112         }
113
114         vector<float> ref;
115         ref.resize(output.size());
116         fread(&ref[0], output.size() * sizeof(float), 1, fp);
117         fclose(fp);
118
119         float max_err = 0.0f, sum_sq_err = 0.0f;
120         for (unsigned i = 0; i < output.size(); ++i) {
121                 float err = output[i] - ref[i];
122                 max_err = max(max_err, fabs(err));
123                 sum_sq_err += err * err;
124         }
125
126         printf("Largest error: %.6f (%+.1f dB)\n", max_err, to_db(max_err));
127         printf("RMS error:     %+.1f dB\n", to_db(sqrt(sum_sq_err) / output.size()));
128 }
129
130 void do_benchmark()
131 {
132         AudioMixer mixer(NUM_BENCHMARK_CARDS);
133         mixer.set_audio_level_callback(callback);
134         init_mapping(&mixer);
135
136         size_t out_samples = 0;
137
138         reset_lcgrand();
139
140         steady_clock::time_point start, end;
141         for (unsigned i = 0; i < NUM_WARMUP_FRAMES + NUM_BENCHMARK_FRAMES; ++i) {
142                 if (i == NUM_WARMUP_FRAMES) {
143                         start = steady_clock::now();
144                 }
145                 vector<float> output = process_frame(i, &mixer);
146                 if (i >= NUM_WARMUP_FRAMES) {
147                         out_samples += output.size();
148                 }
149         }
150         end = steady_clock::now();
151
152         double elapsed = duration<double>(end - start).count();
153         double simulated = double(out_samples) / (OUTPUT_FREQUENCY * 2);
154         printf("%ld samples produced in %.1f ms (%.1f%% CPU, %.1fx realtime).\n",
155                 out_samples, elapsed * 1e3, 100.0 * elapsed / simulated, simulated / elapsed);
156 }
157
158 int main(int argc, char **argv)
159 {
160         for (unsigned i = 0; i < NUM_SAMPLES * NUM_CHANNELS + 1024; ++i) {
161                 samples16[i * 2] = lcgrand() & 0xff;
162                 samples16[i * 2 + 1] = lcgrand() & 0xff;
163
164                 samples24[i * 3] = lcgrand() & 0xff;
165                 samples24[i * 3 + 1] = lcgrand() & 0xff;
166                 samples24[i * 3 + 2] = 0;
167         }
168
169         if (argc == 2) {
170                 do_test(argv[1]);
171         }
172         do_benchmark();
173 }
174