static ALCboolean alsa_open_playback(ALCdevice *device, const ALCchar *deviceName) { alsa_data *data; char driver[64]; int i; if(!alsa_load()) return ALC_FALSE; strncpy(driver, GetConfigValue("alsa", "device", "default"), sizeof(driver)-1); driver[sizeof(driver)-1] = 0; if(!deviceName) deviceName = alsaDevice; else if(strcmp(deviceName, alsaDevice) != 0) { size_t idx; if(!allDevNameMap) allDevNameMap = probe_devices(SND_PCM_STREAM_PLAYBACK, &numDevNames); for(idx = 0;idx < numDevNames;idx++) { if(allDevNameMap[idx].name && strcmp(deviceName, allDevNameMap[idx].name) == 0) { if(idx > 0) sprintf(driver, "hw:%d,%d", allDevNameMap[idx].card, allDevNameMap[idx].dev); break; } } if(idx == numDevNames) return ALC_FALSE; } data = (alsa_data*)calloc(1, sizeof(alsa_data)); i = psnd_pcm_open(&data->pcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if(i < 0) { Sleep(200); i = psnd_pcm_open(&data->pcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); } if(i >= 0) { i = psnd_pcm_nonblock(data->pcmHandle, 0); if(i < 0) psnd_pcm_close(data->pcmHandle); } if(i < 0) { free(data); AL_PRINT("Could not open playback device '%s': %s\n", driver, psnd_strerror(i)); return ALC_FALSE; } device->szDeviceName = strdup(deviceName); device->ExtraData = data; return ALC_TRUE; }
static ALuint ALSANoMMapProc(ALvoid *ptr) { ALCdevice *pDevice = (ALCdevice*)ptr; alsa_data *data = (alsa_data*)pDevice->ExtraData; snd_pcm_sframes_t avail; char *WritePtr; SetRTPriority(); while(!data->killNow) { int state = verify_state(data->pcmHandle); if(state < 0) { AL_PRINT("Invalid state detected: %s\n", psnd_strerror(state)); aluHandleDisconnect(pDevice); break; } WritePtr = data->buffer; avail = data->size / psnd_pcm_frames_to_bytes(data->pcmHandle, 1); aluMixData(pDevice, WritePtr, avail); while(avail > 0) { int ret = psnd_pcm_writei(data->pcmHandle, WritePtr, avail); switch (ret) { case -EAGAIN: continue; case -ESTRPIPE: case -EPIPE: case -EINTR: ret = psnd_pcm_recover(data->pcmHandle, ret, 1); if(ret >= 0) psnd_pcm_prepare(data->pcmHandle); break; default: if (ret >= 0) { WritePtr += psnd_pcm_frames_to_bytes(data->pcmHandle, ret); avail -= ret; } break; } if (ret < 0) { ret = psnd_pcm_prepare(data->pcmHandle); if(ret < 0) break; } } } return 0; }
static ALuint ALSANoMMapCaptureProc(ALvoid *ptr) { ALCdevice *pDevice = (ALCdevice*)ptr; alsa_data *data = (alsa_data*)pDevice->ExtraData; snd_pcm_sframes_t avail; while(!data->killNow) { int state = verify_state(data->pcmHandle); if(state < 0) { AL_PRINT("Invalid state detected: %s\n", psnd_strerror(state)); aluHandleDisconnect(pDevice); break; } avail = (snd_pcm_uframes_t)data->size / psnd_pcm_frames_to_bytes(data->pcmHandle, 1); avail = psnd_pcm_readi(data->pcmHandle, data->buffer, avail); switch(avail) { case -EAGAIN: continue; case -ESTRPIPE: while((avail=psnd_pcm_resume(data->pcmHandle)) == -EAGAIN) Sleep(1); break; case -EPIPE: break; default: if (avail >= 0 && data->doCapture) WriteRingBuffer(data->ring, data->buffer, avail); break; } if(avail < 0) { avail = psnd_pcm_prepare(data->pcmHandle); if(avail < 0) AL_PRINT("prepare error: %s\n", psnd_strerror(avail)); } } return 0; }
static int xrun_recovery(snd_pcm_t *handle, int err) { if (err == -EPIPE) { /* under-run */ err = psnd_pcm_prepare(handle); if (err < 0) AL_PRINT("prepare failed: %s\n", psnd_strerror(err)); } else if (err == -ESTRPIPE) { while ((err = psnd_pcm_resume(handle)) == -EAGAIN) Sleep(1); /* wait until the suspend flag is released */ if (err < 0) { err = psnd_pcm_prepare(handle); if (err < 0) AL_PRINT("prepare failed: %s\n", psnd_strerror(err)); } } return err; }
static int xrun_recovery(snd_pcm_t *handle, int err) { if(err == -EINTR || err == -EPIPE || err == -ESTRPIPE) { err = psnd_pcm_recover(handle, err, 1); if(err >= 0) err = psnd_pcm_prepare(handle); if(err < 0) AL_PRINT("recover failed: %s\n", psnd_strerror(err)); } return err; }
static void alsa_start_capture(ALCdevice *Device) { alsa_data *data = (alsa_data*)Device->ExtraData; int err; err = psnd_pcm_start(data->pcmHandle); if(err < 0) { AL_PRINT("start failed: %s\n", psnd_strerror(err)); aluHandleDisconnect(Device); } else data->doCapture = AL_TRUE; }
void alc_alsa_probe(int type) { snd_ctl_t *handle; int card, err, dev, idx; snd_ctl_card_info_t *info; snd_pcm_info_t *pcminfo; snd_pcm_stream_t stream; char name[128]; ALuint i; if(!alsa_handle) alc_alsa_init(NULL); if(!alsa_handle) return; psnd_ctl_card_info_malloc(&info); psnd_pcm_info_malloc(&pcminfo); if(type == DEVICE_PROBE) AppendDeviceList(alsaDevice); else if(type == ALL_DEVICE_PROBE) { stream = SND_PCM_STREAM_PLAYBACK; card = -1; if(psnd_card_next(&card) < 0 || card < 0) { AL_PRINT("no playback cards found...\n"); psnd_pcm_info_free(pcminfo); psnd_ctl_card_info_free(info); return; } for(i = 0;i < numDevNames;++i) free(allDevNameMap[i].name); allDevNameMap = realloc(allDevNameMap, sizeof(DevMap) * 1); allDevNameMap[0].name = strdup("ALSA Software on default"); AppendAllDeviceList(allDevNameMap[0].name); idx = 1; while(card >= 0) { sprintf(name, "hw:%d", card); if ((err = psnd_ctl_open(&handle, name, 0)) < 0) { AL_PRINT("control open (%i): %s\n", card, psnd_strerror(err)); goto next_card; } if ((err = psnd_ctl_card_info(handle, info)) < 0) { AL_PRINT("control hardware info (%i): %s\n", card, psnd_strerror(err)); psnd_ctl_close(handle); goto next_card; } dev = -1; while(1) { const char *cname, *dname; void *temp; if (psnd_ctl_pcm_next_device(handle, &dev)<0) AL_PRINT("snd_ctl_pcm_next_device failed\n"); if (dev < 0) break; psnd_pcm_info_set_device(pcminfo, dev); psnd_pcm_info_set_subdevice(pcminfo, 0); psnd_pcm_info_set_stream(pcminfo, stream); if ((err = psnd_ctl_pcm_info(handle, pcminfo)) < 0) { if (err != -ENOENT) AL_PRINT("control digital audio info (%i): %s\n", card, psnd_strerror(err)); continue; } temp = realloc(allDevNameMap, sizeof(DevMap) * (idx+1)); if(temp) { allDevNameMap = temp; cname = psnd_ctl_card_info_get_name(info); dname = psnd_pcm_info_get_name(pcminfo); snprintf(name, sizeof(name), "ALSA Software on %s [%s] (hw:%d,%d)", cname, dname, card, dev); AppendAllDeviceList(name); allDevNameMap[idx].name = strdup(name); allDevNameMap[idx].card = card; allDevNameMap[idx].dev = dev; idx++; } } psnd_ctl_close(handle); next_card: if(psnd_card_next(&card) < 0) { AL_PRINT("snd_card_next failed\n"); break; } } numDevNames = idx; } else if(type == CAPTURE_DEVICE_PROBE) { stream = SND_PCM_STREAM_CAPTURE; card = -1; if(psnd_card_next(&card) < 0 || card < 0) { AL_PRINT("no capture cards found...\n"); psnd_pcm_info_free(pcminfo); psnd_ctl_card_info_free(info); return; } for(i = 0;i < numCaptureDevNames;++i) free(allCaptureDevNameMap[i].name); allCaptureDevNameMap = realloc(allCaptureDevNameMap, sizeof(DevMap) * 1); allCaptureDevNameMap[0].name = strdup("ALSA Capture on default"); AppendCaptureDeviceList(allCaptureDevNameMap[0].name); idx = 1; while (card >= 0) { sprintf(name, "hw:%d", card); handle = NULL; if ((err = psnd_ctl_open(&handle, name, 0)) < 0) { AL_PRINT("control open (%i): %s\n", card, psnd_strerror(err)); } if (err >= 0 && (err = psnd_ctl_card_info(handle, info)) < 0) { AL_PRINT("control hardware info (%i): %s\n", card, psnd_strerror(err)); } else if (err >= 0) { dev = -1; while(1) { const char *cname, *dname; void *temp; if (psnd_ctl_pcm_next_device(handle, &dev)<0) AL_PRINT("snd_ctl_pcm_next_device failed\n"); if (dev < 0) break; psnd_pcm_info_set_device(pcminfo, dev); psnd_pcm_info_set_subdevice(pcminfo, 0); psnd_pcm_info_set_stream(pcminfo, stream); if ((err = psnd_ctl_pcm_info(handle, pcminfo)) < 0) { if (err != -ENOENT) AL_PRINT("control digital audio info (%i): %s\n", card, psnd_strerror(err)); continue; } temp = realloc(allCaptureDevNameMap, sizeof(DevMap) * (idx+1)); if(temp) { allCaptureDevNameMap = temp; cname = psnd_ctl_card_info_get_name(info); dname = psnd_pcm_info_get_name(pcminfo); snprintf(name, sizeof(name), "ALSA Capture on %s [%s] (hw:%d,%d)", cname, dname, card, dev); AppendCaptureDeviceList(name); allCaptureDevNameMap[idx].name = strdup(name); allCaptureDevNameMap[idx].card = card; allCaptureDevNameMap[idx].dev = dev; idx++; } } } if(handle) psnd_ctl_close(handle); if(psnd_card_next(&card) < 0) { AL_PRINT("snd_card_next failed\n"); break; } } numCaptureDevNames = idx; } psnd_pcm_info_free(pcminfo); psnd_ctl_card_info_free(info); }
static ALCboolean alsa_open_capture(ALCdevice *pDevice, const ALCchar *deviceName) { const char *devName; snd_pcm_hw_params_t *p; snd_pcm_uframes_t bufferSizeInFrames; ALuint frameSize; alsa_data *data; char driver[64]; char *err; int i; if(!alsa_handle) return ALC_FALSE; strncpy(driver, GetConfigValue("alsa", "capture", "default"), sizeof(driver)-1); driver[sizeof(driver)-1] = 0; if(!deviceName) deviceName = allCaptureDevNameMap[0].name; else { size_t idx; for(idx = 0;idx < numCaptureDevNames;idx++) { if(allCaptureDevNameMap[idx].name && strcmp(deviceName, allCaptureDevNameMap[idx].name) == 0) { devName = allCaptureDevNameMap[idx].name; if(idx > 0) sprintf(driver, "plughw:%d,%d", allCaptureDevNameMap[idx].card, allCaptureDevNameMap[idx].dev); goto open_alsa; } } return ALC_FALSE; } open_alsa: data = (alsa_data*)calloc(1, sizeof(alsa_data)); i = psnd_pcm_open(&data->pcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); if(i < 0) { Sleep(200); i = psnd_pcm_open(&data->pcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); } if(i >= 0) { i = psnd_pcm_nonblock(data->pcmHandle, 0); if(i < 0) psnd_pcm_close(data->pcmHandle); } if(i < 0) { free(data); AL_PRINT("Could not open capture device '%s': %s\n", driver, psnd_strerror(i)); return ALC_FALSE; } switch(aluBytesFromFormat(pDevice->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", pDevice->Format); psnd_pcm_close(data->pcmHandle); free(data); return ALC_FALSE; } err = NULL; bufferSizeInFrames = pDevice->UpdateSize * pDevice->NumUpdates; 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 && (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(pDevice->Format))) < 0) err = "set channels"; /* set rate (implicitly constrains period/buffer parameters) */ if(err == NULL && (i=psnd_pcm_hw_params_set_rate(data->pcmHandle, p, pDevice->Frequency, 0)) < 0) err = "set rate near"; /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */ if(err == NULL && (i=psnd_pcm_hw_params_set_buffer_size_near(data->pcmHandle, p, &bufferSizeInFrames)) < 0) err = "set buffer 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) { AL_PRINT("%s failed: %s\n", err, psnd_strerror(i)); psnd_pcm_hw_params_free(p); psnd_pcm_close(data->pcmHandle); free(data); return ALC_FALSE; } if((i=psnd_pcm_hw_params_get_period_size(p, &bufferSizeInFrames, NULL)) < 0) { AL_PRINT("get size failed: %s\n", psnd_strerror(i)); psnd_pcm_hw_params_free(p); psnd_pcm_close(data->pcmHandle); free(data); return ALC_FALSE; } psnd_pcm_hw_params_free(p); frameSize = aluChannelsFromFormat(pDevice->Format); frameSize *= aluBytesFromFormat(pDevice->Format); data->ring = CreateRingBuffer(frameSize, pDevice->UpdateSize*pDevice->NumUpdates); if(!data->ring) { AL_PRINT("ring buffer create failed\n"); psnd_pcm_close(data->pcmHandle); free(data); return ALC_FALSE; } data->size = psnd_pcm_frames_to_bytes(data->pcmHandle, bufferSizeInFrames); data->buffer = malloc(data->size); if(!data->buffer) { AL_PRINT("buffer malloc failed\n"); psnd_pcm_close(data->pcmHandle); DestroyRingBuffer(data->ring); free(data); return ALC_FALSE; } pDevice->ExtraData = data; data->thread = StartThread(ALSANoMMapCaptureProc, pDevice); if(data->thread == NULL) { AL_PRINT("Could not create capture thread\n"); psnd_pcm_close(data->pcmHandle); DestroyRingBuffer(data->ring); pDevice->ExtraData = NULL; free(data->buffer); free(data); return ALC_FALSE; } pDevice->szDeviceName = strdup(deviceName); return ALC_TRUE; }
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 ALuint ALSAProc(ALvoid *ptr) { ALCdevice *pDevice = (ALCdevice*)ptr; alsa_data *data = (alsa_data*)pDevice->ExtraData; const snd_pcm_channel_area_t *areas = NULL; snd_pcm_sframes_t avail, commitres; snd_pcm_uframes_t offset, frames; char *WritePtr; int err; while(!data->killNow) { int state = verify_state(data->pcmHandle); if(state < 0) { AL_PRINT("Invalid state detected: %s\n", psnd_strerror(state)); aluHandleDisconnect(pDevice); break; } avail = psnd_pcm_avail_update(data->pcmHandle); if(avail < 0) { AL_PRINT("available update failed: %s\n", psnd_strerror(avail)); continue; } // make sure there's frames to process if(avail >= 0 && avail < (snd_pcm_sframes_t)pDevice->UpdateSize) { if(state != SND_PCM_STATE_RUNNING) { err = psnd_pcm_start(data->pcmHandle); if(err < 0) { AL_PRINT("start failed: %s\n", psnd_strerror(err)); continue; } } if(psnd_pcm_wait(data->pcmHandle, 1000) == 0) AL_PRINT("Wait timeout... buffer size too low?\n"); continue; } avail -= avail%pDevice->UpdateSize; // it is possible that contiguous areas are smaller, thus we use a loop while(avail > 0) { frames = avail; err = psnd_pcm_mmap_begin(data->pcmHandle, &areas, &offset, &frames); if(err < 0) { AL_PRINT("mmap begin error: %s\n", psnd_strerror(err)); break; } WritePtr = (char*)areas->addr + (offset * areas->step / 8); aluMixData(pDevice, WritePtr, frames); commitres = psnd_pcm_mmap_commit(data->pcmHandle, offset, frames); if(commitres < 0 || (commitres-frames) != 0) { AL_PRINT("mmap commit error: %s\n", psnd_strerror(commitres >= 0 ? -EPIPE : commitres)); break; } avail -= frames; } } return 0; }
static ALCuint alsa_available_samples(ALCdevice *Device) { alsa_data *data = (alsa_data*)Device->ExtraData; snd_pcm_sframes_t avail; avail = (Device->Connected ? psnd_pcm_avail_update(data->pcmHandle) : 0); if(avail < 0) { AL_PRINT("avail update failed: %s\n", psnd_strerror(avail)); if((avail=psnd_pcm_recover(data->pcmHandle, avail, 1)) >= 0 && (avail=psnd_pcm_prepare(data->pcmHandle)) >= 0) { if(data->doCapture) avail = psnd_pcm_start(data->pcmHandle); if(avail >= 0) avail = psnd_pcm_avail_update(data->pcmHandle); } if(avail < 0) { AL_PRINT("restore error: %s\n", psnd_strerror(avail)); aluHandleDisconnect(Device); } } while(avail > 0) { snd_pcm_sframes_t amt; amt = psnd_pcm_bytes_to_frames(data->pcmHandle, data->size); if(avail < amt) amt = avail; amt = psnd_pcm_readi(data->pcmHandle, data->buffer, amt); if(amt < 0) { AL_PRINT("read error: %s\n", psnd_strerror(amt)); if(amt == -EAGAIN) continue; if((amt=psnd_pcm_recover(data->pcmHandle, amt, 1)) >= 0 && (amt=psnd_pcm_prepare(data->pcmHandle)) >= 0) { if(data->doCapture) amt = psnd_pcm_start(data->pcmHandle); if(amt >= 0) amt = psnd_pcm_avail_update(data->pcmHandle); } if(amt < 0) { AL_PRINT("restore error: %s\n", psnd_strerror(amt)); aluHandleDisconnect(Device); break; } avail = amt; continue; } WriteRingBuffer(data->ring, data->buffer, amt); avail -= amt; } return RingBufferSize(data->ring); }
static DevMap *probe_devices(snd_pcm_stream_t stream, ALuint *count) { snd_ctl_t *handle; int card, err, dev, idx; snd_ctl_card_info_t *info; snd_pcm_info_t *pcminfo; DevMap *DevList; char name[1024]; psnd_ctl_card_info_malloc(&info); psnd_pcm_info_malloc(&pcminfo); card = -1; if((err=psnd_card_next(&card)) < 0) AL_PRINT("Failed to find a card: %s\n", psnd_strerror(err)); DevList = malloc(sizeof(DevMap) * 1); DevList[0].name = strdup("ALSA Default"); idx = 1; while(card >= 0) { sprintf(name, "hw:%d", card); if((err = psnd_ctl_open(&handle, name, 0)) < 0) { AL_PRINT("control open (%i): %s\n", card, psnd_strerror(err)); goto next_card; } if((err = psnd_ctl_card_info(handle, info)) < 0) { AL_PRINT("control hardware info (%i): %s\n", card, psnd_strerror(err)); psnd_ctl_close(handle); goto next_card; } dev = -1; while(1) { const char *cname, *dname; void *temp; if(psnd_ctl_pcm_next_device(handle, &dev) < 0) AL_PRINT("snd_ctl_pcm_next_device failed\n"); if(dev < 0) break; psnd_pcm_info_set_device(pcminfo, dev); psnd_pcm_info_set_subdevice(pcminfo, 0); psnd_pcm_info_set_stream(pcminfo, stream); if((err = psnd_ctl_pcm_info(handle, pcminfo)) < 0) { if(err != -ENOENT) AL_PRINT("control digital audio info (%i): %s\n", card, psnd_strerror(err)); continue; } temp = realloc(DevList, sizeof(DevMap) * (idx+1)); if(temp) { DevList = temp; cname = psnd_ctl_card_info_get_name(info); dname = psnd_pcm_info_get_name(pcminfo); snprintf(name, sizeof(name), "%s [%s] (hw:%d,%d) via ALSA", cname, dname, card, dev); DevList[idx].name = strdup(name); DevList[idx].card = card; DevList[idx].dev = dev; idx++; } } psnd_ctl_close(handle); next_card: if(psnd_card_next(&card) < 0) { AL_PRINT("snd_card_next failed\n"); break; } } psnd_pcm_info_free(pcminfo); psnd_ctl_card_info_free(info); *count = idx; return DevList; }
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; }