Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDL audio callback uses wrong buffer size after WASAPI device change #11122

Open
StephenCWills opened this issue Oct 8, 2024 · 0 comments
Open
Assignees
Milestone

Comments

@StephenCWills
Copy link
Contributor

We recently received a report from a user who has encountered audio distortion and app crashes as a result of plugging/unplugging his headphones. He appears to have a somewhat rare audio setup where his speakers and headphones are connected as separate playback devices with different default device periods. As a result, the WASAPI driver assigns this->spec.samples = 236 for the speakers, and this->spec.samples = 221 for the headphones. This can be observed in the logs from our game client that he provided to us in the issue report.

diasurgical/devilutionX#7452 (comment)
Speakers: VERBOSE: Aulib sampleRate=22050 channels=2 frameSize=236 format=0x8120
Headphones: VERBOSE: Aulib sampleRate=22050 channels=2 frameSize=221 format=0x8120

This version of our application uses SDL release version 2.30.5.


In SDL_RunAudio(), the callback is invoked with data and data_len parameters. udata is irrelevant in our case.

callback(udata, data, data_len);

data is the return value of WASAPI_GetDeviceBuf().

data = current_audio.impl.GetDeviceBuf(device);

data_len is assigned the value from device->callbackspec.size.

data_len = device->callbackspec.size;

When the WASAPI driver updates the number of samples in the audio spec, it calls SDL_CalculateAudioSpec(&this->spec) to update device->spec.size. But that does nothing to device->callbackspec.

/* Match the callback size to the period size to cut down on the number of
interrupts waited for in each call to WaitDevice */
{
const float period_millis = default_period / 10000.0f;
const float period_frames = period_millis * this->spec.freq / 1000.0f;
this->spec.samples = (Uint16)SDL_ceilf(period_frames);
}
/* regardless of what we calculated for the period size, clamp it to the expected hardware buffer size. */
if (this->spec.samples > bufsize) {
this->spec.samples = bufsize;
}
/* Update the fragment size as size in bytes */
SDL_CalculateAudioSpec(&this->spec);

WASAPI_GetDeviceBuf() uses this->spec.samples to allocate the buffer for audio samples.

const HRESULT ret = IAudioRenderClient_GetBuffer(this->hidden->render, this->spec.samples, &buffer);

I believe that if the channels, format, or freq are also updated, then SDL will create an audio stream to resample internally. But if only the number of samples changes, then it will "use the existing audio stream" which means that nothing actually changes. Even if the stream was created, I have doubts about whether the callback would be providing the right number of samples to the resampler.

if ((this->callbackspec.channels == this->spec.channels) &&
(this->callbackspec.format == this->spec.format) &&
(this->callbackspec.freq == this->spec.freq) &&
(this->callbackspec.samples == this->spec.samples)) {
/* no need to buffer/convert in an AudioStream! */
SDL_FreeAudioStream(this->stream);
this->stream = NULL;
} else if ((oldspec->channels == this->spec.channels) &&
(oldspec->format == this->spec.format) &&
(oldspec->freq == this->spec.freq)) {
/* The existing audio stream is okay to keep using. */
} else {

Therefore, unless I've missed something, after the device change the callback is made to fill a 221-sample buffer with 236 samples or vice versa.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants