SDL_MixAudio() crash when trying to play converted audio data

I’m trying to write a game, so I’m experimenting with SDL’s sound API
and SDL_sound. I want to play a 22 khz mono wav file on an audio device
opened as 44 khz 2 stereo, so I use SDL_BuildCVT() and
SDL_ConvertAudio().
However, doing that makes SDL_MixAudio() crash, and I can’t find out the
reason.
44 khz stereo MP3s play just fine.

Can anybody help me?
Here’s the source code (the comments contain more details about the
problem):

#include “SDL.h”
#include “SDL_sound.h”
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int done = 0;

void
mix_audio (void *user_data, Uint8 *stream, int len)
{
Uint32 amount;
Sound_Sample *sample;
Sound_AudioInfo *info;
SDL_AudioCVT cvt;

    sample = (Sound_Sample *) user_data;
    if (done)
    	return;

    /* Decode the sound file */
    Sound_SetBufferSize (sample, len);
    amount = Sound_Decode (sample);

    /* If end-of-file has been reached, free the sound data */
    if (amount < sample->buffer_size
        && (sample->flags & SOUND_SAMPLEFLAG_EOF))
       {
            done = 1;
       }

    /* Convert the audio data (sample rate, channels, etc.) and mix
       it */
    info = &sample->actual;
    SDL_BuildAudioCVT (&cvt,
            info->format, info->channels, info->rate,
            AUDIO_S16,    2,              44100);
    cvt.buf = (Uint8 *) malloc (amount * cvt.len_mult);
    memcpy (cvt.buf, sample->buffer, amount);
    cvt.len = amount;
    SDL_ConvertAudio (&cvt);

    /* This function makes the program crash when playing the
       22khz wav file, but works just fine when playing a 44khz MP3;
       when commented out, the program doesn't crash */
    SDL_MixAudio (stream, cvt.buf, amount * cvt.len_mult,
                  SDL_MIX_MAXVOLUME);

    if (done)
            Sound_FreeSample (sample);

}

int
main (int argc, char *argv[])
{
SDL_AudioSpec spec;
Sound_Sample *sample;

    /* Initialize SDL and SDL_sound */
    SDL_Init (SDL_INIT_AUDIO);
    atexit (SDL_Quit);
    Sound_Init ();

    if (argc > 1)
        {
            sample = Sound_NewSampleFromFile (argv[1], NULL,
                                              1024 * 4);
        }
    else
        {
            /* Open a 22khz 1 channel wav file */
            sample = Sound_NewSampleFromFile ("gunshot.wav", NULL,
                                              1024 * 4);
        }

    /* Open audio device */
    spec.freq = 44100;
    spec.format = AUDIO_S16;
    spec.channels = 2;
    spec.samples = 1024 * 4;
    spec.callback = mix_audio;
    spec.userdata = sample;
    SDL_OpenAudio (&spec, NULL);

    /* Play the wav file */
    SDL_PauseAudio (0);
    while (!done)
            usleep (100000);

    /* Shutdown audio system */
    SDL_CloseAudio ();
    Sound_Quit ();
    SDL_QuitSubSystem (SDL_INIT_AUDIO);

return 0;

}

    /* Decode the sound file */
    Sound_SetBufferSize (sample, len);

Don’t do this everytime; you don’t want to be calling realloc() every 100
milliseconds or so while the audio device is open. Look at playsound.c in
the SDL_sound package for a very robust example of decoding during
callback (“robust” means “complex” in this case, but you could still trim
a lot of that out and still have a very stable means of playback).

    /* Convert the audio data (sample rate, channels, etc.) and mix
       it */
    info = &sample->actual;
    SDL_BuildAudioCVT (&cvt,
            info->format, info->channels, info->rate,
            AUDIO_S16,    2,              44100);
    cvt.buf = (Uint8 *) malloc (amount * cvt.len_mult);
    memcpy (cvt.buf, sample->buffer, amount);
    cvt.len = amount;
    SDL_ConvertAudio (&cvt);

Just tell SDL_sound to do the conversion during the call to
Sound_NewSample(), and then Sound_Decode() will convert the data before
handing it back to you.

    /* This function makes the program crash when playing the
       22khz wav file, but works just fine when playing a 44khz MP3;
       when commented out, the program doesn't crash */
    SDL_MixAudio (stream, cvt.buf, amount * cvt.len_mult,
                  SDL_MIX_MAXVOLUME);

After conversion, you might be mixing more data than the audio callback
wants, since the size of the data might double or more…best to let
SDL_sound handle this internally, or you’ll end up with skips in the audio
at best…but probably segfaults when you write too much to (stream).

            sample = Sound_NewSampleFromFile (argv[1], NULL,
                                              1024 * 4);

Just replace that (NULL) with a Sound_AudioInfo structure.

            sample = Sound_NewSampleFromFile ("gunshot.wav", NULL,
                                              1024 * 4);

(And that one too, to be safe)

–ryan.