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