diff --git a/src/snd_alsa.c b/src/snd_alsa.c index 3a394e1..5c569f8 100644 --- a/src/snd_alsa.c +++ b/src/snd_alsa.c @@ -1,18 +1,18 @@ /* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +*(at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ #ifdef HAVE_CONFIG_H #include "config.h" @@ -25,166 +25,227 @@ #include "client.h" #include "snd_loc.h" -static int snd_inited; +static snd_pcm_t *pcm_handle; +static snd_pcm_hw_params_t *hw_params; -static snd_pcm_t * pcm_handle; -static snd_pcm_hw_params_t * hw_params; +static snd_pcm_uframes_t period_size; +static snd_pcm_uframes_t buffer_size; -#define BUFFER_SIZE 4096 +static int periods; -int tryrates[] = { 44100, 22051, 11025, 8000 }; +static int period_bytes; +static int buffer_bytes; -/* sound info */ static struct sndinfo * si; -qboolean SNDDMA_Init(struct sndinfo * s) { - int i; - int err; +/* +* The sample rates which will be attempted. +*/ +static int RATES[] = { + 44100, 22050, 11025, 8000 +}; - if (snd_inited) - return 1; - - snd_inited = 0; - - si = s; - - si->dma->samples = 1024; - - if ((err = snd_pcm_open(&pcm_handle, si->device->string, - SND_PCM_STREAM_PLAYBACK, 0)) < 0) { - si->Com_Printf("ALSA snd error, cannot open device %s (%s)\n", - si->device->string, - snd_strerror(err)); - return 0; - } - - if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) { - si->Com_Printf("ALSA snd error, cannot allocate hw params (%s)\n", - snd_strerror(err)); - return 0; - } - - if ((err = snd_pcm_hw_params_any(pcm_handle, hw_params)) < 0) { - si->Com_Printf("ALSA snd error, cannot init hw params (%s)\n", - snd_strerror(err)); - snd_pcm_hw_params_free(hw_params); - return 0; - } - - if ((err = snd_pcm_hw_params_set_access(pcm_handle, hw_params, - SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { - si->Com_Printf("ALSA snd error, cannot set access (%s)\n", - snd_strerror(err)); - snd_pcm_hw_params_free(hw_params); - return 0; - } - - si->dma->samplebits = si->bits->value; - if (si->dma->samplebits == 16 || si->dma->samplebits != 8) { - if ((err = snd_pcm_hw_params_set_format(pcm_handle, hw_params, - SND_PCM_FORMAT_S16_LE)) < 0) { - si->Com_Printf("ALSA snd error, 16 bit sound not supported, trying 8\n"); - si->dma->samplebits = 8; +/* +* Initialize ALSA pcm device, and bind it to sndinfo. +*/ +qboolean SNDDMA_Init(struct sndinfo *s){ + int i, r, err, dir; + + si = s; + + if(!strcmp(si->device->string, "/dev/dsp")) //silly oss default + si->device->string = "default"; + + if((err = snd_pcm_open(&pcm_handle, si->device->string, + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0){ + si->Com_Printf("ALSA: cannot open device %s(%s)\n", + si->device->string, snd_strerror(err)); + return false; } - } - if (si->dma->samplebits == 8) { - if ((err = snd_pcm_hw_params_set_format(pcm_handle, hw_params, - SND_PCM_FORMAT_U8)) < 0) { - si->Com_Printf("ALSA snd error, cannot set sample format (%s)\n", - snd_strerror(err)); - snd_pcm_hw_params_free(hw_params); - return 0; + + if((err = snd_pcm_hw_params_malloc(&hw_params)) < 0){ + si->Com_Printf("ALSA: cannot allocate hw params(%s)\n", + snd_strerror(err)); + return false; } - } - - si->dma->speed = (int)si->speed->value; - if (!si->dma->speed) { - for (i = 0; i < sizeof(tryrates); i++) { - int dir = 0; - int test = tryrates[i]; - - if ((err = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, - &test, &dir)) < 0) { - si->Com_Printf("ALSA snd error, cannot set sample rate %d (%s)\n", - tryrates[i], snd_strerror(err)); - } else { - si->dma->speed = test; - if (dir != 0) { - si->Com_Printf("alsa: The rate %d Hz is not supported by your hardware, using %d Hz instead.\n", test, err); + + if((err = snd_pcm_hw_params_any(pcm_handle, hw_params)) < 0){ + si->Com_Printf("ALSA: cannot init hw params(%s)\n", snd_strerror(err)); + snd_pcm_hw_params_free(hw_params); + return false; + } + + if((err = snd_pcm_hw_params_set_access(pcm_handle, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED)) < 0){ + si->Com_Printf("ALSA: cannot set access(%s)\n", snd_strerror(err)); + snd_pcm_hw_params_free(hw_params); + return false; + } + + si->dma->samplebits = si->bits->value; + if(si->dma->samplebits != 8){ //try 16 by default + + si->dma->samplebits = 16; //ensure this is set for other calculations + + if((err = snd_pcm_hw_params_set_format(pcm_handle, hw_params, + SND_PCM_FORMAT_S16)) < 0){ + si->Com_Printf("ALSA: 16 bit not supported, trying 8\n"); + si->dma->samplebits = 8; } - break; - } } - } - if (!si->dma->speed) { - si->Com_Printf("ALSA snd error couldn't set rate.\n"); - snd_pcm_hw_params_free(hw_params); - return 0; - } + if(si->dma->samplebits == 8){ //or 8 if specifically asked to + if((err = snd_pcm_hw_params_set_format(pcm_handle, hw_params, + SND_PCM_FORMAT_U8)) < 0){ + si->Com_Printf("ALSA: cannot set format(%s)\n", snd_strerror(err)); + snd_pcm_hw_params_free(hw_params); + return false; + } + } + + si->dma->speed =(int)si->speed->value; + if(si->dma->speed){ //try specified rate + + r = si->dma->speed; + + if((err = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &r, &dir)) < 0) + si->Com_Printf("ALSA: cannot set rate %d(%s)\n", r, snd_strerror(err)); + else { //rate succeeded, but is perhaps slightly different + if(dir != 0) si->Com_Printf("ALSA: rate %d not supported, using %d\n", si->dma->speed, r); + si->dma->speed = r; + } + } + if(!si->dma->speed){ //or all available ones + + for(i = 0; i < sizeof(RATES); i++){ + + r = RATES[i]; + dir = 0; + + if((err = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &r, &dir)) < 0) + si->Com_Printf("ALSA: cannot set rate %d(%s)\n", r, snd_strerror(err)); + else { //rate succeeded, but is perhaps slightly different + si->dma->speed = r; + if(dir != 0) si->Com_Printf("ALSA: rate %d not supported, using %d\n", RATES[i], r); + break; + } + } + } + if(!si->dma->speed){ //failed + si->Com_Printf("ALSA: cannot set rate\n"); + snd_pcm_hw_params_free(hw_params); + return false; + } + + si->dma->channels = (int)si->channels->value; + + if(si->dma->channels < 1 || si->dma->channels > 2) + si->dma->channels = 2; //ensure either stereo or mono + + if((err = snd_pcm_hw_params_set_channels(pcm_handle, hw_params, + si->dma->channels)) < 0){ + si->Com_Printf("ALSA: cannot set channels %d(%s)\n", + si->dma->channels, snd_strerror(err)); + snd_pcm_hw_params_free(hw_params); + return false; + } + + if((err = snd_pcm_hw_params(pcm_handle, hw_params)) < 0){ //set params + si->Com_Printf("ALSA: cannot set params(%s)\n", snd_strerror(err)); + snd_pcm_hw_params_free(hw_params); + return false; + } + + if((err = snd_pcm_hw_params_get_period_size(hw_params, &period_size, 0)) < 0){ + si->Com_Printf("ALSA: cannot get period size(%s)\n", snd_strerror(err)); + snd_pcm_hw_params_free(hw_params); + return false; + } - si->dma->channels = (int)si->channels->value; - if (si->dma->channels < 1 || si->dma->channels > 2) - si->dma->channels = 2; - if ((err = snd_pcm_hw_params_set_channels(pcm_handle, hw_params, si->dma->channels)) < 0) { - si->Com_Printf("ALSA snd error couldn't set channels %d (%s).\n", - si->dma->channels, snd_strerror(err)); - snd_pcm_hw_params_free(hw_params); - return 0; - } - - if ((err = snd_pcm_hw_params(pcm_handle, hw_params)) < 0) { - si->Com_Printf("ALSA snd error couldn't set params (%s).\n",snd_strerror(err)); - snd_pcm_hw_params_free(hw_params); - return 0; - } - - si->dma->buffer = malloc(BUFFER_SIZE); - memset(si->dma->buffer, 0, BUFFER_SIZE); - - si->dma->samplepos = 0; - si->dma->samples = BUFFER_SIZE / (si->dma->samplebits / 8); - si->dma->submission_chunk = 1; - - si->Com_Printf("alsa: buffer size is %d, %d samples\n", BUFFER_SIZE, si->dma->samples); - - snd_inited = 1; - return 1; -} - -int SNDDMA_GetDMAPos(void) { - if (snd_inited) - return si->dma->samplepos; - else - si->Com_Printf("Sound not inizialized\n"); - return 0; -} - -void SNDDMA_Shutdown(void) { - if (snd_inited) { - snd_pcm_drop(pcm_handle); - snd_pcm_close(pcm_handle); - snd_inited = 0; - } - free(si->dma->buffer); - si->dma->buffer = NULL; -} - -/* SNDDMA_Submit - * Send sound to device if buffer isn't really the dma buffer - */ -void SNDDMA_Submit(void) { - int written; - - if(!snd_inited) - return; - - if ((written = snd_pcm_writei(pcm_handle, si->dma->buffer, si->dma->samples * (si->dma->samplebits / 8))) < 0) { + if((err = snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size)) < 0){ + si->Com_Printf("ALSA snd error, cannot get buffer size(%s)\n", snd_strerror(err)); + snd_pcm_hw_params_free(hw_params); + return false; + } + + if((err = snd_pcm_hw_params_get_periods(hw_params, &periods, 0)) < 0){ + si->Com_Printf("ALSA: cannot get periods(%s)\n", snd_strerror(err)); + snd_pcm_hw_params_free(hw_params); + return false; + } + + period_bytes = period_size * si->dma->channels * si->dma->samplebits / 8; + buffer_bytes = buffer_size * si->dma->channels * si->dma->samplebits / 8; + + si->dma->buffer = malloc(buffer_bytes); //allocate pcm frame buffer + memset(si->dma->buffer, 0, buffer_bytes); + + si->dma->samplepos = 0; + + si->dma->samples = buffer_size * si->dma->channels; + si->dma->submission_chunk = period_size * si->dma->channels; + + si->Com_Printf("period size is %d (%d bytes)\n" + "buffer size is %d (%d bytes)\n%d periods\n", (int)period_size, + period_bytes, (int)buffer_size, buffer_bytes, periods + ); + snd_pcm_prepare(pcm_handle); - si->Com_Printf("alsa: buffer underrun\n"); - } - si->dma->samplepos += written / (si->dma->samplebits / 8); + + return true; } - -void SNDDMA_BeginPainting(void) { +/* +* Returns the current sample position, if sound is running. +*/ +int SNDDMA_GetDMAPos(void){ + + if(si->dma->buffer) + return si->dma->samplepos; + + si->Com_Printf("Sound not inizialized\n"); + return 0; } + +/* +* Closes the ALSA pcm device and frees the dma buffer. +*/ +void SNDDMA_Shutdown(void){ + + if(si->dma->buffer){ + snd_pcm_drop(pcm_handle); + snd_pcm_close(pcm_handle); + } + + free(si->dma->buffer); + si->dma->buffer = NULL; +} + +/* +* Writes the dma buffer to the ALSA pcm device. +*/ +void SNDDMA_Submit(void){ + int w; + void *start; + + if(!si->dma->buffer) + return; + + start = (void *)&si->dma->buffer[si->dma->samplepos]; + + if((w = snd_pcm_writei(pcm_handle, start, period_size)) < 0){ //xrun + //si->Com_Printf("ALSA: buffer underrun(%s)\n", snd_strerror(w)); + snd_pcm_prepare(pcm_handle); + } + else { //mark progress + //si->Com_Printf("wrote %d frames\n", w); + si->dma->samplepos += w * si->dma->channels; + + if(si->dma->samplepos >= si->dma->samples) + si->dma->samplepos = 0; //wrap + } +} + +/* +* No clue :) +*/ +void SNDDMA_BeginPainting(void){}