src/audio/sndio/SDL_sndioaudio.c
author Ryan C. Gordon <icculus@icculus.org>
Sun, 08 Jul 2012 07:20:50 -0400
branchSDL-1.2
changeset 6353 dfcbd0d9209c
child 13925 d3f74efa82e7
permissions -rw-r--r--
Added OpenBSD "sndio" audio target.

Thanks to Brad Smith and the OpenBSD team for the patch!
icculus@6353
     1
/*
icculus@6353
     2
 * Copyright (c) 2008 Jacob Meuser <jakemsr@sdf.lonestar.org>
icculus@6353
     3
 *
icculus@6353
     4
 * Permission to use, copy, modify, and distribute this software for any
icculus@6353
     5
 * purpose with or without fee is hereby granted, provided that the above
icculus@6353
     6
 * copyright notice and this permission notice appear in all copies.
icculus@6353
     7
 *
icculus@6353
     8
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
icculus@6353
     9
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
icculus@6353
    10
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
icculus@6353
    11
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
icculus@6353
    12
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
icculus@6353
    13
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
icculus@6353
    14
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
icculus@6353
    15
 */
icculus@6353
    16
icculus@6353
    17
#include "SDL_config.h"
icculus@6353
    18
icculus@6353
    19
/* Allow access to a raw mixing buffer */
icculus@6353
    20
icculus@6353
    21
#ifdef HAVE_SIGNAL_H
icculus@6353
    22
#include <signal.h>
icculus@6353
    23
#endif
icculus@6353
    24
#include <unistd.h>
icculus@6353
    25
icculus@6353
    26
#include "SDL_timer.h"
icculus@6353
    27
#include "SDL_audio.h"
icculus@6353
    28
#include "../SDL_audiomem.h"
icculus@6353
    29
#include "../SDL_audio_c.h"
icculus@6353
    30
#include "../SDL_audiodev_c.h"
icculus@6353
    31
#include "SDL_sndioaudio.h"
icculus@6353
    32
icculus@6353
    33
/* The tag name used by sndio audio */
icculus@6353
    34
#define SNDIO_DRIVER_NAME         "sndio"
icculus@6353
    35
icculus@6353
    36
/* Audio driver functions */
icculus@6353
    37
static int SNDIO_OpenAudio(_THIS, SDL_AudioSpec *spec);
icculus@6353
    38
static void SNDIO_WaitAudio(_THIS);
icculus@6353
    39
static void SNDIO_PlayAudio(_THIS);
icculus@6353
    40
static Uint8 *SNDIO_GetAudioBuf(_THIS);
icculus@6353
    41
static void SNDIO_CloseAudio(_THIS);
icculus@6353
    42
icculus@6353
    43
/* Audio driver bootstrap functions */
icculus@6353
    44
icculus@6353
    45
static int Audio_Available(void)
icculus@6353
    46
{
icculus@6353
    47
	struct sio_hdl *this_hdl;
icculus@6353
    48
	int available = 0;
icculus@6353
    49
icculus@6353
    50
	if ( (this_hdl = sio_open(NULL, SIO_PLAY, 0)) != NULL ) {
icculus@6353
    51
		sio_close(this_hdl);
icculus@6353
    52
		available = 1;
icculus@6353
    53
	}
icculus@6353
    54
icculus@6353
    55
	return available;
icculus@6353
    56
}
icculus@6353
    57
icculus@6353
    58
static void Audio_DeleteDevice(SDL_AudioDevice *device)
icculus@6353
    59
{
icculus@6353
    60
	SDL_free(device->hidden);
icculus@6353
    61
	SDL_free(device);
icculus@6353
    62
}
icculus@6353
    63
icculus@6353
    64
static SDL_AudioDevice *Audio_CreateDevice(int devindex)
icculus@6353
    65
{
icculus@6353
    66
	SDL_AudioDevice *this;
icculus@6353
    67
icculus@6353
    68
	/* Initialize all variables that we clean on shutdown */
icculus@6353
    69
	this = (SDL_AudioDevice *)SDL_malloc(sizeof(SDL_AudioDevice));
icculus@6353
    70
	if ( this ) {
icculus@6353
    71
		SDL_memset(this, 0, (sizeof *this));
icculus@6353
    72
		this->hidden = (struct SDL_PrivateAudioData *)
icculus@6353
    73
				SDL_malloc((sizeof *this->hidden));
icculus@6353
    74
	}
icculus@6353
    75
	if ( (this == NULL) || (this->hidden == NULL) ) {
icculus@6353
    76
		SDL_OutOfMemory();
icculus@6353
    77
		if ( this ) {
icculus@6353
    78
			SDL_free(this);
icculus@6353
    79
		}
icculus@6353
    80
		return(0);
icculus@6353
    81
	}
icculus@6353
    82
	SDL_memset(this->hidden, 0, (sizeof *this->hidden));
icculus@6353
    83
icculus@6353
    84
	/* Set the function pointers */
icculus@6353
    85
	this->OpenAudio = SNDIO_OpenAudio;
icculus@6353
    86
	this->WaitAudio = SNDIO_WaitAudio;
icculus@6353
    87
	this->PlayAudio = SNDIO_PlayAudio;
icculus@6353
    88
	this->GetAudioBuf = SNDIO_GetAudioBuf;
icculus@6353
    89
	this->CloseAudio = SNDIO_CloseAudio;
icculus@6353
    90
icculus@6353
    91
	this->free = Audio_DeleteDevice;
icculus@6353
    92
icculus@6353
    93
	hdl = NULL;
icculus@6353
    94
icculus@6353
    95
	return this;
icculus@6353
    96
}
icculus@6353
    97
icculus@6353
    98
AudioBootStrap SNDIO_bootstrap = {
icculus@6353
    99
	SNDIO_DRIVER_NAME, "sndio",
icculus@6353
   100
	Audio_Available, Audio_CreateDevice
icculus@6353
   101
};
icculus@6353
   102
icculus@6353
   103
icculus@6353
   104
icculus@6353
   105
/* This function waits until it is possible to write a full sound buffer */
icculus@6353
   106
static void SNDIO_WaitAudio(_THIS)
icculus@6353
   107
{
icculus@6353
   108
	/* Check to see if the thread-parent process is still alive */
icculus@6353
   109
	{ static int cnt = 0;
icculus@6353
   110
		/* Note that this only works with thread implementations 
icculus@6353
   111
		   that use a different process id for each thread.
icculus@6353
   112
		*/
icculus@6353
   113
		if (parent && (((++cnt)%10) == 0)) { /* Check every 10 loops */
icculus@6353
   114
			if ( kill(parent, 0) < 0 ) {
icculus@6353
   115
				this->enabled = 0;
icculus@6353
   116
			}
icculus@6353
   117
		}
icculus@6353
   118
	}
icculus@6353
   119
}
icculus@6353
   120
icculus@6353
   121
static void SNDIO_PlayAudio(_THIS)
icculus@6353
   122
{
icculus@6353
   123
	int written;
icculus@6353
   124
icculus@6353
   125
	/* Write the audio data */
icculus@6353
   126
	written = sio_write(hdl, mixbuf, mixlen);
icculus@6353
   127
	
icculus@6353
   128
	/* If we couldn't write, assume fatal error for now */
icculus@6353
   129
	if ( written == 0 ) {
icculus@6353
   130
		this->enabled = 0;
icculus@6353
   131
	}
icculus@6353
   132
#ifdef DEBUG_AUDIO
icculus@6353
   133
	fprintf(stderr, "Wrote %d bytes of audio data\n", written);
icculus@6353
   134
#endif
icculus@6353
   135
}
icculus@6353
   136
icculus@6353
   137
static Uint8 *SNDIO_GetAudioBuf(_THIS)
icculus@6353
   138
{
icculus@6353
   139
	return(mixbuf);
icculus@6353
   140
}
icculus@6353
   141
icculus@6353
   142
static void SNDIO_CloseAudio(_THIS)
icculus@6353
   143
{
icculus@6353
   144
	if ( mixbuf != NULL ) {
icculus@6353
   145
		SDL_FreeAudioMem(mixbuf);
icculus@6353
   146
		mixbuf = NULL;
icculus@6353
   147
	}
icculus@6353
   148
	if ( hdl != NULL ) {
icculus@6353
   149
		sio_close(hdl);
icculus@6353
   150
		hdl = NULL;
icculus@6353
   151
	}
icculus@6353
   152
}
icculus@6353
   153
icculus@6353
   154
static int SNDIO_OpenAudio(_THIS, SDL_AudioSpec *spec)
icculus@6353
   155
{
icculus@6353
   156
	struct sio_par par, reqpar;
icculus@6353
   157
	int newrate;
icculus@6353
   158
icculus@6353
   159
	mixbuf = NULL;
icculus@6353
   160
icculus@6353
   161
	if ((hdl = sio_open(NULL, SIO_PLAY, 0)) == NULL) {
icculus@6353
   162
		SDL_SetError("sio_open() failed");
icculus@6353
   163
		return(-1);
icculus@6353
   164
	}
icculus@6353
   165
icculus@6353
   166
	sio_initpar(&par);
icculus@6353
   167
icculus@6353
   168
	switch (spec->format) {
icculus@6353
   169
	case AUDIO_S16LSB:
icculus@6353
   170
		par.bits = 16;
icculus@6353
   171
		par.sig = 1;
icculus@6353
   172
		par.le = 1;
icculus@6353
   173
		break;
icculus@6353
   174
	case AUDIO_S16MSB:
icculus@6353
   175
		par.bits = 16;
icculus@6353
   176
		par.sig = 1;
icculus@6353
   177
		par.le = 0;
icculus@6353
   178
		break;
icculus@6353
   179
	case AUDIO_S8:
icculus@6353
   180
		par.bits = 8;
icculus@6353
   181
		par.sig = 1;
icculus@6353
   182
		break;
icculus@6353
   183
	case AUDIO_U16LSB:
icculus@6353
   184
		par.bits = 16;
icculus@6353
   185
		par.sig = 0;
icculus@6353
   186
		par.le = 1;
icculus@6353
   187
		break;
icculus@6353
   188
	case AUDIO_U16MSB:
icculus@6353
   189
		par.bits = 16;
icculus@6353
   190
		par.sig = 0;
icculus@6353
   191
		par.le = 0;
icculus@6353
   192
		break;
icculus@6353
   193
	case AUDIO_U8:
icculus@6353
   194
		par.bits = 8;
icculus@6353
   195
		par.sig = 0;
icculus@6353
   196
		break;
icculus@6353
   197
	default:
icculus@6353
   198
		SDL_SetError("SNDIO unknown format");
icculus@6353
   199
		return(-1);
icculus@6353
   200
	}
icculus@6353
   201
icculus@6353
   202
	par.rate = spec->freq;
icculus@6353
   203
	par.pchan = spec->channels;
icculus@6353
   204
	par.round = spec->samples;
icculus@6353
   205
	par.appbufsz = par.round * 2;
icculus@6353
   206
icculus@6353
   207
	reqpar = par;
icculus@6353
   208
icculus@6353
   209
	if (sio_setpar(hdl, &par) == 0) {
icculus@6353
   210
		SDL_SetError("sio_setpar() failed");
icculus@6353
   211
		return(-1);
icculus@6353
   212
	}
icculus@6353
   213
icculus@6353
   214
	if (sio_getpar(hdl, &par) == 0) {
icculus@6353
   215
		SDL_SetError("sio_getpar() failed");
icculus@6353
   216
		return(-1);
icculus@6353
   217
	}
icculus@6353
   218
icculus@6353
   219
	/* if wanted rate not found, find a multiple/factor */
icculus@6353
   220
	if (par.rate != spec->freq) {
icculus@6353
   221
		newrate = par.rate;
icculus@6353
   222
		if ((newrate > spec->freq && newrate % spec->freq != 0) ||
icculus@6353
   223
		     (newrate < spec->freq && spec->freq % newrate != 0)) {
icculus@6353
   224
			if ((spec->freq < 44100 && 44100 % spec->freq == 0) ||
icculus@6353
   225
			     (spec->freq > 44100 && spec->freq % 44100 == 0)) {
icculus@6353
   226
				newrate = 44100;
icculus@6353
   227
			}
icculus@6353
   228
		}
icculus@6353
   229
		/* only change sample rate */
icculus@6353
   230
		par = reqpar;
icculus@6353
   231
		par.rate = newrate;
icculus@6353
   232
		/* keep same latency */
icculus@6353
   233
		par.round = spec->samples * par.rate / reqpar.rate;
icculus@6353
   234
		par.appbufsz = par.round * 2;
icculus@6353
   235
		if (sio_setpar(hdl, &par) == 0) {
icculus@6353
   236
			SDL_SetError("sio_setpar() failed");
icculus@6353
   237
			return(-1);
icculus@6353
   238
		}
icculus@6353
   239
	}
icculus@6353
   240
icculus@6353
   241
	if (sio_getpar(hdl, &par) == 0) {
icculus@6353
   242
		SDL_SetError("sio_getpar() failed");
icculus@6353
   243
		return(-1);
icculus@6353
   244
	}
icculus@6353
   245
icculus@6353
   246
	if (par.bits == 16) {
icculus@6353
   247
		if (par.sig && par.le) {
icculus@6353
   248
			spec->format = AUDIO_S16LSB;
icculus@6353
   249
		} else if (par.sig && !par.le) {
icculus@6353
   250
			spec->format = AUDIO_S16MSB;
icculus@6353
   251
		} else if (!par.sig && par.le) {
icculus@6353
   252
			spec->format = AUDIO_U16LSB;
icculus@6353
   253
		} else 
icculus@6353
   254
			spec->format = AUDIO_U16MSB;
icculus@6353
   255
	} else if (par.bits == 8) {
icculus@6353
   256
		spec->format = par.sig ? AUDIO_S8 : AUDIO_U8;
icculus@6353
   257
	} else {
icculus@6353
   258
		SDL_SetError("SNDIO couldn't configure a suitable format");
icculus@6353
   259
		return(-1);
icculus@6353
   260
	}
icculus@6353
   261
icculus@6353
   262
	spec->freq = par.rate;
icculus@6353
   263
	spec->channels = par.pchan;
icculus@6353
   264
	spec->samples = par.round;
icculus@6353
   265
icculus@6353
   266
	SDL_CalculateAudioSpec(spec);
icculus@6353
   267
icculus@6353
   268
	/* Allocate mixing buffer */
icculus@6353
   269
	mixlen = spec->size;
icculus@6353
   270
	mixbuf = (Uint8 *)SDL_AllocAudioMem(mixlen);
icculus@6353
   271
	if ( mixbuf == NULL ) {
icculus@6353
   272
		return(-1);
icculus@6353
   273
	}
icculus@6353
   274
	SDL_memset(mixbuf, spec->silence, spec->size);
icculus@6353
   275
icculus@6353
   276
	/* Get the parent process id (we're the parent of the audio thread) */
icculus@6353
   277
	parent = getpid();
icculus@6353
   278
icculus@6353
   279
	if ( sio_start(hdl) == 0 ) {
icculus@6353
   280
		SDL_SetError("sio_start() failed");
icculus@6353
   281
		return(-1);
icculus@6353
   282
	}
icculus@6353
   283
icculus@6353
   284
	/* We're ready to rock and roll. :-) */
icculus@6353
   285
	return(0);
icculus@6353
   286
}