summarylogtreecommitdiffstats
path: root/snd_alsa.c
diff options
context:
space:
mode:
Diffstat (limited to 'snd_alsa.c')
-rw-r--r--snd_alsa.c254
1 files changed, 254 insertions, 0 deletions
diff --git a/snd_alsa.c b/snd_alsa.c
new file mode 100644
index 000000000000..2fb7411ce68d
--- /dev/null
+++ b/snd_alsa.c
@@ -0,0 +1,254 @@
+/*
+* 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"
+#endif
+
+#include <alsa/asoundlib.h>
+
+#include "../client/client.h"
+#include "../client/snd_loc.h"
+
+#define BUFFER_SAMPLES 4096
+#define SUBMISSION_CHUNK BUFFER_SAMPLES / 2
+
+static snd_pcm_t *pcm_handle;
+static snd_pcm_hw_params_t *hw_params;
+
+static struct sndinfo * si;
+
+static int sample_bytes;
+static int buffer_bytes;
+
+cvar_t *sndbits;
+cvar_t *sndspeed;
+cvar_t *sndchannels;
+cvar_t *snddevice;
+
+
+/*
+* The sample rates which will be attempted.
+*/
+static int RATES[] = {
+ 44100, 22050, 11025, 8000
+};
+
+/*
+* Initialize ALSA pcm device, and bind it to sndinfo.
+*/
+qboolean SNDDMA_Init(void){
+ int i, err, dir;
+ unsigned int r;
+ snd_pcm_uframes_t p;
+
+
+ if (!snddevice)
+ {
+ sndbits = Cvar_Get("sndbits", "16", CVAR_ARCHIVE);
+ sndspeed = Cvar_Get("sndspeed", "0", CVAR_ARCHIVE);
+ sndchannels = Cvar_Get("sndchannels", "2", CVAR_ARCHIVE);
+ snddevice = Cvar_Get("snddevice", "/dev/dsp", CVAR_ARCHIVE);
+ }
+
+ if(!strcmp(snddevice->string, "/dev/dsp")) //silly oss default
+ snddevice->string = "default";
+
+ if((err = snd_pcm_open(&pcm_handle, snddevice->string,
+ SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0)
+ {
+ Com_Printf("ALSA: cannot open device %s(%s)\n",
+ snddevice->string, snd_strerror(err));
+ return false;
+ }
+
+ if((err = snd_pcm_hw_params_malloc(&hw_params)) < 0){
+ Com_Printf("ALSA: cannot allocate hw params(%s)\n",
+ snd_strerror(err));
+ return false;
+ }
+
+ if((err = snd_pcm_hw_params_any(pcm_handle, hw_params)) < 0){
+ 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)
+ {
+ Com_Printf("ALSA: cannot set access(%s)\n", snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ return false;
+ }
+
+ dma.samplebits = (int)sndbits->value;
+ if(dma.samplebits != 8){ //try 16 by default
+ 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){
+ Com_Printf("ALSA: 16 bit not supported, trying 8\n");
+ dma.samplebits = 8;
+ }
+ }
+ if(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){
+ Com_Printf("ALSA: cannot set format(%s)\n", snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ return false;
+ }
+ }
+
+ dma.speed = (int)sndspeed->value;
+ if(dma.speed){ //try specified rate
+ r = dma.speed;
+
+ if((err = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &r, &dir)) < 0)
+ Com_Printf("ALSA: cannot set rate %d(%s)\n", r, snd_strerror(err));
+ else { //rate succeeded, but is perhaps slightly different
+ if(dir != 0)
+ Com_Printf("ALSA: rate %d not supported, using %d\n", sndspeed->value, r);
+ dma.speed = r;
+ }
+ }
+ if(!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)
+ Com_Printf("ALSA: cannot set rate %d(%s)\n", r, snd_strerror(err));
+ else { //rate succeeded, but is perhaps slightly different
+ dma.speed = r;
+ if(dir != 0)
+ Com_Printf("ALSA: rate %d not supported, using %d\n", RATES[i], r);
+ break;
+ }
+ }
+ }
+ if(!dma.speed){ //failed
+ Com_Printf("ALSA: cannot set rate\n");
+ snd_pcm_hw_params_free(hw_params);
+ return false;
+ }
+
+ dma.channels = sndchannels->value;
+ if(dma.channels < 1 || dma.channels > 2)
+ dma.channels = 2; //ensure either stereo or mono
+
+
+
+ if((err = snd_pcm_hw_params_set_channels(pcm_handle, hw_params,
+ dma.channels)) < 0)
+ {
+ Com_Printf("ALSA: cannot set channels %d(%s)\n",
+ sndchannels->value, snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ return false;
+ }
+
+ p = BUFFER_SAMPLES / dma.channels;
+ if((err = snd_pcm_hw_params_set_period_size_near(pcm_handle, hw_params,
+ &p, &dir)) < 0){
+ Com_Printf("ALSA: cannot set period size (%s)\n", snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ return false;
+ }
+ else { //rate succeeded, but is perhaps slightly different
+ if(dir != 0)
+ Com_Printf("ALSA: period %d not supported, using %d\n", (BUFFER_SAMPLES/dma.channels), p);
+ }
+
+ if((err = snd_pcm_hw_params(pcm_handle, hw_params)) < 0){ //set params
+ Com_Printf("ALSA: cannot set params(%s)\n", snd_strerror(err));
+ snd_pcm_hw_params_free(hw_params);
+ return false;
+ }
+
+ sample_bytes = dma.samplebits / 8;
+ buffer_bytes = BUFFER_SAMPLES * sample_bytes;
+
+ dma.buffer = malloc(buffer_bytes); //allocate pcm frame buffer
+ memset(dma.buffer, 0, buffer_bytes);
+
+ dma.samplepos = 0;
+
+ dma.samples = BUFFER_SAMPLES;
+ dma.submission_chunk = SUBMISSION_CHUNK;
+
+ snd_pcm_prepare(pcm_handle);
+
+ return true;
+}
+
+/*
+* Returns the current sample position, if sound is running.
+*/
+int SNDDMA_GetDMAPos(void){
+
+ if(dma.buffer)
+ return dma.samplepos;
+
+ Com_Printf("Sound not inizialized\n");
+ return 0;
+}
+
+/*
+* Closes the ALSA pcm device and frees the dma buffer.
+*/
+void SNDDMA_Shutdown(void){
+
+ if(dma.buffer){
+ snd_pcm_drop(pcm_handle);
+ snd_pcm_close(pcm_handle);
+ }
+
+ free(dma.buffer);
+ dma.buffer = 0;
+}
+
+/*
+* Writes the dma buffer to the ALSA pcm device.
+*/
+void SNDDMA_Submit(void){
+ int s, w, frames;
+ void *start;
+
+ if(!dma.buffer)
+ return;
+
+ s = dma.samplepos * sample_bytes;
+ start = (void *)&dma.buffer[s];
+
+ frames = dma.submission_chunk / dma.channels;
+
+ if((w = snd_pcm_writei(pcm_handle, start, frames)) < 0){ //write to card
+ snd_pcm_prepare(pcm_handle); //xrun occured
+ return;
+ }
+
+ dma.samplepos += w * dma.channels; //mark progress
+
+ if(dma.samplepos >= dma.samples)
+ dma.samplepos = 0; //wrap buffer
+}
+
+/*
+* Callback provided by the engine in case we need it. We don't.
+*/
+void SNDDMA_BeginPainting(void){}