Line data Source code
1 : #include "audio/backend/sdl_backend.h"
2 :
3 : #include <algorithm>
4 : #include <cassert>
5 : #include <cctype>
6 : #include <cstring>
7 : #include <iostream>
8 : #include <vector>
9 :
10 : #include "audio/engine/i_audio_engine.h"
11 :
12 : namespace Amplitron {
13 :
14 0 : static void sdl_audio_callback(void* userdata, Uint8* stream, int len) {
15 0 : auto* be = static_cast<SdlBackend*>(userdata);
16 0 : auto* engine = be->get_engine();
17 0 : if (!engine) return;
18 :
19 0 : auto* out = reinterpret_cast<float*>(stream);
20 0 : int frame_count = len / static_cast<int>(2 * sizeof(float));
21 :
22 0 : auto& cap = be->get_capture_buffer();
23 0 : if (frame_count > static_cast<int>(cap.size())) {
24 0 : cap.resize(frame_count * 2, 0.0f);
25 0 : }
26 :
27 0 : SDL_AudioDeviceID cap_dev = be->get_capture_device();
28 0 : if (cap_dev != 0) {
29 0 : Uint32 queued = SDL_GetQueuedAudioSize(cap_dev);
30 0 : Uint32 need = static_cast<Uint32>(frame_count * sizeof(float));
31 :
32 0 : Uint32 max_queued = need * 2;
33 0 : while (queued > max_queued) {
34 0 : Uint8 junk[4096];
35 0 : Uint32 chunk = (queued - need) > 4096 ? 4096 : (queued - need);
36 0 : SDL_DequeueAudio(cap_dev, junk, chunk);
37 0 : queued -= chunk;
38 : }
39 :
40 0 : Uint32 got = SDL_DequeueAudio(cap_dev, cap.data(), need);
41 0 : int captured = static_cast<int>(got / sizeof(float));
42 0 : if (captured < frame_count)
43 0 : std::memset(cap.data() + captured, 0,
44 0 : static_cast<size_t>(frame_count - captured) * sizeof(float));
45 0 : } else {
46 0 : std::memset(cap.data(), 0, static_cast<size_t>(frame_count) * sizeof(float));
47 : }
48 :
49 0 : engine->process_audio(cap.data(), out, frame_count);
50 0 : }
51 :
52 0 : SdlBackend::SdlBackend() = default;
53 0 : SdlBackend::~SdlBackend() { shutdown(); }
54 :
55 0 : bool SdlBackend::initialize(IAudioEngine* engine) {
56 0 : if (initialized_) return true;
57 0 : engine_ = engine;
58 :
59 0 : if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) {
60 0 : std::cerr << "SDL audio init failed: " << SDL_GetError() << std::endl;
61 0 : return false;
62 : }
63 0 : initialized_ = true;
64 0 : return true;
65 0 : }
66 :
67 0 : void SdlBackend::shutdown() {
68 0 : stop();
69 0 : if (initialized_) {
70 0 : SDL_QuitSubSystem(SDL_INIT_AUDIO);
71 0 : initialized_ = false;
72 0 : }
73 0 : }
74 :
75 0 : bool SdlBackend::start() {
76 0 : if (!initialized_ || running_) return false;
77 :
78 0 : int target_buffer = engine_->get_buffer_size();
79 : #ifdef __EMSCRIPTEN__
80 : if (target_buffer < 256) {
81 : target_buffer = 256;
82 : } else {
83 : int p = 256;
84 : while (p < target_buffer && p < 16384) {
85 : p *= 2;
86 : }
87 : target_buffer = p;
88 : }
89 : #endif
90 0 : int target_rate = engine_->get_sample_rate();
91 :
92 0 : SDL_AudioSpec want_out, have_out;
93 0 : SDL_memset(&want_out, 0, sizeof(want_out));
94 0 : want_out.freq = target_rate;
95 0 : want_out.format = AUDIO_F32;
96 0 : want_out.channels = 2;
97 0 : want_out.samples = static_cast<Uint16>(target_buffer);
98 0 : want_out.callback = sdl_audio_callback;
99 0 : want_out.userdata = this;
100 :
101 0 : audio_device_ =
102 0 : SDL_OpenAudioDevice(nullptr, 0, &want_out, &have_out, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
103 0 : if (audio_device_ == 0) {
104 0 : std::cerr << "[SDL] Failed to open output audio: " << SDL_GetError() << std::endl;
105 0 : return false;
106 : }
107 :
108 0 : sample_rate_ = have_out.freq;
109 0 : buffer_size_ = have_out.samples;
110 :
111 0 : SDL_AudioSpec want_cap, have_cap;
112 0 : SDL_memset(&want_cap, 0, sizeof(want_cap));
113 0 : want_cap.freq = sample_rate_;
114 0 : want_cap.format = AUDIO_F32;
115 0 : want_cap.channels = 1;
116 0 : want_cap.samples = static_cast<Uint16>(buffer_size_);
117 0 : want_cap.callback = nullptr;
118 :
119 0 : static const char* usb_keywords[] = {
120 : "usb", "guitar", "irig", "scarlett", "behringer",
121 : "focusrite", "presonus", "steinberg", "audio interface", "line 6",
122 : "rocksmith", "umc", "um2", "uphoria", "podcast",
123 : "xenyx", "external"};
124 :
125 0 : const char* preferred_device = nullptr;
126 0 : int num_capture = SDL_GetNumAudioDevices(1);
127 0 : for (int i = 0; i < num_capture; ++i) {
128 0 : const char* name = SDL_GetAudioDeviceName(i, 1);
129 0 : if (!name) continue;
130 0 : std::string lower = name;
131 0 : std::transform(lower.begin(), lower.end(), lower.begin(),
132 0 : [](unsigned char c) { return std::tolower(c); });
133 0 : for (const auto* kw : usb_keywords) {
134 0 : if (lower.find(kw) != std::string::npos) {
135 0 : preferred_device = name;
136 0 : break;
137 : }
138 : }
139 0 : if (preferred_device) break;
140 0 : }
141 :
142 0 : capture_device_ = SDL_OpenAudioDevice(preferred_device, 1, &want_cap, &have_cap,
143 : SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
144 :
145 0 : capture_buffer_.resize(static_cast<size_t>(buffer_size_ * 2), 0.0f);
146 :
147 0 : SDL_PauseAudioDevice(audio_device_, 0);
148 0 : if (capture_device_ != 0) SDL_PauseAudioDevice(capture_device_, 0);
149 :
150 0 : running_ = true;
151 0 : return true;
152 0 : }
153 :
154 0 : void SdlBackend::stop() {
155 0 : if (running_) {
156 0 : if (audio_device_ != 0) SDL_PauseAudioDevice(audio_device_, 1);
157 0 : if (capture_device_ != 0) SDL_PauseAudioDevice(capture_device_, 1);
158 0 : running_ = false;
159 0 : }
160 0 : if (capture_device_ != 0) {
161 0 : SDL_CloseAudioDevice(capture_device_);
162 0 : capture_device_ = 0;
163 0 : }
164 0 : if (audio_device_ != 0) {
165 0 : SDL_CloseAudioDevice(audio_device_);
166 0 : audio_device_ = 0;
167 0 : }
168 0 : }
169 :
170 0 : std::vector<AudioDeviceInfo> SdlBackend::get_input_devices() const {
171 0 : return {{0, "Browser Microphone", 1, 0, 48000.0, false}};
172 0 : }
173 :
174 0 : std::vector<AudioDeviceInfo> SdlBackend::get_output_devices() const {
175 0 : return {{0, "Browser Audio Output", 0, 2, 48000.0, false}};
176 0 : }
177 :
178 0 : bool SdlBackend::set_input_device(int device_index) {
179 0 : selected_input_device_ = device_index;
180 0 : return true;
181 : }
182 :
183 0 : bool SdlBackend::set_output_device(int device_index) {
184 0 : selected_output_device_ = device_index;
185 0 : return true;
186 : }
187 :
188 0 : std::string SdlBackend::get_input_device_name() const { return "Browser Microphone"; }
189 :
190 0 : std::string SdlBackend::get_output_device_name() const { return "Browser Audio Output"; }
191 :
192 0 : int SdlBackend::get_sample_rate() const { return sample_rate_; }
193 :
194 0 : int SdlBackend::get_buffer_size() const { return buffer_size_; }
195 :
196 : } // namespace Amplitron
|