src/audio/alsa/SDL_alsa_audio.c
author Sam Lantinga
Sat, 17 Oct 2009 06:55:17 +0000
branchSDL-1.2
changeset 4347 38f22ed3a433
parent 4339 819270e2f893
child 4348 b312352d8c8d
permissions -rw-r--r--
Option to fix bug #851

For some people setting the period size works better (and is what SDL 1.2.13 did), but for most people it's the same or worse. You can use an environment variable to pick which one you want.
slouken@0
     1
/*
slouken@0
     2
    SDL - Simple DirectMedia Layer
slouken@4159
     3
    Copyright (C) 1997-2009 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@1402
    22
#include "SDL_config.h"
slouken@0
    23
slouken@0
    24
/* Allow access to a raw mixing buffer */
slouken@0
    25
slouken@0
    26
#include <sys/types.h>
slouken@1338
    27
#include <signal.h>	/* For kill() */
slouken@0
    28
slouken@1361
    29
#include "SDL_timer.h"
slouken@0
    30
#include "SDL_audio.h"
slouken@1361
    31
#include "../SDL_audiomem.h"
slouken@1361
    32
#include "../SDL_audio_c.h"
slouken@0
    33
#include "SDL_alsa_audio.h"
slouken@0
    34
slouken@1361
    35
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
slouken@865
    36
#include "SDL_name.h"
slouken@865
    37
#include "SDL_loadso.h"
slouken@865
    38
#else
slouken@865
    39
#define SDL_NAME(X)	X
slouken@865
    40
#endif
slouken@865
    41
slouken@865
    42
slouken@0
    43
/* The tag name used by ALSA audio */
slouken@0
    44
#define DRIVER_NAME         "alsa"
slouken@0
    45
slouken@4333
    46
/*#define DEBUG_PERIOD_SIZE*/
slouken@4333
    47
slouken@0
    48
/* Audio driver functions */
slouken@354
    49
static int ALSA_OpenAudio(_THIS, SDL_AudioSpec *spec);
slouken@354
    50
static void ALSA_WaitAudio(_THIS);
slouken@354
    51
static void ALSA_PlayAudio(_THIS);
slouken@354
    52
static Uint8 *ALSA_GetAudioBuf(_THIS);
slouken@354
    53
static void ALSA_CloseAudio(_THIS);
slouken@0
    54
slouken@1361
    55
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
slouken@865
    56
slouken@1361
    57
static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC;
slouken@865
    58
static void *alsa_handle = NULL;
slouken@865
    59
static int alsa_loaded = 0;
slouken@865
    60
slouken@865
    61
static int (*SDL_NAME(snd_pcm_open))(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode);
slouken@865
    62
static int (*SDL_NAME(snd_pcm_close))(snd_pcm_t *pcm);
slouken@865
    63
static snd_pcm_sframes_t (*SDL_NAME(snd_pcm_writei))(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size);
slouken@865
    64
static int (*SDL_NAME(snd_pcm_resume))(snd_pcm_t *pcm);
slouken@865
    65
static int (*SDL_NAME(snd_pcm_prepare))(snd_pcm_t *pcm);
slouken@865
    66
static int (*SDL_NAME(snd_pcm_drain))(snd_pcm_t *pcm);
slouken@865
    67
static const char *(*SDL_NAME(snd_strerror))(int errnum);
slouken@865
    68
static size_t (*SDL_NAME(snd_pcm_hw_params_sizeof))(void);
slouken@1552
    69
static size_t (*SDL_NAME(snd_pcm_sw_params_sizeof))(void);
slouken@865
    70
static int (*SDL_NAME(snd_pcm_hw_params_any))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
slouken@865
    71
static int (*SDL_NAME(snd_pcm_hw_params_set_access))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t access);
slouken@865
    72
static int (*SDL_NAME(snd_pcm_hw_params_set_format))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val);
slouken@865
    73
static int (*SDL_NAME(snd_pcm_hw_params_set_channels))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val);
slouken@4331
    74
static int (*SDL_NAME(snd_pcm_hw_params_get_channels))(const snd_pcm_hw_params_t *params, unsigned int *val);
icculus@4292
    75
static int (*SDL_NAME(snd_pcm_hw_params_set_rate_near))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
icculus@4292
    76
static int (*SDL_NAME(snd_pcm_hw_params_set_period_size_near))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir);
slouken@4331
    77
static int (*SDL_NAME(snd_pcm_hw_params_get_period_size))(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir);
icculus@4292
    78
static int (*SDL_NAME(snd_pcm_hw_params_set_periods_near))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
slouken@4331
    79
static int (*SDL_NAME(snd_pcm_hw_params_get_periods))(const snd_pcm_hw_params_t *params, unsigned int *val, int *dir);
slouken@4333
    80
static int (*SDL_NAME(snd_pcm_hw_params_set_buffer_size_near))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
slouken@4331
    81
static int (*SDL_NAME(snd_pcm_hw_params_get_buffer_size))(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val);
slouken@865
    82
static int (*SDL_NAME(snd_pcm_hw_params))(snd_pcm_t *pcm, snd_pcm_hw_params_t *params);
slouken@1552
    83
/*
slouken@1552
    84
*/
slouken@1552
    85
static int (*SDL_NAME(snd_pcm_sw_params_current))(snd_pcm_t *pcm, snd_pcm_sw_params_t *swparams);
slouken@1552
    86
static int (*SDL_NAME(snd_pcm_sw_params_set_start_threshold))(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val);
slouken@1552
    87
static int (*SDL_NAME(snd_pcm_sw_params))(snd_pcm_t *pcm, snd_pcm_sw_params_t *params);
slouken@1553
    88
static int (*SDL_NAME(snd_pcm_nonblock))(snd_pcm_t *pcm, int nonblock);
slouken@865
    89
#define snd_pcm_hw_params_sizeof SDL_NAME(snd_pcm_hw_params_sizeof)
slouken@1552
    90
#define snd_pcm_sw_params_sizeof SDL_NAME(snd_pcm_sw_params_sizeof)
slouken@865
    91
icculus@1161
    92
/* cast funcs to char* first, to please GCC's strict aliasing rules. */
slouken@865
    93
static struct {
slouken@865
    94
	const char *name;
slouken@865
    95
	void **func;
slouken@865
    96
} alsa_functions[] = {
icculus@1161
    97
	{ "snd_pcm_open",	(void**)(char*)&SDL_NAME(snd_pcm_open)		},
icculus@1161
    98
	{ "snd_pcm_close",	(void**)(char*)&SDL_NAME(snd_pcm_close)	},
icculus@1161
    99
	{ "snd_pcm_writei",	(void**)(char*)&SDL_NAME(snd_pcm_writei)	},
icculus@1161
   100
	{ "snd_pcm_resume",	(void**)(char*)&SDL_NAME(snd_pcm_resume)	},
icculus@1161
   101
	{ "snd_pcm_prepare",	(void**)(char*)&SDL_NAME(snd_pcm_prepare)	},
icculus@1161
   102
	{ "snd_pcm_drain",	(void**)(char*)&SDL_NAME(snd_pcm_drain)	},
icculus@1161
   103
	{ "snd_strerror",	(void**)(char*)&SDL_NAME(snd_strerror)		},
icculus@1161
   104
	{ "snd_pcm_hw_params_sizeof",		(void**)(char*)&SDL_NAME(snd_pcm_hw_params_sizeof)		},
slouken@1552
   105
	{ "snd_pcm_sw_params_sizeof",		(void**)(char*)&SDL_NAME(snd_pcm_sw_params_sizeof)		},
icculus@1161
   106
	{ "snd_pcm_hw_params_any",		(void**)(char*)&SDL_NAME(snd_pcm_hw_params_any)		},
icculus@1161
   107
	{ "snd_pcm_hw_params_set_access",	(void**)(char*)&SDL_NAME(snd_pcm_hw_params_set_access)		},
icculus@1161
   108
	{ "snd_pcm_hw_params_set_format",	(void**)(char*)&SDL_NAME(snd_pcm_hw_params_set_format)		},
icculus@1161
   109
	{ "snd_pcm_hw_params_set_channels",	(void**)(char*)&SDL_NAME(snd_pcm_hw_params_set_channels)	},
icculus@1161
   110
	{ "snd_pcm_hw_params_get_channels",	(void**)(char*)&SDL_NAME(snd_pcm_hw_params_get_channels)	},
icculus@1161
   111
	{ "snd_pcm_hw_params_set_rate_near",	(void**)(char*)&SDL_NAME(snd_pcm_hw_params_set_rate_near)	},
icculus@1161
   112
	{ "snd_pcm_hw_params_set_period_size_near",	(void**)(char*)&SDL_NAME(snd_pcm_hw_params_set_period_size_near)	},
slouken@1553
   113
	{ "snd_pcm_hw_params_get_period_size",	(void**)(char*)&SDL_NAME(snd_pcm_hw_params_get_period_size)	},
icculus@1161
   114
	{ "snd_pcm_hw_params_set_periods_near",	(void**)(char*)&SDL_NAME(snd_pcm_hw_params_set_periods_near)	},
slouken@1553
   115
	{ "snd_pcm_hw_params_get_periods",	(void**)(char*)&SDL_NAME(snd_pcm_hw_params_get_periods)	},
slouken@4333
   116
	{ "snd_pcm_hw_params_set_buffer_size_near",	(void**)(char*)&SDL_NAME(snd_pcm_hw_params_set_buffer_size_near) },
slouken@4331
   117
	{ "snd_pcm_hw_params_get_buffer_size",	(void**)(char*)&SDL_NAME(snd_pcm_hw_params_get_buffer_size) },
icculus@1161
   118
	{ "snd_pcm_hw_params",	(void**)(char*)&SDL_NAME(snd_pcm_hw_params)	},
slouken@1552
   119
	{ "snd_pcm_sw_params_current",	(void**)(char*)&SDL_NAME(snd_pcm_sw_params_current)	},
slouken@1552
   120
	{ "snd_pcm_sw_params_set_start_threshold",	(void**)(char*)&SDL_NAME(snd_pcm_sw_params_set_start_threshold)	},
slouken@1552
   121
	{ "snd_pcm_sw_params",	(void**)(char*)&SDL_NAME(snd_pcm_sw_params)	},
slouken@1553
   122
	{ "snd_pcm_nonblock",	(void**)(char*)&SDL_NAME(snd_pcm_nonblock)	},
slouken@865
   123
};
slouken@865
   124
slouken@865
   125
static void UnloadALSALibrary(void) {
slouken@865
   126
	if (alsa_loaded) {
icculus@4292
   127
		SDL_UnloadObject(alsa_handle);
slouken@865
   128
		alsa_handle = NULL;
slouken@865
   129
		alsa_loaded = 0;
slouken@865
   130
	}
slouken@865
   131
}
slouken@865
   132
slouken@865
   133
static int LoadALSALibrary(void) {
slouken@865
   134
	int i, retval = -1;
slouken@865
   135
icculus@4292
   136
	alsa_handle = SDL_LoadObject(alsa_library);
slouken@865
   137
	if (alsa_handle) {
slouken@865
   138
		alsa_loaded = 1;
slouken@865
   139
		retval = 0;
slouken@1379
   140
		for (i = 0; i < SDL_arraysize(alsa_functions); i++) {
icculus@4292
   141
			*alsa_functions[i].func = SDL_LoadFunction(alsa_handle,alsa_functions[i].name);
slouken@865
   142
			if (!*alsa_functions[i].func) {
slouken@865
   143
				retval = -1;
slouken@865
   144
				UnloadALSALibrary();
slouken@865
   145
				break;
slouken@865
   146
			}
slouken@865
   147
		}
slouken@865
   148
	}
slouken@865
   149
	return retval;
slouken@865
   150
}
slouken@865
   151
slouken@865
   152
#else
slouken@865
   153
slouken@865
   154
static void UnloadALSALibrary(void) {
slouken@865
   155
	return;
slouken@865
   156
}
slouken@865
   157
slouken@865
   158
static int LoadALSALibrary(void) {
slouken@865
   159
	return 0;
slouken@865
   160
}
slouken@865
   161
slouken@1361
   162
#endif /* SDL_AUDIO_DRIVER_ALSA_DYNAMIC */
slouken@865
   163
slouken@942
   164
static const char *get_audio_device(int channels)
slouken@354
   165
{
slouken@354
   166
	const char *device;
slouken@354
   167
	
slouken@1336
   168
	device = SDL_getenv("AUDIODEV");	/* Is there a standard variable name? */
slouken@354
   169
	if ( device == NULL ) {
slouken@4334
   170
		switch (channels) {
slouken@4334
   171
		case 6:
slouken@4334
   172
			device = "plug:surround51";
slouken@4334
   173
			break;
slouken@4334
   174
		case 4:
slouken@4334
   175
			device = "plug:surround40";
slouken@4334
   176
			break;
slouken@4334
   177
		default:
slouken@4334
   178
			device = "default";
slouken@4334
   179
			break;
slouken@4334
   180
		}
slouken@354
   181
	}
slouken@354
   182
	return device;
slouken@0
   183
}
slouken@0
   184
slouken@0
   185
/* Audio driver bootstrap functions */
slouken@0
   186
slouken@0
   187
static int Audio_Available(void)
slouken@0
   188
{
slouken@0
   189
	int available;
slouken@354
   190
	int status;
slouken@0
   191
	snd_pcm_t *handle;
slouken@0
   192
slouken@0
   193
	available = 0;
slouken@865
   194
	if (LoadALSALibrary() < 0) {
slouken@865
   195
		return available;
slouken@865
   196
	}
slouken@942
   197
	status = SDL_NAME(snd_pcm_open)(&handle, get_audio_device(2), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
slouken@354
   198
	if ( status >= 0 ) {
slouken@354
   199
		available = 1;
slouken@865
   200
        	SDL_NAME(snd_pcm_close)(handle);
slouken@0
   201
	}
slouken@865
   202
	UnloadALSALibrary();
slouken@0
   203
	return(available);
slouken@0
   204
}
slouken@0
   205
slouken@0
   206
static void Audio_DeleteDevice(SDL_AudioDevice *device)
slouken@0
   207
{
slouken@1336
   208
	SDL_free(device->hidden);
slouken@1336
   209
	SDL_free(device);
slouken@865
   210
	UnloadALSALibrary();
slouken@0
   211
}
slouken@0
   212
slouken@0
   213
static SDL_AudioDevice *Audio_CreateDevice(int devindex)
slouken@0
   214
{
slouken@0
   215
	SDL_AudioDevice *this;
slouken@0
   216
slouken@0
   217
	/* Initialize all variables that we clean on shutdown */
slouken@865
   218
	LoadALSALibrary();
slouken@1336
   219
	this = (SDL_AudioDevice *)SDL_malloc(sizeof(SDL_AudioDevice));
slouken@0
   220
	if ( this ) {
slouken@1336
   221
		SDL_memset(this, 0, (sizeof *this));
slouken@0
   222
		this->hidden = (struct SDL_PrivateAudioData *)
slouken@1336
   223
				SDL_malloc((sizeof *this->hidden));
slouken@0
   224
	}
slouken@0
   225
	if ( (this == NULL) || (this->hidden == NULL) ) {
slouken@0
   226
		SDL_OutOfMemory();
slouken@0
   227
		if ( this ) {
slouken@1336
   228
			SDL_free(this);
slouken@0
   229
		}
slouken@0
   230
		return(0);
slouken@0
   231
	}
slouken@1336
   232
	SDL_memset(this->hidden, 0, (sizeof *this->hidden));
slouken@0
   233
slouken@0
   234
	/* Set the function pointers */
slouken@354
   235
	this->OpenAudio = ALSA_OpenAudio;
slouken@354
   236
	this->WaitAudio = ALSA_WaitAudio;
slouken@354
   237
	this->PlayAudio = ALSA_PlayAudio;
slouken@354
   238
	this->GetAudioBuf = ALSA_GetAudioBuf;
slouken@354
   239
	this->CloseAudio = ALSA_CloseAudio;
slouken@0
   240
slouken@0
   241
	this->free = Audio_DeleteDevice;
slouken@0
   242
slouken@0
   243
	return this;
slouken@0
   244
}
slouken@0
   245
slouken@0
   246
AudioBootStrap ALSA_bootstrap = {
icculus@4339
   247
	DRIVER_NAME, "ALSA PCM audio",
slouken@0
   248
	Audio_Available, Audio_CreateDevice
slouken@0
   249
};
slouken@0
   250
slouken@0
   251
/* This function waits until it is possible to write a full sound buffer */
slouken@354
   252
static void ALSA_WaitAudio(_THIS)
slouken@0
   253
{
slouken@0
   254
	/* Check to see if the thread-parent process is still alive */
slouken@0
   255
	{ static int cnt = 0;
slouken@0
   256
		/* Note that this only works with thread implementations 
slouken@0
   257
		   that use a different process id for each thread.
slouken@0
   258
		*/
slouken@0
   259
		if (parent && (((++cnt)%10) == 0)) { /* Check every 10 loops */
slouken@0
   260
			if ( kill(parent, 0) < 0 ) {
slouken@0
   261
				this->enabled = 0;
slouken@0
   262
			}
slouken@0
   263
		}
slouken@0
   264
	}
slouken@0
   265
}
slouken@0
   266
icculus@1878
   267
icculus@1878
   268
/*
icculus@1878
   269
 * http://bugzilla.libsdl.org/show_bug.cgi?id=110
icculus@1878
   270
 * "For Linux ALSA, this is FL-FR-RL-RR-C-LFE
icculus@1878
   271
 *  and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR"
icculus@1878
   272
 */
icculus@1878
   273
#define SWIZ6(T) \
icculus@1878
   274
    T *ptr = (T *) mixbuf; \
icculus@1878
   275
    const Uint32 count = (this->spec.samples / 6); \
icculus@1878
   276
    Uint32 i; \
icculus@1878
   277
    for (i = 0; i < count; i++, ptr += 6) { \
icculus@1878
   278
        T tmp; \
icculus@1878
   279
        tmp = ptr[2]; ptr[2] = ptr[4]; ptr[4] = tmp; \
icculus@1878
   280
        tmp = ptr[3]; ptr[3] = ptr[5]; ptr[5] = tmp; \
icculus@1878
   281
    }
icculus@1878
   282
icculus@1878
   283
static __inline__ void swizzle_alsa_channels_6_64bit(_THIS) { SWIZ6(Uint64); }
icculus@1878
   284
static __inline__ void swizzle_alsa_channels_6_32bit(_THIS) { SWIZ6(Uint32); }
icculus@1878
   285
static __inline__ void swizzle_alsa_channels_6_16bit(_THIS) { SWIZ6(Uint16); }
icculus@1878
   286
static __inline__ void swizzle_alsa_channels_6_8bit(_THIS) { SWIZ6(Uint8); }
icculus@1878
   287
icculus@1878
   288
#undef SWIZ6
icculus@1878
   289
icculus@1878
   290
icculus@1878
   291
/*
icculus@1878
   292
 * Called right before feeding this->mixbuf to the hardware. Swizzle channels
icculus@1878
   293
 *  from Windows/Mac order to the format alsalib will want.
icculus@1878
   294
 */
icculus@1878
   295
static __inline__ void swizzle_alsa_channels(_THIS)
icculus@1878
   296
{
icculus@1878
   297
    if (this->spec.channels == 6) {
icculus@1878
   298
        const Uint16 fmtsize = (this->spec.format & 0xFF); /* bits/channel. */
icculus@1878
   299
        if (fmtsize == 16)
icculus@1878
   300
            swizzle_alsa_channels_6_16bit(this);
icculus@1878
   301
        else if (fmtsize == 8)
icculus@1878
   302
            swizzle_alsa_channels_6_8bit(this);
icculus@1878
   303
        else if (fmtsize == 32)
icculus@1878
   304
            swizzle_alsa_channels_6_32bit(this);
icculus@1878
   305
        else if (fmtsize == 64)
icculus@1878
   306
            swizzle_alsa_channels_6_64bit(this);
icculus@1878
   307
    }
icculus@1878
   308
icculus@1878
   309
    /* !!! FIXME: update this for 7.1 if needed, later. */
icculus@1878
   310
}
icculus@1878
   311
icculus@1878
   312
slouken@354
   313
static void ALSA_PlayAudio(_THIS)
slouken@0
   314
{
icculus@4320
   315
	int status;
slouken@4332
   316
	snd_pcm_uframes_t frames_left;
icculus@4320
   317
	const Uint8 *sample_buf = (const Uint8 *) mixbuf;
slouken@4336
   318
	const int frame_size = (((int) (this->spec.format & 0xFF)) / 8) * this->spec.channels;
slouken@765
   319
icculus@1878
   320
	swizzle_alsa_channels(this);
icculus@1878
   321
slouken@4332
   322
	frames_left = ((snd_pcm_uframes_t) this->spec.samples);
icculus@1878
   323
slouken@4337
   324
	while ( frames_left > 0 && this->enabled ) {
slouken@4332
   325
		status = SDL_NAME(snd_pcm_writei)(pcm_handle, sample_buf, frames_left);
slouken@354
   326
		if ( status < 0 ) {
slouken@354
   327
			if ( status == -EAGAIN ) {
slouken@765
   328
				SDL_Delay(1);
slouken@354
   329
				continue;
slouken@354
   330
			}
slouken@354
   331
			if ( status == -ESTRPIPE ) {
slouken@354
   332
				do {
slouken@765
   333
					SDL_Delay(1);
slouken@865
   334
					status = SDL_NAME(snd_pcm_resume)(pcm_handle);
slouken@354
   335
				} while ( status == -EAGAIN );
slouken@354
   336
			}
slouken@354
   337
			if ( status < 0 ) {
slouken@865
   338
				status = SDL_NAME(snd_pcm_prepare)(pcm_handle);
slouken@354
   339
			}
slouken@354
   340
			if ( status < 0 ) {
slouken@354
   341
				/* Hmm, not much we can do - abort */
slouken@354
   342
				this->enabled = 0;
slouken@354
   343
				return;
slouken@0
   344
			}
slouken@356
   345
			continue;
slouken@0
   346
		}
slouken@4336
   347
		sample_buf += status * frame_size;
slouken@4332
   348
		frames_left -= status;
slouken@0
   349
	}
slouken@0
   350
}
slouken@0
   351
slouken@354
   352
static Uint8 *ALSA_GetAudioBuf(_THIS)
slouken@0
   353
{
slouken@354
   354
	return(mixbuf);
slouken@0
   355
}
slouken@0
   356
slouken@354
   357
static void ALSA_CloseAudio(_THIS)
slouken@0
   358
{
slouken@354
   359
	if ( mixbuf != NULL ) {
slouken@354
   360
		SDL_FreeAudioMem(mixbuf);
slouken@354
   361
		mixbuf = NULL;
slouken@0
   362
	}
slouken@354
   363
	if ( pcm_handle ) {
slouken@865
   364
		SDL_NAME(snd_pcm_drain)(pcm_handle);
slouken@865
   365
		SDL_NAME(snd_pcm_close)(pcm_handle);
slouken@354
   366
		pcm_handle = NULL;
slouken@0
   367
	}
slouken@0
   368
}
slouken@0
   369
slouken@354
   370
static int ALSA_OpenAudio(_THIS, SDL_AudioSpec *spec)
slouken@0
   371
{
slouken@354
   372
	int                  status;
slouken@1552
   373
	snd_pcm_hw_params_t *hwparams;
slouken@1552
   374
	snd_pcm_sw_params_t *swparams;
slouken@354
   375
	snd_pcm_format_t     format;
slouken@354
   376
	snd_pcm_uframes_t    frames;
icculus@4292
   377
	unsigned int         rate;
icculus@4292
   378
	unsigned int         periods;
slouken@4333
   379
	unsigned int 	     channels;
slouken@354
   380
	Uint16               test_format;
slouken@0
   381
slouken@0
   382
	/* Open the audio device */
slouken@942
   383
	/* Name of device should depend on # channels in spec */
slouken@942
   384
	status = SDL_NAME(snd_pcm_open)(&pcm_handle, get_audio_device(spec->channels), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
slouken@942
   385
slouken@354
   386
	if ( status < 0 ) {
slouken@865
   387
		SDL_SetError("Couldn't open audio device: %s", SDL_NAME(snd_strerror)(status));
slouken@354
   388
		return(-1);
slouken@354
   389
	}
slouken@354
   390
slouken@354
   391
	/* Figure out what the hardware is capable of */
slouken@1552
   392
	snd_pcm_hw_params_alloca(&hwparams);
slouken@1552
   393
	status = SDL_NAME(snd_pcm_hw_params_any)(pcm_handle, hwparams);
slouken@354
   394
	if ( status < 0 ) {
slouken@865
   395
		SDL_SetError("Couldn't get hardware config: %s", SDL_NAME(snd_strerror)(status));
slouken@354
   396
		ALSA_CloseAudio(this);
slouken@354
   397
		return(-1);
slouken@354
   398
	}
slouken@354
   399
slouken@354
   400
	/* SDL only uses interleaved sample output */
slouken@1552
   401
	status = SDL_NAME(snd_pcm_hw_params_set_access)(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
slouken@354
   402
	if ( status < 0 ) {
slouken@865
   403
		SDL_SetError("Couldn't set interleaved access: %s", SDL_NAME(snd_strerror)(status));
slouken@354
   404
		ALSA_CloseAudio(this);
slouken@0
   405
		return(-1);
slouken@0
   406
	}
slouken@0
   407
slouken@0
   408
	/* Try for a closest match on audio format */
slouken@354
   409
	status = -1;
slouken@0
   410
	for ( test_format = SDL_FirstAudioFormat(spec->format);
slouken@354
   411
	      test_format && (status < 0); ) {
slouken@354
   412
		switch ( test_format ) {
slouken@0
   413
			case AUDIO_U8:
slouken@354
   414
				format = SND_PCM_FORMAT_U8;
slouken@0
   415
				break;
slouken@0
   416
			case AUDIO_S8:
slouken@354
   417
				format = SND_PCM_FORMAT_S8;
slouken@0
   418
				break;
slouken@0
   419
			case AUDIO_S16LSB:
slouken@354
   420
				format = SND_PCM_FORMAT_S16_LE;
slouken@0
   421
				break;
slouken@0
   422
			case AUDIO_S16MSB:
slouken@354
   423
				format = SND_PCM_FORMAT_S16_BE;
slouken@0
   424
				break;
slouken@0
   425
			case AUDIO_U16LSB:
slouken@354
   426
				format = SND_PCM_FORMAT_U16_LE;
slouken@0
   427
				break;
slouken@0
   428
			case AUDIO_U16MSB:
slouken@354
   429
				format = SND_PCM_FORMAT_U16_BE;
slouken@0
   430
				break;
slouken@0
   431
			default:
slouken@354
   432
				format = 0;
slouken@0
   433
				break;
slouken@0
   434
		}
slouken@354
   435
		if ( format != 0 ) {
slouken@1552
   436
			status = SDL_NAME(snd_pcm_hw_params_set_format)(pcm_handle, hwparams, format);
slouken@354
   437
		}
slouken@354
   438
		if ( status < 0 ) {
slouken@0
   439
			test_format = SDL_NextAudioFormat();
slouken@0
   440
		}
slouken@0
   441
	}
slouken@354
   442
	if ( status < 0 ) {
slouken@0
   443
		SDL_SetError("Couldn't find any hardware audio formats");
slouken@354
   444
		ALSA_CloseAudio(this);
slouken@0
   445
		return(-1);
slouken@0
   446
	}
slouken@0
   447
	spec->format = test_format;
slouken@0
   448
slouken@354
   449
	/* Set the number of channels */
slouken@1552
   450
	status = SDL_NAME(snd_pcm_hw_params_set_channels)(pcm_handle, hwparams, spec->channels);
icculus@4292
   451
	channels = spec->channels;
slouken@354
   452
	if ( status < 0 ) {
icculus@4292
   453
		status = SDL_NAME(snd_pcm_hw_params_get_channels)(hwparams, &channels);
icculus@4292
   454
		if ( status < 0 ) {
slouken@354
   455
			SDL_SetError("Couldn't set audio channels");
slouken@354
   456
			ALSA_CloseAudio(this);
slouken@354
   457
			return(-1);
slouken@354
   458
		}
icculus@4292
   459
		spec->channels = channels;
slouken@354
   460
	}
slouken@0
   461
slouken@354
   462
	/* Set the audio rate */
icculus@4292
   463
	rate = spec->freq;
icculus@4292
   464
icculus@4292
   465
	status = SDL_NAME(snd_pcm_hw_params_set_rate_near)(pcm_handle, hwparams, &rate, NULL);
slouken@354
   466
	if ( status < 0 ) {
slouken@865
   467
		SDL_SetError("Couldn't set audio frequency: %s", SDL_NAME(snd_strerror)(status));
slouken@354
   468
		ALSA_CloseAudio(this);
slouken@354
   469
		return(-1);
slouken@354
   470
	}
icculus@4292
   471
	spec->freq = rate;
slouken@0
   472
slouken@354
   473
	/* Set the buffer size, in samples */
slouken@4347
   474
	if (getenv("SDL_AUDIO_ALSA_SET_PERIOD_SIZE")) {
slouken@4347
   475
		frames = spec->samples;
slouken@4347
   476
		status = SDL_NAME(snd_pcm_hw_params_set_period_size_near)(pcm_handle, hwparams, &frames, NULL);
slouken@4347
   477
		if ( status < 0 ) {
slouken@4347
   478
			SDL_SetError("Couldn't set period size: %s", SDL_NAME(snd_strerror)(status));
slouken@4347
   479
			ALSA_CloseAudio(this);
slouken@4347
   480
			return(-1);
slouken@4347
   481
		}
icculus@4292
   482
slouken@4347
   483
		spec->samples = frames;
icculus@4292
   484
slouken@4347
   485
		periods = 2;
slouken@4347
   486
		status = SDL_NAME(snd_pcm_hw_params_set_periods_near)(pcm_handle, hwparams, &periods, NULL);
slouken@4347
   487
		if ( status < 0 ) {
slouken@4347
   488
			SDL_SetError("Couldn't set period count: %s", SDL_NAME(snd_strerror)(status));
slouken@4347
   489
			ALSA_CloseAudio(this);
slouken@4347
   490
			return(-1);
slouken@4347
   491
		}
slouken@4347
   492
	} else {
slouken@4347
   493
		frames = spec->samples * 2;
slouken@4347
   494
		status = SDL_NAME(snd_pcm_hw_params_set_buffer_size_near)(pcm_handle, hwparams, &frames);
slouken@4347
   495
		if ( status < 0 ) {
slouken@4347
   496
			SDL_SetError("Couldn't set buffer size: %s", SDL_NAME(snd_strerror)(status));
slouken@4347
   497
			ALSA_CloseAudio(this);
slouken@4347
   498
			return(-1);
slouken@4347
   499
		}
slouken@4333
   500
	}
slouken@354
   501
slouken@354
   502
	/* "set" the hardware with the desired parameters */
slouken@1552
   503
	status = SDL_NAME(snd_pcm_hw_params)(pcm_handle, hwparams);
slouken@1552
   504
	if ( status < 0 ) {
slouken@1552
   505
		SDL_SetError("Couldn't set hardware audio parameters: %s", SDL_NAME(snd_strerror)(status));
slouken@1552
   506
		ALSA_CloseAudio(this);
slouken@1552
   507
		return(-1);
slouken@1552
   508
	}
slouken@1552
   509
slouken@1553
   510
/* This is useful for debugging... */
slouken@4333
   511
#ifdef DEBUG_PERIOD_SIZE
slouken@4331
   512
{ snd_pcm_uframes_t bufsize; snd_pcm_sframes_t persize; unsigned int periods; int dir;
slouken@4331
   513
   SDL_NAME(snd_pcm_hw_params_get_buffer_size)(hwparams, &bufsize);
slouken@4331
   514
   SDL_NAME(snd_pcm_hw_params_get_period_size)(hwparams, &persize, &dir);
slouken@4331
   515
   SDL_NAME(snd_pcm_hw_params_get_periods)(hwparams, &periods, &dir);
slouken@1552
   516
slouken@4331
   517
   fprintf(stderr, "ALSA: period size = %ld, periods = %u, buffer size = %lu\n", persize, periods, bufsize);
slouken@1552
   518
}
slouken@4331
   519
#endif
slouken@1552
   520
slouken@1552
   521
	/* Set the software parameters */
slouken@1552
   522
	snd_pcm_sw_params_alloca(&swparams);
slouken@1552
   523
	status = SDL_NAME(snd_pcm_sw_params_current)(pcm_handle, swparams);
slouken@354
   524
	if ( status < 0 ) {
slouken@1552
   525
		SDL_SetError("Couldn't get software config: %s", SDL_NAME(snd_strerror)(status));
slouken@1552
   526
		ALSA_CloseAudio(this);
slouken@1552
   527
		return(-1);
slouken@1552
   528
	}
slouken@4331
   529
	status = SDL_NAME(snd_pcm_sw_params_set_start_threshold)(pcm_handle, swparams, 1);
slouken@1552
   530
	if ( status < 0 ) {
slouken@1552
   531
		SDL_SetError("Couldn't set start threshold: %s", SDL_NAME(snd_strerror)(status));
slouken@1552
   532
		ALSA_CloseAudio(this);
slouken@1552
   533
		return(-1);
slouken@1552
   534
	}
slouken@1552
   535
	status = SDL_NAME(snd_pcm_sw_params)(pcm_handle, swparams);
slouken@1552
   536
	if ( status < 0 ) {
slouken@1552
   537
		SDL_SetError("Couldn't set software audio parameters: %s", SDL_NAME(snd_strerror)(status));
slouken@354
   538
		ALSA_CloseAudio(this);
slouken@0
   539
		return(-1);
slouken@0
   540
	}
slouken@0
   541
slouken@354
   542
	/* Calculate the final parameters for this audio specification */
slouken@354
   543
	SDL_CalculateAudioSpec(spec);
slouken@0
   544
slouken@354
   545
	/* Allocate mixing buffer */
slouken@354
   546
	mixlen = spec->size;
slouken@354
   547
	mixbuf = (Uint8 *)SDL_AllocAudioMem(mixlen);
slouken@354
   548
	if ( mixbuf == NULL ) {
slouken@354
   549
		ALSA_CloseAudio(this);
slouken@354
   550
		return(-1);
slouken@0
   551
	}
slouken@1336
   552
	SDL_memset(mixbuf, spec->silence, spec->size);
slouken@0
   553
slouken@0
   554
	/* Get the parent process id (we're the parent of the audio thread) */
slouken@0
   555
	parent = getpid();
slouken@0
   556
slouken@765
   557
	/* Switch to blocking mode for playback */
slouken@865
   558
	SDL_NAME(snd_pcm_nonblock)(pcm_handle, 0);
slouken@765
   559
slouken@0
   560
	/* We're ready to rock and roll. :-) */
slouken@0
   561
	return(0);
slouken@0
   562
}