static ALCboolean alsa_reset_playback(ALCdevice *device) { alsa_data *data = (alsa_data*)device->ExtraData; snd_pcm_uframes_t periodSizeInFrames; snd_pcm_sw_params_t *sp = NULL; snd_pcm_hw_params_t *p = NULL; snd_pcm_access_t access; unsigned int periods; unsigned int rate; int allowmmap; char *err; int i; switch(aluBytesFromFormat(device->Format)) { case 1: data->format = SND_PCM_FORMAT_U8; break; case 2: data->format = SND_PCM_FORMAT_S16; break; case 4: data->format = SND_PCM_FORMAT_FLOAT; break; default: AL_PRINT("Unknown format: 0x%x\n", device->Format); return ALC_FALSE; } allowmmap = GetConfigValueBool("alsa", "mmap", 1); periods = device->NumUpdates; periodSizeInFrames = device->UpdateSize; rate = device->Frequency; err = NULL; psnd_pcm_hw_params_malloc(&p); if((i=psnd_pcm_hw_params_any(data->pcmHandle, p)) < 0) err = "any"; /* set interleaved access */ if(err == NULL && (!allowmmap || (i=psnd_pcm_hw_params_set_access(data->pcmHandle, p, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0)) { if(periods > 2) periods--; if((i=psnd_pcm_hw_params_set_access(data->pcmHandle, p, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) err = "set access"; } /* set format (implicitly sets sample bits) */ if(err == NULL && (i=psnd_pcm_hw_params_set_format(data->pcmHandle, p, data->format)) < 0) err = "set format"; /* set channels (implicitly sets frame bits) */ if(err == NULL && (i=psnd_pcm_hw_params_set_channels(data->pcmHandle, p, aluChannelsFromFormat(device->Format))) < 0) err = "set channels"; /* set periods (implicitly constrains period/buffer parameters) */ if(err == NULL && (i=psnd_pcm_hw_params_set_periods_near(data->pcmHandle, p, &periods, NULL)) < 0) err = "set periods near"; /* set rate (implicitly constrains period/buffer parameters) */ if(err == NULL && (i=psnd_pcm_hw_params_set_rate_near(data->pcmHandle, p, &rate, NULL)) < 0) err = "set rate near"; /* set period size in frame units (implicitly sets buffer size/bytes/time and period time/bytes) */ if(err == NULL && (i=psnd_pcm_hw_params_set_period_size_near(data->pcmHandle, p, &periodSizeInFrames, NULL)) < 0) err = "set period size near"; /* install and prepare hardware configuration */ if(err == NULL && (i=psnd_pcm_hw_params(data->pcmHandle, p)) < 0) err = "set params"; if(err == NULL && (i=psnd_pcm_hw_params_get_access(p, &access)) < 0) err = "get access"; if(err == NULL && (i=psnd_pcm_hw_params_get_period_size(p, &periodSizeInFrames, NULL)) < 0) err = "get period size"; if(err == NULL && (i=psnd_pcm_hw_params_get_periods(p, &periods, NULL)) < 0) err = "get periods"; if(err != NULL) { AL_PRINT("%s failed: %s\n", err, psnd_strerror(i)); psnd_pcm_hw_params_free(p); return ALC_FALSE; } psnd_pcm_hw_params_free(p); err = NULL; psnd_pcm_sw_params_malloc(&sp); if((i=psnd_pcm_sw_params_current(data->pcmHandle, sp)) != 0) err = "sw current"; if(err == NULL && (i=psnd_pcm_sw_params_set_avail_min(data->pcmHandle, sp, periodSizeInFrames)) != 0) err = "sw set avail min"; if(err == NULL && (i=psnd_pcm_sw_params(data->pcmHandle, sp)) != 0) err = "sw set params"; if(err != NULL) { AL_PRINT("%s failed: %s\n", err, psnd_strerror(i)); psnd_pcm_sw_params_free(sp); return ALC_FALSE; } psnd_pcm_sw_params_free(sp); data->size = psnd_pcm_frames_to_bytes(data->pcmHandle, periodSizeInFrames); if(access == SND_PCM_ACCESS_RW_INTERLEAVED) { /* Increase periods by one, since the temp buffer counts as an extra * period */ periods++; data->buffer = malloc(data->size); if(!data->buffer) { AL_PRINT("buffer malloc failed\n"); return ALC_FALSE; } data->thread = StartThread(ALSANoMMapProc, device); } else { i = psnd_pcm_prepare(data->pcmHandle); if(i < 0) { AL_PRINT("prepare error: %s\n", psnd_strerror(i)); free(data->buffer); data->buffer = NULL; return ALC_FALSE; } data->thread = StartThread(ALSAProc, device); } if(data->thread == NULL) { AL_PRINT("Could not create playback thread\n"); free(data->buffer); data->buffer = NULL; return ALC_FALSE; } device->UpdateSize = periodSizeInFrames; device->NumUpdates = periods; device->Frequency = rate; return ALC_TRUE; }
static qboolean QDECL ALSA_InitCard (soundcardinfo_t *sc, const char *pcmname) { snd_pcm_t *pcm; snd_pcm_uframes_t buffer_size; int err; snd_pcm_hw_params_t *hw; snd_pcm_sw_params_t *sw; #if 0 int bps, stereo; unsigned int rate; snd_pcm_uframes_t frag_size; #endif qboolean mmap = false; if (!Alsa_InitAlsa()) { Con_Printf(CON_ERROR "Alsa does not appear to be installed or compatible\n"); return false; } hw = alloca(psnd_pcm_hw_params_sizeof()); sw = alloca(psnd_pcm_sw_params_sizeof()); memset(sw, 0, psnd_pcm_sw_params_sizeof()); memset(hw, 0, psnd_pcm_hw_params_sizeof()); //WARNING: 'default' as the default sucks arse. it adds about a second's worth of lag. if (!pcmname) pcmname = "default"; sc->inactive_sound = true; //linux sound devices always play sound, even when we're not the active app... Con_Printf("Initing ALSA sound device \"%s\"\n", pcmname); err = psnd_pcm_open (&pcm, pcmname, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (0 > err) { Con_Printf (CON_ERROR "ALSA Error: open error (%s): %s\n", pcmname, psnd_strerror (err)); return 0; } Con_Printf ("ALSA: Using PCM %s.\n", pcmname); #if 1 err = psnd_pcm_set_params(pcm, ((sc->sn.samplebits==8)?SND_PCM_FORMAT_U8:SND_PCM_FORMAT_S16), (mmap?SND_PCM_ACCESS_MMAP_INTERLEAVED:SND_PCM_ACCESS_RW_INTERLEAVED), sc->sn.numchannels, sc->sn.speed, true, 0.04*1000000); if (0 > err) { Con_Printf (CON_ERROR "ALSA: error setting params. %s\n", psnd_strerror (err)); goto error; } // sc->sn.numchannels = stereo; // sc->sn.samplepos = 0; // sc->sn.samplebits = bps; sc->samplequeue = buffer_size = 2048; #else err = psnd_pcm_hw_params_any (pcm, hw); if (0 > err) { Con_Printf (CON_ERROR "ALSA: error setting hw_params_any. %s\n", psnd_strerror (err)); goto error; } err = psnd_pcm_hw_params_set_access (pcm, hw, mmap?SND_PCM_ACCESS_MMAP_INTERLEAVED:SND_PCM_ACCESS_RW_INTERLEAVED); if (0 > err) { Con_Printf (CON_ERROR "ALSA: Failure to set interleaved PCM access. %s\n", psnd_strerror (err)); goto error; } // get sample bit size bps = sc->sn.samplebits; { snd_pcm_format_t spft; if (bps == 16) spft = SND_PCM_FORMAT_S16; else spft = SND_PCM_FORMAT_U8; err = psnd_pcm_hw_params_set_format (pcm, hw, spft); while (err < 0) { if (spft == SND_PCM_FORMAT_S16) { bps = 8; spft = SND_PCM_FORMAT_U8; } else { Con_Printf (CON_ERROR "ALSA: no usable formats. %s\n", psnd_strerror (err)); goto error; } err = psnd_pcm_hw_params_set_format (pcm, hw, spft); } } // get speaker channels stereo = sc->sn.numchannels; err = psnd_pcm_hw_params_set_channels (pcm, hw, stereo); while (err < 0) { if (stereo > 2) stereo = 2; else if (stereo > 1) stereo = 1; else { Con_Printf (CON_ERROR "ALSA: no usable number of channels. %s\n", psnd_strerror (err)); goto error; } err = psnd_pcm_hw_params_set_channels (pcm, hw, stereo); } // get rate rate = sc->sn.speed; err = psnd_pcm_hw_params_set_rate_near (pcm, hw, &rate, 0); while (err < 0) { if (rate > 48000) rate = 48000; else if (rate > 44100) rate = 44100; else if (rate > 22150) rate = 22150; else if (rate > 11025) rate = 11025; else if (rate > 800) rate = 800; else { Con_Printf (CON_ERROR "ALSA: no usable rates. %s\n", psnd_strerror (err)); goto error; } err = psnd_pcm_hw_params_set_rate_near (pcm, hw, &rate, 0); } if (rate > 11025) frag_size = 8 * bps * rate / 11025; else frag_size = 8 * bps; err = psnd_pcm_hw_params_set_period_size_near (pcm, hw, &frag_size, 0); if (0 > err) { Con_Printf (CON_ERROR "ALSA: unable to set period size near %i. %s\n", (int) frag_size, psnd_strerror (err)); goto error; } err = psnd_pcm_hw_params (pcm, hw); if (0 > err) { Con_Printf (CON_ERROR "ALSA: unable to install hw params: %s\n", psnd_strerror (err)); goto error; } err = psnd_pcm_sw_params_current (pcm, sw); if (0 > err) { Con_Printf (CON_ERROR "ALSA: unable to determine current sw params. %s\n", psnd_strerror (err)); goto error; } err = psnd_pcm_sw_params_set_start_threshold (pcm, sw, ~0U); if (0 > err) { Con_Printf (CON_ERROR "ALSA: unable to set playback threshold. %s\n", psnd_strerror (err)); goto error; } err = psnd_pcm_sw_params_set_stop_threshold (pcm, sw, ~0U); if (0 > err) { Con_Printf (CON_ERROR "ALSA: unable to set playback stop threshold. %s\n", psnd_strerror (err)); goto error; } err = psnd_pcm_sw_params (pcm, sw); if (0 > err) { Con_Printf (CON_ERROR "ALSA: unable to install sw params. %s\n", psnd_strerror (err)); goto error; } sc->sn.numchannels = stereo; sc->sn.samplepos = 0; sc->sn.samplebits = bps; buffer_size = sc->sn.samples / stereo; if (buffer_size) { err = psnd_pcm_hw_params_set_buffer_size_near(pcm, hw, &buffer_size); if (err < 0) { Con_Printf (CON_ERROR "ALSA: unable to set buffer size. %s\n", psnd_strerror (err)); goto error; } } err = psnd_pcm_hw_params_get_buffer_size (hw, &buffer_size); if (0 > err) { Con_Printf (CON_ERROR "ALSA: unable to get buffer size. %s\n", psnd_strerror (err)); goto error; } sc->sn.speed = rate; #endif sc->sn.samples = buffer_size * sc->sn.numchannels; // mono samples in buffer sc->handle = pcm; sc->Lock = ALSA_LockBuffer; sc->Unlock = ALSA_UnlockBuffer; sc->Shutdown = ALSA_Shutdown; if (mmap) { sc->GetDMAPos = ALSA_MMap_GetDMAPos; sc->Submit = ALSA_MMap_Submit; sc->GetDMAPos(sc); // sets shm->buffer //alsa doesn't seem to like high mixahead values //(maybe it tells us above somehow...) //so force it lower //quake's default of 0.2 was for 10fps rendering anyway //so force it down to 0.1 which is the default for halflife at least, and should give better latency { extern cvar_t _snd_mixahead; if (_snd_mixahead.value >= 0.2) { Con_Printf("Alsa Hack: _snd_mixahead forced lower\n"); _snd_mixahead.value = 0.1; } } } else { sc->GetDMAPos = ALSA_RW_GetDMAPos; sc->Submit = ALSA_RW_Submit; sc->samplequeue = sc->sn.samples; sc->sn.buffer = malloc(sc->sn.samples * (sc->sn.samplebits/8)); err = psnd_pcm_prepare(pcm); if (0 > err) { Con_Printf (CON_ERROR "ALSA: unable to prepare for use. %s\n", psnd_strerror (err)); goto error; } } return true; error: psnd_pcm_close (pcm); return false; }