Supporting platforms where SDL_OpenAudioDevice fails with "Audio device already open"

I have a game using SDL2 that plays 4 streams of audio. Each audio stream is dynamically generated at runtime - I’m not playing from a file. On Windows, Linux, and Mac, I’m able to call SDL_OpenAudioDevice four times (once for each stream) and provide a different callback function for each. This works well.

However, on Android and iOS, only the first call to SDL_OpenAudioDevice is successful, and subsequent calls to SDL_OpenAudioDevice fail with “Audio device already open”.

I’m looking for guidance on the best way to handle this.

  1. Is there any way to open the default audio device on Android or iOS multiple times?

  2. If I’m limited to calling SDL_OpenAudioDevice only once, any recommendations for the simplest way to combine my existing callback functions that generate samples into a single callback / single buffer?

I know I could write a new callback that merges the logic from all 4 callbacks. Then I’d have to generate samples for a new merged waveform, but I’m hoping there’s a simpler solution.

Thanks!

Are you saying that you’re opening a single device with four different callbacks, and they’re all working at the same time? If so, I’m surprised that works! Conceptually, there should really only be one callback per device at a time. The callback is called by the system any time the device needs new data; so if a device had multiple callbacks assigned to it, in theory, they would all just be called simultaneously every time.

If you’re switching between callbacks, where only one is active at a time, then I wonder if calling SDL_CloseAudioDevice before attempting to reopen the device with a new callback would allow you to preserve your existing design.

But in general, I think separating the logic that generates and mixes the audio data from the actual callback is a very good idea. There a lot of different ways to do this, so it’s tough to give helpful advice with understanding more about your application. But basically, you’d be iterating through the samples in the four streams and adding the values to get a mixdown. Here’s a crude idea of what this might look like:

/* Audio streams as global arrays. I'm assuming the actual audio data is written elsewhere, in response to events that occur in the course of the game. */
int16_t stream_a[LEN];
int16_t stream_b[LEN];
int16_t stream_c[LEN];
int16_t stream_d[LEN];

/* Mix audio data from four streams,  */
int16_t *get_mixdown_chunk(int length_in_bytes)
{

    int samples_to_read = length_in_bytes / sizeof(int16_t);
    int16_t *chunk = malloc(length_in_bytes);
    /* Iterate through the samples of the four streams*/
    for (int i=0; i < samples_to_read; i++) {
        int16_t sample = stream_a[i];
        sample += stream_b[i];
        sample += stream_c[i];
        sample += stream_d[i];
        chunk[i] = sample;
    }
    return chunk;
}

void audio_callback(void *user_data, uint8_t stream, int streamLength)
{
    /* Get a pointer to a chunk that mixes audio from all four streams */
    int16_t *chunk = get_mixdown_chunk(streamLength);

    /* Copy data from the array pointed to by "chunk" into the device stream */
    memcpy(stream, chunk, streamLength);

    /* Free the audio chunk */
    free(chunk);

}

There’s also the SDL_mixer library, which I haven’t used but seems relevant.

I’m not sure how well this answers your question, but happy to check out more specifics of your application and give more specific advice if it’d be helpful.

1 Like

Thanks for the response!

Are you saying that you’re opening a single device with four different callbacks, and they’re all working at the same time?

Yes, that’s exactly what I’m doing! Each callback gets invoked regularly and the samples are mixed together… somehow. I haven’t looked into where that happens, if SDL is doing it or if the OS / driver is handling it. In any case, it does work on Windows, Linux, and Mac, but not on iOS or Android.

Your sample code is more or less what I was going to try as a first attempt unless I got some other feedback here. I had some concerns with using a simple sum of values, thinking about potential overflows in particular, but I work to avoid that. Your response gives me some confidence in giving this a try - it might be fairly straightforward after all.

1 Like

Nice. Yeah, you’ll need to make sure to clip the sample values to avoid overflow; or if each of the input streams is free to range in amplitude from INT16_MIN to INT16_MAX, divide each component by 4 in the sum. I have to guess that whatever the system is doing with the multiple callbacks situation is just a simple sum – but who knows.

To close the loop on this, I did end up simply summing the values from the various streams to create a single buffer. I made sure the max/min values from each stream could be added safely without exceeding the integer range of my 16-bit signed samples. I also had to re-work some places where I was previously pausing/unpausing a single stream via SDL_PauseAudioDevice. Thanks again chvolow24 for your help.

1 Like