@@ -133,6 +133,16 @@ bool AudioEngine::openInput(const std::string& device) {
133133 want.callback = inputCallback;
134134 want.userdata = this ;
135135
136+ #ifdef _WIN32
137+ // Win10 low-end stability: prefer queued capture (no SDL input callback thread).
138+ // Main thread will poll/dequeue via getRxSamples().
139+ want.callback = nullptr ;
140+ want.userdata = nullptr ;
141+ input_queue_mode_ = true ;
142+ #else
143+ input_queue_mode_ = false ;
144+ #endif
145+
136146 const char * dev_name = (device.empty () || device == " Default" ) ? nullptr : device.c_str ();
137147
138148 input_device_ = SDL_OpenAudioDevice (dev_name, 1 , &want, &have, 0 );
@@ -142,8 +152,11 @@ bool AudioEngine::openInput(const std::string& device) {
142152 return false ;
143153 }
144154
145- LOG_MODEM (INFO, " AudioEngine: Opened INPUT device: %s (id=%d, rate=%d)" ,
146- dev_name ? dev_name : " Default" , input_device_, have.freq );
155+ LOG_MODEM (INFO, " AudioEngine: Opened INPUT device: %s (id=%d, rate=%d, mode=%s)" ,
156+ dev_name ? dev_name : " Default" , input_device_, have.freq ,
157+ input_queue_mode_ ? " queue" : " callback" );
158+ input_dc_x_prev_ = 0 .0f ;
159+ input_dc_y_prev_ = 0 .0f ;
147160 return true ;
148161}
149162
@@ -161,6 +174,7 @@ void AudioEngine::closeInput() {
161174 input_device_ = 0 ;
162175 }
163176 capturing_ = false ;
177+ input_queue_mode_ = false ;
164178}
165179
166180void AudioEngine::queueTxSamples (const std::vector<float >& samples) {
@@ -212,6 +226,25 @@ size_t AudioEngine::getTxQueueSize() const {
212226}
213227
214228std::vector<float > AudioEngine::getRxSamples (size_t max_samples) {
229+ if (input_queue_mode_ && input_device_ != 0 ) {
230+ // Dequeue capture data in main thread (no SDL callback thread).
231+ Uint32 queued_bytes = SDL_GetQueuedAudioSize (input_device_);
232+ if (queued_bytes > 0 ) {
233+ size_t queued_samples = queued_bytes / sizeof (float );
234+ // Drain a bounded chunk each poll to keep latency low without UI stalls.
235+ size_t drain_samples = std::min (queued_samples, std::max<size_t >(max_samples * 4 , max_samples));
236+ if (drain_samples > 0 ) {
237+ std::vector<float > raw (drain_samples, 0 .0f );
238+ Uint32 got_bytes = SDL_DequeueAudio (input_device_, raw.data (),
239+ static_cast <Uint32>(drain_samples * sizeof (float )));
240+ size_t got_samples = got_bytes / sizeof (float );
241+ if (got_samples > 0 ) {
242+ appendCapturedSamples (raw.data (), got_samples, input_gain_.load ());
243+ }
244+ }
245+ }
246+ }
247+
215248 std::lock_guard<AudioEngineMutex> lock (rx_mutex_);
216249
217250 size_t count = std::min (max_samples, rx_buffer_.size ());
@@ -297,44 +330,15 @@ void AudioEngine::inputCallback(void* userdata, Uint8* stream, int len) {
297330 const float * input = reinterpret_cast <const float *>(stream);
298331 int samples = len / sizeof (float );
299332
300- // Apply input gain and DC blocking filter
301- // DC blocker: y[n] = x[n] - x[n-1] + alpha * y[n-1]
302- // This removes DC offset that can cause false sync detection
303- static float dc_x_prev = 0 .0f ;
304- static float dc_y_prev = 0 .0f ;
305- constexpr float DC_ALPHA = 0 .995f ;
306-
307- float gain = engine->input_gain_ .load ();
308- std::vector<float > captured (samples);
309- for (int i = 0 ; i < samples; ++i) {
310- float x = input[i] * gain;
311- float y = x - dc_x_prev + DC_ALPHA * dc_y_prev;
312- dc_x_prev = x;
313- dc_y_prev = y;
314- captured[i] = y;
315- }
316-
317- // Compute input level (RMS) after gain
318- float sum_sq = 0 .0f ;
319- for (int i = 0 ; i < samples; ++i) {
320- sum_sq += captured[i] * captured[i];
321- }
322- float rms = std::sqrt (sum_sq / samples);
323- engine->input_level_ = rms;
333+ engine->appendCapturedSamples (input, static_cast <size_t >(samples), engine->input_gain_ .load ());
324334
335+ std::vector<float > captured;
325336 {
326337 std::lock_guard<AudioEngineMutex> lock (engine->rx_mutex_ );
327-
328- // Cap buffer size to prevent unbounded growth if main loop stalls
329- if (engine->rx_buffer_ .size () + captured.size () > MAX_RX_BUFFER_SAMPLES) {
330- size_t to_remove = engine->rx_buffer_ .size () + captured.size () - MAX_RX_BUFFER_SAMPLES;
331- if (to_remove >= engine->rx_buffer_ .size ()) {
332- engine->rx_buffer_ .clear ();
333- } else {
334- engine->rx_buffer_ .erase (engine->rx_buffer_ .begin (), engine->rx_buffer_ .begin () + to_remove);
335- }
338+ size_t count = std::min<size_t >(samples, engine->rx_buffer_ .size ());
339+ if (count > 0 ) {
340+ captured.assign (engine->rx_buffer_ .end () - count, engine->rx_buffer_ .end ());
336341 }
337- engine->rx_buffer_ .insert (engine->rx_buffer_ .end (), captured.begin (), captured.end ());
338342 }
339343
340344 // Notify via callback (skip if muted during TX)
@@ -348,6 +352,44 @@ void AudioEngine::inputCallback(void* userdata, Uint8* stream, int len) {
348352 }
349353}
350354
355+ void AudioEngine::appendCapturedSamples (const float * input, size_t samples, float gain) {
356+ if (!input || samples == 0 ) {
357+ return ;
358+ }
359+
360+ // Apply input gain and DC blocking filter.
361+ constexpr float DC_ALPHA = 0 .995f ;
362+
363+ std::vector<float > captured (samples);
364+ for (size_t i = 0 ; i < samples; ++i) {
365+ float x = input[i] * gain;
366+ float y = x - input_dc_x_prev_ + DC_ALPHA * input_dc_y_prev_;
367+ input_dc_x_prev_ = x;
368+ input_dc_y_prev_ = y;
369+ captured[i] = y;
370+ }
371+
372+ float sum_sq = 0 .0f ;
373+ for (float s : captured) {
374+ sum_sq += s * s;
375+ }
376+ float rms = std::sqrt (sum_sq / static_cast <float >(samples));
377+ input_level_ = rms;
378+
379+ std::lock_guard<AudioEngineMutex> lock (rx_mutex_);
380+
381+ // Cap buffer size to prevent unbounded growth if main loop stalls.
382+ if (rx_buffer_.size () + captured.size () > MAX_RX_BUFFER_SAMPLES) {
383+ size_t to_remove = rx_buffer_.size () + captured.size () - MAX_RX_BUFFER_SAMPLES;
384+ if (to_remove >= rx_buffer_.size ()) {
385+ rx_buffer_.clear ();
386+ } else {
387+ rx_buffer_.erase (rx_buffer_.begin (), rx_buffer_.begin () + to_remove);
388+ }
389+ }
390+ rx_buffer_.insert (rx_buffer_.end (), captured.begin (), captured.end ());
391+ }
392+
351393void AudioEngine::addChannelNoise (std::vector<float >& samples) {
352394 float snr_db = loopback_snr_db_.load ();
353395
0 commit comments