SDL_mixer hangs

Hi,

Sometimes SDL_mixer hangs when plays a sound chunk continuously in the same
channel during playing music because of a dead lock.

When the function Mix_PlayChannel() is called, it locks mixer_lock.
Then, if that channel is playing, _Mix_channel_done_playing() is called.
It calls SDL_LockAudio() to block audio callback.

On the other hand, mix_channels() is called from SDL as a callback funciton.
mix_channels() tries to lock mixer_lock.

So that, when mix_channels is trying to lock mixer_lock, and _Mix_channel_done_playing
is trying to call SDL_LockAudio at the same time, they causes a dead lock.

I hacked Mix_PlayChannelTimed(); changed the second arg of _Mix_channel_done_playing()
from 1 to 0. I have no idea this hack is valid or invalid, but it works well.

-----Test code is following-----

#include <stdio.h>
#include <stdlib.h>
#include <SDL/SDL.h>
#include <SDL/SDL_mixer.h>
#include <unistd.h>

static int audio_rate;
static Uint16 audio_format;
static int audio_channels;
static int audio_buffers;

static char *musicNormal = “music.mp3”;
static char soundFiles = “sound.wav”; / modify these file name */

static Mix_Music *currentMusic;
static Mix_Chunk *soundEffect;

int main()
{
SDL_Event event;

audio_rate = 22050;
audio_format = AUDIO_S16;
audio_channels = 2;
audio_buffers = 512;

SDL_Init(SDL_INIT_AUDIO|SDL_INIT_TIMER);
atexit(SDL_Quit);
if (Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers) < 0) {
	printf("Couldn't open audio: %s\n", SDL_GetError());
	exit(1);
}
Mix_QuerySpec(&audio_rate, &audio_format, &audio_channels);
printf("Opened audio at %d Hz %d bit %s, %d bytes audio buffer\n", audio_rate,
	(audio_format&0xFF),
	(audio_channels > 1) ? "stereo" : "mono", 
	audio_buffers );

Mix_ReserveChannels(audio_channels);
Mix_SetMusicCMD(getenv("MUSIC_CMD"));

soundEffect = Mix_LoadWAV(soundFiles);
if(soundEffect == NULL) {
	printf("Failed to load sound file %s.\n", soundFiles);
	exit(1);
}

currentMusic = Mix_LoadMUS(musicNormal);
Mix_PlayMusic(currentMusic, 0);
while(Mix_PlayingMusic()) {
	Mix_PlayChannel(2, soundEffect, 0);
	usleep(10000);
	while(SDL_PollEvent(&event)) {
		if(event.type == SDL_QUIT) exit(1);
	}
}

return 0;

}

Hi,

Sometimes SDL_mixer hangs when plays a sound chunk continuously in the
same
channel during playing music because of a dead lock.

And mysteriously, you don’t mention what OS you are using. That’s not good.

----- Original Message -----
From: fukuchi@is.titech.ac.jp (Kentarou Fukuchi)
To:
Sent: Monday, April 22, 2002 1:09 AM
Subject: [SDL] SDL_mixer hangs

And mysteriously, you don’t mention what OS you are using. That’s not good.

Oops, very sory for that.

OS: Linux-2.4.18
Distro: Debian GNU/Linux Woody (glibc-2.2.5)

Anyway, I think that dead lock issue is OS-free.

Thanks,

KentarouFrom: jason@hoffoss.com (Jason Hoffoss)

When the function Mix_PlayChannel() is called, it locks mixer_lock.
Then, if that channel is playing, _Mix_channel_done_playing() is called.
It calls SDL_LockAudio() to block audio callback.

Hhm.

Your fix is valid enough if you want to use Linux and Win32, but there’s a
deeper problem here.

The reason there are two mutexes is to a) prevent a definite race
condition in the mix_channels audio callback (this is the SDL_LockAudio
mutex) and b) make SDL_mixer’s API thread safe against the calling
application (the mixer_lock mutex). mixer_lock is really not needed for a
single-threaded application, and is, in fact, harmful on MacOS, which
turns SDL_LockMutex()/SDL_UnlockMutex() into a no-op and does some magic
in its SDL_LockAudio()/SDL_UnlockAudio() implementation.

Here’s my completely unthought-through solution:

  • Remove the accesses to mixer_lock from mix_channels altogether. The
    audio callback should be guarded by SDL_LockAudio and only SDL_LockAudio.

  • Add this to mixer.c:

static void lock_mixer(void)
{
/*
* Waits until audio callback finishes, if running, and prevents it
* from preempting us.
*/
SDL_LockAudio();

/*
 * Waits until the mixer API itself is in a sane state.
 */
SDL_LockMutex(mixer_lock)

}

static void unlock_mixer(void)
{
SDL_UnlockMixer(mixer_lock);
SDL_UnlockAudio();
}

  • Replace all accesses to mixer_lock with these wrapper functions.

This should allow us to prevent all deadlocks and race conditions, but I
might be experiencing brainfart. It does depend on multiple threads being
able to safely call SDL_LockAudio at the same time.

Alternately, if SDL can guarantee that SDL_LockAudio will block the audio
callback and any other calling threads, we can just ditch mixer_lock and
use SDL_LockAudio() where needed. Under MacOS this would work (since it
blocks the audio callback and there are no threads), and (to my knowledge)
every other platform just uses a recursive mutex in SDL_LockAudio(), so
it’s feasible, which isn’t the same thing as being a good idea. If we can
get that guarantee about SDL_LockAudio’s behaviour, this is the better
solution, but that might be a big “if”.

I will proceed with whatever solution y’all think is better. Please discuss.

–ryan.

Alternately, if SDL can guarantee that SDL_LockAudio will block the audio
callback and any other calling threads, we can just ditch mixer_lock and
use SDL_LockAudio() where needed.

I believe this is a safe guarantee.

The reason a separate mutex was introduced initially was so that the mixer
API wouldn’t be blocked during the potentially relatively long time that
music is being mixed. However, there isn’t any special protection in the
music API, so to be safe, we should probably not access any SDL_mixer data
while the audio callback is running.

I’m in favor of this solution, since it simplifies things. :slight_smile:

See ya,
-Sam Lantinga, Software Engineer, Blizzard Entertainment