src/audio/alsa/SDL_alsa_audio.c
author Sam Lantinga <slouken@libsdl.org>
Sun, 04 Jan 2004 15:40:50 +0000
changeset 765 4c2ba6161939
parent 547 5d07f9a47f17
child 769 b8d311d90021
permissions -rw-r--r--
Editors Note: The original patch was modified to use SDL_Delay() instead of
nanosleep because nanosleep may not be portable to all systems
using SDL with the ALSA backend. This may be a moot point with
the switch to blocking writes anyway...

Date: Sat, 27 Dec 2003 21:47:36 +0100
From: Michel Daenzer
To: Debian Bug Tracking System
Subject: [SDL] Bug#225252: [PATCH] ALSA fixes

Package: libsdl1.2debian-all
Version: 1.2.6-2
Severity: normal
Tags: patch

For SDL 1.2.6, the ALSA backend was changed to call snd_pcm_open() with
SND_PCM_NONBLOCK. That's a good idea per se, however, it causes high CPU
usage, interrupted sound and stuttering in some games here. Taking a nanosleep
whenever snd_pcm_writei() returns -EAGAIN fixes this, but I think it's more
efficient to use blocking mode for the actual sound playback. Feedback from the
SDL and ALSA lists appreciated.

The patch also fixes the default ALSA device to be used.
slouken@0
     1
/*
slouken@0
     2
    SDL - Simple DirectMedia Layer
slouken@297
     3
    Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002  Sam Lantinga
slouken@0
     4
slouken@0
     5
    This library is free software; you can redistribute it and/or
slouken@0
     6
    modify it under the terms of the GNU Library General Public
slouken@0
     7
    License as published by the Free Software Foundation; either
slouken@0
     8
    version 2 of the License, or (at your option) any later version.
slouken@0
     9
slouken@0
    10
    This library is distributed in the hope that it will be useful,
slouken@0
    11
    but WITHOUT ANY WARRANTY; without even the implied warranty of
slouken@0
    12
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
slouken@0
    13
    Library General Public License for more details.
slouken@0
    14
slouken@0
    15
    You should have received a copy of the GNU Library General Public
slouken@0
    16
    License along with this library; if not, write to the Free
slouken@0
    17
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
slouken@0
    18
slouken@0
    19
    Sam Lantinga
slouken@252
    20
    slouken@libsdl.org
slouken@0
    21
*/
slouken@0
    22
slouken@0
    23
slouken@0
    24
slouken@0
    25
/* Allow access to a raw mixing buffer */
slouken@0
    26
slouken@0
    27
#include <stdlib.h>
slouken@0
    28
#include <stdio.h>
slouken@0
    29
#include <string.h>
slouken@0
    30
#include <errno.h>
slouken@0
    31
#include <unistd.h>
slouken@0
    32
#include <fcntl.h>
slouken@0
    33
#include <signal.h>
slouken@0
    34
#include <sys/types.h>
slouken@0
    35
#include <sys/time.h>
slouken@0
    36
slouken@0
    37
#include "SDL_audio.h"
slouken@0
    38
#include "SDL_error.h"
slouken@0
    39
#include "SDL_audiomem.h"
slouken@0
    40
#include "SDL_audio_c.h"
slouken@0
    41
#include "SDL_timer.h"
slouken@0
    42
#include "SDL_alsa_audio.h"
slouken@0
    43
slouken@0
    44
/* The tag name used by ALSA audio */
slouken@0
    45
#define DRIVER_NAME         "alsa"
slouken@0
    46
slouken@354
    47
/* The default ALSA audio driver */
slouken@765
    48
#define DEFAULT_DEVICE	"default"
slouken@0
    49
slouken@0
    50
/* Audio driver functions */
slouken@354
    51
static int ALSA_OpenAudio(_THIS, SDL_AudioSpec *spec);
slouken@354
    52
static void ALSA_WaitAudio(_THIS);
slouken@354
    53
static void ALSA_PlayAudio(_THIS);
slouken@354
    54
static Uint8 *ALSA_GetAudioBuf(_THIS);
slouken@354
    55
static void ALSA_CloseAudio(_THIS);
slouken@0
    56
slouken@354
    57
static const char *get_audio_device()
slouken@0
    58
{
slouken@354
    59
	const char *device;
slouken@354
    60
	
slouken@354
    61
	device = getenv("AUDIODEV");	/* Is there a standard variable name? */
slouken@354
    62
	if ( device == NULL ) {
slouken@354
    63
		device = DEFAULT_DEVICE;
slouken@354
    64
	}
slouken@354
    65
	return device;
slouken@0
    66
}
slouken@0
    67
slouken@0
    68
/* Audio driver bootstrap functions */
slouken@0
    69
slouken@0
    70
static int Audio_Available(void)
slouken@0
    71
{
slouken@0
    72
	int available;
slouken@354
    73
	int status;
slouken@0
    74
	snd_pcm_t *handle;
slouken@0
    75
slouken@0
    76
	available = 0;
slouken@547
    77
	status = snd_pcm_open(&handle, get_audio_device(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
slouken@354
    78
	if ( status >= 0 ) {
slouken@354
    79
		available = 1;
slouken@354
    80
        	snd_pcm_close(handle);
slouken@0
    81
	}
slouken@0
    82
	return(available);
slouken@0
    83
}
slouken@0
    84
slouken@0
    85
static void Audio_DeleteDevice(SDL_AudioDevice *device)
slouken@0
    86
{
slouken@0
    87
	free(device->hidden);
slouken@0
    88
	free(device);
slouken@0
    89
}
slouken@0
    90
slouken@0
    91
static SDL_AudioDevice *Audio_CreateDevice(int devindex)
slouken@0
    92
{
slouken@0
    93
	SDL_AudioDevice *this;
slouken@0
    94
slouken@0
    95
	/* Initialize all variables that we clean on shutdown */
slouken@0
    96
	this = (SDL_AudioDevice *)malloc(sizeof(SDL_AudioDevice));
slouken@0
    97
	if ( this ) {
slouken@0
    98
		memset(this, 0, (sizeof *this));
slouken@0
    99
		this->hidden = (struct SDL_PrivateAudioData *)
slouken@0
   100
				malloc((sizeof *this->hidden));
slouken@0
   101
	}
slouken@0
   102
	if ( (this == NULL) || (this->hidden == NULL) ) {
slouken@0
   103
		SDL_OutOfMemory();
slouken@0
   104
		if ( this ) {
slouken@0
   105
			free(this);
slouken@0
   106
		}
slouken@0
   107
		return(0);
slouken@0
   108
	}
slouken@0
   109
	memset(this->hidden, 0, (sizeof *this->hidden));
slouken@0
   110
slouken@0
   111
	/* Set the function pointers */
slouken@354
   112
	this->OpenAudio = ALSA_OpenAudio;
slouken@354
   113
	this->WaitAudio = ALSA_WaitAudio;
slouken@354
   114
	this->PlayAudio = ALSA_PlayAudio;
slouken@354
   115
	this->GetAudioBuf = ALSA_GetAudioBuf;
slouken@354
   116
	this->CloseAudio = ALSA_CloseAudio;
slouken@0
   117
slouken@0
   118
	this->free = Audio_DeleteDevice;
slouken@0
   119
slouken@0
   120
	return this;
slouken@0
   121
}
slouken@0
   122
slouken@0
   123
AudioBootStrap ALSA_bootstrap = {
slouken@354
   124
	DRIVER_NAME, "ALSA 0.9 PCM audio",
slouken@0
   125
	Audio_Available, Audio_CreateDevice
slouken@0
   126
};
slouken@0
   127
slouken@0
   128
/* This function waits until it is possible to write a full sound buffer */
slouken@354
   129
static void ALSA_WaitAudio(_THIS)
slouken@0
   130
{
slouken@0
   131
	/* Check to see if the thread-parent process is still alive */
slouken@0
   132
	{ static int cnt = 0;
slouken@0
   133
		/* Note that this only works with thread implementations 
slouken@0
   134
		   that use a different process id for each thread.
slouken@0
   135
		*/
slouken@0
   136
		if (parent && (((++cnt)%10) == 0)) { /* Check every 10 loops */
slouken@0
   137
			if ( kill(parent, 0) < 0 ) {
slouken@0
   138
				this->enabled = 0;
slouken@0
   139
			}
slouken@0
   140
		}
slouken@0
   141
	}
slouken@0
   142
}
slouken@0
   143
slouken@354
   144
static void ALSA_PlayAudio(_THIS)
slouken@0
   145
{
slouken@354
   146
	int           status;
slouken@354
   147
	int           sample_len;
slouken@354
   148
	signed short *sample_buf;
slouken@765
   149
slouken@354
   150
	sample_len = this->spec.samples;
slouken@354
   151
	sample_buf = (signed short *)mixbuf;
slouken@354
   152
	while ( sample_len > 0 ) {
slouken@354
   153
		status = snd_pcm_writei(pcm_handle, sample_buf, sample_len);
slouken@354
   154
		if ( status < 0 ) {
slouken@354
   155
			if ( status == -EAGAIN ) {
slouken@765
   156
				SDL_Delay(1);
slouken@354
   157
				continue;
slouken@354
   158
			}
slouken@354
   159
			if ( status == -ESTRPIPE ) {
slouken@354
   160
				do {
slouken@765
   161
					SDL_Delay(1);
slouken@354
   162
					status = snd_pcm_resume(pcm_handle);
slouken@354
   163
				} while ( status == -EAGAIN );
slouken@354
   164
			}
slouken@354
   165
			if ( status < 0 ) {
slouken@354
   166
				status = snd_pcm_prepare(pcm_handle);
slouken@354
   167
			}
slouken@354
   168
			if ( status < 0 ) {
slouken@354
   169
				/* Hmm, not much we can do - abort */
slouken@354
   170
				this->enabled = 0;
slouken@354
   171
				return;
slouken@0
   172
			}
slouken@356
   173
			continue;
slouken@0
   174
		}
slouken@354
   175
		sample_buf += status * this->spec.channels;
slouken@354
   176
		sample_len -= status;
slouken@0
   177
	}
slouken@0
   178
}
slouken@0
   179
slouken@354
   180
static Uint8 *ALSA_GetAudioBuf(_THIS)
slouken@0
   181
{
slouken@354
   182
	return(mixbuf);
slouken@354
   183
}
slouken@0
   184
slouken@354
   185
static void ALSA_CloseAudio(_THIS)
slouken@354
   186
{
slouken@354
   187
	if ( mixbuf != NULL ) {
slouken@354
   188
		SDL_FreeAudioMem(mixbuf);
slouken@354
   189
		mixbuf = NULL;
slouken@354
   190
	}
slouken@354
   191
	if ( pcm_handle ) {
slouken@357
   192
		snd_pcm_drain(pcm_handle);
slouken@354
   193
		snd_pcm_close(pcm_handle);
slouken@354
   194
		pcm_handle = NULL;
slouken@354
   195
	}
slouken@354
   196
}
slouken@0
   197
slouken@354
   198
static int ALSA_OpenAudio(_THIS, SDL_AudioSpec *spec)
slouken@354
   199
{
slouken@354
   200
	int                  status;
slouken@354
   201
	snd_pcm_hw_params_t *params;
slouken@354
   202
	snd_pcm_format_t     format;
slouken@354
   203
	snd_pcm_uframes_t    frames;
slouken@354
   204
	Uint16               test_format;
slouken@0
   205
slouken@0
   206
	/* Open the audio device */
slouken@547
   207
	status = snd_pcm_open(&pcm_handle, get_audio_device(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
slouken@354
   208
	if ( status < 0 ) {
slouken@354
   209
		SDL_SetError("Couldn't open audio device: %s", snd_strerror(status));
slouken@0
   210
		return(-1);
slouken@0
   211
	}
slouken@0
   212
slouken@354
   213
	/* Figure out what the hardware is capable of */
slouken@354
   214
	snd_pcm_hw_params_alloca(&params);
slouken@354
   215
	status = snd_pcm_hw_params_any(pcm_handle, params);
slouken@354
   216
	if ( status < 0 ) {
slouken@354
   217
		SDL_SetError("Couldn't get hardware config: %s", snd_strerror(status));
slouken@354
   218
		ALSA_CloseAudio(this);
slouken@354
   219
		return(-1);
slouken@354
   220
	}
slouken@0
   221
slouken@354
   222
	/* SDL only uses interleaved sample output */
slouken@354
   223
	status = snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
slouken@354
   224
	if ( status < 0 ) {
slouken@354
   225
		SDL_SetError("Couldn't set interleaved access: %s", snd_strerror(status));
slouken@354
   226
		ALSA_CloseAudio(this);
slouken@354
   227
		return(-1);
slouken@354
   228
	}
slouken@0
   229
slouken@0
   230
	/* Try for a closest match on audio format */
slouken@354
   231
	status = -1;
slouken@0
   232
	for ( test_format = SDL_FirstAudioFormat(spec->format);
slouken@354
   233
	      test_format && (status < 0); ) {
slouken@354
   234
		switch ( test_format ) {
slouken@0
   235
			case AUDIO_U8:
slouken@354
   236
				format = SND_PCM_FORMAT_U8;
slouken@0
   237
				break;
slouken@0
   238
			case AUDIO_S8:
slouken@354
   239
				format = SND_PCM_FORMAT_S8;
slouken@0
   240
				break;
slouken@0
   241
			case AUDIO_S16LSB:
slouken@354
   242
				format = SND_PCM_FORMAT_S16_LE;
slouken@0
   243
				break;
slouken@0
   244
			case AUDIO_S16MSB:
slouken@354
   245
				format = SND_PCM_FORMAT_S16_BE;
slouken@0
   246
				break;
slouken@0
   247
			case AUDIO_U16LSB:
slouken@354
   248
				format = SND_PCM_FORMAT_U16_LE;
slouken@0
   249
				break;
slouken@0
   250
			case AUDIO_U16MSB:
slouken@354
   251
				format = SND_PCM_FORMAT_U16_BE;
slouken@0
   252
				break;
slouken@0
   253
			default:
slouken@354
   254
				format = 0;
slouken@0
   255
				break;
slouken@0
   256
		}
slouken@354
   257
		if ( format != 0 ) {
slouken@354
   258
			status = snd_pcm_hw_params_set_format(pcm_handle, params, format);
slouken@354
   259
		}
slouken@354
   260
		if ( status < 0 ) {
slouken@0
   261
			test_format = SDL_NextAudioFormat();
slouken@0
   262
		}
slouken@0
   263
	}
slouken@354
   264
	if ( status < 0 ) {
slouken@0
   265
		SDL_SetError("Couldn't find any hardware audio formats");
slouken@354
   266
		ALSA_CloseAudio(this);
slouken@0
   267
		return(-1);
slouken@0
   268
	}
slouken@0
   269
	spec->format = test_format;
slouken@0
   270
slouken@354
   271
	/* Set the number of channels */
slouken@354
   272
	status = snd_pcm_hw_params_set_channels(pcm_handle, params, spec->channels);
slouken@354
   273
	if ( status < 0 ) {
slouken@354
   274
		status = snd_pcm_hw_params_get_channels(params);
slouken@354
   275
		if ( (status <= 0) || (status > 2) ) {
slouken@354
   276
			SDL_SetError("Couldn't set audio channels");
slouken@354
   277
			ALSA_CloseAudio(this);
slouken@354
   278
			return(-1);
slouken@354
   279
		}
slouken@354
   280
		spec->channels = status;
slouken@354
   281
	}
slouken@0
   282
slouken@354
   283
	/* Set the audio rate */
slouken@354
   284
	status = snd_pcm_hw_params_set_rate_near(pcm_handle, params, spec->freq, NULL);
slouken@354
   285
	if ( status < 0 ) {
slouken@354
   286
		SDL_SetError("Couldn't set audio frequency: %s", snd_strerror(status));
slouken@354
   287
		ALSA_CloseAudio(this);
slouken@354
   288
		return(-1);
slouken@354
   289
	}
slouken@354
   290
	spec->freq = status;
slouken@0
   291
slouken@354
   292
	/* Set the buffer size, in samples */
slouken@354
   293
	frames = spec->samples;
slouken@354
   294
	frames = snd_pcm_hw_params_set_period_size_near(pcm_handle, params, frames, NULL);
slouken@354
   295
	spec->samples = frames;
slouken@354
   296
	snd_pcm_hw_params_set_periods_near(pcm_handle, params, 2, NULL);
slouken@354
   297
slouken@354
   298
	/* "set" the hardware with the desired parameters */
slouken@354
   299
	status = snd_pcm_hw_params(pcm_handle, params);
slouken@354
   300
	if ( status < 0 ) {
slouken@354
   301
		SDL_SetError("Couldn't set audio parameters: %s", snd_strerror(status));
slouken@354
   302
		ALSA_CloseAudio(this);
slouken@0
   303
		return(-1);
slouken@0
   304
	}
slouken@0
   305
slouken@354
   306
	/* Calculate the final parameters for this audio specification */
slouken@354
   307
	SDL_CalculateAudioSpec(spec);
slouken@0
   308
slouken@354
   309
	/* Allocate mixing buffer */
slouken@354
   310
	mixlen = spec->size;
slouken@354
   311
	mixbuf = (Uint8 *)SDL_AllocAudioMem(mixlen);
slouken@354
   312
	if ( mixbuf == NULL ) {
slouken@354
   313
		ALSA_CloseAudio(this);
slouken@354
   314
		return(-1);
slouken@0
   315
	}
slouken@354
   316
	memset(mixbuf, spec->silence, spec->size);
slouken@0
   317
slouken@0
   318
	/* Get the parent process id (we're the parent of the audio thread) */
slouken@0
   319
	parent = getpid();
slouken@0
   320
slouken@765
   321
	/* Switch to blocking mode for playback */
slouken@765
   322
	snd_pcm_nonblock(pcm_handle, 0);
slouken@765
   323
slouken@0
   324
	/* We're ready to rock and roll. :-) */
slouken@0
   325
	return(0);
slouken@0
   326
}