diff options
Diffstat (limited to 'Alc/backends')
-rw-r--r-- | Alc/backends/alsa.c | 1138 | ||||
-rw-r--r-- | Alc/backends/coreaudio.c | 719 | ||||
-rw-r--r-- | Alc/backends/dsound.c | 627 | ||||
-rw-r--r-- | Alc/backends/loopback.c | 77 | ||||
-rw-r--r-- | Alc/backends/mmdevapi.c | 775 | ||||
-rw-r--r-- | Alc/backends/null.c | 164 | ||||
-rw-r--r-- | Alc/backends/opensl.c | 425 | ||||
-rw-r--r-- | Alc/backends/oss.c | 536 | ||||
-rw-r--r-- | Alc/backends/portaudio.c | 449 | ||||
-rw-r--r-- | Alc/backends/pulseaudio.c | 1430 | ||||
-rw-r--r-- | Alc/backends/sndio.c | 381 | ||||
-rw-r--r-- | Alc/backends/solaris.c | 282 | ||||
-rw-r--r-- | Alc/backends/wave.c | 355 | ||||
-rw-r--r-- | Alc/backends/winmm.c | 780 |
14 files changed, 8138 insertions, 0 deletions
diff --git a/Alc/backends/alsa.c b/Alc/backends/alsa.c new file mode 100644 index 00000000..ffed94a8 --- /dev/null +++ b/Alc/backends/alsa.c @@ -0,0 +1,1138 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <memory.h> + +#include "alMain.h" + +#include <alsa/asoundlib.h> + + +static const ALCchar alsaDevice[] = "ALSA Default"; + + +static void *alsa_handle; +#ifdef HAVE_DYNLOAD +#define MAKE_FUNC(f) static typeof(f) * p##f +MAKE_FUNC(snd_strerror); +MAKE_FUNC(snd_pcm_open); +MAKE_FUNC(snd_pcm_close); +MAKE_FUNC(snd_pcm_nonblock); +MAKE_FUNC(snd_pcm_frames_to_bytes); +MAKE_FUNC(snd_pcm_bytes_to_frames); +MAKE_FUNC(snd_pcm_hw_params_malloc); +MAKE_FUNC(snd_pcm_hw_params_free); +MAKE_FUNC(snd_pcm_hw_params_any); +MAKE_FUNC(snd_pcm_hw_params_set_access); +MAKE_FUNC(snd_pcm_hw_params_set_format); +MAKE_FUNC(snd_pcm_hw_params_set_channels); +MAKE_FUNC(snd_pcm_hw_params_set_periods_near); +MAKE_FUNC(snd_pcm_hw_params_set_rate_near); +MAKE_FUNC(snd_pcm_hw_params_set_rate); +MAKE_FUNC(snd_pcm_hw_params_set_rate_resample); +MAKE_FUNC(snd_pcm_hw_params_set_buffer_time_near); +MAKE_FUNC(snd_pcm_hw_params_set_period_time_near); +MAKE_FUNC(snd_pcm_hw_params_set_buffer_size_near); +MAKE_FUNC(snd_pcm_hw_params_set_period_size_near); +MAKE_FUNC(snd_pcm_hw_params_set_buffer_size_min); +MAKE_FUNC(snd_pcm_hw_params_get_buffer_size); +MAKE_FUNC(snd_pcm_hw_params_get_period_size); +MAKE_FUNC(snd_pcm_hw_params_get_access); +MAKE_FUNC(snd_pcm_hw_params_get_periods); +MAKE_FUNC(snd_pcm_hw_params); +MAKE_FUNC(snd_pcm_sw_params_malloc); +MAKE_FUNC(snd_pcm_sw_params_current); +MAKE_FUNC(snd_pcm_sw_params_set_avail_min); +MAKE_FUNC(snd_pcm_sw_params); +MAKE_FUNC(snd_pcm_sw_params_free); +MAKE_FUNC(snd_pcm_prepare); +MAKE_FUNC(snd_pcm_start); +MAKE_FUNC(snd_pcm_resume); +MAKE_FUNC(snd_pcm_wait); +MAKE_FUNC(snd_pcm_state); +MAKE_FUNC(snd_pcm_avail_update); +MAKE_FUNC(snd_pcm_areas_silence); +MAKE_FUNC(snd_pcm_mmap_begin); +MAKE_FUNC(snd_pcm_mmap_commit); +MAKE_FUNC(snd_pcm_readi); +MAKE_FUNC(snd_pcm_writei); +MAKE_FUNC(snd_pcm_drain); +MAKE_FUNC(snd_pcm_recover); +MAKE_FUNC(snd_pcm_info_malloc); +MAKE_FUNC(snd_pcm_info_free); +MAKE_FUNC(snd_pcm_info_set_device); +MAKE_FUNC(snd_pcm_info_set_subdevice); +MAKE_FUNC(snd_pcm_info_set_stream); +MAKE_FUNC(snd_pcm_info_get_name); +MAKE_FUNC(snd_ctl_pcm_next_device); +MAKE_FUNC(snd_ctl_pcm_info); +MAKE_FUNC(snd_ctl_open); +MAKE_FUNC(snd_ctl_close); +MAKE_FUNC(snd_ctl_card_info_malloc); +MAKE_FUNC(snd_ctl_card_info_free); +MAKE_FUNC(snd_ctl_card_info); +MAKE_FUNC(snd_ctl_card_info_get_name); +MAKE_FUNC(snd_ctl_card_info_get_id); +MAKE_FUNC(snd_card_next); +#undef MAKE_FUNC + +#define snd_strerror psnd_strerror +#define snd_pcm_open psnd_pcm_open +#define snd_pcm_close psnd_pcm_close +#define snd_pcm_nonblock psnd_pcm_nonblock +#define snd_pcm_frames_to_bytes psnd_pcm_frames_to_bytes +#define snd_pcm_bytes_to_frames psnd_pcm_bytes_to_frames +#define snd_pcm_hw_params_malloc psnd_pcm_hw_params_malloc +#define snd_pcm_hw_params_free psnd_pcm_hw_params_free +#define snd_pcm_hw_params_any psnd_pcm_hw_params_any +#define snd_pcm_hw_params_set_access psnd_pcm_hw_params_set_access +#define snd_pcm_hw_params_set_format psnd_pcm_hw_params_set_format +#define snd_pcm_hw_params_set_channels psnd_pcm_hw_params_set_channels +#define snd_pcm_hw_params_set_periods_near psnd_pcm_hw_params_set_periods_near +#define snd_pcm_hw_params_set_rate_near psnd_pcm_hw_params_set_rate_near +#define snd_pcm_hw_params_set_rate psnd_pcm_hw_params_set_rate +#define snd_pcm_hw_params_set_rate_resample psnd_pcm_hw_params_set_rate_resample +#define snd_pcm_hw_params_set_buffer_time_near psnd_pcm_hw_params_set_buffer_time_near +#define snd_pcm_hw_params_set_period_time_near psnd_pcm_hw_params_set_period_time_near +#define snd_pcm_hw_params_set_buffer_size_near psnd_pcm_hw_params_set_buffer_size_near +#define snd_pcm_hw_params_set_period_size_near psnd_pcm_hw_params_set_period_size_near +#define snd_pcm_hw_params_set_buffer_size_min psnd_pcm_hw_params_set_buffer_size_min +#define snd_pcm_hw_params_get_buffer_size psnd_pcm_hw_params_get_buffer_size +#define snd_pcm_hw_params_get_period_size psnd_pcm_hw_params_get_period_size +#define snd_pcm_hw_params_get_access psnd_pcm_hw_params_get_access +#define snd_pcm_hw_params_get_periods psnd_pcm_hw_params_get_periods +#define snd_pcm_hw_params psnd_pcm_hw_params +#define snd_pcm_sw_params_malloc psnd_pcm_sw_params_malloc +#define snd_pcm_sw_params_current psnd_pcm_sw_params_current +#define snd_pcm_sw_params_set_avail_min psnd_pcm_sw_params_set_avail_min +#define snd_pcm_sw_params psnd_pcm_sw_params +#define snd_pcm_sw_params_free psnd_pcm_sw_params_free +#define snd_pcm_prepare psnd_pcm_prepare +#define snd_pcm_start psnd_pcm_start +#define snd_pcm_resume psnd_pcm_resume +#define snd_pcm_wait psnd_pcm_wait +#define snd_pcm_state psnd_pcm_state +#define snd_pcm_avail_update psnd_pcm_avail_update +#define snd_pcm_areas_silence psnd_pcm_areas_silence +#define snd_pcm_mmap_begin psnd_pcm_mmap_begin +#define snd_pcm_mmap_commit psnd_pcm_mmap_commit +#define snd_pcm_readi psnd_pcm_readi +#define snd_pcm_writei psnd_pcm_writei +#define snd_pcm_drain psnd_pcm_drain +#define snd_pcm_recover psnd_pcm_recover +#define snd_pcm_info_malloc psnd_pcm_info_malloc +#define snd_pcm_info_free psnd_pcm_info_free +#define snd_pcm_info_set_device psnd_pcm_info_set_device +#define snd_pcm_info_set_subdevice psnd_pcm_info_set_subdevice +#define snd_pcm_info_set_stream psnd_pcm_info_set_stream +#define snd_pcm_info_get_name psnd_pcm_info_get_name +#define snd_ctl_pcm_next_device psnd_ctl_pcm_next_device +#define snd_ctl_pcm_info psnd_ctl_pcm_info +#define snd_ctl_open psnd_ctl_open +#define snd_ctl_close psnd_ctl_close +#define snd_ctl_card_info_malloc psnd_ctl_card_info_malloc +#define snd_ctl_card_info_free psnd_ctl_card_info_free +#define snd_ctl_card_info psnd_ctl_card_info +#define snd_ctl_card_info_get_name psnd_ctl_card_info_get_name +#define snd_ctl_card_info_get_id psnd_ctl_card_info_get_id +#define snd_card_next psnd_card_next +#endif + + +static ALCboolean alsa_load(void) +{ + if(!alsa_handle) + { +#ifdef HAVE_DYNLOAD + alsa_handle = LoadLib("libasound.so.2"); + if(!alsa_handle) + return ALC_FALSE; + +#define LOAD_FUNC(f) do { \ + p##f = GetSymbol(alsa_handle, #f); \ + if(p##f == NULL) { \ + CloseLib(alsa_handle); \ + alsa_handle = NULL; \ + return ALC_FALSE; \ + } \ +} while(0) + LOAD_FUNC(snd_strerror); + LOAD_FUNC(snd_pcm_open); + LOAD_FUNC(snd_pcm_close); + LOAD_FUNC(snd_pcm_nonblock); + LOAD_FUNC(snd_pcm_frames_to_bytes); + LOAD_FUNC(snd_pcm_bytes_to_frames); + LOAD_FUNC(snd_pcm_hw_params_malloc); + LOAD_FUNC(snd_pcm_hw_params_free); + LOAD_FUNC(snd_pcm_hw_params_any); + LOAD_FUNC(snd_pcm_hw_params_set_access); + LOAD_FUNC(snd_pcm_hw_params_set_format); + LOAD_FUNC(snd_pcm_hw_params_set_channels); + LOAD_FUNC(snd_pcm_hw_params_set_periods_near); + LOAD_FUNC(snd_pcm_hw_params_set_rate_near); + LOAD_FUNC(snd_pcm_hw_params_set_rate); + LOAD_FUNC(snd_pcm_hw_params_set_rate_resample); + LOAD_FUNC(snd_pcm_hw_params_set_buffer_time_near); + LOAD_FUNC(snd_pcm_hw_params_set_period_time_near); + LOAD_FUNC(snd_pcm_hw_params_set_buffer_size_near); + LOAD_FUNC(snd_pcm_hw_params_set_buffer_size_min); + LOAD_FUNC(snd_pcm_hw_params_set_period_size_near); + LOAD_FUNC(snd_pcm_hw_params_get_buffer_size); + LOAD_FUNC(snd_pcm_hw_params_get_period_size); + LOAD_FUNC(snd_pcm_hw_params_get_access); + LOAD_FUNC(snd_pcm_hw_params_get_periods); + LOAD_FUNC(snd_pcm_hw_params); + LOAD_FUNC(snd_pcm_sw_params_malloc); + LOAD_FUNC(snd_pcm_sw_params_current); + LOAD_FUNC(snd_pcm_sw_params_set_avail_min); + LOAD_FUNC(snd_pcm_sw_params); + LOAD_FUNC(snd_pcm_sw_params_free); + LOAD_FUNC(snd_pcm_prepare); + LOAD_FUNC(snd_pcm_start); + LOAD_FUNC(snd_pcm_resume); + LOAD_FUNC(snd_pcm_wait); + LOAD_FUNC(snd_pcm_state); + LOAD_FUNC(snd_pcm_avail_update); + LOAD_FUNC(snd_pcm_areas_silence); + LOAD_FUNC(snd_pcm_mmap_begin); + LOAD_FUNC(snd_pcm_mmap_commit); + LOAD_FUNC(snd_pcm_readi); + LOAD_FUNC(snd_pcm_writei); + LOAD_FUNC(snd_pcm_drain); + LOAD_FUNC(snd_pcm_recover); + LOAD_FUNC(snd_pcm_info_malloc); + LOAD_FUNC(snd_pcm_info_free); + LOAD_FUNC(snd_pcm_info_set_device); + LOAD_FUNC(snd_pcm_info_set_subdevice); + LOAD_FUNC(snd_pcm_info_set_stream); + LOAD_FUNC(snd_pcm_info_get_name); + LOAD_FUNC(snd_ctl_pcm_next_device); + LOAD_FUNC(snd_ctl_pcm_info); + LOAD_FUNC(snd_ctl_open); + LOAD_FUNC(snd_ctl_close); + LOAD_FUNC(snd_ctl_card_info_malloc); + LOAD_FUNC(snd_ctl_card_info_free); + LOAD_FUNC(snd_ctl_card_info); + LOAD_FUNC(snd_ctl_card_info_get_name); + LOAD_FUNC(snd_ctl_card_info_get_id); + LOAD_FUNC(snd_card_next); +#undef LOAD_FUNC +#else + alsa_handle = (void*)0xDEADBEEF; +#endif + } + return ALC_TRUE; +} + + +typedef struct { + snd_pcm_t *pcmHandle; + + ALvoid *buffer; + ALsizei size; + + ALboolean doCapture; + RingBuffer *ring; + + volatile int killNow; + ALvoid *thread; +} alsa_data; + +typedef struct { + ALCchar *name; + char *card; + int dev; +} DevMap; + +static DevMap *allDevNameMap; +static ALuint numDevNames; +static DevMap *allCaptureDevNameMap; +static ALuint numCaptureDevNames; + +static const char *device_prefix; +static const char *capture_prefix; + + +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]; + + snd_ctl_card_info_malloc(&info); + snd_pcm_info_malloc(&pcminfo); + + card = -1; + if((err=snd_card_next(&card)) < 0) + ERR("Failed to find a card: %s\n", snd_strerror(err)); + + DevList = malloc(sizeof(DevMap) * 1); + DevList[0].name = strdup("ALSA Default"); + DevList[0].card = NULL; + DevList[0].dev = 0; + idx = 1; + while(card >= 0) + { + sprintf(name, "hw:%d", card); + if((err = snd_ctl_open(&handle, name, 0)) < 0) + { + ERR("control open (%i): %s\n", card, snd_strerror(err)); + goto next_card; + } + if((err = snd_ctl_card_info(handle, info)) < 0) + { + ERR("control hardware info (%i): %s\n", card, snd_strerror(err)); + snd_ctl_close(handle); + goto next_card; + } + + dev = -1; + while(1) + { + const char *cname, *dname, *cid; + void *temp; + + if(snd_ctl_pcm_next_device(handle, &dev) < 0) + ERR("snd_ctl_pcm_next_device failed\n"); + if(dev < 0) + break; + + snd_pcm_info_set_device(pcminfo, dev); + snd_pcm_info_set_subdevice(pcminfo, 0); + snd_pcm_info_set_stream(pcminfo, stream); + if((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { + if(err != -ENOENT) + ERR("control digital audio info (%i): %s\n", card, snd_strerror(err)); + continue; + } + + temp = realloc(DevList, sizeof(DevMap) * (idx+1)); + if(temp) + { + DevList = temp; + cname = snd_ctl_card_info_get_name(info); + dname = snd_pcm_info_get_name(pcminfo); + cid = snd_ctl_card_info_get_id(info); + snprintf(name, sizeof(name), "%s, %s (CARD=%s,DEV=%d)", + cname, dname, cid, dev); + DevList[idx].name = strdup(name); + DevList[idx].card = strdup(cid); + DevList[idx].dev = dev; + idx++; + } + } + snd_ctl_close(handle); + next_card: + if(snd_card_next(&card) < 0) { + ERR("snd_card_next failed\n"); + break; + } + } + + snd_pcm_info_free(pcminfo); + snd_ctl_card_info_free(info); + + *count = idx; + return DevList; +} + + +static int xrun_recovery(snd_pcm_t *handle, int err) +{ + err = snd_pcm_recover(handle, err, 1); + if(err < 0) + ERR("recover failed: %s\n", snd_strerror(err)); + return err; +} + +static int verify_state(snd_pcm_t *handle) +{ + snd_pcm_state_t state = snd_pcm_state(handle); + if(state == SND_PCM_STATE_DISCONNECTED) + return -ENODEV; + if(state == SND_PCM_STATE_XRUN) + { + int err = xrun_recovery(handle, -EPIPE); + if(err < 0) return err; + } + else if(state == SND_PCM_STATE_SUSPENDED) + { + int err = xrun_recovery(handle, -ESTRPIPE); + if(err < 0) return err; + } + + return state; +} + + +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; + + SetRTPriority(); + + while(!data->killNow) + { + int state = verify_state(data->pcmHandle); + if(state < 0) + { + ERR("Invalid state detected: %s\n", snd_strerror(state)); + aluHandleDisconnect(pDevice); + break; + } + + avail = snd_pcm_avail_update(data->pcmHandle); + if(avail < 0) + { + ERR("available update failed: %s\n", snd_strerror(avail)); + continue; + } + + // make sure there's frames to process + if((snd_pcm_uframes_t)avail < pDevice->UpdateSize) + { + if(state != SND_PCM_STATE_RUNNING) + { + err = snd_pcm_start(data->pcmHandle); + if(err < 0) + { + ERR("start failed: %s\n", snd_strerror(err)); + continue; + } + } + if(snd_pcm_wait(data->pcmHandle, 1000) == 0) + ERR("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 = snd_pcm_mmap_begin(data->pcmHandle, &areas, &offset, &frames); + if(err < 0) + { + ERR("mmap begin error: %s\n", snd_strerror(err)); + break; + } + + WritePtr = (char*)areas->addr + (offset * areas->step / 8); + aluMixData(pDevice, WritePtr, frames); + + commitres = snd_pcm_mmap_commit(data->pcmHandle, offset, frames); + if(commitres < 0 || (commitres-frames) != 0) + { + ERR("mmap commit error: %s\n", + snd_strerror(commitres >= 0 ? -EPIPE : commitres)); + break; + } + + avail -= frames; + } + } + + return 0; +} + +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) + { + ERR("Invalid state detected: %s\n", snd_strerror(state)); + aluHandleDisconnect(pDevice); + break; + } + + WritePtr = data->buffer; + avail = data->size / snd_pcm_frames_to_bytes(data->pcmHandle, 1); + aluMixData(pDevice, WritePtr, avail); + + while(avail > 0) + { + int ret = snd_pcm_writei(data->pcmHandle, WritePtr, avail); + switch (ret) + { + case -EAGAIN: + continue; + case -ESTRPIPE: + case -EPIPE: + case -EINTR: + ret = snd_pcm_recover(data->pcmHandle, ret, 1); + if(ret < 0) + avail = 0; + break; + default: + if (ret >= 0) + { + WritePtr += snd_pcm_frames_to_bytes(data->pcmHandle, ret); + avail -= ret; + } + break; + } + if (ret < 0) + { + ret = snd_pcm_prepare(data->pcmHandle); + if(ret < 0) + break; + } + } + } + + return 0; +} + +static ALCboolean alsa_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + alsa_data *data; + char driver[128]; + int i; + + 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) + snprintf(driver, sizeof(driver), "%sCARD=%s,DEV=%d", device_prefix, + allDevNameMap[idx].card, allDevNameMap[idx].dev); + break; + } + } + if(idx == numDevNames) + return ALC_FALSE; + } + + data = (alsa_data*)calloc(1, sizeof(alsa_data)); + + i = snd_pcm_open(&data->pcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if(i >= 0) + { + i = snd_pcm_nonblock(data->pcmHandle, 0); + if(i < 0) + snd_pcm_close(data->pcmHandle); + } + if(i < 0) + { + free(data); + ERR("Could not open playback device '%s': %s\n", driver, snd_strerror(i)); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + device->ExtraData = data; + return ALC_TRUE; +} + +static void alsa_close_playback(ALCdevice *device) +{ + alsa_data *data = (alsa_data*)device->ExtraData; + + snd_pcm_close(data->pcmHandle); + free(data); + device->ExtraData = NULL; +} + +static ALCboolean alsa_reset_playback(ALCdevice *device) +{ + alsa_data *data = (alsa_data*)device->ExtraData; + snd_pcm_uframes_t periodSizeInFrames; + unsigned int periodLen, bufferLen; + snd_pcm_sw_params_t *sp = NULL; + snd_pcm_hw_params_t *p = NULL; + snd_pcm_access_t access; + snd_pcm_format_t format; + unsigned int periods; + unsigned int rate; + int allowmmap; + char *err; + int i; + + + format = -1; + switch(device->FmtType) + { + case DevFmtByte: + format = SND_PCM_FORMAT_S8; + break; + case DevFmtUByte: + format = SND_PCM_FORMAT_U8; + break; + case DevFmtShort: + format = SND_PCM_FORMAT_S16; + break; + case DevFmtUShort: + format = SND_PCM_FORMAT_U16; + break; + case DevFmtFloat: + format = SND_PCM_FORMAT_FLOAT; + break; + } + + allowmmap = GetConfigValueBool("alsa", "mmap", 1); + periods = device->NumUpdates; + periodLen = (ALuint64)device->UpdateSize * 1000000 / device->Frequency; + bufferLen = periodLen * periods; + rate = device->Frequency; + + err = NULL; + snd_pcm_hw_params_malloc(&p); + + if((i=snd_pcm_hw_params_any(data->pcmHandle, p)) < 0) + err = "any"; + /* set interleaved access */ + if(i >= 0 && (!allowmmap || (i=snd_pcm_hw_params_set_access(data->pcmHandle, p, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0)) + { + if(periods > 2) + { + periods--; + bufferLen = periodLen * periods; + } + if((i=snd_pcm_hw_params_set_access(data->pcmHandle, p, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + err = "set access"; + } + /* set format (implicitly sets sample bits) */ + if(i >= 0 && (i=snd_pcm_hw_params_set_format(data->pcmHandle, p, format)) < 0) + { + device->FmtType = DevFmtFloat; + if(format == SND_PCM_FORMAT_FLOAT || + (i=snd_pcm_hw_params_set_format(data->pcmHandle, p, SND_PCM_FORMAT_FLOAT)) < 0) + { + device->FmtType = DevFmtShort; + if(format == SND_PCM_FORMAT_S16 || + (i=snd_pcm_hw_params_set_format(data->pcmHandle, p, SND_PCM_FORMAT_S16)) < 0) + { + device->FmtType = DevFmtUByte; + if(format == SND_PCM_FORMAT_U8 || + (i=snd_pcm_hw_params_set_format(data->pcmHandle, p, SND_PCM_FORMAT_U8)) < 0) + err = "set format"; + } + } + } + /* set channels (implicitly sets frame bits) */ + if(i >= 0 && (i=snd_pcm_hw_params_set_channels(data->pcmHandle, p, ChannelsFromDevFmt(device->FmtChans))) < 0) + { + if((i=snd_pcm_hw_params_set_channels(data->pcmHandle, p, 2)) < 0) + { + if((i=snd_pcm_hw_params_set_channels(data->pcmHandle, p, 1)) < 0) + err = "set channels"; + else + { + if((device->Flags&DEVICE_CHANNELS_REQUEST)) + ERR("Failed to set %s, got Mono instead\n", DevFmtChannelsString(device->FmtChans)); + device->FmtChans = DevFmtMono; + } + } + else + { + if((device->Flags&DEVICE_CHANNELS_REQUEST)) + ERR("Failed to set %s, got Stereo instead\n", DevFmtChannelsString(device->FmtChans)); + device->FmtChans = DevFmtStereo; + } + device->Flags &= ~DEVICE_CHANNELS_REQUEST; + } + if(i >= 0 && (i=snd_pcm_hw_params_set_rate_resample(data->pcmHandle, p, 0)) < 0) + { + ERR("Failed to disable ALSA resampler\n"); + i = 0; + } + /* set rate (implicitly constrains period/buffer parameters) */ + if(i >= 0 && (i=snd_pcm_hw_params_set_rate_near(data->pcmHandle, p, &rate, NULL)) < 0) + err = "set rate near"; + /* set buffer time (implicitly constrains period/buffer parameters) */ + if(i >= 0 && (i=snd_pcm_hw_params_set_buffer_time_near(data->pcmHandle, p, &bufferLen, NULL)) < 0) + err = "set buffer time near"; + /* set period time in frame units (implicitly sets buffer size/bytes/time and period size/bytes) */ + if(i >= 0 && (i=snd_pcm_hw_params_set_period_time_near(data->pcmHandle, p, &periodLen, NULL)) < 0) + err = "set period time near"; + /* install and prepare hardware configuration */ + if(i >= 0 && (i=snd_pcm_hw_params(data->pcmHandle, p)) < 0) + err = "set params"; + if(i >= 0 && (i=snd_pcm_hw_params_get_access(p, &access)) < 0) + err = "get access"; + if(i >= 0 && (i=snd_pcm_hw_params_get_period_size(p, &periodSizeInFrames, NULL)) < 0) + err = "get period size"; + if(i >= 0 && (i=snd_pcm_hw_params_get_periods(p, &periods, NULL)) < 0) + err = "get periods"; + if(i < 0) + { + ERR("%s failed: %s\n", err, snd_strerror(i)); + snd_pcm_hw_params_free(p); + return ALC_FALSE; + } + + snd_pcm_hw_params_free(p); + + err = NULL; + snd_pcm_sw_params_malloc(&sp); + + if((i=snd_pcm_sw_params_current(data->pcmHandle, sp)) != 0) + err = "sw current"; + if(i == 0 && (i=snd_pcm_sw_params_set_avail_min(data->pcmHandle, sp, periodSizeInFrames)) != 0) + err = "sw set avail min"; + if(i == 0 && (i=snd_pcm_sw_params(data->pcmHandle, sp)) != 0) + err = "sw set params"; + if(i != 0) + { + ERR("%s failed: %s\n", err, snd_strerror(i)); + snd_pcm_sw_params_free(sp); + return ALC_FALSE; + } + + snd_pcm_sw_params_free(sp); + + if(device->Frequency != rate) + { + if((device->Flags&DEVICE_FREQUENCY_REQUEST)) + ERR("Failed to set %dhz, got %dhz instead\n", device->Frequency, rate); + device->Flags &= ~DEVICE_FREQUENCY_REQUEST; + device->Frequency = rate; + } + + SetDefaultChannelOrder(device); + + data->size = snd_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) + { + ERR("buffer malloc failed\n"); + return ALC_FALSE; + } + device->UpdateSize = periodSizeInFrames; + device->NumUpdates = periods; + data->thread = StartThread(ALSANoMMapProc, device); + } + else + { + i = snd_pcm_prepare(data->pcmHandle); + if(i < 0) + { + ERR("prepare error: %s\n", snd_strerror(i)); + return ALC_FALSE; + } + device->UpdateSize = periodSizeInFrames; + device->NumUpdates = periods; + data->thread = StartThread(ALSAProc, device); + } + if(data->thread == NULL) + { + ERR("Could not create playback thread\n"); + free(data->buffer); + data->buffer = NULL; + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void alsa_stop_playback(ALCdevice *device) +{ + alsa_data *data = (alsa_data*)device->ExtraData; + + if(data->thread) + { + data->killNow = 1; + StopThread(data->thread); + data->thread = NULL; + } + data->killNow = 0; + free(data->buffer); + data->buffer = NULL; +} + + +static ALCboolean alsa_open_capture(ALCdevice *pDevice, const ALCchar *deviceName) +{ + snd_pcm_hw_params_t *p; + snd_pcm_uframes_t bufferSizeInFrames; + snd_pcm_format_t format; + ALuint frameSize; + alsa_data *data; + char driver[128]; + char *err; + int i; + + strncpy(driver, GetConfigValue("alsa", "capture", "default"), sizeof(driver)-1); + driver[sizeof(driver)-1] = 0; + + if(!allCaptureDevNameMap) + allCaptureDevNameMap = probe_devices(SND_PCM_STREAM_CAPTURE, &numCaptureDevNames); + + 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) + { + if(idx > 0) + snprintf(driver, sizeof(driver), "%sCARD=%s,DEV=%d", capture_prefix, + allCaptureDevNameMap[idx].card, allCaptureDevNameMap[idx].dev); + break; + } + } + if(idx == numCaptureDevNames) + return ALC_FALSE; + } + + data = (alsa_data*)calloc(1, sizeof(alsa_data)); + + i = snd_pcm_open(&data->pcmHandle, driver, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); + if(i < 0) + { + ERR("Could not open capture device '%s': %s\n", driver, snd_strerror(i)); + free(data); + return ALC_FALSE; + } + + format = -1; + switch(pDevice->FmtType) + { + case DevFmtByte: + format = SND_PCM_FORMAT_S8; + break; + case DevFmtUByte: + format = SND_PCM_FORMAT_U8; + break; + case DevFmtShort: + format = SND_PCM_FORMAT_S16; + break; + case DevFmtUShort: + format = SND_PCM_FORMAT_U16; + break; + case DevFmtFloat: + format = SND_PCM_FORMAT_FLOAT; + break; + } + + err = NULL; + bufferSizeInFrames = pDevice->UpdateSize * pDevice->NumUpdates; + snd_pcm_hw_params_malloc(&p); + + if((i=snd_pcm_hw_params_any(data->pcmHandle, p)) < 0) + err = "any"; + /* set interleaved access */ + if(i >= 0 && (i=snd_pcm_hw_params_set_access(data->pcmHandle, p, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + err = "set access"; + /* set format (implicitly sets sample bits) */ + if(i >= 0 && (i=snd_pcm_hw_params_set_format(data->pcmHandle, p, format)) < 0) + err = "set format"; + /* set channels (implicitly sets frame bits) */ + if(i >= 0 && (i=snd_pcm_hw_params_set_channels(data->pcmHandle, p, ChannelsFromDevFmt(pDevice->FmtChans))) < 0) + err = "set channels"; + /* set rate (implicitly constrains period/buffer parameters) */ + if(i >= 0 && (i=snd_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(i >= 0 && (i=snd_pcm_hw_params_set_buffer_size_near(data->pcmHandle, p, &bufferSizeInFrames)) < 0) + err = "set buffer size near"; + /* install and prepare hardware configuration */ + if(i >= 0 && (i=snd_pcm_hw_params(data->pcmHandle, p)) < 0) + err = "set params"; + if(i < 0) + { + ERR("%s failed: %s\n", err, snd_strerror(i)); + snd_pcm_hw_params_free(p); + goto error; + } + + if((i=snd_pcm_hw_params_get_period_size(p, &bufferSizeInFrames, NULL)) < 0) + { + ERR("get size failed: %s\n", snd_strerror(i)); + snd_pcm_hw_params_free(p); + goto error; + } + + snd_pcm_hw_params_free(p); + + frameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + + data->ring = CreateRingBuffer(frameSize, pDevice->UpdateSize*pDevice->NumUpdates); + if(!data->ring) + { + ERR("ring buffer create failed\n"); + goto error; + } + + data->size = snd_pcm_frames_to_bytes(data->pcmHandle, bufferSizeInFrames); + data->buffer = malloc(data->size); + if(!data->buffer) + { + ERR("buffer malloc failed\n"); + goto error; + } + + pDevice->szDeviceName = strdup(deviceName); + + pDevice->ExtraData = data; + return ALC_TRUE; + +error: + free(data->buffer); + DestroyRingBuffer(data->ring); + snd_pcm_close(data->pcmHandle); + free(data); + + pDevice->ExtraData = NULL; + return ALC_FALSE; +} + +static void alsa_close_capture(ALCdevice *pDevice) +{ + alsa_data *data = (alsa_data*)pDevice->ExtraData; + + snd_pcm_close(data->pcmHandle); + DestroyRingBuffer(data->ring); + + free(data->buffer); + free(data); + pDevice->ExtraData = NULL; +} + +static void alsa_start_capture(ALCdevice *Device) +{ + alsa_data *data = (alsa_data*)Device->ExtraData; + int err; + + err = snd_pcm_start(data->pcmHandle); + if(err < 0) + { + ERR("start failed: %s\n", snd_strerror(err)); + aluHandleDisconnect(Device); + } + else + data->doCapture = AL_TRUE; +} + +static void alsa_stop_capture(ALCdevice *Device) +{ + alsa_data *data = (alsa_data*)Device->ExtraData; + snd_pcm_drain(data->pcmHandle); + data->doCapture = AL_FALSE; +} + +static ALCuint alsa_available_samples(ALCdevice *Device) +{ + alsa_data *data = (alsa_data*)Device->ExtraData; + snd_pcm_sframes_t avail; + + avail = (Device->Connected ? snd_pcm_avail_update(data->pcmHandle) : 0); + if(avail < 0) + { + ERR("avail update failed: %s\n", snd_strerror(avail)); + + if((avail=snd_pcm_recover(data->pcmHandle, avail, 1)) >= 0) + { + if(data->doCapture) + avail = snd_pcm_start(data->pcmHandle); + if(avail >= 0) + avail = snd_pcm_avail_update(data->pcmHandle); + } + if(avail < 0) + { + ERR("restore error: %s\n", snd_strerror(avail)); + aluHandleDisconnect(Device); + } + } + while(avail > 0) + { + snd_pcm_sframes_t amt; + + amt = snd_pcm_bytes_to_frames(data->pcmHandle, data->size); + if(avail < amt) amt = avail; + + amt = snd_pcm_readi(data->pcmHandle, data->buffer, amt); + if(amt < 0) + { + ERR("read error: %s\n", snd_strerror(amt)); + + if(amt == -EAGAIN) + continue; + if((amt=snd_pcm_recover(data->pcmHandle, amt, 1)) >= 0) + { + if(data->doCapture) + amt = snd_pcm_start(data->pcmHandle); + if(amt >= 0) + amt = snd_pcm_avail_update(data->pcmHandle); + } + if(amt < 0) + { + ERR("restore error: %s\n", snd_strerror(amt)); + aluHandleDisconnect(Device); + break; + } + avail = amt; + continue; + } + + WriteRingBuffer(data->ring, data->buffer, amt); + avail -= amt; + } + + return RingBufferSize(data->ring); +} + +static void alsa_capture_samples(ALCdevice *Device, ALCvoid *Buffer, ALCuint Samples) +{ + alsa_data *data = (alsa_data*)Device->ExtraData; + + if(Samples <= alsa_available_samples(Device)) + ReadRingBuffer(data->ring, Buffer, Samples); + else + alcSetError(Device, ALC_INVALID_VALUE); +} + + +static const BackendFuncs alsa_funcs = { + alsa_open_playback, + alsa_close_playback, + alsa_reset_playback, + alsa_stop_playback, + alsa_open_capture, + alsa_close_capture, + alsa_start_capture, + alsa_stop_capture, + alsa_capture_samples, + alsa_available_samples +}; + +ALCboolean alc_alsa_init(BackendFuncs *func_list) +{ + if(!alsa_load()) + return ALC_FALSE; + device_prefix = GetConfigValue("alsa", "device-prefix", "plughw:"); + capture_prefix = GetConfigValue("alsa", "capture-prefix", "plughw:"); + *func_list = alsa_funcs; + return ALC_TRUE; +} + +void alc_alsa_deinit(void) +{ + ALuint i; + + for(i = 0;i < numDevNames;++i) + { + free(allDevNameMap[i].name); + free(allDevNameMap[i].card); + } + free(allDevNameMap); + allDevNameMap = NULL; + numDevNames = 0; + + for(i = 0;i < numCaptureDevNames;++i) + { + free(allCaptureDevNameMap[i].name); + free(allCaptureDevNameMap[i].card); + } + free(allCaptureDevNameMap); + allCaptureDevNameMap = NULL; + numCaptureDevNames = 0; + +#ifdef HAVE_DYNLOAD + if(alsa_handle) + CloseLib(alsa_handle); + alsa_handle = NULL; +#endif +} + +void alc_alsa_probe(enum DevProbe type) +{ + ALuint i; + + switch(type) + { + case DEVICE_PROBE: + AppendDeviceList(alsaDevice); + break; + + case ALL_DEVICE_PROBE: + for(i = 0;i < numDevNames;++i) + { + free(allDevNameMap[i].name); + free(allDevNameMap[i].card); + } + + free(allDevNameMap); + allDevNameMap = probe_devices(SND_PCM_STREAM_PLAYBACK, &numDevNames); + + for(i = 0;i < numDevNames;++i) + AppendAllDeviceList(allDevNameMap[i].name); + break; + + case CAPTURE_DEVICE_PROBE: + for(i = 0;i < numCaptureDevNames;++i) + { + free(allCaptureDevNameMap[i].name); + free(allCaptureDevNameMap[i].card); + } + + free(allCaptureDevNameMap); + allCaptureDevNameMap = probe_devices(SND_PCM_STREAM_CAPTURE, &numCaptureDevNames); + + for(i = 0;i < numCaptureDevNames;++i) + AppendCaptureDeviceList(allCaptureDevNameMap[i].name); + break; + } +} diff --git a/Alc/backends/coreaudio.c b/Alc/backends/coreaudio.c new file mode 100644 index 00000000..c84be846 --- /dev/null +++ b/Alc/backends/coreaudio.c @@ -0,0 +1,719 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + +#include <CoreServices/CoreServices.h> +#include <unistd.h> +#include <AudioUnit/AudioUnit.h> +#include <AudioToolbox/AudioToolbox.h> + + +typedef struct { + AudioUnit audioUnit; + + ALuint frameSize; + ALdouble sampleRateRatio; // Ratio of hardware sample rate / requested sample rate + AudioStreamBasicDescription format; // This is the OpenAL format as a CoreAudio ASBD + + AudioConverterRef audioConverter; // Sample rate converter if needed + AudioBufferList *bufferList; // Buffer for data coming from the input device + ALCvoid *resampleBuffer; // Buffer for returned RingBuffer data when resampling + + RingBuffer *ring; +} ca_data; + +static const ALCchar ca_device[] = "CoreAudio Default"; + + +static void destroy_buffer_list(AudioBufferList* list) +{ + if(list) + { + UInt32 i; + for(i = 0;i < list->mNumberBuffers;i++) + free(list->mBuffers[i].mData); + free(list); + } +} + +static AudioBufferList* allocate_buffer_list(UInt32 channelCount, UInt32 byteSize) +{ + AudioBufferList *list; + + list = calloc(1, sizeof(AudioBufferList) + sizeof(AudioBuffer)); + if(list) + { + list->mNumberBuffers = 1; + + list->mBuffers[0].mNumberChannels = channelCount; + list->mBuffers[0].mDataByteSize = byteSize; + list->mBuffers[0].mData = malloc(byteSize); + if(list->mBuffers[0].mData == NULL) + { + free(list); + list = NULL; + } + } + return list; +} + +static OSStatus ca_callback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) +{ + ALCdevice *device = (ALCdevice*)inRefCon; + ca_data *data = (ca_data*)device->ExtraData; + + aluMixData(device, ioData->mBuffers[0].mData, + ioData->mBuffers[0].mDataByteSize / data->frameSize); + + return noErr; +} + +static OSStatus ca_capture_conversion_callback(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, + AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void* inUserData) +{ + ALCdevice *device = (ALCdevice*)inUserData; + ca_data *data = (ca_data*)device->ExtraData; + + // Read from the ring buffer and store temporarily in a large buffer + ReadRingBuffer(data->ring, data->resampleBuffer, (ALsizei)(*ioNumberDataPackets)); + + // Set the input data + ioData->mNumberBuffers = 1; + ioData->mBuffers[0].mNumberChannels = data->format.mChannelsPerFrame; + ioData->mBuffers[0].mData = data->resampleBuffer; + ioData->mBuffers[0].mDataByteSize = (*ioNumberDataPackets) * data->format.mBytesPerFrame; + + return noErr; +} + +static OSStatus ca_capture_callback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, + UInt32 inNumberFrames, AudioBufferList *ioData) +{ + ALCdevice *device = (ALCdevice*)inRefCon; + ca_data *data = (ca_data*)device->ExtraData; + AudioUnitRenderActionFlags flags = 0; + OSStatus err; + + // fill the bufferList with data from the input device + err = AudioUnitRender(data->audioUnit, &flags, inTimeStamp, 1, inNumberFrames, data->bufferList); + if(err != noErr) + { + ERR("AudioUnitRender error: %d\n", err); + return err; + } + + WriteRingBuffer(data->ring, data->bufferList->mBuffers[0].mData, inNumberFrames); + + return noErr; +} + +static ALCboolean ca_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + ComponentDescription desc; + Component comp; + ca_data *data; + OSStatus err; + + if(!deviceName) + deviceName = ca_device; + else if(strcmp(deviceName, ca_device) != 0) + return ALC_FALSE; + + /* open the default output unit */ + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + comp = FindNextComponent(NULL, &desc); + if(comp == NULL) + { + ERR("FindNextComponent failed\n"); + return ALC_FALSE; + } + + data = calloc(1, sizeof(*data)); + device->ExtraData = data; + + err = OpenAComponent(comp, &data->audioUnit); + if(err != noErr) + { + ERR("OpenAComponent failed\n"); + free(data); + device->ExtraData = NULL; + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void ca_close_playback(ALCdevice *device) +{ + ca_data *data = (ca_data*)device->ExtraData; + + CloseComponent(data->audioUnit); + + free(data); + device->ExtraData = NULL; +} + +static ALCboolean ca_reset_playback(ALCdevice *device) +{ + ca_data *data = (ca_data*)device->ExtraData; + AudioStreamBasicDescription streamFormat; + AURenderCallbackStruct input; + OSStatus err; + UInt32 size; + + /* init and start the default audio unit... */ + err = AudioUnitInitialize(data->audioUnit); + if(err != noErr) + { + ERR("AudioUnitInitialize failed\n"); + return ALC_FALSE; + } + + err = AudioOutputUnitStart(data->audioUnit); + if(err != noErr) + { + ERR("AudioOutputUnitStart failed\n"); + return ALC_FALSE; + } + + /* retrieve default output unit's properties (output side) */ + size = sizeof(AudioStreamBasicDescription); + err = AudioUnitGetProperty(data->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamFormat, &size); + if(err != noErr || size != sizeof(AudioStreamBasicDescription)) + { + ERR("AudioUnitGetProperty failed\n"); + return ALC_FALSE; + } + +#if 0 + TRACE("Output streamFormat of default output unit -\n"); + TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket); + TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame); + TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel); + TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket); + TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame); + TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate); +#endif + + /* set default output unit's input side to match output side */ + err = AudioUnitSetProperty(data->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, size); + if(err != noErr) + { + ERR("AudioUnitSetProperty failed\n"); + return ALC_FALSE; + } + + if(device->Frequency != streamFormat.mSampleRate) + { + if((device->Flags&DEVICE_FREQUENCY_REQUEST)) + ERR("CoreAudio does not support changing sample rates (wanted %dhz, got %dhz)\n", device->Frequency, streamFormat.mSampleRate); + device->Flags &= ~DEVICE_FREQUENCY_REQUEST; + + device->UpdateSize = (ALuint)((ALuint64)device->UpdateSize * + streamFormat.mSampleRate / + device->Frequency); + device->Frequency = streamFormat.mSampleRate; + } + + /* FIXME: How to tell what channels are what in the output device, and how + * to specify what we're giving? eg, 6.0 vs 5.1 */ + switch(streamFormat.mChannelsPerFrame) + { + case 1: + if((device->Flags&DEVICE_CHANNELS_REQUEST) && + device->FmtChans != DevFmtMono) + { + ERR("Failed to set %s, got Mono instead\n", DevFmtChannelsString(device->FmtChans)); + device->Flags &= ~DEVICE_CHANNELS_REQUEST; + } + device->FmtChans = DevFmtMono; + break; + case 2: + if((device->Flags&DEVICE_CHANNELS_REQUEST) && + device->FmtChans != DevFmtStereo) + { + ERR("Failed to set %s, got Stereo instead\n", DevFmtChannelsString(device->FmtChans)); + device->Flags &= ~DEVICE_CHANNELS_REQUEST; + } + device->FmtChans = DevFmtStereo; + break; + case 4: + if((device->Flags&DEVICE_CHANNELS_REQUEST) && + device->FmtChans != DevFmtQuad) + { + ERR("Failed to set %s, got Quad instead\n", DevFmtChannelsString(device->FmtChans)); + device->Flags &= ~DEVICE_CHANNELS_REQUEST; + } + device->FmtChans = DevFmtQuad; + break; + case 6: + if((device->Flags&DEVICE_CHANNELS_REQUEST) && + device->FmtChans != DevFmtX51) + { + ERR("Failed to set %s, got 5.1 Surround instead\n", DevFmtChannelsString(device->FmtChans)); + device->Flags &= ~DEVICE_CHANNELS_REQUEST; + } + device->FmtChans = DevFmtX51; + break; + case 7: + if((device->Flags&DEVICE_CHANNELS_REQUEST) && + device->FmtChans != DevFmtX61) + { + ERR("Failed to set %s, got 6.1 Surround instead\n", DevFmtChannelsString(device->FmtChans)); + device->Flags &= ~DEVICE_CHANNELS_REQUEST; + } + device->FmtChans = DevFmtX61; + break; + case 8: + if((device->Flags&DEVICE_CHANNELS_REQUEST) && + device->FmtChans != DevFmtX71) + { + ERR("Failed to set %s, got 7.1 Surround instead\n", DevFmtChannelsString(device->FmtChans)); + device->Flags &= ~DEVICE_CHANNELS_REQUEST; + } + device->FmtChans = DevFmtX71; + break; + default: + ERR("Unhandled channel count (%d), using Stereo\n", streamFormat.mChannelsPerFrame); + device->Flags &= ~DEVICE_CHANNELS_REQUEST; + device->FmtChans = DevFmtStereo; + streamFormat.mChannelsPerFrame = 2; + break; + } + SetDefaultWFXChannelOrder(device); + + /* use channel count and sample rate from the default output unit's current + * parameters, but reset everything else */ + streamFormat.mFramesPerPacket = 1; + switch(device->FmtType) + { + case DevFmtUByte: + device->FmtType = DevFmtByte; + /* fall-through */ + case DevFmtByte: + streamFormat.mBitsPerChannel = 8; + streamFormat.mBytesPerPacket = streamFormat.mChannelsPerFrame; + streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame; + break; + case DevFmtUShort: + case DevFmtFloat: + device->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + streamFormat.mBitsPerChannel = 16; + streamFormat.mBytesPerPacket = 2 * streamFormat.mChannelsPerFrame; + streamFormat.mBytesPerFrame = 2 * streamFormat.mChannelsPerFrame; + break; + } + streamFormat.mFormatID = kAudioFormatLinearPCM; + streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | + kAudioFormatFlagsNativeEndian | + kLinearPCMFormatFlagIsPacked; + + err = AudioUnitSetProperty(data->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, sizeof(AudioStreamBasicDescription)); + if(err != noErr) + { + ERR("AudioUnitSetProperty failed\n"); + return ALC_FALSE; + } + + /* setup callback */ + data->frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType); + input.inputProc = ca_callback; + input.inputProcRefCon = device; + + err = AudioUnitSetProperty(data->audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct)); + if(err != noErr) + { + ERR("AudioUnitSetProperty failed\n"); + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void ca_stop_playback(ALCdevice *device) +{ + ca_data *data = (ca_data*)device->ExtraData; + OSStatus err; + + AudioOutputUnitStop(data->audioUnit); + err = AudioUnitUninitialize(data->audioUnit); + if(err != noErr) + ERR("-- AudioUnitUninitialize failed.\n"); +} + +static ALCboolean ca_open_capture(ALCdevice *device, const ALCchar *deviceName) +{ + AudioStreamBasicDescription requestedFormat; // The application requested format + AudioStreamBasicDescription hardwareFormat; // The hardware format + AudioStreamBasicDescription outputFormat; // The AudioUnit output format + AURenderCallbackStruct input; + ComponentDescription desc; + AudioDeviceID inputDevice; + UInt32 outputFrameCount; + UInt32 propertySize; + UInt32 enableIO; + Component comp; + ca_data *data; + OSStatus err; + + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_HALOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + // Search for component with given description + comp = FindNextComponent(NULL, &desc); + if(comp == NULL) + { + ERR("FindNextComponent failed\n"); + return ALC_FALSE; + } + + data = calloc(1, sizeof(*data)); + device->ExtraData = data; + + // Open the component + err = OpenAComponent(comp, &data->audioUnit); + if(err != noErr) + { + ERR("OpenAComponent failed\n"); + goto error; + } + + // Turn off AudioUnit output + enableIO = 0; + err = AudioUnitSetProperty(data->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &enableIO, sizeof(ALuint)); + if(err != noErr) + { + ERR("AudioUnitSetProperty failed\n"); + goto error; + } + + // Turn on AudioUnit input + enableIO = 1; + err = AudioUnitSetProperty(data->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &enableIO, sizeof(ALuint)); + if(err != noErr) + { + ERR("AudioUnitSetProperty failed\n"); + goto error; + } + + // Get the default input device + propertySize = sizeof(AudioDeviceID); + err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &propertySize, &inputDevice); + if(err != noErr) + { + ERR("AudioHardwareGetProperty failed\n"); + goto error; + } + + if(inputDevice == kAudioDeviceUnknown) + { + ERR("No input device found\n"); + goto error; + } + + // Track the input device + err = AudioUnitSetProperty(data->audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID)); + if(err != noErr) + { + ERR("AudioUnitSetProperty failed\n"); + goto error; + } + + // set capture callback + input.inputProc = ca_capture_callback; + input.inputProcRefCon = device; + + err = AudioUnitSetProperty(data->audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct)); + if(err != noErr) + { + ERR("AudioUnitSetProperty failed\n"); + goto error; + } + + // Initialize the device + err = AudioUnitInitialize(data->audioUnit); + if(err != noErr) + { + ERR("AudioUnitInitialize failed\n"); + goto error; + } + + // Get the hardware format + propertySize = sizeof(AudioStreamBasicDescription); + err = AudioUnitGetProperty(data->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1, &hardwareFormat, &propertySize); + if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription)) + { + ERR("AudioUnitGetProperty failed\n"); + goto error; + } + + // Set up the requested format description + switch(device->FmtType) + { + case DevFmtUByte: + requestedFormat.mBitsPerChannel = 8; + requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked; + break; + case DevFmtShort: + requestedFormat.mBitsPerChannel = 16; + requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; + break; + case DevFmtFloat: + requestedFormat.mBitsPerChannel = 32; + requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked; + break; + case DevFmtByte: + case DevFmtUShort: + ERR("%s samples not supported\n", DevFmtTypeString(device->FmtType)); + goto error; + } + + switch(device->FmtChans) + { + case DevFmtMono: + requestedFormat.mChannelsPerFrame = 1; + break; + case DevFmtStereo: + requestedFormat.mChannelsPerFrame = 2; + break; + + case DevFmtQuad: + case DevFmtX51: + case DevFmtX51Side: + case DevFmtX61: + case DevFmtX71: + ERR("%s not supported\n", DevFmtChannelsString(device->FmtChans)); + goto error; + } + + requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8; + requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame; + requestedFormat.mSampleRate = device->Frequency; + requestedFormat.mFormatID = kAudioFormatLinearPCM; + requestedFormat.mReserved = 0; + requestedFormat.mFramesPerPacket = 1; + + // save requested format description for later use + data->format = requestedFormat; + data->frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType); + + // Use intermediate format for sample rate conversion (outputFormat) + // Set sample rate to the same as hardware for resampling later + outputFormat = requestedFormat; + outputFormat.mSampleRate = hardwareFormat.mSampleRate; + + // Determine sample rate ratio for resampling + data->sampleRateRatio = outputFormat.mSampleRate / device->Frequency; + + // The output format should be the requested format, but using the hardware sample rate + // This is because the AudioUnit will automatically scale other properties, except for sample rate + err = AudioUnitSetProperty(data->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, (void *)&outputFormat, sizeof(outputFormat)); + if(err != noErr) + { + ERR("AudioUnitSetProperty failed\n"); + goto error; + } + + // Set the AudioUnit output format frame count + outputFrameCount = device->UpdateSize * data->sampleRateRatio; + err = AudioUnitSetProperty(data->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Output, 0, &outputFrameCount, sizeof(outputFrameCount)); + if(err != noErr) + { + ERR("AudioUnitSetProperty failed: %d\n", err); + goto error; + } + + // Set up sample converter + err = AudioConverterNew(&outputFormat, &requestedFormat, &data->audioConverter); + if(err != noErr) + { + ERR("AudioConverterNew failed: %d\n", err); + goto error; + } + + // Create a buffer for use in the resample callback + data->resampleBuffer = malloc(device->UpdateSize * data->frameSize * data->sampleRateRatio); + + // Allocate buffer for the AudioUnit output + data->bufferList = allocate_buffer_list(outputFormat.mChannelsPerFrame, device->UpdateSize * data->frameSize * data->sampleRateRatio); + if(data->bufferList == NULL) + { + alcSetError(device, ALC_OUT_OF_MEMORY); + goto error; + } + + data->ring = CreateRingBuffer(data->frameSize, (device->UpdateSize * data->sampleRateRatio) * device->NumUpdates); + if(data->ring == NULL) + { + alcSetError(device, ALC_OUT_OF_MEMORY); + goto error; + } + + return ALC_TRUE; + +error: + DestroyRingBuffer(data->ring); + free(data->resampleBuffer); + destroy_buffer_list(data->bufferList); + + if(data->audioConverter) + AudioConverterDispose(data->audioConverter); + if(data->audioUnit) + CloseComponent(data->audioUnit); + + free(data); + device->ExtraData = NULL; + + return ALC_FALSE; +} + +static void ca_close_capture(ALCdevice *device) +{ + ca_data *data = (ca_data*)device->ExtraData; + + DestroyRingBuffer(data->ring); + free(data->resampleBuffer); + destroy_buffer_list(data->bufferList); + + AudioConverterDispose(data->audioConverter); + CloseComponent(data->audioUnit); + + free(data); + device->ExtraData = NULL; +} + +static void ca_start_capture(ALCdevice *device) +{ + ca_data *data = (ca_data*)device->ExtraData; + OSStatus err = AudioOutputUnitStart(data->audioUnit); + if(err != noErr) + ERR("AudioOutputUnitStart failed\n"); +} + +static void ca_stop_capture(ALCdevice *device) +{ + ca_data *data = (ca_data*)device->ExtraData; + OSStatus err = AudioOutputUnitStop(data->audioUnit); + if(err != noErr) + ERR("AudioOutputUnitStop failed\n"); +} + +static ALCuint ca_available_samples(ALCdevice *device) +{ + ca_data *data = device->ExtraData; + return RingBufferSize(data->ring) / data->sampleRateRatio; +} + +static void ca_capture_samples(ALCdevice *device, ALCvoid *buffer, ALCuint samples) +{ + ca_data *data = (ca_data*)device->ExtraData; + + if(samples <= ca_available_samples(device)) + { + AudioBufferList *list; + UInt32 frameCount; + OSStatus err; + + // If no samples are requested, just return + if(samples == 0) + return; + + // Allocate a temporary AudioBufferList to use as the return resamples data + list = alloca(sizeof(AudioBufferList) + sizeof(AudioBuffer)); + + // Point the resampling buffer to the capture buffer + list->mNumberBuffers = 1; + list->mBuffers[0].mNumberChannels = data->format.mChannelsPerFrame; + list->mBuffers[0].mDataByteSize = samples * data->frameSize; + list->mBuffers[0].mData = buffer; + + // Resample into another AudioBufferList + frameCount = samples; + err = AudioConverterFillComplexBuffer(data->audioConverter, ca_capture_conversion_callback, device, + &frameCount, list, NULL); + if(err != noErr) + { + ERR("AudioConverterFillComplexBuffer error: %d\n", err); + alcSetError(device, ALC_INVALID_VALUE); + } + } + else + alcSetError(device, ALC_INVALID_VALUE); +} + +static const BackendFuncs ca_funcs = { + ca_open_playback, + ca_close_playback, + ca_reset_playback, + ca_stop_playback, + ca_open_capture, + ca_close_capture, + ca_start_capture, + ca_stop_capture, + ca_capture_samples, + ca_available_samples +}; + +ALCboolean alc_ca_init(BackendFuncs *func_list) +{ + *func_list = ca_funcs; + return ALC_TRUE; +} + +void alc_ca_deinit(void) +{ +} + +void alc_ca_probe(enum DevProbe type) +{ + switch(type) + { + case DEVICE_PROBE: + AppendDeviceList(ca_device); + break; + case ALL_DEVICE_PROBE: + AppendAllDeviceList(ca_device); + break; + case CAPTURE_DEVICE_PROBE: + AppendCaptureDeviceList(ca_device); + break; + } +} diff --git a/Alc/backends/dsound.c b/Alc/backends/dsound.c new file mode 100644 index 00000000..08a1d13a --- /dev/null +++ b/Alc/backends/dsound.c @@ -0,0 +1,627 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#define _WIN32_WINNT 0x0500 +#include <stdlib.h> +#include <stdio.h> +#include <memory.h> + +#include <dsound.h> +#include <cguid.h> +#include <mmreg.h> +#ifndef _WAVEFORMATEXTENSIBLE_ +#include <ks.h> +#include <ksmedia.h> +#endif + +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + +#ifndef DSSPEAKER_5POINT1 +#define DSSPEAKER_5POINT1 6 +#endif +#ifndef DSSPEAKER_7POINT1 +#define DSSPEAKER_7POINT1 7 +#endif + +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); + + +static HMODULE ds_handle; +static HRESULT (WINAPI *pDirectSoundCreate)(LPCGUID pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter); +static HRESULT (WINAPI *pDirectSoundEnumerateA)(LPDSENUMCALLBACKA pDSEnumCallback, void *pContext); + +#define DirectSoundCreate pDirectSoundCreate +#define DirectSoundEnumerateA pDirectSoundEnumerateA + + +typedef struct { + // DirectSound Playback Device + IDirectSound *lpDS; + IDirectSoundBuffer *DSpbuffer; + IDirectSoundBuffer *DSsbuffer; + IDirectSoundNotify *DSnotify; + HANDLE hNotifyEvent; + + volatile int killNow; + ALvoid *thread; +} DSoundData; + + +typedef struct { + ALCchar *name; + GUID guid; +} DevMap; + +static const ALCchar dsDevice[] = "DirectSound Default"; +static DevMap *DeviceList; +static ALuint NumDevices; + +#define MAX_UPDATES 128 + +static ALCboolean DSoundLoad(void) +{ + ALCboolean ok = ALC_TRUE; + if(!ds_handle) + { + ds_handle = LoadLibraryA("dsound.dll"); + if(ds_handle == NULL) + { + ERR("Failed to load dsound.dll\n"); + return ALC_FALSE; + } + +#define LOAD_FUNC(x) do { \ + if((p##x = (void*)GetProcAddress(ds_handle, #x)) == NULL) { \ + ERR("Could not load %s from dsound.dll\n", #x); \ + ok = ALC_FALSE; \ + } \ +} while(0) + LOAD_FUNC(DirectSoundCreate); + LOAD_FUNC(DirectSoundEnumerateA); +#undef LOAD_FUNC + + if(!ok) + { + FreeLibrary(ds_handle); + ds_handle = NULL; + } + } + return ok; +} + + +static BOOL CALLBACK DSoundEnumDevices(LPGUID guid, LPCSTR desc, LPCSTR drvname, LPVOID data) +{ + char str[1024]; + void *temp; + int count; + ALuint i; + + (void)data; + (void)drvname; + + if(NumDevices == 0) + { + temp = realloc(DeviceList, sizeof(DevMap) * (NumDevices+1)); + if(temp) + { + DeviceList = temp; + DeviceList[NumDevices].name = strdup(dsDevice); + DeviceList[NumDevices].guid = GUID_NULL; + NumDevices++; + } + } + + if(!guid) + return TRUE; + + count = 0; + do { + if(count == 0) + snprintf(str, sizeof(str), "%s", desc); + else + snprintf(str, sizeof(str), "%s #%d", desc, count+1); + count++; + + for(i = 0;i < NumDevices;i++) + { + if(strcmp(str, DeviceList[i].name) == 0) + break; + } + } while(i != NumDevices); + + temp = realloc(DeviceList, sizeof(DevMap) * (NumDevices+1)); + if(temp) + { + DeviceList = temp; + DeviceList[NumDevices].name = strdup(str); + DeviceList[NumDevices].guid = *guid; + NumDevices++; + } + + return TRUE; +} + + +static ALuint DSoundProc(ALvoid *ptr) +{ + ALCdevice *pDevice = (ALCdevice*)ptr; + DSoundData *pData = (DSoundData*)pDevice->ExtraData; + DSBCAPS DSBCaps; + DWORD LastCursor = 0; + DWORD PlayCursor; + VOID *WritePtr1, *WritePtr2; + DWORD WriteCnt1, WriteCnt2; + BOOL Playing = FALSE; + DWORD FrameSize; + DWORD FragSize; + DWORD avail; + HRESULT err; + + SetRTPriority(); + + memset(&DSBCaps, 0, sizeof(DSBCaps)); + DSBCaps.dwSize = sizeof(DSBCaps); + err = IDirectSoundBuffer_GetCaps(pData->DSsbuffer, &DSBCaps); + if(FAILED(err)) + { + ERR("Failed to get buffer caps: 0x%lx\n", err); + aluHandleDisconnect(pDevice); + return 1; + } + + FrameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + FragSize = pDevice->UpdateSize * FrameSize; + + IDirectSoundBuffer_GetCurrentPosition(pData->DSsbuffer, &LastCursor, NULL); + while(!pData->killNow) + { + // Get current play cursor + IDirectSoundBuffer_GetCurrentPosition(pData->DSsbuffer, &PlayCursor, NULL); + avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes; + + if(avail < FragSize) + { + if(!Playing) + { + err = IDirectSoundBuffer_Play(pData->DSsbuffer, 0, 0, DSBPLAY_LOOPING); + if(FAILED(err)) + { + ERR("Failed to play buffer: 0x%lx\n", err); + aluHandleDisconnect(pDevice); + return 1; + } + Playing = TRUE; + } + + avail = WaitForSingleObjectEx(pData->hNotifyEvent, 2000, FALSE); + if(avail != WAIT_OBJECT_0) + ERR("WaitForSingleObjectEx error: 0x%lx\n", avail); + continue; + } + avail -= avail%FragSize; + + // Lock output buffer + WriteCnt1 = 0; + WriteCnt2 = 0; + err = IDirectSoundBuffer_Lock(pData->DSsbuffer, LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0); + + // If the buffer is lost, restore it and lock + if(err == DSERR_BUFFERLOST) + { + WARN("Buffer lost, restoring...\n"); + err = IDirectSoundBuffer_Restore(pData->DSsbuffer); + if(SUCCEEDED(err)) + { + Playing = FALSE; + LastCursor = 0; + err = IDirectSoundBuffer_Lock(pData->DSsbuffer, 0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0); + } + } + + // Successfully locked the output buffer + if(SUCCEEDED(err)) + { + // If we have an active context, mix data directly into output buffer otherwise fill with silence + aluMixData(pDevice, WritePtr1, WriteCnt1/FrameSize); + aluMixData(pDevice, WritePtr2, WriteCnt2/FrameSize); + + // Unlock output buffer only when successfully locked + IDirectSoundBuffer_Unlock(pData->DSsbuffer, WritePtr1, WriteCnt1, WritePtr2, WriteCnt2); + } + else + { + ERR("Buffer lock error: %#lx\n", err); + aluHandleDisconnect(pDevice); + return 1; + } + + // Update old write cursor location + LastCursor += WriteCnt1+WriteCnt2; + LastCursor %= DSBCaps.dwBufferBytes; + } + + return 0; +} + +static ALCboolean DSoundOpenPlayback(ALCdevice *device, const ALCchar *deviceName) +{ + DSoundData *pData = NULL; + LPGUID guid = NULL; + HRESULT hr; + + if(!deviceName) + deviceName = dsDevice; + else if(strcmp(deviceName, dsDevice) != 0) + { + ALuint i; + + if(!DeviceList) + { + hr = DirectSoundEnumerateA(DSoundEnumDevices, NULL); + if(FAILED(hr)) + ERR("Error enumerating DirectSound devices (%#x)!\n", (unsigned int)hr); + } + + for(i = 0;i < NumDevices;i++) + { + if(strcmp(deviceName, DeviceList[i].name) == 0) + { + if(i > 0) + guid = &DeviceList[i].guid; + break; + } + } + if(i == NumDevices) + return ALC_FALSE; + } + + //Initialise requested device + pData = calloc(1, sizeof(DSoundData)); + if(!pData) + { + alcSetError(device, ALC_OUT_OF_MEMORY); + return ALC_FALSE; + } + + hr = DS_OK; + pData->hNotifyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if(pData->hNotifyEvent == NULL) + hr = E_FAIL; + + //DirectSound Init code + if(SUCCEEDED(hr)) + hr = DirectSoundCreate(guid, &pData->lpDS, NULL); + if(SUCCEEDED(hr)) + hr = IDirectSound_SetCooperativeLevel(pData->lpDS, GetForegroundWindow(), DSSCL_PRIORITY); + if(FAILED(hr)) + { + if(pData->lpDS) + IDirectSound_Release(pData->lpDS); + if(pData->hNotifyEvent) + CloseHandle(pData->hNotifyEvent); + free(pData); + ERR("Device init failed: 0x%08lx\n", hr); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + device->ExtraData = pData; + return ALC_TRUE; +} + +static void DSoundClosePlayback(ALCdevice *device) +{ + DSoundData *pData = device->ExtraData; + + IDirectSound_Release(pData->lpDS); + CloseHandle(pData->hNotifyEvent); + free(pData); + device->ExtraData = NULL; +} + +static ALCboolean DSoundResetPlayback(ALCdevice *device) +{ + DSoundData *pData = (DSoundData*)device->ExtraData; + DSBUFFERDESC DSBDescription; + WAVEFORMATEXTENSIBLE OutputType; + DWORD speakers; + HRESULT hr; + + memset(&OutputType, 0, sizeof(OutputType)); + + switch(device->FmtType) + { + case DevFmtByte: + device->FmtType = DevFmtUByte; + break; + case DevFmtUShort: + device->FmtType = DevFmtShort; + break; + case DevFmtUByte: + case DevFmtShort: + case DevFmtFloat: + break; + } + + hr = IDirectSound_GetSpeakerConfig(pData->lpDS, &speakers); + if(SUCCEEDED(hr)) + { + if(!(device->Flags&DEVICE_CHANNELS_REQUEST)) + { + speakers = DSSPEAKER_CONFIG(speakers); + if(speakers == DSSPEAKER_MONO) + device->FmtChans = DevFmtMono; + else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE) + device->FmtChans = DevFmtStereo; + else if(speakers == DSSPEAKER_QUAD) + device->FmtChans = DevFmtQuad; + else if(speakers == DSSPEAKER_5POINT1) + device->FmtChans = DevFmtX51; + else if(speakers == DSSPEAKER_7POINT1) + device->FmtChans = DevFmtX71; + else + ERR("Unknown system speaker config: 0x%lx\n", speakers); + } + + switch(device->FmtChans) + { + case DevFmtMono: + OutputType.dwChannelMask = SPEAKER_FRONT_CENTER; + break; + case DevFmtStereo: + OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT; + break; + case DevFmtQuad: + OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT; + break; + case DevFmtX51: + OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT; + break; + case DevFmtX51Side: + OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_SIDE_LEFT | + SPEAKER_SIDE_RIGHT; + break; + case DevFmtX61: + OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_CENTER | + SPEAKER_SIDE_LEFT | + SPEAKER_SIDE_RIGHT; + break; + case DevFmtX71: + OutputType.dwChannelMask = SPEAKER_FRONT_LEFT | + SPEAKER_FRONT_RIGHT | + SPEAKER_FRONT_CENTER | + SPEAKER_LOW_FREQUENCY | + SPEAKER_BACK_LEFT | + SPEAKER_BACK_RIGHT | + SPEAKER_SIDE_LEFT | + SPEAKER_SIDE_RIGHT; + break; + } + + OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; + OutputType.Format.nChannels = ChannelsFromDevFmt(device->FmtChans); + OutputType.Format.wBitsPerSample = BytesFromDevFmt(device->FmtType) * 8; + OutputType.Format.nBlockAlign = OutputType.Format.nChannels*OutputType.Format.wBitsPerSample/8; + OutputType.Format.nSamplesPerSec = device->Frequency; + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec*OutputType.Format.nBlockAlign; + OutputType.Format.cbSize = 0; + } + + if(OutputType.Format.nChannels > 2 || device->FmtType == DevFmtFloat) + { + OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + if(device->FmtType == DevFmtFloat) + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + else + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + else + { + if(SUCCEEDED(hr)) + { + memset(&DSBDescription,0,sizeof(DSBUFFERDESC)); + DSBDescription.dwSize=sizeof(DSBUFFERDESC); + DSBDescription.dwFlags=DSBCAPS_PRIMARYBUFFER; + hr = IDirectSound_CreateSoundBuffer(pData->lpDS, &DSBDescription, &pData->DSpbuffer, NULL); + } + if(SUCCEEDED(hr)) + hr = IDirectSoundBuffer_SetFormat(pData->DSpbuffer,&OutputType.Format); + } + + if(SUCCEEDED(hr)) + { + if(device->NumUpdates > MAX_UPDATES) + { + device->UpdateSize = (device->UpdateSize*device->NumUpdates + + MAX_UPDATES-1) / MAX_UPDATES; + device->NumUpdates = MAX_UPDATES; + } + + memset(&DSBDescription,0,sizeof(DSBUFFERDESC)); + DSBDescription.dwSize=sizeof(DSBUFFERDESC); + DSBDescription.dwFlags=DSBCAPS_CTRLPOSITIONNOTIFY|DSBCAPS_GETCURRENTPOSITION2|DSBCAPS_GLOBALFOCUS; + DSBDescription.dwBufferBytes=device->UpdateSize * device->NumUpdates * + OutputType.Format.nBlockAlign; + DSBDescription.lpwfxFormat=&OutputType.Format; + hr = IDirectSound_CreateSoundBuffer(pData->lpDS, &DSBDescription, &pData->DSsbuffer, NULL); + } + + if(SUCCEEDED(hr)) + { + hr = IDirectSoundBuffer_QueryInterface(pData->DSsbuffer, &IID_IDirectSoundNotify, (LPVOID *)&pData->DSnotify); + if(SUCCEEDED(hr)) + { + DSBPOSITIONNOTIFY notifies[MAX_UPDATES]; + ALuint i; + + for(i = 0;i < device->NumUpdates;++i) + { + notifies[i].dwOffset = i * device->UpdateSize * + OutputType.Format.nBlockAlign; + notifies[i].hEventNotify = pData->hNotifyEvent; + } + if(IDirectSoundNotify_SetNotificationPositions(pData->DSnotify, device->NumUpdates, notifies) != DS_OK) + hr = E_FAIL; + } + } + + if(SUCCEEDED(hr)) + { + ResetEvent(pData->hNotifyEvent); + SetDefaultWFXChannelOrder(device); + pData->thread = StartThread(DSoundProc, device); + if(pData->thread == NULL) + hr = E_FAIL; + } + + if(FAILED(hr)) + { + if(pData->DSnotify != NULL) + IDirectSoundNotify_Release(pData->DSnotify); + pData->DSnotify = NULL; + if(pData->DSsbuffer != NULL) + IDirectSoundBuffer_Release(pData->DSsbuffer); + pData->DSsbuffer = NULL; + if(pData->DSpbuffer != NULL) + IDirectSoundBuffer_Release(pData->DSpbuffer); + pData->DSpbuffer = NULL; + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void DSoundStopPlayback(ALCdevice *device) +{ + DSoundData *pData = device->ExtraData; + + if(!pData->thread) + return; + + pData->killNow = 1; + StopThread(pData->thread); + pData->thread = NULL; + + pData->killNow = 0; + + IDirectSoundNotify_Release(pData->DSnotify); + pData->DSnotify = NULL; + IDirectSoundBuffer_Release(pData->DSsbuffer); + pData->DSsbuffer = NULL; + if(pData->DSpbuffer != NULL) + IDirectSoundBuffer_Release(pData->DSpbuffer); + pData->DSpbuffer = NULL; +} + + +static const BackendFuncs DSoundFuncs = { + DSoundOpenPlayback, + DSoundClosePlayback, + DSoundResetPlayback, + DSoundStopPlayback, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + + +ALCboolean alcDSoundInit(BackendFuncs *FuncList) +{ + if(!DSoundLoad()) + return ALC_FALSE; + *FuncList = DSoundFuncs; + return ALC_TRUE; +} + +void alcDSoundDeinit(void) +{ + ALuint i; + + for(i = 0;i < NumDevices;++i) + free(DeviceList[i].name); + free(DeviceList); + DeviceList = NULL; + NumDevices = 0; + + if(ds_handle) + FreeLibrary(ds_handle); + ds_handle = NULL; +} + +void alcDSoundProbe(enum DevProbe type) +{ + HRESULT hr; + ALuint i; + + switch(type) + { + case DEVICE_PROBE: + AppendDeviceList(dsDevice); + break; + + case ALL_DEVICE_PROBE: + for(i = 0;i < NumDevices;++i) + free(DeviceList[i].name); + free(DeviceList); + DeviceList = NULL; + NumDevices = 0; + + hr = DirectSoundEnumerateA(DSoundEnumDevices, NULL); + if(FAILED(hr)) + ERR("Error enumerating DirectSound devices (%#x)!\n", (unsigned int)hr); + else + { + for(i = 0;i < NumDevices;i++) + AppendAllDeviceList(DeviceList[i].name); + } + break; + + case CAPTURE_DEVICE_PROBE: + break; + } +} diff --git a/Alc/backends/loopback.c b/Alc/backends/loopback.c new file mode 100644 index 00000000..86e53555 --- /dev/null +++ b/Alc/backends/loopback.c @@ -0,0 +1,77 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2011 by Chris Robinson + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include <stdlib.h> +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + + +static ALCboolean loopback_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + device->szDeviceName = strdup(deviceName); + return ALC_TRUE; +} + +static void loopback_close_playback(ALCdevice *device) +{ + (void)device; +} + +static ALCboolean loopback_reset_playback(ALCdevice *device) +{ + SetDefaultWFXChannelOrder(device); + return ALC_TRUE; +} + +static void loopback_stop_playback(ALCdevice *device) +{ + (void)device; +} + +static const BackendFuncs loopback_funcs = { + loopback_open_playback, + loopback_close_playback, + loopback_reset_playback, + loopback_stop_playback, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +ALCboolean alc_loopback_init(BackendFuncs *func_list) +{ + *func_list = loopback_funcs; + return ALC_TRUE; +} + +void alc_loopback_deinit(void) +{ +} + +void alc_loopback_probe(enum DevProbe type) +{ + (void)type; +} diff --git a/Alc/backends/mmdevapi.c b/Alc/backends/mmdevapi.c new file mode 100644 index 00000000..702569c6 --- /dev/null +++ b/Alc/backends/mmdevapi.c @@ -0,0 +1,775 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2011 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#define COBJMACROS +#define _WIN32_WINNT 0x0500 +#include <stdlib.h> +#include <stdio.h> +#include <memory.h> + +#include <mmdeviceapi.h> +#include <audioclient.h> +#include <cguid.h> +#include <mmreg.h> +#ifndef _WAVEFORMATEXTENSIBLE_ +#include <ks.h> +#include <ksmedia.h> +#endif + +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + + +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); + +#define MONO SPEAKER_FRONT_CENTER +#define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT) +#define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) +#define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) +#define X5DOT1SIDE (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) +#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT) + + +typedef struct { + IMMDevice *mmdev; + IAudioClient *client; + HANDLE hNotifyEvent; + + HANDLE MsgEvent; + + volatile int killNow; + ALvoid *thread; +} MMDevApiData; + + +static const ALCchar mmDevice[] = "WASAPI Default"; + + +static HANDLE ThreadHdl; +static DWORD ThreadID; + +typedef struct { + HANDLE FinishedEvt; + HRESULT result; +} ThreadRequest; + +#define WM_USER_OpenDevice (WM_USER+0) +#define WM_USER_ResetDevice (WM_USER+1) +#define WM_USER_StopDevice (WM_USER+2) +#define WM_USER_CloseDevice (WM_USER+3) + +static HRESULT WaitForResponse(ThreadRequest *req) +{ + if(WaitForSingleObject(req->FinishedEvt, INFINITE) == WAIT_OBJECT_0) + return req->result; + ERR("Message response error: %lu\n", GetLastError()); + return E_FAIL; +} + + +static ALuint MMDevApiProc(ALvoid *ptr) +{ + ALCdevice *device = ptr; + MMDevApiData *data = device->ExtraData; + union { + IAudioRenderClient *iface; + void *ptr; + } render; + UINT32 written, len; + BYTE *buffer; + HRESULT hr; + + hr = CoInitialize(NULL); + if(FAILED(hr)) + { + ERR("CoInitialize(NULL) failed: 0x%08lx\n", hr); + aluHandleDisconnect(device); + return 0; + } + + hr = IAudioClient_GetService(data->client, &IID_IAudioRenderClient, &render.ptr); + if(FAILED(hr)) + { + ERR("Failed to get AudioRenderClient service: 0x%08lx\n", hr); + aluHandleDisconnect(device); + return 0; + } + + SetRTPriority(); + + while(!data->killNow) + { + hr = IAudioClient_GetCurrentPadding(data->client, &written); + if(FAILED(hr)) + { + ERR("Failed to get padding: 0x%08lx\n", hr); + aluHandleDisconnect(device); + break; + } + + len = device->UpdateSize*device->NumUpdates - written; + if(len < device->UpdateSize) + { + DWORD res; + res = WaitForSingleObjectEx(data->hNotifyEvent, 2000, FALSE); + if(res != WAIT_OBJECT_0) + ERR("WaitForSingleObjectEx error: 0x%lx\n", res); + continue; + } + len -= len%device->UpdateSize; + + hr = IAudioRenderClient_GetBuffer(render.iface, len, &buffer); + if(SUCCEEDED(hr)) + { + aluMixData(device, buffer, len); + hr = IAudioRenderClient_ReleaseBuffer(render.iface, len, 0); + } + if(FAILED(hr)) + { + ERR("Failed to buffer data: 0x%08lx\n", hr); + aluHandleDisconnect(device); + break; + } + } + + IAudioRenderClient_Release(render.iface); + + CoUninitialize(); + return 0; +} + + +static ALCboolean MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) +{ + memset(out, 0, sizeof(*out)); + if(in->wFormatTag == WAVE_FORMAT_EXTENSIBLE) + *out = *(WAVEFORMATEXTENSIBLE*)in; + else if(in->wFormatTag == WAVE_FORMAT_PCM) + { + out->Format = *in; + out->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + out->Format.cbSize = sizeof(*out) - sizeof(*in); + if(out->Format.nChannels == 1) + out->dwChannelMask = MONO; + else if(out->Format.nChannels == 2) + out->dwChannelMask = STEREO; + else + ERR("Unhandled PCM channel count: %d\n", out->Format.nChannels); + out->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + else if(in->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) + { + out->Format = *in; + out->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + out->Format.cbSize = sizeof(*out) - sizeof(*in); + if(out->Format.nChannels == 1) + out->dwChannelMask = MONO; + else if(out->Format.nChannels == 2) + out->dwChannelMask = STEREO; + else + ERR("Unhandled IEEE float channel count: %d\n", out->Format.nChannels); + out->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + } + else + { + ERR("Unhandled format tag: 0x%04x\n", in->wFormatTag); + return ALC_FALSE; + } + return ALC_TRUE; +} + +static HRESULT DoReset(ALCdevice *device) +{ + MMDevApiData *data = device->ExtraData; + WAVEFORMATEXTENSIBLE OutputType; + WAVEFORMATEX *wfx = NULL; + REFERENCE_TIME min_per; + UINT32 buffer_len, min_len; + HRESULT hr; + + hr = IAudioClient_GetMixFormat(data->client, &wfx); + if(FAILED(hr)) + { + ERR("Failed to get mix format: 0x%08lx\n", hr); + return hr; + } + + if(!MakeExtensible(&OutputType, wfx)) + { + CoTaskMemFree(wfx); + return E_FAIL; + } + CoTaskMemFree(wfx); + wfx = NULL; + + if(!(device->Flags&DEVICE_FREQUENCY_REQUEST)) + device->Frequency = OutputType.Format.nSamplesPerSec; + if(!(device->Flags&DEVICE_CHANNELS_REQUEST)) + { + if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO) + device->FmtChans = DevFmtMono; + else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO) + device->FmtChans = DevFmtStereo; + else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD) + device->FmtChans = DevFmtQuad; + else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1) + device->FmtChans = DevFmtX51; + else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1SIDE) + device->FmtChans = DevFmtX51Side; + else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1) + device->FmtChans = DevFmtX61; + else if(OutputType.Format.nChannels == 8 && OutputType.dwChannelMask == X7DOT1) + device->FmtChans = DevFmtX71; + else + ERR("Unhandled channel config: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask); + } + + switch(device->FmtChans) + { + case DevFmtMono: + OutputType.Format.nChannels = 1; + OutputType.dwChannelMask = MONO; + break; + case DevFmtStereo: + OutputType.Format.nChannels = 2; + OutputType.dwChannelMask = STEREO; + break; + case DevFmtQuad: + OutputType.Format.nChannels = 4; + OutputType.dwChannelMask = QUAD; + break; + case DevFmtX51: + OutputType.Format.nChannels = 6; + OutputType.dwChannelMask = X5DOT1; + break; + case DevFmtX51Side: + OutputType.Format.nChannels = 6; + OutputType.dwChannelMask = X5DOT1SIDE; + break; + case DevFmtX61: + OutputType.Format.nChannels = 7; + OutputType.dwChannelMask = X6DOT1; + break; + case DevFmtX71: + OutputType.Format.nChannels = 8; + OutputType.dwChannelMask = X7DOT1; + break; + } + switch(device->FmtType) + { + case DevFmtByte: + device->FmtType = DevFmtUByte; + /* fall-through */ + case DevFmtUByte: + OutputType.Format.wBitsPerSample = 8; + OutputType.Samples.wValidBitsPerSample = 8; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtUShort: + device->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + OutputType.Format.wBitsPerSample = 16; + OutputType.Samples.wValidBitsPerSample = 16; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + break; + case DevFmtFloat: + OutputType.Format.wBitsPerSample = 32; + OutputType.Samples.wValidBitsPerSample = 32; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + break; + } + OutputType.Format.nSamplesPerSec = device->Frequency; + + OutputType.Format.nBlockAlign = OutputType.Format.nChannels * + OutputType.Format.wBitsPerSample / 8; + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * + OutputType.Format.nBlockAlign; + + hr = IAudioClient_IsFormatSupported(data->client, AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); + if(FAILED(hr)) + { + ERR("Failed to check format support: 0x%08lx\n", hr); + hr = IAudioClient_GetMixFormat(data->client, &wfx); + } + if(FAILED(hr)) + { + ERR("Failed to find a supported format: 0x%08lx\n", hr); + return hr; + } + + if(wfx != NULL) + { + if(!MakeExtensible(&OutputType, wfx)) + { + CoTaskMemFree(wfx); + return E_FAIL; + } + CoTaskMemFree(wfx); + wfx = NULL; + + if(device->Frequency != OutputType.Format.nSamplesPerSec) + { + if((device->Flags&DEVICE_FREQUENCY_REQUEST)) + ERR("Failed to set %dhz, got %ldhz instead\n", device->Frequency, OutputType.Format.nSamplesPerSec); + device->Flags &= ~DEVICE_FREQUENCY_REQUEST; + device->Frequency = OutputType.Format.nSamplesPerSec; + } + + if(!((device->FmtChans == DevFmtMono && OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO) || + (device->FmtChans == DevFmtStereo && OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO) || + (device->FmtChans == DevFmtQuad && OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD) || + (device->FmtChans == DevFmtX51 && OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1) || + (device->FmtChans == DevFmtX51Side && OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1SIDE) || + (device->FmtChans == DevFmtX61 && OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1) || + (device->FmtChans == DevFmtX71 && OutputType.Format.nChannels == 8 && OutputType.dwChannelMask == X7DOT1))) + { + if((device->Flags&DEVICE_CHANNELS_REQUEST)) + ERR("Failed to set %s, got %d channels (0x%08lx) instead\n", DevFmtChannelsString(device->FmtChans), OutputType.Format.nChannels, OutputType.dwChannelMask); + device->Flags &= ~DEVICE_CHANNELS_REQUEST; + + if(OutputType.Format.nChannels == 1 && OutputType.dwChannelMask == MONO) + device->FmtChans = DevFmtMono; + else if(OutputType.Format.nChannels == 2 && OutputType.dwChannelMask == STEREO) + device->FmtChans = DevFmtStereo; + else if(OutputType.Format.nChannels == 4 && OutputType.dwChannelMask == QUAD) + device->FmtChans = DevFmtQuad; + else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1) + device->FmtChans = DevFmtX51; + else if(OutputType.Format.nChannels == 6 && OutputType.dwChannelMask == X5DOT1SIDE) + device->FmtChans = DevFmtX51Side; + else if(OutputType.Format.nChannels == 7 && OutputType.dwChannelMask == X6DOT1) + device->FmtChans = DevFmtX61; + else if(OutputType.Format.nChannels == 8 && OutputType.dwChannelMask == X7DOT1) + device->FmtChans = DevFmtX71; + else + { + ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, OutputType.dwChannelMask); + device->FmtChans = DevFmtStereo; + OutputType.Format.nChannels = 2; + OutputType.dwChannelMask = STEREO; + } + } + + if(IsEqualGUID(&OutputType.SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) + { + if(OutputType.Samples.wValidBitsPerSample == 0) + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + if(OutputType.Samples.wValidBitsPerSample != OutputType.Format.wBitsPerSample || + !((device->FmtType == DevFmtUByte && OutputType.Format.wBitsPerSample == 8) || + (device->FmtType == DevFmtShort && OutputType.Format.wBitsPerSample == 16))) + { + ERR("Failed to set %s samples, got %d/%d-bit instead\n", DevFmtTypeString(device->FmtType), OutputType.Samples.wValidBitsPerSample, OutputType.Format.wBitsPerSample); + if(OutputType.Format.wBitsPerSample == 8) + device->FmtType = DevFmtUByte; + else if(OutputType.Format.wBitsPerSample == 16) + device->FmtType = DevFmtShort; + else + { + device->FmtType = DevFmtShort; + OutputType.Format.wBitsPerSample = 16; + } + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + } + } + else if(IsEqualGUID(&OutputType.SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) + { + if(OutputType.Samples.wValidBitsPerSample == 0) + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + if(OutputType.Samples.wValidBitsPerSample != OutputType.Format.wBitsPerSample || + !((device->FmtType == DevFmtFloat && OutputType.Format.wBitsPerSample == 32))) + { + ERR("Failed to set %s samples, got %d/%d-bit instead\n", DevFmtTypeString(device->FmtType), OutputType.Samples.wValidBitsPerSample, OutputType.Format.wBitsPerSample); + if(OutputType.Format.wBitsPerSample != 32) + { + device->FmtType = DevFmtFloat; + OutputType.Format.wBitsPerSample = 32; + } + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + } + } + else + { + ERR("Unhandled format sub-type\n"); + device->FmtType = DevFmtShort; + OutputType.Format.wBitsPerSample = 16; + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + } + + SetDefaultWFXChannelOrder(device); + + hr = IAudioClient_GetDevicePeriod(data->client, &min_per, NULL); + if(SUCCEEDED(hr)) + { + min_len = (min_per*device->Frequency + 10000000-1) / 10000000; + if(min_len < device->UpdateSize) + min_len *= (device->UpdateSize + min_len/2)/min_len; + + device->NumUpdates = (device->NumUpdates*device->UpdateSize + min_len/2) / + min_len; + device->NumUpdates = maxu(device->NumUpdates, 2); + device->UpdateSize = min_len; + + hr = IAudioClient_Initialize(data->client, AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + ((REFERENCE_TIME)device->UpdateSize* + device->NumUpdates*10000000 + + device->Frequency-1) / device->Frequency, + 0, &OutputType.Format, NULL); + } + if(FAILED(hr)) + { + ERR("Failed to initialize audio client: 0x%08lx\n", hr); + return hr; + } + + hr = IAudioClient_GetBufferSize(data->client, &buffer_len); + if(FAILED(hr)) + { + ERR("Failed to get audio buffer info: 0x%08lx\n", hr); + return hr; + } + + device->NumUpdates = buffer_len / device->UpdateSize; + if(device->NumUpdates <= 1) + { + device->NumUpdates = 1; + ERR("Audio client returned buffer_len < period*2; expect break up\n"); + } + + ResetEvent(data->hNotifyEvent); + hr = IAudioClient_SetEventHandle(data->client, data->hNotifyEvent); + if(SUCCEEDED(hr)) + hr = IAudioClient_Start(data->client); + if(FAILED(hr)) + { + ERR("Failed to start audio client: 0x%08lx\n", hr); + return hr; + } + + data->thread = StartThread(MMDevApiProc, device); + if(!data->thread) + { + IAudioClient_Stop(data->client); + ERR("Failed to start thread\n"); + return E_FAIL; + } + + return hr; +} + + +static DWORD CALLBACK MessageProc(void *ptr) +{ + ThreadRequest *req = ptr; + IMMDeviceEnumerator *Enumerator; + MMDevApiData *data; + ALCdevice *device; + HRESULT hr; + MSG msg; + + TRACE("Starting message thread\n"); + + hr = CoInitialize(NULL); + if(FAILED(hr)) + { + WARN("Failed to initialize COM: 0x%08lx\n", hr); + req->result = hr; + SetEvent(req->FinishedEvt); + return 0; + } + + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &ptr); + if(FAILED(hr)) + { + WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); + CoUninitialize(); + req->result = hr; + SetEvent(req->FinishedEvt); + return 0; + } + Enumerator = ptr; + IMMDeviceEnumerator_Release(Enumerator); + Enumerator = NULL; + + req->result = S_OK; + SetEvent(req->FinishedEvt); + + TRACE("Starting message loop\n"); + while(GetMessage(&msg, NULL, 0, 0)) + { + TRACE("Got message %u\n", msg.message); + switch(msg.message) + { + case WM_USER_OpenDevice: + req = (ThreadRequest*)msg.wParam; + device = (ALCdevice*)msg.lParam; + data = device->ExtraData; + + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, &ptr); + if(SUCCEEDED(hr)) + { + Enumerator = ptr; + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(Enumerator, eRender, eMultimedia, &data->mmdev); + IMMDeviceEnumerator_Release(Enumerator); + Enumerator = NULL; + } + if(SUCCEEDED(hr)) + hr = IMMDevice_Activate(data->mmdev, &IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, &ptr); + if(SUCCEEDED(hr)) + data->client = ptr; + + if(FAILED(hr)) + { + if(data->mmdev) + IMMDevice_Release(data->mmdev); + data->mmdev = NULL; + } + + req->result = hr; + SetEvent(req->FinishedEvt); + continue; + + case WM_USER_ResetDevice: + req = (ThreadRequest*)msg.wParam; + device = (ALCdevice*)msg.lParam; + + req->result = DoReset(device); + SetEvent(req->FinishedEvt); + continue; + + case WM_USER_StopDevice: + req = (ThreadRequest*)msg.wParam; + device = (ALCdevice*)msg.lParam; + data = device->ExtraData; + + if(data->thread) + { + data->killNow = 1; + StopThread(data->thread); + data->thread = NULL; + + data->killNow = 0; + + IAudioClient_Stop(data->client); + } + + req->result = S_OK; + SetEvent(req->FinishedEvt); + continue; + + case WM_USER_CloseDevice: + req = (ThreadRequest*)msg.wParam; + device = (ALCdevice*)msg.lParam; + data = device->ExtraData; + + IAudioClient_Release(data->client); + data->client = NULL; + + IMMDevice_Release(data->mmdev); + data->mmdev = NULL; + + req->result = S_OK; + SetEvent(req->FinishedEvt); + continue; + + default: + ERR("Unexpected message: %u\n", msg.message); + continue; + } + } + TRACE("Message loop finished\n"); + + CoUninitialize(); + return 0; +} + + +static BOOL MMDevApiLoad(void) +{ + static HRESULT InitResult; + if(!ThreadHdl) + { + ThreadRequest req; + InitResult = E_FAIL; + + req.FinishedEvt = CreateEvent(NULL, FALSE, FALSE, NULL); + if(req.FinishedEvt == NULL) + ERR("Failed to create event: %lu\n", GetLastError()); + else + { + ThreadHdl = CreateThread(NULL, 0, MessageProc, &req, 0, &ThreadID); + if(ThreadHdl != NULL) + InitResult = WaitForResponse(&req); + CloseHandle(req.FinishedEvt); + } + } + return SUCCEEDED(InitResult); +} + + +static ALCboolean MMDevApiOpenPlayback(ALCdevice *device, const ALCchar *deviceName) +{ + MMDevApiData *data = NULL; + HRESULT hr; + + if(!deviceName) + deviceName = mmDevice; + else if(strcmp(deviceName, mmDevice) != 0) + return ALC_FALSE; + + //Initialise requested device + data = calloc(1, sizeof(MMDevApiData)); + if(!data) + { + alcSetError(device, ALC_OUT_OF_MEMORY); + return ALC_FALSE; + } + device->ExtraData = data; + + hr = S_OK; + data->hNotifyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + data->MsgEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if(data->hNotifyEvent == NULL || data->MsgEvent == NULL) + hr = E_FAIL; + + if(SUCCEEDED(hr)) + { + ThreadRequest req = { data->MsgEvent, 0 }; + + hr = E_FAIL; + if(PostThreadMessage(ThreadID, WM_USER_OpenDevice, (WPARAM)&req, (LPARAM)device)) + hr = WaitForResponse(&req); + } + + if(FAILED(hr)) + { + if(data->hNotifyEvent != NULL) + CloseHandle(data->hNotifyEvent); + data->hNotifyEvent = NULL; + if(data->MsgEvent != NULL) + CloseHandle(data->MsgEvent); + data->MsgEvent = NULL; + + free(data); + device->ExtraData = NULL; + + ERR("Device init failed: 0x%08lx\n", hr); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + return ALC_TRUE; +} + +static void MMDevApiClosePlayback(ALCdevice *device) +{ + MMDevApiData *data = device->ExtraData; + ThreadRequest req = { data->MsgEvent, 0 }; + + if(PostThreadMessage(ThreadID, WM_USER_CloseDevice, (WPARAM)&req, (LPARAM)device)) + (void)WaitForResponse(&req); + + CloseHandle(data->MsgEvent); + data->MsgEvent = NULL; + + CloseHandle(data->hNotifyEvent); + data->hNotifyEvent = NULL; + + free(data); + device->ExtraData = NULL; +} + +static ALCboolean MMDevApiResetPlayback(ALCdevice *device) +{ + MMDevApiData *data = device->ExtraData; + ThreadRequest req = { data->MsgEvent, 0 }; + HRESULT hr = E_FAIL; + + if(PostThreadMessage(ThreadID, WM_USER_ResetDevice, (WPARAM)&req, (LPARAM)device)) + hr = WaitForResponse(&req); + + return SUCCEEDED(hr) ? ALC_TRUE : ALC_FALSE; +} + +static void MMDevApiStopPlayback(ALCdevice *device) +{ + MMDevApiData *data = device->ExtraData; + ThreadRequest req = { data->MsgEvent, 0 }; + + if(PostThreadMessage(ThreadID, WM_USER_StopDevice, (WPARAM)&req, (LPARAM)device)) + (void)WaitForResponse(&req); +} + + +static const BackendFuncs MMDevApiFuncs = { + MMDevApiOpenPlayback, + MMDevApiClosePlayback, + MMDevApiResetPlayback, + MMDevApiStopPlayback, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + + +ALCboolean alcMMDevApiInit(BackendFuncs *FuncList) +{ + if(!MMDevApiLoad()) + return ALC_FALSE; + *FuncList = MMDevApiFuncs; + return ALC_TRUE; +} + +void alcMMDevApiDeinit(void) +{ + if(ThreadHdl) + { + TRACE("Sending WM_QUIT to Thread %04lx\n", ThreadID); + PostThreadMessage(ThreadID, WM_QUIT, 0, 0); + CloseHandle(ThreadHdl); + ThreadHdl = NULL; + } +} + +void alcMMDevApiProbe(enum DevProbe type) +{ + switch(type) + { + case DEVICE_PROBE: + AppendDeviceList(mmDevice); + break; + case ALL_DEVICE_PROBE: + AppendAllDeviceList(mmDevice); + break; + case CAPTURE_DEVICE_PROBE: + break; + } +} diff --git a/Alc/backends/null.c b/Alc/backends/null.c new file mode 100644 index 00000000..dd1ac216 --- /dev/null +++ b/Alc/backends/null.c @@ -0,0 +1,164 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2010 by Chris Robinson + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include <stdlib.h> +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + + +typedef struct { + volatile int killNow; + ALvoid *thread; +} null_data; + + +static const ALCchar nullDevice[] = "No Output"; + +static ALuint NullProc(ALvoid *ptr) +{ + ALCdevice *Device = (ALCdevice*)ptr; + null_data *data = (null_data*)Device->ExtraData; + ALuint now, start; + ALuint64 avail, done; + const ALuint restTime = (ALuint64)Device->UpdateSize * 1000 / + Device->Frequency / 2; + + done = 0; + start = timeGetTime(); + while(!data->killNow && Device->Connected) + { + now = timeGetTime(); + + avail = (ALuint64)(now-start) * Device->Frequency / 1000; + if(avail < done) + { + /* Timer wrapped. Add the remainder of the cycle to the available + * count and reset the number of samples done */ + avail += (ALuint64)0xFFFFFFFFu*Device->Frequency/1000 - done; + done = 0; + } + if(avail-done < Device->UpdateSize) + { + Sleep(restTime); + continue; + } + + while(avail-done >= Device->UpdateSize) + { + aluMixData(Device, NULL, Device->UpdateSize); + done += Device->UpdateSize; + } + } + + return 0; +} + +static ALCboolean null_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + null_data *data; + + if(!deviceName) + deviceName = nullDevice; + else if(strcmp(deviceName, nullDevice) != 0) + return ALC_FALSE; + + data = (null_data*)calloc(1, sizeof(*data)); + + device->szDeviceName = strdup(deviceName); + device->ExtraData = data; + return ALC_TRUE; +} + +static void null_close_playback(ALCdevice *device) +{ + null_data *data = (null_data*)device->ExtraData; + + free(data); + device->ExtraData = NULL; +} + +static ALCboolean null_reset_playback(ALCdevice *device) +{ + null_data *data = (null_data*)device->ExtraData; + + SetDefaultWFXChannelOrder(device); + + data->thread = StartThread(NullProc, device); + if(data->thread == NULL) + return ALC_FALSE; + + return ALC_TRUE; +} + +static void null_stop_playback(ALCdevice *device) +{ + null_data *data = (null_data*)device->ExtraData; + + if(!data->thread) + return; + + data->killNow = 1; + StopThread(data->thread); + data->thread = NULL; + + data->killNow = 0; +} + + +static const BackendFuncs null_funcs = { + null_open_playback, + null_close_playback, + null_reset_playback, + null_stop_playback, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +ALCboolean alc_null_init(BackendFuncs *func_list) +{ + *func_list = null_funcs; + return ALC_TRUE; +} + +void alc_null_deinit(void) +{ +} + +void alc_null_probe(enum DevProbe type) +{ + switch(type) + { + case DEVICE_PROBE: + AppendDeviceList(nullDevice); + break; + case ALL_DEVICE_PROBE: + AppendAllDeviceList(nullDevice); + break; + case CAPTURE_DEVICE_PROBE: + break; + } +} diff --git a/Alc/backends/opensl.c b/Alc/backends/opensl.c new file mode 100644 index 00000000..88d05505 --- /dev/null +++ b/Alc/backends/opensl.c @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This is an OpenAL backend for Android using the native audio APIs based on + * OpenSL ES 1.0.1. It is based on source code for the native-audio sample app + * bundled with NDK. + */ + +#include "config.h" + +#include <stdlib.h> +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + + +#include <SLES/OpenSLES.h> +#if 1 +#include <SLES/OpenSLES_Android.h> +#else +extern SLAPIENTRY const SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE; + +struct SLAndroidSimpleBufferQueueItf_; +typedef const struct SLAndroidSimpleBufferQueueItf_ * const * SLAndroidSimpleBufferQueueItf; + +typedef void (*slAndroidSimpleBufferQueueCallback)(SLAndroidSimpleBufferQueueItf caller, void *pContext); + +typedef struct SLAndroidSimpleBufferQueueState_ { + SLuint32 count; + SLuint32 index; +} SLAndroidSimpleBufferQueueState; + + +struct SLAndroidSimpleBufferQueueItf_ { + SLresult (*Enqueue) ( + SLAndroidSimpleBufferQueueItf self, + const void *pBuffer, + SLuint32 size + ); + SLresult (*Clear) ( + SLAndroidSimpleBufferQueueItf self + ); + SLresult (*GetState) ( + SLAndroidSimpleBufferQueueItf self, + SLAndroidSimpleBufferQueueState *pState + ); + SLresult (*RegisterCallback) ( + SLAndroidSimpleBufferQueueItf self, + slAndroidSimpleBufferQueueCallback callback, + void* pContext + ); +}; + +#define SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE ((SLuint32) 0x800007BD) + +typedef struct SLDataLocator_AndroidSimpleBufferQueue { + SLuint32 locatorType; + SLuint32 numBuffers; +} SLDataLocator_AndroidSimpleBufferQueue; + +#endif + +/* Helper macros */ +#define SLObjectItf_Realize(a,b) ((*(a))->Realize((a),(b))) +#define SLObjectItf_GetInterface(a,b,c) ((*(a))->GetInterface((a),(b),(c))) +#define SLObjectItf_Destroy(a) ((*(a))->Destroy((a))) + +#define SLEngineItf_CreateOutputMix(a,b,c,d,e) ((*(a))->CreateOutputMix((a),(b),(c),(d),(e))) +#define SLEngineItf_CreateAudioPlayer(a,b,c,d,e,f,g) ((*(a))->CreateAudioPlayer((a),(b),(c),(d),(e),(f),(g))) + +#define SLPlayItf_SetPlayState(a,b) ((*(a))->SetPlayState((a),(b))) + + +typedef struct { + /* engine interfaces */ + SLObjectItf engineObject; + SLEngineItf engine; + + /* output mix interfaces */ + SLObjectItf outputMix; + + /* buffer queue player interfaces */ + SLObjectItf bufferQueueObject; + + void *buffer; + ALuint bufferSize; + + ALuint frameSize; +} osl_data; + + +static const ALCchar opensl_device[] = "OpenSL"; + + +static SLuint32 GetChannelMask(enum DevFmtChannels chans) +{ + switch(chans) + { + case DevFmtMono: return SL_SPEAKER_FRONT_CENTER; + case DevFmtStereo: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT; + case DevFmtQuad: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| + SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT; + case DevFmtX51: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| + SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| + SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT; + case DevFmtX61: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| + SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| + SL_SPEAKER_BACK_CENTER| + SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT; + case DevFmtX71: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| + SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| + SL_SPEAKER_BACK_LEFT|SL_SPEAKER_BACK_RIGHT| + SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT; + case DevFmtX51Side: return SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT| + SL_SPEAKER_FRONT_CENTER|SL_SPEAKER_LOW_FREQUENCY| + SL_SPEAKER_SIDE_LEFT|SL_SPEAKER_SIDE_RIGHT; + } + return 0; +} + +static const char *res_str(SLresult result) +{ + switch(result) + { + case SL_RESULT_SUCCESS: return "Success"; + case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated"; + case SL_RESULT_PARAMETER_INVALID: return "Parameter invalid"; + case SL_RESULT_MEMORY_FAILURE: return "Memory failure"; + case SL_RESULT_RESOURCE_ERROR: return "Resource error"; + case SL_RESULT_RESOURCE_LOST: return "Resource lost"; + case SL_RESULT_IO_ERROR: return "I/O error"; + case SL_RESULT_BUFFER_INSUFFICIENT: return "Buffer insufficient"; + case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted"; + case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported"; + case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found"; + case SL_RESULT_PERMISSION_DENIED: return "Permission denied"; + case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported"; + case SL_RESULT_INTERNAL_ERROR: return "Internal error"; + case SL_RESULT_UNKNOWN_ERROR: return "Unknown error"; + case SL_RESULT_OPERATION_ABORTED: return "Operation aborted"; + case SL_RESULT_CONTROL_LOST: return "Control lost"; + case SL_RESULT_READONLY: return "ReadOnly"; + case SL_RESULT_ENGINEOPTION_UNSUPPORTED: return "Engine option unsupported"; + case SL_RESULT_SOURCE_SINK_INCOMPATIBLE: return "Source/Sink incompatible"; + } + return "Unknown error code"; +} + +#define PRINTERR(x, s) do { \ + if((x) != SL_RESULT_SUCCESS) \ + ERR("%s: %s\n", (s), res_str((x))); \ +} while(0) + +/* this callback handler is called every time a buffer finishes playing */ +static void opensl_callback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + ALCdevice *Device = context; + osl_data *data = Device->ExtraData; + SLresult result; + + aluMixData(Device, data->buffer, data->bufferSize/data->frameSize); + + result = (*bq)->Enqueue(bq, data->buffer, data->bufferSize); + PRINTERR(result, "bq->Enqueue"); +} + + +static ALCboolean opensl_open_playback(ALCdevice *Device, const ALCchar *deviceName) +{ + osl_data *data = NULL; + SLresult result; + + if(!deviceName) + deviceName = opensl_device; + else if(strcmp(deviceName, opensl_device) != 0) + return ALC_FALSE; + + data = calloc(1, sizeof(*data)); + if(!data) + { + alcSetError(Device, ALC_OUT_OF_MEMORY); + return ALC_FALSE; + } + + // create engine + result = slCreateEngine(&data->engineObject, 0, NULL, 0, NULL, NULL); + PRINTERR(result, "slCreateEngine"); + if(SL_RESULT_SUCCESS == result) + { + result = SLObjectItf_Realize(data->engineObject, SL_BOOLEAN_FALSE); + PRINTERR(result, "engine->Realize"); + } + if(SL_RESULT_SUCCESS == result) + { + result = SLObjectItf_GetInterface(data->engineObject, SL_IID_ENGINE, &data->engine); + PRINTERR(result, "engine->GetInterface"); + } + if(SL_RESULT_SUCCESS == result) + { + result = SLEngineItf_CreateOutputMix(data->engine, &data->outputMix, 0, NULL, NULL); + PRINTERR(result, "engine->CreateOutputMix"); + } + if(SL_RESULT_SUCCESS == result) + { + result = SLObjectItf_Realize(data->outputMix, SL_BOOLEAN_FALSE); + PRINTERR(result, "outputMix->Realize"); + } + + if(SL_RESULT_SUCCESS != result) + { + if(data->outputMix != NULL) + SLObjectItf_Destroy(data->outputMix); + data->outputMix = NULL; + + if(data->engineObject != NULL) + SLObjectItf_Destroy(data->engineObject); + data->engineObject = NULL; + data->engine = NULL; + + free(data); + return ALC_FALSE; + } + + Device->szDeviceName = strdup(deviceName); + Device->ExtraData = data; + + return ALC_TRUE; +} + + +static void opensl_close_playback(ALCdevice *Device) +{ + osl_data *data = Device->ExtraData; + + SLObjectItf_Destroy(data->outputMix); + data->outputMix = NULL; + + SLObjectItf_Destroy(data->engineObject); + data->engineObject = NULL; + data->engine = NULL; + + free(data); + Device->ExtraData = NULL; +} + +static ALCboolean opensl_reset_playback(ALCdevice *Device) +{ + osl_data *data = Device->ExtraData; + SLDataLocator_AndroidSimpleBufferQueue loc_bufq; + SLAndroidSimpleBufferQueueItf bufferQueue; + SLDataLocator_OutputMix loc_outmix; + SLDataFormat_PCM format_pcm; + SLDataSource audioSrc; + SLDataSink audioSnk; + SLPlayItf player; + SLInterfaceID id; + SLboolean req; + SLresult result; + ALuint i; + + + Device->UpdateSize = (ALuint64)Device->UpdateSize * 44100 / Device->Frequency; + Device->UpdateSize = Device->UpdateSize * Device->NumUpdates / 2; + Device->NumUpdates = 2; + + Device->Frequency = 44100; + Device->FmtChans = DevFmtStereo; + Device->FmtType = DevFmtShort; + + SetDefaultWFXChannelOrder(Device); + + + id = SL_IID_ANDROIDSIMPLEBUFFERQUEUE; + req = SL_BOOLEAN_TRUE; + + loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; + loc_bufq.numBuffers = Device->NumUpdates; + + format_pcm.formatType = SL_DATAFORMAT_PCM; + format_pcm.numChannels = ChannelsFromDevFmt(Device->FmtChans); + format_pcm.samplesPerSec = Device->Frequency * 1000; + format_pcm.bitsPerSample = BytesFromDevFmt(Device->FmtType) * 8; + format_pcm.containerSize = format_pcm.bitsPerSample; + format_pcm.channelMask = GetChannelMask(Device->FmtChans); + format_pcm.endianness = SL_BYTEORDER_NATIVE; + + audioSrc.pLocator = &loc_bufq; + audioSrc.pFormat = &format_pcm; + + loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; + loc_outmix.outputMix = data->outputMix; + audioSnk.pLocator = &loc_outmix; + audioSnk.pFormat = NULL; + + + result = SLEngineItf_CreateAudioPlayer(data->engine, &data->bufferQueueObject, &audioSrc, &audioSnk, 1, &id, &req); + PRINTERR(result, "engine->CreateAudioPlayer"); + if(SL_RESULT_SUCCESS == result) + { + result = SLObjectItf_Realize(data->bufferQueueObject, SL_BOOLEAN_FALSE); + PRINTERR(result, "bufferQueue->Realize"); + } + if(SL_RESULT_SUCCESS == result) + { + result = SLObjectItf_GetInterface(data->bufferQueueObject, SL_IID_BUFFERQUEUE, &bufferQueue); + PRINTERR(result, "bufferQueue->GetInterface"); + } + if(SL_RESULT_SUCCESS == result) + { + result = (*bufferQueue)->RegisterCallback(bufferQueue, opensl_callback, Device); + PRINTERR(result, "bufferQueue->RegisterCallback"); + } + if(SL_RESULT_SUCCESS == result) + { + data->frameSize = FrameSizeFromDevFmt(Device->FmtChans, Device->FmtType); + data->bufferSize = Device->UpdateSize * data->frameSize; + data->buffer = calloc(1, data->bufferSize); + if(!data->buffer) + { + result = SL_RESULT_MEMORY_FAILURE; + PRINTERR(result, "calloc"); + } + } + /* enqueue the first buffer to kick off the callbacks */ + for(i = 0;i < Device->NumUpdates;i++) + { + if(SL_RESULT_SUCCESS == result) + { + result = (*bufferQueue)->Enqueue(bufferQueue, data->buffer, data->bufferSize); + PRINTERR(result, "bufferQueue->Enqueue"); + } + } + if(SL_RESULT_SUCCESS == result) + { + result = SLObjectItf_GetInterface(data->bufferQueueObject, SL_IID_PLAY, &player); + PRINTERR(result, "bufferQueue->GetInterface"); + } + if(SL_RESULT_SUCCESS == result) + { + result = SLPlayItf_SetPlayState(player, SL_PLAYSTATE_PLAYING); + PRINTERR(result, "player->SetPlayState"); + } + + if(SL_RESULT_SUCCESS != result) + { + if(data->bufferQueueObject != NULL) + SLObjectItf_Destroy(data->bufferQueueObject); + data->bufferQueueObject = NULL; + + free(data->buffer); + data->buffer = NULL; + data->bufferSize = 0; + + return ALC_FALSE; + } + + return ALC_TRUE; +} + + +static void opensl_stop_playback(ALCdevice *Device) +{ + osl_data *data = Device->ExtraData; + + if(data->bufferQueueObject != NULL) + SLObjectItf_Destroy(data->bufferQueueObject); + data->bufferQueueObject = NULL; + + free(data->buffer); + data->buffer = NULL; + data->bufferSize = 0; +} + + +static const BackendFuncs opensl_funcs = { + opensl_open_playback, + opensl_close_playback, + opensl_reset_playback, + opensl_stop_playback, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + + +ALCboolean alc_opensl_init(BackendFuncs *func_list) +{ + *func_list = opensl_funcs; + return ALC_TRUE; +} + +void alc_opensl_deinit(void) +{ +} + +void alc_opensl_probe(enum DevProbe type) +{ + switch(type) + { + case DEVICE_PROBE: + AppendDeviceList(opensl_device); + break; + case ALL_DEVICE_PROBE: + AppendAllDeviceList(opensl_device); + break; + case CAPTURE_DEVICE_PROBE: + break; + } +} diff --git a/Alc/backends/oss.c b/Alc/backends/oss.c new file mode 100644 index 00000000..724b23c2 --- /dev/null +++ b/Alc/backends/oss.c @@ -0,0 +1,536 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <memory.h> +#include <unistd.h> +#include <errno.h> +#include <math.h> +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + +#include <sys/soundcard.h> + +/* + * The OSS documentation talks about SOUND_MIXER_READ, but the header + * only contains MIXER_READ. Play safe. Same for WRITE. + */ +#ifndef SOUND_MIXER_READ +#define SOUND_MIXER_READ MIXER_READ +#endif +#ifndef SOUND_MIXER_WRITE +#define SOUND_MIXER_WRITE MIXER_WRITE +#endif + +static const ALCchar oss_device[] = "OSS Default"; + +typedef struct { + int fd; + volatile int killNow; + ALvoid *thread; + + ALubyte *mix_data; + int data_size; + + RingBuffer *ring; + int doCapture; +} oss_data; + + +static int log2i(ALCuint x) +{ + int y = 0; + while (x > 1) + { + x >>= 1; + y++; + } + return y; +} + + +static ALuint OSSProc(ALvoid *ptr) +{ + ALCdevice *pDevice = (ALCdevice*)ptr; + oss_data *data = (oss_data*)pDevice->ExtraData; + ALint frameSize; + ssize_t wrote; + + SetRTPriority(); + + frameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + + while(!data->killNow && pDevice->Connected) + { + ALint len = data->data_size; + ALubyte *WritePtr = data->mix_data; + + aluMixData(pDevice, WritePtr, len/frameSize); + while(len > 0 && !data->killNow) + { + wrote = write(data->fd, WritePtr, len); + if(wrote < 0) + { + if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) + { + ERR("write failed: %s\n", strerror(errno)); + aluHandleDisconnect(pDevice); + break; + } + + Sleep(1); + continue; + } + + len -= wrote; + WritePtr += wrote; + } + } + + return 0; +} + +static ALuint OSSCaptureProc(ALvoid *ptr) +{ + ALCdevice *pDevice = (ALCdevice*)ptr; + oss_data *data = (oss_data*)pDevice->ExtraData; + int frameSize; + int amt; + + SetRTPriority(); + + frameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + + while(!data->killNow) + { + amt = read(data->fd, data->mix_data, data->data_size); + if(amt < 0) + { + ERR("read failed: %s\n", strerror(errno)); + aluHandleDisconnect(pDevice); + break; + } + if(amt == 0) + { + Sleep(1); + continue; + } + if(data->doCapture) + WriteRingBuffer(data->ring, data->mix_data, amt/frameSize); + } + + return 0; +} + +static ALCboolean oss_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + char driver[64]; + oss_data *data; + + strncpy(driver, GetConfigValue("oss", "device", "/dev/dsp"), sizeof(driver)-1); + driver[sizeof(driver)-1] = 0; + if(!deviceName) + deviceName = oss_device; + else if(strcmp(deviceName, oss_device) != 0) + return ALC_FALSE; + + data = (oss_data*)calloc(1, sizeof(oss_data)); + data->killNow = 0; + + data->fd = open(driver, O_WRONLY); + if(data->fd == -1) + { + free(data); + ERR("Could not open %s: %s\n", driver, strerror(errno)); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + device->ExtraData = data; + return ALC_TRUE; +} + +static void oss_close_playback(ALCdevice *device) +{ + oss_data *data = (oss_data*)device->ExtraData; + + close(data->fd); + free(data); + device->ExtraData = NULL; +} + +static ALCboolean oss_reset_playback(ALCdevice *device) +{ + oss_data *data = (oss_data*)device->ExtraData; + int numFragmentsLogSize; + int log2FragmentSize; + unsigned int periods; + audio_buf_info info; + ALuint frameSize; + int numChannels; + int ossFormat; + int ossSpeed; + char *err; + + switch(device->FmtType) + { + case DevFmtByte: + ossFormat = AFMT_S8; + break; + case DevFmtUByte: + ossFormat = AFMT_U8; + break; + case DevFmtUShort: + case DevFmtFloat: + device->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + ossFormat = AFMT_S16_NE; + break; + } + + periods = device->NumUpdates; + numChannels = ChannelsFromDevFmt(device->FmtChans); + frameSize = numChannels * BytesFromDevFmt(device->FmtType); + + ossSpeed = device->Frequency; + log2FragmentSize = log2i(device->UpdateSize * frameSize); + + /* according to the OSS spec, 16 bytes are the minimum */ + if (log2FragmentSize < 4) + log2FragmentSize = 4; + /* Subtract one period since the temp mixing buffer counts as one. Still + * need at least two on the card, though. */ + if(periods > 2) periods--; + numFragmentsLogSize = (periods << 16) | log2FragmentSize; + +#define CHECKERR(func) if((func) < 0) { \ + err = #func; \ + goto err; \ +} + /* Don't fail if SETFRAGMENT fails. We can handle just about anything + * that's reported back via GETOSPACE */ + ioctl(data->fd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_SETFMT, &ossFormat)); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_CHANNELS, &numChannels)); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_SPEED, &ossSpeed)); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_GETOSPACE, &info)); + if(0) + { + err: + ERR("%s failed: %s\n", err, strerror(errno)); + return ALC_FALSE; + } +#undef CHECKERR + + if((int)ChannelsFromDevFmt(device->FmtChans) != numChannels) + { + ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(device->FmtChans), numChannels); + return ALC_FALSE; + } + + if(!((ossFormat == AFMT_S8 && device->FmtType == DevFmtByte) || + (ossFormat == AFMT_U8 && device->FmtType == DevFmtUByte) || + (ossFormat == AFMT_S16_NE && device->FmtType == DevFmtShort))) + { + ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(device->FmtType), ossFormat); + return ALC_FALSE; + } + + if(device->Frequency != (ALuint)ossSpeed) + { + if((device->Flags&DEVICE_FREQUENCY_REQUEST)) + ERR("Failed to set %dhz, got %dhz instead\n", device->Frequency, ossSpeed); + device->Flags &= ~DEVICE_FREQUENCY_REQUEST; + device->Frequency = ossSpeed; + } + device->UpdateSize = info.fragsize / frameSize; + device->NumUpdates = info.fragments + 1; + + data->data_size = device->UpdateSize * frameSize; + data->mix_data = calloc(1, data->data_size); + + SetDefaultChannelOrder(device); + + data->thread = StartThread(OSSProc, device); + if(data->thread == NULL) + { + free(data->mix_data); + data->mix_data = NULL; + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void oss_stop_playback(ALCdevice *device) +{ + oss_data *data = (oss_data*)device->ExtraData; + + if(!data->thread) + return; + + data->killNow = 1; + StopThread(data->thread); + data->thread = NULL; + + data->killNow = 0; + if(ioctl(data->fd, SNDCTL_DSP_RESET) != 0) + ERR("Error resetting device: %s\n", strerror(errno)); + + free(data->mix_data); + data->mix_data = NULL; +} + + +static ALCboolean oss_open_capture(ALCdevice *device, const ALCchar *deviceName) +{ + int numFragmentsLogSize; + int log2FragmentSize; + unsigned int periods; + audio_buf_info info; + ALuint frameSize; + int numChannels; + char driver[64]; + oss_data *data; + int ossFormat; + int ossSpeed; + char *err; + + strncpy(driver, GetConfigValue("oss", "capture", "/dev/dsp"), sizeof(driver)-1); + driver[sizeof(driver)-1] = 0; + if(!deviceName) + deviceName = oss_device; + else if(strcmp(deviceName, oss_device) != 0) + return ALC_FALSE; + + data = (oss_data*)calloc(1, sizeof(oss_data)); + data->killNow = 0; + + data->fd = open(driver, O_RDONLY); + if(data->fd == -1) + { + free(data); + ERR("Could not open %s: %s\n", driver, strerror(errno)); + return ALC_FALSE; + } + + switch(device->FmtType) + { + case DevFmtByte: + ossFormat = AFMT_S8; + break; + case DevFmtUByte: + ossFormat = AFMT_U8; + break; + case DevFmtShort: + ossFormat = AFMT_S16_NE; + break; + case DevFmtUShort: + case DevFmtFloat: + free(data); + ERR("%s capture samples not supported on OSS\n", DevFmtTypeString(device->FmtType)); + return ALC_FALSE; + } + + periods = 4; + numChannels = ChannelsFromDevFmt(device->FmtChans); + frameSize = numChannels * BytesFromDevFmt(device->FmtType); + ossSpeed = device->Frequency; + log2FragmentSize = log2i(device->UpdateSize * device->NumUpdates * + frameSize / periods); + + /* according to the OSS spec, 16 bytes are the minimum */ + if (log2FragmentSize < 4) + log2FragmentSize = 4; + numFragmentsLogSize = (periods << 16) | log2FragmentSize; + +#define CHECKERR(func) if((func) < 0) { \ + err = #func; \ + goto err; \ +} + CHECKERR(ioctl(data->fd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize)); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_SETFMT, &ossFormat)); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_CHANNELS, &numChannels)); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_SPEED, &ossSpeed)); + CHECKERR(ioctl(data->fd, SNDCTL_DSP_GETISPACE, &info)); + if(0) + { + err: + ERR("%s failed: %s\n", err, strerror(errno)); + close(data->fd); + free(data); + return ALC_FALSE; + } +#undef CHECKERR + + if((int)ChannelsFromDevFmt(device->FmtChans) != numChannels) + { + ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(device->FmtChans), numChannels); + close(data->fd); + free(data); + return ALC_FALSE; + } + + if(!((ossFormat == AFMT_S8 && device->FmtType == DevFmtByte) || + (ossFormat == AFMT_U8 && device->FmtType == DevFmtUByte) || + (ossFormat == AFMT_S16_NE && device->FmtType == DevFmtShort))) + { + ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(device->FmtType), ossFormat); + close(data->fd); + free(data); + return ALC_FALSE; + } + + data->ring = CreateRingBuffer(frameSize, device->UpdateSize * device->NumUpdates); + if(!data->ring) + { + ERR("Ring buffer create failed\n"); + close(data->fd); + free(data); + return ALC_FALSE; + } + + data->data_size = info.fragsize; + data->mix_data = calloc(1, data->data_size); + + device->ExtraData = data; + data->thread = StartThread(OSSCaptureProc, device); + if(data->thread == NULL) + { + device->ExtraData = NULL; + free(data->mix_data); + free(data); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + return ALC_TRUE; +} + +static void oss_close_capture(ALCdevice *device) +{ + oss_data *data = (oss_data*)device->ExtraData; + data->killNow = 1; + StopThread(data->thread); + + close(data->fd); + + DestroyRingBuffer(data->ring); + + free(data->mix_data); + free(data); + device->ExtraData = NULL; +} + +static void oss_start_capture(ALCdevice *pDevice) +{ + oss_data *data = (oss_data*)pDevice->ExtraData; + data->doCapture = 1; +} + +static void oss_stop_capture(ALCdevice *pDevice) +{ + oss_data *data = (oss_data*)pDevice->ExtraData; + data->doCapture = 0; +} + +static void oss_capture_samples(ALCdevice *pDevice, ALCvoid *pBuffer, ALCuint lSamples) +{ + oss_data *data = (oss_data*)pDevice->ExtraData; + if(lSamples <= (ALCuint)RingBufferSize(data->ring)) + ReadRingBuffer(data->ring, pBuffer, lSamples); + else + alcSetError(pDevice, ALC_INVALID_VALUE); +} + +static ALCuint oss_available_samples(ALCdevice *pDevice) +{ + oss_data *data = (oss_data*)pDevice->ExtraData; + return RingBufferSize(data->ring); +} + + +static const BackendFuncs oss_funcs = { + oss_open_playback, + oss_close_playback, + oss_reset_playback, + oss_stop_playback, + oss_open_capture, + oss_close_capture, + oss_start_capture, + oss_stop_capture, + oss_capture_samples, + oss_available_samples +}; + +ALCboolean alc_oss_init(BackendFuncs *func_list) +{ + *func_list = oss_funcs; + return ALC_TRUE; +} + +void alc_oss_deinit(void) +{ +} + +void alc_oss_probe(enum DevProbe type) +{ + switch(type) + { + case DEVICE_PROBE: + { +#ifdef HAVE_STAT + struct stat buf; + if(stat(GetConfigValue("oss", "device", "/dev/dsp"), &buf) == 0) +#endif + AppendDeviceList(oss_device); + } + break; + + case ALL_DEVICE_PROBE: + { +#ifdef HAVE_STAT + struct stat buf; + if(stat(GetConfigValue("oss", "device", "/dev/dsp"), &buf) == 0) +#endif + AppendAllDeviceList(oss_device); + } + break; + + case CAPTURE_DEVICE_PROBE: + { +#ifdef HAVE_STAT + struct stat buf; + if(stat(GetConfigValue("oss", "capture", "/dev/dsp"), &buf) == 0) +#endif + AppendCaptureDeviceList(oss_device); + } + break; + } +} diff --git a/Alc/backends/portaudio.c b/Alc/backends/portaudio.c new file mode 100644 index 00000000..4f3dfd5f --- /dev/null +++ b/Alc/backends/portaudio.c @@ -0,0 +1,449 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + +#include <portaudio.h> + + +static const ALCchar pa_device[] = "PortAudio Default"; + + +static void *pa_handle; +#ifdef HAVE_DYNLOAD +#define MAKE_FUNC(x) static typeof(x) * p##x +MAKE_FUNC(Pa_Initialize); +MAKE_FUNC(Pa_Terminate); +MAKE_FUNC(Pa_GetErrorText); +MAKE_FUNC(Pa_StartStream); +MAKE_FUNC(Pa_StopStream); +MAKE_FUNC(Pa_OpenStream); +MAKE_FUNC(Pa_CloseStream); +MAKE_FUNC(Pa_GetDefaultOutputDevice); +MAKE_FUNC(Pa_GetStreamInfo); +#undef MAKE_FUNC + +#define Pa_Initialize pPa_Initialize +#define Pa_Terminate pPa_Terminate +#define Pa_GetErrorText pPa_GetErrorText +#define Pa_StartStream pPa_StartStream +#define Pa_StopStream pPa_StopStream +#define Pa_OpenStream pPa_OpenStream +#define Pa_CloseStream pPa_CloseStream +#define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice +#define Pa_GetStreamInfo pPa_GetStreamInfo +#endif + +static ALCboolean pa_load(void) +{ + if(!pa_handle) + { + PaError err; + +#ifdef HAVE_DYNLOAD +#ifdef _WIN32 +# define PALIB "portaudio.dll" +#elif defined(__APPLE__) && defined(__MACH__) +# define PALIB "libportaudio.2.dylib" +#elif defined(__OpenBSD__) +# define PALIB "libportaudio.so" +#else +# define PALIB "libportaudio.so.2" +#endif + + pa_handle = LoadLib(PALIB); + if(!pa_handle) + return ALC_FALSE; + +#define LOAD_FUNC(f) do { \ + p##f = GetSymbol(pa_handle, #f); \ + if(p##f == NULL) \ + { \ + CloseLib(pa_handle); \ + pa_handle = NULL; \ + return ALC_FALSE; \ + } \ +} while(0) + LOAD_FUNC(Pa_Initialize); + LOAD_FUNC(Pa_Terminate); + LOAD_FUNC(Pa_GetErrorText); + LOAD_FUNC(Pa_StartStream); + LOAD_FUNC(Pa_StopStream); + LOAD_FUNC(Pa_OpenStream); + LOAD_FUNC(Pa_CloseStream); + LOAD_FUNC(Pa_GetDefaultOutputDevice); + LOAD_FUNC(Pa_GetStreamInfo); +#undef LOAD_FUNC +#else + pa_handle = (void*)0xDEADBEEF; +#endif + + if((err=Pa_Initialize()) != paNoError) + { + ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); + CloseLib(pa_handle); + pa_handle = NULL; + return ALC_FALSE; + } + } + return ALC_TRUE; +} + + +typedef struct { + PaStream *stream; + ALuint update_size; + + RingBuffer *ring; +} pa_data; + + +static int pa_callback(const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, + const PaStreamCallbackFlags statusFlags, void *userData) +{ + ALCdevice *device = (ALCdevice*)userData; + + (void)inputBuffer; + (void)timeInfo; + (void)statusFlags; + + aluMixData(device, outputBuffer, framesPerBuffer); + return 0; +} + +static int pa_capture_cb(const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, + const PaStreamCallbackFlags statusFlags, void *userData) +{ + ALCdevice *device = (ALCdevice*)userData; + pa_data *data = (pa_data*)device->ExtraData; + + (void)outputBuffer; + (void)timeInfo; + (void)statusFlags; + + WriteRingBuffer(data->ring, inputBuffer, framesPerBuffer); + return 0; +} + + +static ALCboolean pa_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + PaStreamParameters outParams; + pa_data *data; + PaError err; + + if(!deviceName) + deviceName = pa_device; + else if(strcmp(deviceName, pa_device) != 0) + return ALC_FALSE; + + data = (pa_data*)calloc(1, sizeof(pa_data)); + data->update_size = device->UpdateSize; + + device->ExtraData = data; + + outParams.device = GetConfigValueInt("port", "device", -1); + if(outParams.device < 0) + outParams.device = Pa_GetDefaultOutputDevice(); + outParams.suggestedLatency = (device->UpdateSize*device->NumUpdates) / + (float)device->Frequency; + outParams.hostApiSpecificStreamInfo = NULL; + + switch(device->FmtType) + { + case DevFmtByte: + outParams.sampleFormat = paInt8; + break; + case DevFmtUByte: + outParams.sampleFormat = paUInt8; + break; + case DevFmtUShort: + device->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + outParams.sampleFormat = paInt16; + break; + case DevFmtFloat: + outParams.sampleFormat = paFloat32; + break; + } + outParams.channelCount = ((device->FmtChans == DevFmtMono) ? 1 : 2); + + SetDefaultChannelOrder(device); + + err = Pa_OpenStream(&data->stream, NULL, &outParams, device->Frequency, + device->UpdateSize, paNoFlag, pa_callback, device); + if(err != paNoError) + { + ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err)); + device->ExtraData = NULL; + free(data); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + + if((ALuint)outParams.channelCount != ChannelsFromDevFmt(device->FmtChans)) + { + if(outParams.channelCount != 1 && outParams.channelCount != 2) + { + ERR("Unhandled channel count: %u\n", outParams.channelCount); + Pa_CloseStream(data->stream); + device->ExtraData = NULL; + free(data); + return ALC_FALSE; + } + if((device->Flags&DEVICE_CHANNELS_REQUEST)) + ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(device->FmtChans), outParams.channelCount); + device->Flags &= ~DEVICE_CHANNELS_REQUEST; + device->FmtChans = ((outParams.channelCount==1) ? DevFmtMono : DevFmtStereo); + } + + return ALC_TRUE; +} + +static void pa_close_playback(ALCdevice *device) +{ + pa_data *data = (pa_data*)device->ExtraData; + PaError err; + + err = Pa_CloseStream(data->stream); + if(err != paNoError) + ERR("Error closing stream: %s\n", Pa_GetErrorText(err)); + + free(data); + device->ExtraData = NULL; +} + +static ALCboolean pa_reset_playback(ALCdevice *device) +{ + pa_data *data = (pa_data*)device->ExtraData; + const PaStreamInfo *streamInfo; + PaError err; + + streamInfo = Pa_GetStreamInfo(data->stream); + if(device->Frequency != streamInfo->sampleRate) + { + if((device->Flags&DEVICE_FREQUENCY_REQUEST)) + ERR("PortAudio does not support changing sample rates (wanted %dhz, got %.1fhz)\n", device->Frequency, streamInfo->sampleRate); + device->Flags &= ~DEVICE_FREQUENCY_REQUEST; + device->Frequency = streamInfo->sampleRate; + } + device->UpdateSize = data->update_size; + + err = Pa_StartStream(data->stream); + if(err != paNoError) + { + ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err)); + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void pa_stop_playback(ALCdevice *device) +{ + pa_data *data = (pa_data*)device->ExtraData; + PaError err; + + err = Pa_StopStream(data->stream); + if(err != paNoError) + ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); +} + + +static ALCboolean pa_open_capture(ALCdevice *device, const ALCchar *deviceName) +{ + PaStreamParameters inParams; + ALuint frame_size; + pa_data *data; + PaError err; + + if(!deviceName) + deviceName = pa_device; + else if(strcmp(deviceName, pa_device) != 0) + return ALC_FALSE; + + data = (pa_data*)calloc(1, sizeof(pa_data)); + if(data == NULL) + { + alcSetError(device, ALC_OUT_OF_MEMORY); + return ALC_FALSE; + } + + frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType); + data->ring = CreateRingBuffer(frame_size, device->UpdateSize*device->NumUpdates); + if(data->ring == NULL) + { + alcSetError(device, ALC_OUT_OF_MEMORY); + goto error; + } + + inParams.device = GetConfigValueInt("port", "capture", -1); + if(inParams.device < 0) + inParams.device = Pa_GetDefaultOutputDevice(); + inParams.suggestedLatency = 0.0f; + inParams.hostApiSpecificStreamInfo = NULL; + + switch(device->FmtType) + { + case DevFmtByte: + inParams.sampleFormat = paInt8; + break; + case DevFmtUByte: + inParams.sampleFormat = paUInt8; + break; + case DevFmtShort: + inParams.sampleFormat = paInt16; + break; + case DevFmtFloat: + inParams.sampleFormat = paFloat32; + break; + case DevFmtUShort: + ERR("Unsigned short samples not supported\n"); + goto error; + } + inParams.channelCount = ChannelsFromDevFmt(device->FmtChans); + + err = Pa_OpenStream(&data->stream, &inParams, NULL, device->Frequency, + paFramesPerBufferUnspecified, paNoFlag, pa_capture_cb, device); + if(err != paNoError) + { + ERR("Pa_OpenStream() returned an error: %s\n", Pa_GetErrorText(err)); + goto error; + } + + device->szDeviceName = strdup(deviceName); + + device->ExtraData = data; + return ALC_TRUE; + +error: + DestroyRingBuffer(data->ring); + free(data); + return ALC_FALSE; +} + +static void pa_close_capture(ALCdevice *device) +{ + pa_data *data = (pa_data*)device->ExtraData; + PaError err; + + err = Pa_CloseStream(data->stream); + if(err != paNoError) + ERR("Error closing stream: %s\n", Pa_GetErrorText(err)); + + free(data); + device->ExtraData = NULL; +} + +static void pa_start_capture(ALCdevice *device) +{ + pa_data *data = device->ExtraData; + PaError err; + + err = Pa_StartStream(data->stream); + if(err != paNoError) + ERR("Error starting stream: %s\n", Pa_GetErrorText(err)); +} + +static void pa_stop_capture(ALCdevice *device) +{ + pa_data *data = (pa_data*)device->ExtraData; + PaError err; + + err = Pa_StopStream(data->stream); + if(err != paNoError) + ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); +} + +static void pa_capture_samples(ALCdevice *device, ALCvoid *buffer, ALCuint samples) +{ + pa_data *data = device->ExtraData; + if(samples <= (ALCuint)RingBufferSize(data->ring)) + ReadRingBuffer(data->ring, buffer, samples); + else + alcSetError(device, ALC_INVALID_VALUE); +} + +static ALCuint pa_available_samples(ALCdevice *device) +{ + pa_data *data = device->ExtraData; + return RingBufferSize(data->ring); +} + + +static const BackendFuncs pa_funcs = { + pa_open_playback, + pa_close_playback, + pa_reset_playback, + pa_stop_playback, + pa_open_capture, + pa_close_capture, + pa_start_capture, + pa_stop_capture, + pa_capture_samples, + pa_available_samples +}; + +ALCboolean alc_pa_init(BackendFuncs *func_list) +{ + if(!pa_load()) + return ALC_FALSE; + *func_list = pa_funcs; + return ALC_TRUE; +} + +void alc_pa_deinit(void) +{ + if(pa_handle) + { + Pa_Terminate(); +#ifdef HAVE_DYNLOAD + CloseLib(pa_handle); +#endif + pa_handle = NULL; + } +} + +void alc_pa_probe(enum DevProbe type) +{ + switch(type) + { + case DEVICE_PROBE: + AppendDeviceList(pa_device); + break; + case ALL_DEVICE_PROBE: + AppendAllDeviceList(pa_device); + break; + case CAPTURE_DEVICE_PROBE: + AppendCaptureDeviceList(pa_device); + break; + } +} diff --git a/Alc/backends/pulseaudio.c b/Alc/backends/pulseaudio.c new file mode 100644 index 00000000..39df3282 --- /dev/null +++ b/Alc/backends/pulseaudio.c @@ -0,0 +1,1430 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 2009 by Konstantinos Natsakis <[email protected]> + * Copyright (C) 2010 by Chris Robinson <[email protected]> + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include "alMain.h" + +#include <pulse/pulseaudio.h> + +#if PA_API_VERSION == 11 +#define PA_STREAM_ADJUST_LATENCY 0x2000U +#define PA_STREAM_EARLY_REQUESTS 0x4000U +static __inline int PA_STREAM_IS_GOOD(pa_stream_state_t x) +{ + return (x == PA_STREAM_CREATING || x == PA_STREAM_READY); +} +static __inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) +{ + return (x == PA_CONTEXT_CONNECTING || x == PA_CONTEXT_AUTHORIZING || + x == PA_CONTEXT_SETTING_NAME || x == PA_CONTEXT_READY); +} +#define PA_STREAM_IS_GOOD PA_STREAM_IS_GOOD +#define PA_CONTEXT_IS_GOOD PA_CONTEXT_IS_GOOD +#elif PA_API_VERSION != 12 +#error Invalid PulseAudio API version +#endif + +#ifndef PA_CHECK_VERSION +#define PA_CHECK_VERSION(major,minor,micro) \ + ((PA_MAJOR > (major)) || \ + (PA_MAJOR == (major) && PA_MINOR > (minor)) || \ + (PA_MAJOR == (major) && PA_MINOR == (minor) && PA_MICRO >= (micro))) +#endif + +static void *pa_handle; +#ifdef HAVE_DYNLOAD +#define MAKE_FUNC(x) static typeof(x) * p##x +MAKE_FUNC(pa_context_unref); +MAKE_FUNC(pa_sample_spec_valid); +MAKE_FUNC(pa_stream_drop); +MAKE_FUNC(pa_strerror); +MAKE_FUNC(pa_context_get_state); +MAKE_FUNC(pa_stream_get_state); +MAKE_FUNC(pa_threaded_mainloop_signal); +MAKE_FUNC(pa_stream_peek); +MAKE_FUNC(pa_threaded_mainloop_wait); +MAKE_FUNC(pa_threaded_mainloop_unlock); +MAKE_FUNC(pa_threaded_mainloop_in_thread); +MAKE_FUNC(pa_context_new); +MAKE_FUNC(pa_threaded_mainloop_stop); +MAKE_FUNC(pa_context_disconnect); +MAKE_FUNC(pa_threaded_mainloop_start); +MAKE_FUNC(pa_threaded_mainloop_get_api); +MAKE_FUNC(pa_context_set_state_callback); +MAKE_FUNC(pa_stream_write); +MAKE_FUNC(pa_xfree); +MAKE_FUNC(pa_stream_connect_record); +MAKE_FUNC(pa_stream_connect_playback); +MAKE_FUNC(pa_stream_readable_size); +MAKE_FUNC(pa_stream_writable_size); +MAKE_FUNC(pa_stream_cork); +MAKE_FUNC(pa_stream_is_suspended); +MAKE_FUNC(pa_stream_get_device_name); +MAKE_FUNC(pa_path_get_filename); +MAKE_FUNC(pa_get_binary_name); +MAKE_FUNC(pa_threaded_mainloop_free); +MAKE_FUNC(pa_context_errno); +MAKE_FUNC(pa_xmalloc); +MAKE_FUNC(pa_stream_unref); +MAKE_FUNC(pa_threaded_mainloop_accept); +MAKE_FUNC(pa_stream_set_write_callback); +MAKE_FUNC(pa_threaded_mainloop_new); +MAKE_FUNC(pa_context_connect); +MAKE_FUNC(pa_stream_set_buffer_attr); +MAKE_FUNC(pa_stream_get_buffer_attr); +MAKE_FUNC(pa_stream_get_sample_spec); +MAKE_FUNC(pa_stream_get_time); +MAKE_FUNC(pa_stream_set_read_callback); +MAKE_FUNC(pa_stream_set_state_callback); +MAKE_FUNC(pa_stream_set_moved_callback); +MAKE_FUNC(pa_stream_set_underflow_callback); +MAKE_FUNC(pa_stream_new); +MAKE_FUNC(pa_stream_disconnect); +MAKE_FUNC(pa_threaded_mainloop_lock); +MAKE_FUNC(pa_channel_map_init_auto); +MAKE_FUNC(pa_channel_map_parse); +MAKE_FUNC(pa_channel_map_snprint); +MAKE_FUNC(pa_channel_map_equal); +MAKE_FUNC(pa_context_get_server_info); +MAKE_FUNC(pa_context_get_sink_info_by_name); +MAKE_FUNC(pa_context_get_sink_info_list); +MAKE_FUNC(pa_context_get_source_info_list); +MAKE_FUNC(pa_operation_get_state); +MAKE_FUNC(pa_operation_unref); +#if PA_CHECK_VERSION(0,9,15) +MAKE_FUNC(pa_channel_map_superset); +MAKE_FUNC(pa_stream_set_buffer_attr_callback); +#endif +#if PA_CHECK_VERSION(0,9,16) +MAKE_FUNC(pa_stream_begin_write); +#endif +#undef MAKE_FUNC + +#define pa_context_unref ppa_context_unref +#define pa_sample_spec_valid ppa_sample_spec_valid +#define pa_stream_drop ppa_stream_drop +#define pa_strerror ppa_strerror +#define pa_context_get_state ppa_context_get_state +#define pa_stream_get_state ppa_stream_get_state +#define pa_threaded_mainloop_signal ppa_threaded_mainloop_signal +#define pa_stream_peek ppa_stream_peek +#define pa_threaded_mainloop_wait ppa_threaded_mainloop_wait +#define pa_threaded_mainloop_unlock ppa_threaded_mainloop_unlock +#define pa_threaded_mainloop_in_thread ppa_threaded_mainloop_in_thread +#define pa_context_new ppa_context_new +#define pa_threaded_mainloop_stop ppa_threaded_mainloop_stop +#define pa_context_disconnect ppa_context_disconnect +#define pa_threaded_mainloop_start ppa_threaded_mainloop_start +#define pa_threaded_mainloop_get_api ppa_threaded_mainloop_get_api +#define pa_context_set_state_callback ppa_context_set_state_callback +#define pa_stream_write ppa_stream_write +#define pa_xfree ppa_xfree +#define pa_stream_connect_record ppa_stream_connect_record +#define pa_stream_connect_playback ppa_stream_connect_playback +#define pa_stream_readable_size ppa_stream_readable_size +#define pa_stream_writable_size ppa_stream_writable_size +#define pa_stream_cork ppa_stream_cork +#define pa_stream_is_suspended ppa_stream_is_suspended +#define pa_stream_get_device_name ppa_stream_get_device_name +#define pa_path_get_filename ppa_path_get_filename +#define pa_get_binary_name ppa_get_binary_name +#define pa_threaded_mainloop_free ppa_threaded_mainloop_free +#define pa_context_errno ppa_context_errno +#define pa_xmalloc ppa_xmalloc +#define pa_stream_unref ppa_stream_unref +#define pa_threaded_mainloop_accept ppa_threaded_mainloop_accept +#define pa_stream_set_write_callback ppa_stream_set_write_callback +#define pa_threaded_mainloop_new ppa_threaded_mainloop_new +#define pa_context_connect ppa_context_connect +#define pa_stream_set_buffer_attr ppa_stream_set_buffer_attr +#define pa_stream_get_buffer_attr ppa_stream_get_buffer_attr +#define pa_stream_get_sample_spec ppa_stream_get_sample_spec +#define pa_stream_get_time ppa_stream_get_time +#define pa_stream_set_read_callback ppa_stream_set_read_callback +#define pa_stream_set_state_callback ppa_stream_set_state_callback +#define pa_stream_set_moved_callback ppa_stream_set_moved_callback +#define pa_stream_set_underflow_callback ppa_stream_set_underflow_callback +#define pa_stream_new ppa_stream_new +#define pa_stream_disconnect ppa_stream_disconnect +#define pa_threaded_mainloop_lock ppa_threaded_mainloop_lock +#define pa_channel_map_init_auto ppa_channel_map_init_auto +#define pa_channel_map_parse ppa_channel_map_parse +#define pa_channel_map_snprint ppa_channel_map_snprint +#define pa_channel_map_equal ppa_channel_map_equal +#define pa_context_get_server_info ppa_context_get_server_info +#define pa_context_get_sink_info_by_name ppa_context_get_sink_info_by_name +#define pa_context_get_sink_info_list ppa_context_get_sink_info_list +#define pa_context_get_source_info_list ppa_context_get_source_info_list +#define pa_operation_get_state ppa_operation_get_state +#define pa_operation_unref ppa_operation_unref +#if PA_CHECK_VERSION(0,9,15) +#define pa_channel_map_superset ppa_channel_map_superset +#define pa_stream_set_buffer_attr_callback ppa_stream_set_buffer_attr_callback +#endif +#if PA_CHECK_VERSION(0,9,16) +#define pa_stream_begin_write ppa_stream_begin_write +#endif + +#endif + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +typedef struct { + char *device_name; + + ALCuint samples; + ALCuint frame_size; + + RingBuffer *ring; + + pa_buffer_attr attr; + pa_sample_spec spec; + + pa_threaded_mainloop *loop; + + ALvoid *thread; + volatile ALboolean killNow; + + pa_stream *stream; + pa_context *context; +} pulse_data; + +typedef struct { + char *name; + char *device_name; +} DevMap; + + +static const ALCchar pulse_device[] = "PulseAudio Default"; +static DevMap *allDevNameMap; +static ALuint numDevNames; +static DevMap *allCaptureDevNameMap; +static ALuint numCaptureDevNames; +static pa_context_flags_t pulse_ctx_flags; + + +static void context_state_callback(pa_context *context, void *pdata) +{ + pa_threaded_mainloop *loop = pdata; + pa_context_state_t state; + + state = pa_context_get_state(context); + if(state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state)) + pa_threaded_mainloop_signal(loop, 0); +} + +static pa_context *connect_context(pa_threaded_mainloop *loop, ALboolean silent) +{ + const char *name = "OpenAL Soft"; + char path_name[PATH_MAX]; + pa_context_state_t state; + pa_context *context; + int err; + + if(pa_get_binary_name(path_name, sizeof(path_name))) + name = pa_path_get_filename(path_name); + + context = pa_context_new(pa_threaded_mainloop_get_api(loop), name); + if(!context) + { + ERR("pa_context_new() failed\n"); + return NULL; + } + + pa_context_set_state_callback(context, context_state_callback, loop); + + if((err=pa_context_connect(context, NULL, pulse_ctx_flags, NULL)) >= 0) + { + while((state=pa_context_get_state(context)) != PA_CONTEXT_READY) + { + if(!PA_CONTEXT_IS_GOOD(state)) + { + err = pa_context_errno(context); + if(err > 0) err = -err; + break; + } + + pa_threaded_mainloop_wait(loop); + } + } + pa_context_set_state_callback(context, NULL, NULL); + + if(err < 0) + { + if(!silent) + ERR("Context did not connect: %s\n", pa_strerror(err)); + pa_context_unref(context); + return NULL; + } + + return context; +} + + +static ALCboolean pulse_load(void) //{{{ +{ + ALCboolean ret = ALC_FALSE; + if(!pa_handle) + { + pa_threaded_mainloop *loop; + +#ifdef HAVE_DYNLOAD + +#ifdef _WIN32 +#define PALIB "libpulse-0.dll" +#elif defined(__APPLE__) && defined(__MACH__) +#define PALIB "libpulse.0.dylib" +#else +#define PALIB "libpulse.so.0" +#endif + pa_handle = LoadLib(PALIB); + if(!pa_handle) + return ALC_FALSE; + +#define LOAD_FUNC(x) do { \ + p##x = GetSymbol(pa_handle, #x); \ + if(!(p##x)) { \ + CloseLib(pa_handle); \ + pa_handle = NULL; \ + return ALC_FALSE; \ + } \ +} while(0) + LOAD_FUNC(pa_context_unref); + LOAD_FUNC(pa_sample_spec_valid); + LOAD_FUNC(pa_stream_drop); + LOAD_FUNC(pa_strerror); + LOAD_FUNC(pa_context_get_state); + LOAD_FUNC(pa_stream_get_state); + LOAD_FUNC(pa_threaded_mainloop_signal); + LOAD_FUNC(pa_stream_peek); + LOAD_FUNC(pa_threaded_mainloop_wait); + LOAD_FUNC(pa_threaded_mainloop_unlock); + LOAD_FUNC(pa_threaded_mainloop_in_thread); + LOAD_FUNC(pa_context_new); + LOAD_FUNC(pa_threaded_mainloop_stop); + LOAD_FUNC(pa_context_disconnect); + LOAD_FUNC(pa_threaded_mainloop_start); + LOAD_FUNC(pa_threaded_mainloop_get_api); + LOAD_FUNC(pa_context_set_state_callback); + LOAD_FUNC(pa_stream_write); + LOAD_FUNC(pa_xfree); + LOAD_FUNC(pa_stream_connect_record); + LOAD_FUNC(pa_stream_connect_playback); + LOAD_FUNC(pa_stream_readable_size); + LOAD_FUNC(pa_stream_writable_size); + LOAD_FUNC(pa_stream_cork); + LOAD_FUNC(pa_stream_is_suspended); + LOAD_FUNC(pa_stream_get_device_name); + LOAD_FUNC(pa_path_get_filename); + LOAD_FUNC(pa_get_binary_name); + LOAD_FUNC(pa_threaded_mainloop_free); + LOAD_FUNC(pa_context_errno); + LOAD_FUNC(pa_xmalloc); + LOAD_FUNC(pa_stream_unref); + LOAD_FUNC(pa_threaded_mainloop_accept); + LOAD_FUNC(pa_stream_set_write_callback); + LOAD_FUNC(pa_threaded_mainloop_new); + LOAD_FUNC(pa_context_connect); + LOAD_FUNC(pa_stream_set_buffer_attr); + LOAD_FUNC(pa_stream_get_buffer_attr); + LOAD_FUNC(pa_stream_get_sample_spec); + LOAD_FUNC(pa_stream_get_time); + LOAD_FUNC(pa_stream_set_read_callback); + LOAD_FUNC(pa_stream_set_state_callback); + LOAD_FUNC(pa_stream_set_moved_callback); + LOAD_FUNC(pa_stream_set_underflow_callback); + LOAD_FUNC(pa_stream_new); + LOAD_FUNC(pa_stream_disconnect); + LOAD_FUNC(pa_threaded_mainloop_lock); + LOAD_FUNC(pa_channel_map_init_auto); + LOAD_FUNC(pa_channel_map_parse); + LOAD_FUNC(pa_channel_map_snprint); + LOAD_FUNC(pa_channel_map_equal); + LOAD_FUNC(pa_context_get_server_info); + LOAD_FUNC(pa_context_get_sink_info_by_name); + LOAD_FUNC(pa_context_get_sink_info_list); + LOAD_FUNC(pa_context_get_source_info_list); + LOAD_FUNC(pa_operation_get_state); + LOAD_FUNC(pa_operation_unref); +#undef LOAD_FUNC +#define LOAD_OPTIONAL_FUNC(x) do { \ + p##x = GetSymbol(pa_handle, #x); \ +} while(0) +#if PA_CHECK_VERSION(0,9,15) + LOAD_OPTIONAL_FUNC(pa_channel_map_superset); + LOAD_OPTIONAL_FUNC(pa_stream_set_buffer_attr_callback); +#endif +#if PA_CHECK_VERSION(0,9,16) + LOAD_OPTIONAL_FUNC(pa_stream_begin_write); +#endif +#undef LOAD_OPTIONAL_FUNC + +#else /* HAVE_DYNLOAD */ + pa_handle = (void*)0xDEADBEEF; +#endif + + if((loop=pa_threaded_mainloop_new()) && + pa_threaded_mainloop_start(loop) >= 0) + { + pa_context *context; + + pa_threaded_mainloop_lock(loop); + context = connect_context(loop, AL_TRUE); + if(context) + { + ret = ALC_TRUE; + + pa_context_disconnect(context); + pa_context_unref(context); + } + pa_threaded_mainloop_unlock(loop); + pa_threaded_mainloop_stop(loop); + } + if(loop) + pa_threaded_mainloop_free(loop); + + if(!ret) + { +#ifdef HAVE_DYNLOAD + CloseLib(pa_handle); +#endif + pa_handle = NULL; + } + } + return ret; +} //}}} + +// PulseAudio Event Callbacks //{{{ +static void stream_state_callback(pa_stream *stream, void *pdata) //{{{ +{ + pa_threaded_mainloop *loop = pdata; + pa_stream_state_t state; + + state = pa_stream_get_state(stream); + if(state == PA_STREAM_READY || !PA_STREAM_IS_GOOD(state)) + pa_threaded_mainloop_signal(loop, 0); +}//}}} + +static void stream_signal_callback(pa_stream *stream, void *pdata) //{{{ +{ + ALCdevice *Device = pdata; + pulse_data *data = Device->ExtraData; + (void)stream; + + pa_threaded_mainloop_signal(data->loop, 0); +}//}}} + +static void stream_buffer_attr_callback(pa_stream *stream, void *pdata) //{{{ +{ + ALCdevice *Device = pdata; + pulse_data *data = Device->ExtraData; + + LockDevice(Device); + + data->attr = *(pa_stream_get_buffer_attr(stream)); + Device->UpdateSize = data->attr.minreq / data->frame_size; + Device->NumUpdates = (data->attr.tlength/data->frame_size) / Device->UpdateSize; + if(Device->NumUpdates <= 1) + { + Device->NumUpdates = 1; + ERR("PulseAudio returned minreq > tlength/2; expect break up\n"); + } + + UnlockDevice(Device); +}//}}} + +static void stream_device_callback(pa_stream *stream, void *pdata) //{{{ +{ + ALCdevice *Device = pdata; + pulse_data *data = Device->ExtraData; + + free(data->device_name); + data->device_name = strdup(pa_stream_get_device_name(stream)); +}//}}} + +static void context_state_callback2(pa_context *context, void *pdata) //{{{ +{ + ALCdevice *Device = pdata; + pulse_data *data = Device->ExtraData; + + if(pa_context_get_state(context) == PA_CONTEXT_FAILED) + { + ERR("Received context failure!\n"); + aluHandleDisconnect(Device); + } + pa_threaded_mainloop_signal(data->loop, 0); +}//}}} + +static void stream_state_callback2(pa_stream *stream, void *pdata) //{{{ +{ + ALCdevice *Device = pdata; + pulse_data *data = Device->ExtraData; + + if(pa_stream_get_state(stream) == PA_STREAM_FAILED) + { + ERR("Received stream failure!\n"); + aluHandleDisconnect(Device); + } + pa_threaded_mainloop_signal(data->loop, 0); +}//}}} + +static void stream_success_callback(pa_stream *stream, int success, void *pdata) //{{{ +{ + ALCdevice *Device = pdata; + pulse_data *data = Device->ExtraData; + (void)stream; + (void)success; + + pa_threaded_mainloop_signal(data->loop, 0); +}//}}} + +static void sink_info_callback(pa_context *context, const pa_sink_info *info, int eol, void *pdata) //{{{ +{ + ALCdevice *device = pdata; + pulse_data *data = device->ExtraData; + char chanmap_str[256] = ""; + const struct { + const char *str; + enum DevFmtChannels chans; + } chanmaps[] = { + { "front-left,front-right,front-center,lfe,rear-left,rear-right,side-left,side-right", + DevFmtX71 }, + { "front-left,front-right,front-center,lfe,rear-center,side-left,side-right", + DevFmtX61 }, + { "front-left,front-right,front-center,lfe,rear-left,rear-right", + DevFmtX51 }, + { "front-left,front-right,front-center,lfe,side-left,side-right", + DevFmtX51Side }, + { "front-left,front-right,rear-left,rear-right", DevFmtQuad }, + { "front-left,front-right", DevFmtStereo }, + { "mono", DevFmtMono }, + { NULL, 0 } + }; + int i; + (void)context; + + if(eol) + { + pa_threaded_mainloop_signal(data->loop, 0); + return; + } + + for(i = 0;chanmaps[i].str;i++) + { + pa_channel_map map; + if(!pa_channel_map_parse(&map, chanmaps[i].str)) + continue; + + if(pa_channel_map_equal(&info->channel_map, &map) +#if PA_CHECK_VERSION(0,9,15) + || (pa_channel_map_superset && + pa_channel_map_superset(&info->channel_map, &map)) +#endif + ) + { + device->FmtChans = chanmaps[i].chans; + return; + } + } + + pa_channel_map_snprint(chanmap_str, sizeof(chanmap_str), &info->channel_map); + ERR("Failed to find format for channel map:\n %s\n", chanmap_str); +}//}}} + +static void sink_device_callback(pa_context *context, const pa_sink_info *info, int eol, void *pdata) //{{{ +{ + pa_threaded_mainloop *loop = pdata; + char str[1024]; + void *temp; + int count; + ALuint i; + + (void)context; + + if(eol) + { + pa_threaded_mainloop_signal(loop, 0); + return; + } + + count = 0; + do { + if(count == 0) + snprintf(str, sizeof(str), "%s", info->description); + else + snprintf(str, sizeof(str), "%s #%d", info->description, count+1); + count++; + + for(i = 0;i < numDevNames;i++) + { + if(strcmp(str, allDevNameMap[i].name) == 0) + break; + } + } while(i != numDevNames); + + temp = realloc(allDevNameMap, (numDevNames+1) * sizeof(*allDevNameMap)); + if(temp) + { + allDevNameMap = temp; + allDevNameMap[numDevNames].name = strdup(str); + allDevNameMap[numDevNames].device_name = strdup(info->name); + numDevNames++; + } +}//}}} + +static void source_device_callback(pa_context *context, const pa_source_info *info, int eol, void *pdata) //{{{ +{ + pa_threaded_mainloop *loop = pdata; + char str[1024]; + void *temp; + int count; + ALuint i; + + (void)context; + + if(eol) + { + pa_threaded_mainloop_signal(loop, 0); + return; + } + + count = 0; + do { + if(count == 0) + snprintf(str, sizeof(str), "%s", info->description); + else + snprintf(str, sizeof(str), "%s #%d", info->description, count+1); + count++; + + for(i = 0;i < numCaptureDevNames;i++) + { + if(strcmp(str, allCaptureDevNameMap[i].name) == 0) + break; + } + } while(i != numCaptureDevNames); + + temp = realloc(allCaptureDevNameMap, (numCaptureDevNames+1) * sizeof(*allCaptureDevNameMap)); + if(temp) + { + allCaptureDevNameMap = temp; + allCaptureDevNameMap[numCaptureDevNames].name = strdup(str); + allCaptureDevNameMap[numCaptureDevNames].device_name = strdup(info->name); + numCaptureDevNames++; + } +}//}}} +//}}} + +// PulseAudio I/O Callbacks //{{{ +static void stream_write_callback(pa_stream *stream, size_t len, void *pdata) //{{{ +{ + ALCdevice *Device = pdata; + pulse_data *data = Device->ExtraData; + (void)stream; + (void)len; + + pa_threaded_mainloop_signal(data->loop, 0); +} //}}} +//}}} + +static ALuint PulseProc(ALvoid *param) +{ + ALCdevice *Device = param; + pulse_data *data = Device->ExtraData; + ssize_t len; + + SetRTPriority(); + + pa_threaded_mainloop_lock(data->loop); + do { + len = (Device->Connected ? pa_stream_writable_size(data->stream) : 0); + len -= len%(Device->UpdateSize*data->frame_size); + if(len == 0) + { + pa_threaded_mainloop_wait(data->loop); + continue; + } + + while(len > 0) + { + size_t newlen = len; + void *buf; + pa_free_cb_t free_func = NULL; + +#if PA_CHECK_VERSION(0,9,16) + if(!pa_stream_begin_write || + pa_stream_begin_write(data->stream, &buf, &newlen) < 0) +#endif + { + buf = pa_xmalloc(newlen); + free_func = pa_xfree; + } + pa_threaded_mainloop_unlock(data->loop); + + aluMixData(Device, buf, newlen/data->frame_size); + + pa_threaded_mainloop_lock(data->loop); + pa_stream_write(data->stream, buf, newlen, free_func, 0, PA_SEEK_RELATIVE); + len -= newlen; + } + } while(Device->Connected && !data->killNow); + pa_threaded_mainloop_unlock(data->loop); + + return 0; +} + +static pa_stream *connect_playback_stream(ALCdevice *device, + pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, + pa_channel_map *chanmap) +{ + pulse_data *data = device->ExtraData; + pa_stream_state_t state; + pa_stream *stream; + + stream = pa_stream_new(data->context, "Playback Stream", spec, chanmap); + if(!stream) + { + ERR("pa_stream_new() failed: %s\n", + pa_strerror(pa_context_errno(data->context))); + return NULL; + } + + pa_stream_set_state_callback(stream, stream_state_callback, data->loop); + + if(pa_stream_connect_playback(stream, data->device_name, attr, flags, NULL, NULL) < 0) + { + ERR("Stream did not connect: %s\n", + pa_strerror(pa_context_errno(data->context))); + pa_stream_unref(stream); + return NULL; + } + + while((state=pa_stream_get_state(stream)) != PA_STREAM_READY) + { + if(!PA_STREAM_IS_GOOD(state)) + { + ERR("Stream did not get ready: %s\n", + pa_strerror(pa_context_errno(data->context))); + pa_stream_unref(stream); + return NULL; + } + + pa_threaded_mainloop_wait(data->loop); + } + pa_stream_set_state_callback(stream, NULL, NULL); + + return stream; +} + +static void probe_devices(ALboolean capture) +{ + pa_threaded_mainloop *loop; + + if(capture == AL_FALSE) + allDevNameMap = malloc(sizeof(DevMap) * 1); + else + allCaptureDevNameMap = malloc(sizeof(DevMap) * 1); + + if((loop=pa_threaded_mainloop_new()) && + pa_threaded_mainloop_start(loop) >= 0) + { + pa_context *context; + + pa_threaded_mainloop_lock(loop); + context = connect_context(loop, AL_FALSE); + if(context) + { + pa_operation *o; + + if(capture == AL_FALSE) + { + allDevNameMap[0].name = strdup(pulse_device); + allDevNameMap[0].device_name = NULL; + numDevNames = 1; + + o = pa_context_get_sink_info_list(context, sink_device_callback, loop); + } + else + { + allCaptureDevNameMap[0].name = strdup(pulse_device); + allCaptureDevNameMap[0].device_name = NULL; + numCaptureDevNames = 1; + + o = pa_context_get_source_info_list(context, source_device_callback, loop); + } + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait(loop); + pa_operation_unref(o); + + pa_context_disconnect(context); + pa_context_unref(context); + } + pa_threaded_mainloop_unlock(loop); + pa_threaded_mainloop_stop(loop); + } + if(loop) + pa_threaded_mainloop_free(loop); +} + + +static ALCboolean pulse_open(ALCdevice *device, const ALCchar *device_name) //{{{ +{ + pulse_data *data = pa_xmalloc(sizeof(pulse_data)); + memset(data, 0, sizeof(*data)); + + if(!(data->loop = pa_threaded_mainloop_new())) + { + ERR("pa_threaded_mainloop_new() failed!\n"); + goto out; + } + if(pa_threaded_mainloop_start(data->loop) < 0) + { + ERR("pa_threaded_mainloop_start() failed\n"); + goto out; + } + + pa_threaded_mainloop_lock(data->loop); + device->ExtraData = data; + + data->context = connect_context(data->loop, AL_FALSE); + if(!data->context) + { + pa_threaded_mainloop_unlock(data->loop); + goto out; + } + pa_context_set_state_callback(data->context, context_state_callback2, device); + + device->szDeviceName = strdup(device_name); + + pa_threaded_mainloop_unlock(data->loop); + return ALC_TRUE; + +out: + if(data->loop) + { + pa_threaded_mainloop_stop(data->loop); + pa_threaded_mainloop_free(data->loop); + } + + device->ExtraData = NULL; + pa_xfree(data); + return ALC_FALSE; +} //}}} + +static void pulse_close(ALCdevice *device) //{{{ +{ + pulse_data *data = device->ExtraData; + + pa_threaded_mainloop_lock(data->loop); + + if(data->stream) + { + pa_stream_disconnect(data->stream); + pa_stream_unref(data->stream); + } + + pa_context_disconnect(data->context); + pa_context_unref(data->context); + + pa_threaded_mainloop_unlock(data->loop); + + pa_threaded_mainloop_stop(data->loop); + pa_threaded_mainloop_free(data->loop); + + DestroyRingBuffer(data->ring); + free(data->device_name); + + device->ExtraData = NULL; + pa_xfree(data); +} //}}} +//}}} + +// OpenAL {{{ +static ALCboolean pulse_open_playback(ALCdevice *device, const ALCchar *device_name) //{{{ +{ + char *pulse_name = NULL; + pa_sample_spec spec; + pulse_data *data; + + if(!allDevNameMap) + probe_devices(AL_FALSE); + + if(!device_name && numDevNames > 0) + device_name = allDevNameMap[0].name; + else + { + ALuint i; + + for(i = 0;i < numDevNames;i++) + { + if(strcmp(device_name, allDevNameMap[i].name) == 0) + { + pulse_name = allDevNameMap[i].device_name; + break; + } + } + if(i == numDevNames) + return ALC_FALSE; + } + + if(pulse_open(device, device_name) == ALC_FALSE) + return ALC_FALSE; + + data = device->ExtraData; + + pa_threaded_mainloop_lock(data->loop); + + spec.format = PA_SAMPLE_S16NE; + spec.rate = 44100; + spec.channels = 2; + + data->device_name = pulse_name; + pa_stream *stream = connect_playback_stream(device, 0, NULL, &spec, NULL); + if(!stream) + { + pa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + if(pa_stream_is_suspended(stream)) + { + ERR("Device is suspended\n"); + pa_stream_disconnect(stream); + pa_stream_unref(stream); + pa_threaded_mainloop_unlock(data->loop); + goto fail; + } + data->device_name = strdup(pa_stream_get_device_name(stream)); + + pa_stream_disconnect(stream); + pa_stream_unref(stream); + + pa_threaded_mainloop_unlock(data->loop); + + return ALC_TRUE; + +fail: + pulse_close(device); + return ALC_FALSE; +} //}}} + +static void pulse_close_playback(ALCdevice *device) //{{{ +{ + pulse_close(device); +} //}}} + +static ALCboolean pulse_reset_playback(ALCdevice *device) //{{{ +{ + pulse_data *data = device->ExtraData; + pa_stream_flags_t flags = 0; + pa_channel_map chanmap; + + pa_threaded_mainloop_lock(data->loop); + + if(!(device->Flags&DEVICE_CHANNELS_REQUEST)) + { + pa_operation *o; + o = pa_context_get_sink_info_by_name(data->context, data->device_name, sink_info_callback, device); + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait(data->loop); + pa_operation_unref(o); + } + if(!(device->Flags&DEVICE_FREQUENCY_REQUEST)) + flags |= PA_STREAM_FIX_RATE; + + data->frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType); + data->attr.prebuf = -1; + data->attr.fragsize = -1; + data->attr.minreq = device->UpdateSize * data->frame_size; + data->attr.tlength = data->attr.minreq * device->NumUpdates; + if(data->attr.tlength < data->attr.minreq*2) + data->attr.tlength = data->attr.minreq*2; + data->attr.maxlength = data->attr.tlength; + flags |= PA_STREAM_EARLY_REQUESTS; + flags |= PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE; + + switch(device->FmtType) + { + case DevFmtByte: + device->FmtType = DevFmtUByte; + /* fall-through */ + case DevFmtUByte: + data->spec.format = PA_SAMPLE_U8; + break; + case DevFmtUShort: + device->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + data->spec.format = PA_SAMPLE_S16NE; + break; + case DevFmtFloat: + data->spec.format = PA_SAMPLE_FLOAT32NE; + break; + } + data->spec.rate = device->Frequency; + data->spec.channels = ChannelsFromDevFmt(device->FmtChans); + + if(pa_sample_spec_valid(&data->spec) == 0) + { + ERR("Invalid sample format\n"); + pa_threaded_mainloop_unlock(data->loop); + return ALC_FALSE; + } + + if(!pa_channel_map_init_auto(&chanmap, data->spec.channels, PA_CHANNEL_MAP_WAVEEX)) + { + ERR("Couldn't build map for channel count (%d)!\n", data->spec.channels); + pa_threaded_mainloop_unlock(data->loop); + return ALC_FALSE; + } + SetDefaultWFXChannelOrder(device); + + data->stream = connect_playback_stream(device, flags, &data->attr, &data->spec, &chanmap); + if(!data->stream) + { + pa_threaded_mainloop_unlock(data->loop); + return ALC_FALSE; + } + + pa_stream_set_state_callback(data->stream, stream_state_callback2, device); + + data->spec = *(pa_stream_get_sample_spec(data->stream)); + if(device->Frequency != data->spec.rate) + { + pa_operation *o; + + if((device->Flags&DEVICE_FREQUENCY_REQUEST)) + ERR("Failed to set frequency %dhz, got %dhz instead\n", device->Frequency, data->spec.rate); + device->Flags &= ~DEVICE_FREQUENCY_REQUEST; + + /* Server updated our playback rate, so modify the buffer attribs + * accordingly. */ + data->attr.minreq = (ALuint64)(data->attr.minreq/data->frame_size) * + data->spec.rate / device->Frequency * data->frame_size; + data->attr.tlength = data->attr.minreq * device->NumUpdates; + data->attr.maxlength = data->attr.tlength; + + o = pa_stream_set_buffer_attr(data->stream, &data->attr, + stream_success_callback, device); + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait(data->loop); + pa_operation_unref(o); + + device->Frequency = data->spec.rate; + } + + stream_buffer_attr_callback(data->stream, device); +#if PA_CHECK_VERSION(0,9,15) + if(pa_stream_set_buffer_attr_callback) + pa_stream_set_buffer_attr_callback(data->stream, stream_buffer_attr_callback, device); +#endif + pa_stream_set_moved_callback(data->stream, stream_device_callback, device); + pa_stream_set_write_callback(data->stream, stream_write_callback, device); + pa_stream_set_underflow_callback(data->stream, stream_signal_callback, device); + + data->thread = StartThread(PulseProc, device); + if(!data->thread) + { +#if PA_CHECK_VERSION(0,9,15) + if(pa_stream_set_buffer_attr_callback) + pa_stream_set_buffer_attr_callback(data->stream, NULL, NULL); +#endif + pa_stream_set_moved_callback(data->stream, NULL, NULL); + pa_stream_set_write_callback(data->stream, NULL, NULL); + pa_stream_set_underflow_callback(data->stream, NULL, NULL); + pa_stream_disconnect(data->stream); + pa_stream_unref(data->stream); + data->stream = NULL; + + pa_threaded_mainloop_unlock(data->loop); + return ALC_FALSE; + } + + pa_threaded_mainloop_unlock(data->loop); + return ALC_TRUE; +} //}}} + +static void pulse_stop_playback(ALCdevice *device) //{{{ +{ + pulse_data *data = device->ExtraData; + + if(!data->stream) + return; + + data->killNow = AL_TRUE; + if(data->thread) + { + pa_threaded_mainloop_signal(data->loop, 0); + StopThread(data->thread); + data->thread = NULL; + } + data->killNow = AL_FALSE; + + pa_threaded_mainloop_lock(data->loop); + +#if PA_CHECK_VERSION(0,9,15) + if(pa_stream_set_buffer_attr_callback) + pa_stream_set_buffer_attr_callback(data->stream, NULL, NULL); +#endif + pa_stream_set_moved_callback(data->stream, NULL, NULL); + pa_stream_set_write_callback(data->stream, NULL, NULL); + pa_stream_set_underflow_callback(data->stream, NULL, NULL); + pa_stream_disconnect(data->stream); + pa_stream_unref(data->stream); + data->stream = NULL; + + pa_threaded_mainloop_unlock(data->loop); +} //}}} + + +static ALCboolean pulse_open_capture(ALCdevice *device, const ALCchar *device_name) //{{{ +{ + char *pulse_name = NULL; + pulse_data *data; + pa_stream_flags_t flags = 0; + pa_stream_state_t state; + pa_channel_map chanmap; + + if(!allCaptureDevNameMap) + probe_devices(AL_TRUE); + + if(!device_name && numCaptureDevNames > 0) + device_name = allCaptureDevNameMap[0].name; + else + { + ALuint i; + + for(i = 0;i < numCaptureDevNames;i++) + { + if(strcmp(device_name, allCaptureDevNameMap[i].name) == 0) + { + pulse_name = allCaptureDevNameMap[i].device_name; + break; + } + } + if(i == numCaptureDevNames) + return ALC_FALSE; + } + + if(pulse_open(device, device_name) == ALC_FALSE) + return ALC_FALSE; + + data = device->ExtraData; + pa_threaded_mainloop_lock(data->loop); + + data->samples = device->UpdateSize * device->NumUpdates; + data->frame_size = FrameSizeFromDevFmt(device->FmtChans, device->FmtType); + if(data->samples < 100 * device->Frequency / 1000) + data->samples = 100 * device->Frequency / 1000; + + if(!(data->ring = CreateRingBuffer(data->frame_size, data->samples))) + { + pa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + data->attr.minreq = -1; + data->attr.prebuf = -1; + data->attr.maxlength = data->samples * data->frame_size; + data->attr.tlength = -1; + data->attr.fragsize = minu(data->samples, 50*device->Frequency/1000) * + data->frame_size; + + data->spec.rate = device->Frequency; + data->spec.channels = ChannelsFromDevFmt(device->FmtChans); + + switch(device->FmtType) + { + case DevFmtUByte: + data->spec.format = PA_SAMPLE_U8; + break; + case DevFmtShort: + data->spec.format = PA_SAMPLE_S16NE; + break; + case DevFmtFloat: + data->spec.format = PA_SAMPLE_FLOAT32NE; + break; + case DevFmtByte: + case DevFmtUShort: + ERR("Capture format type %#x capture not supported on PulseAudio\n", device->FmtType); + pa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + if(pa_sample_spec_valid(&data->spec) == 0) + { + ERR("Invalid sample format\n"); + pa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + if(!pa_channel_map_init_auto(&chanmap, data->spec.channels, PA_CHANNEL_MAP_WAVEEX)) + { + ERR("Couldn't build map for channel count (%d)!\n", data->spec.channels); + pa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + data->stream = pa_stream_new(data->context, "Capture Stream", &data->spec, &chanmap); + if(!data->stream) + { + ERR("pa_stream_new() failed: %s\n", + pa_strerror(pa_context_errno(data->context))); + + pa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + pa_stream_set_state_callback(data->stream, stream_state_callback, data->loop); + + flags |= PA_STREAM_START_CORKED|PA_STREAM_ADJUST_LATENCY; + if(pa_stream_connect_record(data->stream, pulse_name, &data->attr, flags) < 0) + { + ERR("Stream did not connect: %s\n", + pa_strerror(pa_context_errno(data->context))); + + pa_stream_unref(data->stream); + data->stream = NULL; + + pa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + while((state=pa_stream_get_state(data->stream)) != PA_STREAM_READY) + { + if(!PA_STREAM_IS_GOOD(state)) + { + ERR("Stream did not get ready: %s\n", + pa_strerror(pa_context_errno(data->context))); + + pa_stream_unref(data->stream); + data->stream = NULL; + + pa_threaded_mainloop_unlock(data->loop); + goto fail; + } + + pa_threaded_mainloop_wait(data->loop); + } + pa_stream_set_state_callback(data->stream, stream_state_callback2, device); + + pa_threaded_mainloop_unlock(data->loop); + return ALC_TRUE; + +fail: + pulse_close(device); + return ALC_FALSE; +} //}}} + +static void pulse_close_capture(ALCdevice *device) //{{{ +{ + pulse_close(device); +} //}}} + +static void pulse_start_capture(ALCdevice *device) //{{{ +{ + pulse_data *data = device->ExtraData; + pa_operation *o; + + pa_threaded_mainloop_lock(data->loop); + o = pa_stream_cork(data->stream, 0, stream_success_callback, device); + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait(data->loop); + pa_operation_unref(o); + pa_threaded_mainloop_unlock(data->loop); +} //}}} + +static void pulse_stop_capture(ALCdevice *device) //{{{ +{ + pulse_data *data = device->ExtraData; + pa_operation *o; + + pa_threaded_mainloop_lock(data->loop); + o = pa_stream_cork(data->stream, 1, stream_success_callback, device); + while(pa_operation_get_state(o) == PA_OPERATION_RUNNING) + pa_threaded_mainloop_wait(data->loop); + pa_operation_unref(o); + pa_threaded_mainloop_unlock(data->loop); +} //}}} + +static ALCuint pulse_available_samples(ALCdevice *device) //{{{ +{ + pulse_data *data = device->ExtraData; + size_t samples; + + pa_threaded_mainloop_lock(data->loop); + /* Capture is done in fragment-sized chunks, so we loop until we get all + * that's available */ + samples = (device->Connected ? pa_stream_readable_size(data->stream) : 0); + while(samples > 0) + { + const void *buf; + size_t length; + + if(pa_stream_peek(data->stream, &buf, &length) < 0) + { + ERR("pa_stream_peek() failed: %s\n", + pa_strerror(pa_context_errno(data->context))); + break; + } + + WriteRingBuffer(data->ring, buf, length/data->frame_size); + samples -= length; + + pa_stream_drop(data->stream); + } + pa_threaded_mainloop_unlock(data->loop); + + return RingBufferSize(data->ring); +} //}}} + +static void pulse_capture_samples(ALCdevice *device, ALCvoid *buffer, ALCuint samples) //{{{ +{ + pulse_data *data = device->ExtraData; + + if(pulse_available_samples(device) >= samples) + ReadRingBuffer(data->ring, buffer, samples); + else + alcSetError(device, ALC_INVALID_VALUE); +} //}}} + + +static const BackendFuncs pulse_funcs = { //{{{ + pulse_open_playback, + pulse_close_playback, + pulse_reset_playback, + pulse_stop_playback, + pulse_open_capture, + pulse_close_capture, + pulse_start_capture, + pulse_stop_capture, + pulse_capture_samples, + pulse_available_samples +}; //}}} + +ALCboolean alc_pulse_init(BackendFuncs *func_list) //{{{ +{ + if(!pulse_load()) + return ALC_FALSE; + + *func_list = pulse_funcs; + + pulse_ctx_flags = 0; + if(!GetConfigValueBool("pulse", "spawn-server", 0)) + pulse_ctx_flags |= PA_CONTEXT_NOAUTOSPAWN; + + return ALC_TRUE; +} //}}} + +void alc_pulse_deinit(void) //{{{ +{ + ALuint i; + + for(i = 0;i < numDevNames;++i) + { + free(allDevNameMap[i].name); + free(allDevNameMap[i].device_name); + } + free(allDevNameMap); + allDevNameMap = NULL; + numDevNames = 0; + + for(i = 0;i < numCaptureDevNames;++i) + { + free(allCaptureDevNameMap[i].name); + free(allCaptureDevNameMap[i].device_name); + } + free(allCaptureDevNameMap); + allCaptureDevNameMap = NULL; + numCaptureDevNames = 0; + +#ifdef HAVE_DYNLOAD + if(pa_handle) + CloseLib(pa_handle); + pa_handle = NULL; +#endif +} //}}} + +void alc_pulse_probe(enum DevProbe type) //{{{ +{ + pa_threaded_mainloop *loop; + ALuint i; + + switch(type) + { + case DEVICE_PROBE: + if((loop=pa_threaded_mainloop_new()) && + pa_threaded_mainloop_start(loop) >= 0) + { + pa_context *context; + + pa_threaded_mainloop_lock(loop); + context = connect_context(loop, AL_FALSE); + if(context) + { + AppendDeviceList(pulse_device); + + pa_context_disconnect(context); + pa_context_unref(context); + } + pa_threaded_mainloop_unlock(loop); + pa_threaded_mainloop_stop(loop); + } + if(loop) + pa_threaded_mainloop_free(loop); + break; + + case ALL_DEVICE_PROBE: + for(i = 0;i < numDevNames;++i) + { + free(allDevNameMap[i].name); + free(allDevNameMap[i].device_name); + } + free(allDevNameMap); + allDevNameMap = NULL; + numDevNames = 0; + + probe_devices(AL_FALSE); + + for(i = 0;i < numDevNames;i++) + AppendAllDeviceList(allDevNameMap[i].name); + break; + + case CAPTURE_DEVICE_PROBE: + for(i = 0;i < numCaptureDevNames;++i) + { + free(allCaptureDevNameMap[i].name); + free(allCaptureDevNameMap[i].device_name); + } + free(allCaptureDevNameMap); + allCaptureDevNameMap = NULL; + numCaptureDevNames = 0; + + probe_devices(AL_TRUE); + + for(i = 0;i < numCaptureDevNames;i++) + AppendCaptureDeviceList(allCaptureDevNameMap[i].name); + break; + } +} //}}} +//}}} diff --git a/Alc/backends/sndio.c b/Alc/backends/sndio.c new file mode 100644 index 00000000..5f9ad0cd --- /dev/null +++ b/Alc/backends/sndio.c @@ -0,0 +1,381 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + +#include <sndio.h> + + +static const ALCchar sndio_device[] = "SndIO Default"; + + +static void *sndio_handle; +#ifdef HAVE_DYNLOAD +#define MAKE_FUNC(x) static typeof(x) * p##x +MAKE_FUNC(sio_initpar); +MAKE_FUNC(sio_open); +MAKE_FUNC(sio_close); +MAKE_FUNC(sio_setpar); +MAKE_FUNC(sio_getpar); +MAKE_FUNC(sio_getcap); +MAKE_FUNC(sio_onmove); +MAKE_FUNC(sio_write); +MAKE_FUNC(sio_read); +MAKE_FUNC(sio_start); +MAKE_FUNC(sio_stop); +MAKE_FUNC(sio_nfds); +MAKE_FUNC(sio_pollfd); +MAKE_FUNC(sio_revents); +MAKE_FUNC(sio_eof); +MAKE_FUNC(sio_setvol); +MAKE_FUNC(sio_onvol); + +#define sio_initpar psio_initpar +#define sio_open psio_open +#define sio_close psio_close +#define sio_setpar psio_setpar +#define sio_getpar psio_getpar +#define sio_getcap psio_getcap +#define sio_onmove psio_onmove +#define sio_write psio_write +#define sio_read psio_read +#define sio_start psio_start +#define sio_stop psio_stop +#define sio_nfds psio_nfds +#define sio_pollfd psio_pollfd +#define sio_revents psio_revents +#define sio_eof psio_eof +#define sio_setvol psio_setvol +#define sio_onvol psio_onvol +#endif + + +static ALCboolean sndio_load(void) +{ + if(!sndio_handle) + { +#ifdef HAVE_DYNLOAD + sndio_handle = LoadLib("libsndio.so"); + if(!sndio_handle) + return ALC_FALSE; + +#define LOAD_FUNC(f) do { \ + p##f = GetSymbol(sndio_handle, #f); \ + if(p##f == NULL) { \ + CloseLib(sndio_handle); \ + sndio_handle = NULL; \ + return ALC_FALSE; \ + } \ +} while(0) + LOAD_FUNC(sio_initpar); + LOAD_FUNC(sio_open); + LOAD_FUNC(sio_close); + LOAD_FUNC(sio_setpar); + LOAD_FUNC(sio_getpar); + LOAD_FUNC(sio_getcap); + LOAD_FUNC(sio_onmove); + LOAD_FUNC(sio_write); + LOAD_FUNC(sio_read); + LOAD_FUNC(sio_start); + LOAD_FUNC(sio_stop); + LOAD_FUNC(sio_nfds); + LOAD_FUNC(sio_pollfd); + LOAD_FUNC(sio_revents); + LOAD_FUNC(sio_eof); + LOAD_FUNC(sio_setvol); + LOAD_FUNC(sio_onvol); +#undef LOAD_FUNC +#else + sndio_handle = (void*)0xDEADBEEF; +#endif + } + return ALC_TRUE; +} + + +typedef struct { + struct sio_hdl *sndHandle; + + ALvoid *mix_data; + ALsizei data_size; + + volatile int killNow; + ALvoid *thread; +} sndio_data; + + +static ALuint sndio_proc(ALvoid *ptr) +{ + ALCdevice *device = ptr; + sndio_data *data = device->ExtraData; + ALsizei frameSize; + size_t wrote; + + SetRTPriority(); + + frameSize = FrameSizeFromDevFmt(device->FmtChans, device->FmtType); + + while(!data->killNow && device->Connected) + { + ALsizei len = data->data_size; + ALubyte *WritePtr = data->mix_data; + + aluMixData(device, WritePtr, len/frameSize); + while(len > 0 && !data->killNow) + { + wrote = sio_write(data->sndHandle, WritePtr, len); + if(wrote == 0) + { + ERR("sio_write failed\n"); + aluHandleDisconnect(device); + break; + } + + len -= wrote; + WritePtr += wrote; + } + } + + return 0; +} + + + +static ALCboolean sndio_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + sndio_data *data; + + if(!deviceName) + deviceName = sndio_device; + else if(strcmp(deviceName, sndio_device) != 0) + return ALC_FALSE; + + data = calloc(1, sizeof(*data)); + data->killNow = 0; + + data->sndHandle = sio_open(NULL, SIO_PLAY, 0); + if(data->sndHandle == NULL) + { + free(data); + ERR("Could not open device\n"); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + device->ExtraData = data; + + return ALC_TRUE; +} + +static void sndio_close_playback(ALCdevice *device) +{ + sndio_data *data = device->ExtraData; + + sio_close(data->sndHandle); + free(data); + device->ExtraData = NULL; +} + +static ALCboolean sndio_reset_playback(ALCdevice *device) +{ + sndio_data *data = device->ExtraData; + struct sio_par par; + + sio_initpar(&par); + + par.rate = device->Frequency; + + par.pchan = ((device->FmtChans != DevFmtMono) ? 2 : 1); + + switch(device->FmtType) + { + case DevFmtByte: + par.bits = 8; + par.sig = 1; + break; + case DevFmtUByte: + par.bits = 8; + par.sig = 0; + break; + case DevFmtFloat: + device->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + par.bits = 16; + par.sig = 1; + break; + case DevFmtUShort: + par.bits = 16; + par.sig = 0; + break; + } + par.le = SIO_LE_NATIVE; + + par.round = device->UpdateSize; + par.appbufsz = device->UpdateSize * (device->NumUpdates-1); + if(!par.appbufsz) par.appbufsz = device->UpdateSize; + + + if(!sio_setpar(data->sndHandle, &par) || !sio_getpar(data->sndHandle, &par)) + { + ERR("Failed to set device parameters\n"); + return ALC_FALSE; + } + + if(par.rate != device->Frequency) + { + if((device->Flags&DEVICE_FREQUENCY_REQUEST)) + ERR("Failed to set frequency %uhz, got %uhz instead\n", device->Frequency, par.rate); + device->Flags &= ~DEVICE_FREQUENCY_REQUEST; + device->Frequency = par.rate; + } + + if(par.pchan != ChannelsFromDevFmt(device->FmtChans)) + { + if(par.pchan != 1 && par.pchan != 2) + { + ERR("Unhandled channel count: %u\n", par.pchan); + return ALC_FALSE; + } + if((device->Flags&DEVICE_CHANNELS_REQUEST)) + ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(device->FmtChans), par.pchan); + device->Flags &= ~DEVICE_CHANNELS_REQUEST; + device->FmtChans = ((par.pchan==1) ? DevFmtMono : DevFmtStereo); + } + + if(par.bits != par.bps*8) + { + ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8); + return ALC_FALSE; + } + + if(par.bits == 8 && par.sig == 1) + device->FmtType = DevFmtByte; + else if(par.bits == 8 && par.sig == 0) + device->FmtType = DevFmtUByte; + else if(par.bits == 16 && par.sig == 1) + device->FmtType = DevFmtShort; + else if(par.bits == 16 && par.sig == 0) + device->FmtType = DevFmtUShort; + else + { + ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits); + return ALC_FALSE; + } + + + device->UpdateSize = par.round; + device->NumUpdates = (par.bufsz/par.round) + 1; + + SetDefaultChannelOrder(device); + + + if(!sio_start(data->sndHandle)) + { + ERR("Error starting playback\n"); + return ALC_FALSE; + } + + data->data_size = device->UpdateSize * par.bps * par.pchan; + data->mix_data = calloc(1, data->data_size); + + data->thread = StartThread(sndio_proc, device); + if(data->thread == NULL) + { + sio_stop(data->sndHandle); + free(data->mix_data); + data->mix_data = NULL; + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void sndio_stop_playback(ALCdevice *device) +{ + sndio_data *data = device->ExtraData; + + if(!data->thread) + return; + + data->killNow = 1; + StopThread(data->thread); + data->thread = NULL; + + data->killNow = 0; + if(!sio_stop(data->sndHandle)) + ERR("Error stopping device\n"); + + free(data->mix_data); + data->mix_data = NULL; +} + + +static const BackendFuncs sndio_funcs = { + sndio_open_playback, + sndio_close_playback, + sndio_reset_playback, + sndio_stop_playback, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +ALCboolean alc_sndio_init(BackendFuncs *func_list) +{ + if(!sndio_load()) + return ALC_FALSE; + *func_list = sndio_funcs; + return ALC_TRUE; +} + +void alc_sndio_deinit(void) +{ +#ifdef HAVE_DYNLOAD + if(sndio_handle) + CloseLib(sndio_handle); + sndio_handle = NULL; +#endif +} + +void alc_sndio_probe(enum DevProbe type) +{ + switch(type) + { + case DEVICE_PROBE: + AppendDeviceList(sndio_device); + break; + case ALL_DEVICE_PROBE: + AppendAllDeviceList(sndio_device); + break; + case CAPTURE_DEVICE_PROBE: + break; + } +} diff --git a/Alc/backends/solaris.c b/Alc/backends/solaris.c new file mode 100644 index 00000000..b2b8196d --- /dev/null +++ b/Alc/backends/solaris.c @@ -0,0 +1,282 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <memory.h> +#include <unistd.h> +#include <errno.h> +#include <math.h> +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + +#include <sys/audioio.h> + + +static const ALCchar solaris_device[] = "Solaris Default"; + +typedef struct { + int fd; + volatile int killNow; + ALvoid *thread; + + ALubyte *mix_data; + int data_size; +} solaris_data; + + +static ALuint SolarisProc(ALvoid *ptr) +{ + ALCdevice *pDevice = (ALCdevice*)ptr; + solaris_data *data = (solaris_data*)pDevice->ExtraData; + ALint frameSize; + int wrote; + + SetRTPriority(); + + frameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + + while(!data->killNow && pDevice->Connected) + { + ALint len = data->data_size; + ALubyte *WritePtr = data->mix_data; + + aluMixData(pDevice, WritePtr, len/frameSize); + while(len > 0 && !data->killNow) + { + wrote = write(data->fd, WritePtr, len); + if(wrote < 0) + { + if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) + { + ERR("write failed: %s\n", strerror(errno)); + aluHandleDisconnect(pDevice); + break; + } + + Sleep(1); + continue; + } + + len -= wrote; + WritePtr += wrote; + } + } + + return 0; +} + + +static ALCboolean solaris_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + char driver[64]; + solaris_data *data; + + strncpy(driver, GetConfigValue("solaris", "device", "/dev/audio"), sizeof(driver)-1); + driver[sizeof(driver)-1] = 0; + + if(!deviceName) + deviceName = solaris_device; + else if(strcmp(deviceName, solaris_device) != 0) + return ALC_FALSE; + + data = (solaris_data*)calloc(1, sizeof(solaris_data)); + data->killNow = 0; + + data->fd = open(driver, O_WRONLY); + if(data->fd == -1) + { + free(data); + ERR("Could not open %s: %s\n", driver, strerror(errno)); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + device->ExtraData = data; + return ALC_TRUE; +} + +static void solaris_close_playback(ALCdevice *device) +{ + solaris_data *data = (solaris_data*)device->ExtraData; + + close(data->fd); + free(data); + device->ExtraData = NULL; +} + +static ALCboolean solaris_reset_playback(ALCdevice *device) +{ + solaris_data *data = (solaris_data*)device->ExtraData; + audio_info_t info; + ALuint frameSize; + int numChannels; + + AUDIO_INITINFO(&info); + + info.play.sample_rate = device->Frequency; + + if(device->FmtChans != DevFmtMono) + device->FmtChans = DevFmtStereo; + numChannels = ChannelsFromDevFmt(device->FmtChans); + info.play.channels = numChannels; + + switch(device->FmtType) + { + case DevFmtByte: + info.play.precision = 8; + info.play.encoding = AUDIO_ENCODING_LINEAR; + break; + case DevFmtUByte: + info.play.precision = 8; + info.play.encoding = AUDIO_ENCODING_LINEAR8; + break; + case DevFmtUShort: + case DevFmtFloat: + device->FmtType = DevFmtShort; + /* fall-through */ + case DevFmtShort: + info.play.precision = 16; + info.play.encoding = AUDIO_ENCODING_LINEAR; + break; + } + + frameSize = numChannels * BytesFromDevFmt(device->FmtType); + info.play.buffer_size = device->UpdateSize*device->NumUpdates * frameSize; + + if(ioctl(data->fd, AUDIO_SETINFO, &info) < 0) + { + ERR("ioctl failed: %s\n", strerror(errno)); + return ALC_FALSE; + } + + if(ChannelsFromDevFmt(device->FmtChans) != info.play.channels) + { + ERR("Could not set %d channels, got %d instead\n", ChannelsFromDevFmt(device->FmtChans), info.play.channels); + return ALC_FALSE; + } + + if(!((info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && + device->FmtType == DevFmtByte) || + (info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && + device->FmtType == DevFmtUByte) || + (info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && + device->FmtType == DevFmtShort))) + { + ERR("Could not set %#x sample type, got %d (%#x)\n", + device->FmtType, info.play.precision, info.play.encoding); + return ALC_FALSE; + } + + if(device->Frequency != info.play.sample_rate) + { + if((device->Flags&DEVICE_FREQUENCY_REQUEST)) + ERR("Failed to set requested frequency %dhz, got %dhz instead\n", device->Frequency, info.play.sample_rate); + device->Flags &= ~DEVICE_FREQUENCY_REQUEST; + device->Frequency = info.play.sample_rate; + } + device->UpdateSize = (info.play.buffer_size/device->NumUpdates) + 1; + + data->data_size = device->UpdateSize * frameSize; + data->mix_data = calloc(1, data->data_size); + + SetDefaultChannelOrder(device); + + data->thread = StartThread(SolarisProc, device); + if(data->thread == NULL) + { + free(data->mix_data); + data->mix_data = NULL; + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void solaris_stop_playback(ALCdevice *device) +{ + solaris_data *data = (solaris_data*)device->ExtraData; + + if(!data->thread) + return; + + data->killNow = 1; + StopThread(data->thread); + data->thread = NULL; + + data->killNow = 0; + if(ioctl(data->fd, AUDIO_DRAIN) < 0) + ERR("Error draining device: %s\n", strerror(errno)); + + free(data->mix_data); + data->mix_data = NULL; +} + + +static const BackendFuncs solaris_funcs = { + solaris_open_playback, + solaris_close_playback, + solaris_reset_playback, + solaris_stop_playback, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +ALCboolean alc_solaris_init(BackendFuncs *func_list) +{ + *func_list = solaris_funcs; + return ALC_TRUE; +} + +void alc_solaris_deinit(void) +{ +} + +void alc_solaris_probe(enum DevProbe type) +{ +#ifdef HAVE_STAT + struct stat buf; + if(stat(GetConfigValue("solaris", "device", "/dev/audio"), &buf) != 0) + return; +#endif + + switch(type) + { + case DEVICE_PROBE: + AppendDeviceList(solaris_device); + break; + case ALL_DEVICE_PROBE: + AppendAllDeviceList(solaris_device); + break; + case CAPTURE_DEVICE_PROBE: + break; + } +} diff --git a/Alc/backends/wave.c b/Alc/backends/wave.c new file mode 100644 index 00000000..465087ce --- /dev/null +++ b/Alc/backends/wave.c @@ -0,0 +1,355 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <memory.h> +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + + +typedef struct { + FILE *f; + long DataStart; + + ALvoid *buffer; + ALuint size; + + volatile int killNow; + ALvoid *thread; +} wave_data; + + +static const ALCchar waveDevice[] = "Wave File Writer"; + +static const ALubyte SUBTYPE_PCM[] = { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, + 0x00, 0x38, 0x9b, 0x71 +}; +static const ALubyte SUBTYPE_FLOAT[] = { + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, + 0x00, 0x38, 0x9b, 0x71 +}; + +static const ALuint channel_masks[] = { + 0, /* invalid */ + 0x4, /* Mono */ + 0x1 | 0x2, /* Stereo */ + 0, /* 3 channel */ + 0x1 | 0x2 | 0x10 | 0x20, /* Quad */ + 0, /* 5 channel */ + 0x1 | 0x2 | 0x4 | 0x8 | 0x10 | 0x20, /* 5.1 */ + 0x1 | 0x2 | 0x4 | 0x8 | 0x100 | 0x200 | 0x400, /* 6.1 */ + 0x1 | 0x2 | 0x4 | 0x8 | 0x10 | 0x20 | 0x200 | 0x400, /* 7.1 */ +}; + + +static void fwrite16le(ALushort val, FILE *f) +{ + fputc(val&0xff, f); + fputc((val>>8)&0xff, f); +} + +static void fwrite32le(ALuint val, FILE *f) +{ + fputc(val&0xff, f); + fputc((val>>8)&0xff, f); + fputc((val>>16)&0xff, f); + fputc((val>>24)&0xff, f); +} + + +static ALuint WaveProc(ALvoid *ptr) +{ + ALCdevice *pDevice = (ALCdevice*)ptr; + wave_data *data = (wave_data*)pDevice->ExtraData; + ALuint frameSize; + ALuint now, start; + ALuint64 avail, done; + size_t fs; + union { + short s; + char b[sizeof(short)]; + } uSB; + const ALuint restTime = (ALuint64)pDevice->UpdateSize * 1000 / + pDevice->Frequency / 2; + + uSB.s = 1; + frameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + + done = 0; + start = timeGetTime(); + while(!data->killNow && pDevice->Connected) + { + now = timeGetTime(); + + avail = (ALuint64)(now-start) * pDevice->Frequency / 1000; + if(avail < done) + { + /* Timer wrapped. Add the remainder of the cycle to the available + * count and reset the number of samples done */ + avail += (ALuint64)0xFFFFFFFFu*pDevice->Frequency/1000 - done; + done = 0; + } + if(avail-done < pDevice->UpdateSize) + { + Sleep(restTime); + continue; + } + + while(avail-done >= pDevice->UpdateSize) + { + aluMixData(pDevice, data->buffer, pDevice->UpdateSize); + done += pDevice->UpdateSize; + + if(uSB.b[0] != 1) + { + ALuint bytesize = BytesFromDevFmt(pDevice->FmtType); + ALubyte *bytes = data->buffer; + ALuint i; + + if(bytesize == 1) + { + for(i = 0;i < data->size;i++) + fputc(bytes[i], data->f); + } + else if(bytesize == 2) + { + for(i = 0;i < data->size;i++) + fputc(bytes[i^1], data->f); + } + else if(bytesize == 4) + { + for(i = 0;i < data->size;i++) + fputc(bytes[i^3], data->f); + } + } + else + fs = fwrite(data->buffer, frameSize, pDevice->UpdateSize, + data->f); + if(ferror(data->f)) + { + ERR("Error writing to file\n"); + aluHandleDisconnect(pDevice); + break; + } + } + } + + return 0; +} + +static ALCboolean wave_open_playback(ALCdevice *device, const ALCchar *deviceName) +{ + wave_data *data; + const char *fname; + + fname = GetConfigValue("wave", "file", ""); + if(!fname[0]) + return ALC_FALSE; + + if(!deviceName) + deviceName = waveDevice; + else if(strcmp(deviceName, waveDevice) != 0) + return ALC_FALSE; + + data = (wave_data*)calloc(1, sizeof(wave_data)); + + data->f = fopen(fname, "wb"); + if(!data->f) + { + free(data); + ERR("Could not open file '%s': %s\n", fname, strerror(errno)); + return ALC_FALSE; + } + + device->szDeviceName = strdup(deviceName); + device->ExtraData = data; + return ALC_TRUE; +} + +static void wave_close_playback(ALCdevice *device) +{ + wave_data *data = (wave_data*)device->ExtraData; + + fclose(data->f); + free(data); + device->ExtraData = NULL; +} + +static ALCboolean wave_reset_playback(ALCdevice *device) +{ + wave_data *data = (wave_data*)device->ExtraData; + ALuint channels=0, bits=0; + size_t val; + + fseek(data->f, 0, SEEK_SET); + clearerr(data->f); + + switch(device->FmtType) + { + case DevFmtByte: + device->FmtType = DevFmtUByte; + break; + case DevFmtUShort: + device->FmtType = DevFmtShort; + break; + case DevFmtUByte: + case DevFmtShort: + case DevFmtFloat: + break; + } + bits = BytesFromDevFmt(device->FmtType) * 8; + channels = ChannelsFromDevFmt(device->FmtChans); + + fprintf(data->f, "RIFF"); + fwrite32le(0xFFFFFFFF, data->f); // 'RIFF' header len; filled in at close + + fprintf(data->f, "WAVE"); + + fprintf(data->f, "fmt "); + fwrite32le(40, data->f); // 'fmt ' header len; 40 bytes for EXTENSIBLE + + // 16-bit val, format type id (extensible: 0xFFFE) + fwrite16le(0xFFFE, data->f); + // 16-bit val, channel count + fwrite16le(channels, data->f); + // 32-bit val, frequency + fwrite32le(device->Frequency, data->f); + // 32-bit val, bytes per second + fwrite32le(device->Frequency * channels * bits / 8, data->f); + // 16-bit val, frame size + fwrite16le(channels * bits / 8, data->f); + // 16-bit val, bits per sample + fwrite16le(bits, data->f); + // 16-bit val, extra byte count + fwrite16le(22, data->f); + // 16-bit val, valid bits per sample + fwrite16le(bits, data->f); + // 32-bit val, channel mask + fwrite32le(channel_masks[channels], data->f); + // 16 byte GUID, sub-type format + val = fwrite(((bits==32) ? SUBTYPE_FLOAT : SUBTYPE_PCM), 1, 16, data->f); + + fprintf(data->f, "data"); + fwrite32le(0xFFFFFFFF, data->f); // 'data' header len; filled in at close + + if(ferror(data->f)) + { + ERR("Error writing header: %s\n", strerror(errno)); + return ALC_FALSE; + } + + data->DataStart = ftell(data->f); + + data->size = device->UpdateSize * channels * bits / 8; + data->buffer = malloc(data->size); + if(!data->buffer) + { + ERR("Buffer malloc failed\n"); + return ALC_FALSE; + } + + SetDefaultWFXChannelOrder(device); + + data->thread = StartThread(WaveProc, device); + if(data->thread == NULL) + { + free(data->buffer); + data->buffer = NULL; + return ALC_FALSE; + } + + return ALC_TRUE; +} + +static void wave_stop_playback(ALCdevice *device) +{ + wave_data *data = (wave_data*)device->ExtraData; + ALuint dataLen; + long size; + + if(!data->thread) + return; + + data->killNow = 1; + StopThread(data->thread); + data->thread = NULL; + + data->killNow = 0; + + free(data->buffer); + data->buffer = NULL; + + size = ftell(data->f); + if(size > 0) + { + dataLen = size - data->DataStart; + if(fseek(data->f, data->DataStart-4, SEEK_SET) == 0) + fwrite32le(dataLen, data->f); // 'data' header len + if(fseek(data->f, 4, SEEK_SET) == 0) + fwrite32le(size-8, data->f); // 'WAVE' header len + } +} + + +static const BackendFuncs wave_funcs = { + wave_open_playback, + wave_close_playback, + wave_reset_playback, + wave_stop_playback, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +ALCboolean alc_wave_init(BackendFuncs *func_list) +{ + *func_list = wave_funcs; + return ALC_TRUE; +} + +void alc_wave_deinit(void) +{ +} + +void alc_wave_probe(enum DevProbe type) +{ + if(!ConfigValueExists("wave", "file")) + return; + + switch(type) + { + case DEVICE_PROBE: + AppendDeviceList(waveDevice); + break; + case ALL_DEVICE_PROBE: + AppendAllDeviceList(waveDevice); + break; + case CAPTURE_DEVICE_PROBE: + break; + } +} diff --git a/Alc/backends/winmm.c b/Alc/backends/winmm.c new file mode 100644 index 00000000..445dd51e --- /dev/null +++ b/Alc/backends/winmm.c @@ -0,0 +1,780 @@ +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2007 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +#include "config.h" + +#define _WIN32_WINNT 0x0500 +#include <stdlib.h> +#include <stdio.h> +#include <memory.h> + +#include <windows.h> +#include <mmsystem.h> + +#include "alMain.h" +#include "AL/al.h" +#include "AL/alc.h" + + +typedef struct { + // MMSYSTEM Device + volatile ALboolean bWaveShutdown; + HANDLE hWaveThreadEvent; + HANDLE hWaveThread; + DWORD ulWaveThreadID; + LONG lWaveBuffersCommitted; + WAVEHDR WaveBuffer[4]; + + union { + HWAVEIN In; + HWAVEOUT Out; + } hWaveHandle; + + ALuint Frequency; + + RingBuffer *pRing; +} WinMMData; + + +static const ALCchar woDefault[] = "WaveOut Default"; + +static ALCchar **PlaybackDeviceList; +static ALuint NumPlaybackDevices; +static ALCchar **CaptureDeviceList; +static ALuint NumCaptureDevices; + + +static void ProbePlaybackDevices(void) +{ + ALuint i; + + for(i = 0;i < NumPlaybackDevices;i++) + free(PlaybackDeviceList[i]); + + NumPlaybackDevices = waveOutGetNumDevs(); + PlaybackDeviceList = realloc(PlaybackDeviceList, sizeof(ALCchar*) * NumPlaybackDevices); + for(i = 0;i < NumPlaybackDevices;i++) + { + WAVEOUTCAPS WaveCaps; + + PlaybackDeviceList[i] = NULL; + if(waveOutGetDevCaps(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) + { + char name[1024]; + ALuint count, j; + + count = 0; + do { + if(count == 0) + snprintf(name, sizeof(name), "%s", WaveCaps.szPname); + else + snprintf(name, sizeof(name), "%s #%d", WaveCaps.szPname, count+1); + count++; + + for(j = 0;j < i;j++) + { + if(strcmp(name, PlaybackDeviceList[j]) == 0) + break; + } + } while(j != i); + + PlaybackDeviceList[i] = strdup(name); + } + } +} + +static void ProbeCaptureDevices(void) +{ + ALuint i; + + for(i = 0;i < NumCaptureDevices;i++) + free(CaptureDeviceList[i]); + + NumCaptureDevices = waveInGetNumDevs(); + CaptureDeviceList = realloc(CaptureDeviceList, sizeof(ALCchar*) * NumCaptureDevices); + for(i = 0;i < NumCaptureDevices;i++) + { + WAVEINCAPS WaveInCaps; + + CaptureDeviceList[i] = NULL; + if(waveInGetDevCaps(i, &WaveInCaps, sizeof(WAVEINCAPS)) == MMSYSERR_NOERROR) + { + char name[1024]; + ALuint count, j; + + count = 0; + do { + if(count == 0) + snprintf(name, sizeof(name), "%s", WaveInCaps.szPname); + else + snprintf(name, sizeof(name), "%s #%d", WaveInCaps.szPname, count+1); + count++; + + for(j = 0;j < i;j++) + { + if(strcmp(name, CaptureDeviceList[j]) == 0) + break; + } + } while(j != i); + + CaptureDeviceList[i] = strdup(name); + } + } +} + + +/* + WaveOutProc + + Posts a message to 'PlaybackThreadProc' everytime a WaveOut Buffer is completed and + returns to the application (for more data) +*/ +static void CALLBACK WaveOutProc(HWAVEOUT hDevice,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2) +{ + ALCdevice *pDevice = (ALCdevice*)dwInstance; + WinMMData *pData = pDevice->ExtraData; + + (void)hDevice; + (void)dwParam2; + + if(uMsg != WOM_DONE) + return; + + // Decrement number of buffers in use + InterlockedDecrement(&pData->lWaveBuffersCommitted); + + if(pData->bWaveShutdown == AL_FALSE) + { + // Notify Wave Processor Thread that a Wave Header has returned + PostThreadMessage(pData->ulWaveThreadID, uMsg, 0, dwParam1); + } + else + { + if(pData->lWaveBuffersCommitted == 0) + { + // Post 'Quit' Message to WaveOut Processor Thread + PostThreadMessage(pData->ulWaveThreadID, WM_QUIT, 0, 0); + } + } +} + +/* + PlaybackThreadProc + + Used by "MMSYSTEM" Device. Called when a WaveOut buffer has used up its + audio data. +*/ +static DWORD WINAPI PlaybackThreadProc(LPVOID lpParameter) +{ + ALCdevice *pDevice = (ALCdevice*)lpParameter; + WinMMData *pData = pDevice->ExtraData; + LPWAVEHDR pWaveHdr; + ALuint FrameSize; + MSG msg; + + FrameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + + SetRTPriority(); + + while(GetMessage(&msg, NULL, 0, 0)) + { + if(msg.message != WOM_DONE || pData->bWaveShutdown) + continue; + + pWaveHdr = ((LPWAVEHDR)msg.lParam); + + aluMixData(pDevice, pWaveHdr->lpData, pWaveHdr->dwBufferLength/FrameSize); + + // Send buffer back to play more data + waveOutWrite(pData->hWaveHandle.Out, pWaveHdr, sizeof(WAVEHDR)); + InterlockedIncrement(&pData->lWaveBuffersCommitted); + } + + // Signal Wave Thread completed event + if(pData->hWaveThreadEvent) + SetEvent(pData->hWaveThreadEvent); + + ExitThread(0); + + return 0; +} + +/* + WaveInProc + + Posts a message to 'CaptureThreadProc' everytime a WaveIn Buffer is completed and + returns to the application (with more data) +*/ +static void CALLBACK WaveInProc(HWAVEIN hDevice,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2) +{ + ALCdevice *pDevice = (ALCdevice*)dwInstance; + WinMMData *pData = pDevice->ExtraData; + + (void)hDevice; + (void)dwParam2; + + if(uMsg != WIM_DATA) + return; + + // Decrement number of buffers in use + InterlockedDecrement(&pData->lWaveBuffersCommitted); + + if(pData->bWaveShutdown == AL_FALSE) + { + // Notify Wave Processor Thread that a Wave Header has returned + PostThreadMessage(pData->ulWaveThreadID,uMsg,0,dwParam1); + } + else + { + if(pData->lWaveBuffersCommitted == 0) + { + // Post 'Quit' Message to WaveIn Processor Thread + PostThreadMessage(pData->ulWaveThreadID,WM_QUIT,0,0); + } + } +} + +/* + CaptureThreadProc + + Used by "MMSYSTEM" Device. Called when a WaveIn buffer had been filled with new + audio data. +*/ +static DWORD WINAPI CaptureThreadProc(LPVOID lpParameter) +{ + ALCdevice *pDevice = (ALCdevice*)lpParameter; + WinMMData *pData = pDevice->ExtraData; + LPWAVEHDR pWaveHdr; + ALuint FrameSize; + MSG msg; + + FrameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); + + while(GetMessage(&msg, NULL, 0, 0)) + { + if(msg.message != WIM_DATA || pData->bWaveShutdown) + continue; + + pWaveHdr = ((LPWAVEHDR)msg.lParam); + + WriteRingBuffer(pData->pRing, (ALubyte*)pWaveHdr->lpData, + pWaveHdr->dwBytesRecorded/FrameSize); + + // Send buffer back to capture more data + waveInAddBuffer(pData->hWaveHandle.In,pWaveHdr,sizeof(WAVEHDR)); + InterlockedIncrement(&pData->lWaveBuffersCommitted); + } + + // Signal Wave Thread completed event + if(pData->hWaveThreadEvent) + SetEvent(pData->hWaveThreadEvent); + + ExitThread(0); + + return 0; +} + + +static ALCboolean WinMMOpenPlayback(ALCdevice *pDevice, const ALCchar *deviceName) +{ + WAVEFORMATEX wfexFormat; + WinMMData *pData = NULL; + UINT lDeviceID = 0; + MMRESULT res; + ALuint i = 0; + + // Find the Device ID matching the deviceName if valid + if(!deviceName || strcmp(deviceName, woDefault) == 0) + lDeviceID = WAVE_MAPPER; + else + { + if(!PlaybackDeviceList) + ProbePlaybackDevices(); + + for(i = 0;i < NumPlaybackDevices;i++) + { + if(PlaybackDeviceList[i] && + strcmp(deviceName, PlaybackDeviceList[i]) == 0) + { + lDeviceID = i; + break; + } + } + if(i == NumPlaybackDevices) + return ALC_FALSE; + } + + pData = calloc(1, sizeof(*pData)); + if(!pData) + { + alcSetError(pDevice, ALC_OUT_OF_MEMORY); + return ALC_FALSE; + } + pDevice->ExtraData = pData; + + if(pDevice->FmtChans != DevFmtMono) + { + if((pDevice->Flags&DEVICE_CHANNELS_REQUEST) && + pDevice->FmtChans != DevFmtStereo) + { + ERR("Failed to set %s, got Stereo instead\n", DevFmtChannelsString(pDevice->FmtChans)); + pDevice->Flags &= ~DEVICE_CHANNELS_REQUEST; + } + pDevice->FmtChans = DevFmtStereo; + } + switch(pDevice->FmtType) + { + case DevFmtByte: + pDevice->FmtType = DevFmtUByte; + break; + case DevFmtUShort: + case DevFmtFloat: + pDevice->FmtType = DevFmtShort; + break; + case DevFmtUByte: + case DevFmtShort: + break; + } + + memset(&wfexFormat, 0, sizeof(WAVEFORMATEX)); + wfexFormat.wFormatTag = WAVE_FORMAT_PCM; + wfexFormat.nChannels = ChannelsFromDevFmt(pDevice->FmtChans); + wfexFormat.wBitsPerSample = BytesFromDevFmt(pDevice->FmtType) * 8; + wfexFormat.nBlockAlign = wfexFormat.wBitsPerSample * + wfexFormat.nChannels / 8; + wfexFormat.nSamplesPerSec = pDevice->Frequency; + wfexFormat.nAvgBytesPerSec = wfexFormat.nSamplesPerSec * + wfexFormat.nBlockAlign; + wfexFormat.cbSize = 0; + + if((res=waveOutOpen(&pData->hWaveHandle.Out, lDeviceID, &wfexFormat, (DWORD_PTR)&WaveOutProc, (DWORD_PTR)pDevice, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR) + { + ERR("waveOutOpen failed: %u\n", res); + goto failure; + } + + pData->hWaveThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if(pData->hWaveThreadEvent == NULL) + { + ERR("CreateEvent failed: %lu\n", GetLastError()); + goto failure; + } + + pData->Frequency = pDevice->Frequency; + + pDevice->szDeviceName = strdup((lDeviceID==WAVE_MAPPER) ? woDefault : + PlaybackDeviceList[lDeviceID]); + return ALC_TRUE; + +failure: + if(pData->hWaveThreadEvent) + CloseHandle(pData->hWaveThreadEvent); + + if(pData->hWaveHandle.Out) + waveOutClose(pData->hWaveHandle.Out); + + free(pData); + pDevice->ExtraData = NULL; + return ALC_FALSE; +} + +static void WinMMClosePlayback(ALCdevice *device) +{ + WinMMData *pData = (WinMMData*)device->ExtraData; + + // Close the Wave device + CloseHandle(pData->hWaveThreadEvent); + pData->hWaveThreadEvent = 0; + + waveOutClose(pData->hWaveHandle.Out); + pData->hWaveHandle.Out = 0; + + free(pData); + device->ExtraData = NULL; +} + +static ALCboolean WinMMResetPlayback(ALCdevice *device) +{ + WinMMData *pData = (WinMMData*)device->ExtraData; + ALbyte *BufferData; + ALint lBufferSize; + ALuint i; + + pData->hWaveThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PlaybackThreadProc, (LPVOID)device, 0, &pData->ulWaveThreadID); + if(pData->hWaveThread == NULL) + return ALC_FALSE; + + device->UpdateSize = (ALuint)((ALuint64)device->UpdateSize * + pData->Frequency / device->Frequency); + if(device->Frequency != pData->Frequency) + { + if((device->Flags&DEVICE_FREQUENCY_REQUEST)) + ERR("WinMM does not support changing sample rates (wanted %dhz, got %dhz)\n", device->Frequency, pData->Frequency); + device->Flags &= ~DEVICE_FREQUENCY_REQUEST; + device->Frequency = pData->Frequency; + } + + SetDefaultWFXChannelOrder(device); + + pData->lWaveBuffersCommitted = 0; + + // Create 4 Buffers + lBufferSize = device->UpdateSize*device->NumUpdates / 4; + lBufferSize *= FrameSizeFromDevFmt(device->FmtChans, device->FmtType); + + BufferData = calloc(4, lBufferSize); + for(i = 0;i < 4;i++) + { + memset(&pData->WaveBuffer[i], 0, sizeof(WAVEHDR)); + pData->WaveBuffer[i].dwBufferLength = lBufferSize; + pData->WaveBuffer[i].lpData = ((i==0) ? (LPSTR)BufferData : + (pData->WaveBuffer[i-1].lpData + + pData->WaveBuffer[i-1].dwBufferLength)); + waveOutPrepareHeader(pData->hWaveHandle.Out, &pData->WaveBuffer[i], sizeof(WAVEHDR)); + waveOutWrite(pData->hWaveHandle.Out, &pData->WaveBuffer[i], sizeof(WAVEHDR)); + InterlockedIncrement(&pData->lWaveBuffersCommitted); + } + + return ALC_TRUE; +} + +static void WinMMStopPlayback(ALCdevice *device) +{ + WinMMData *pData = (WinMMData*)device->ExtraData; + void *buffer = NULL; + int i; + + if(pData->hWaveThread == NULL) + return; + + // Set flag to stop processing headers + pData->bWaveShutdown = AL_TRUE; + + // Wait for signal that Wave Thread has been destroyed + WaitForSingleObjectEx(pData->hWaveThreadEvent, 5000, FALSE); + + CloseHandle(pData->hWaveThread); + pData->hWaveThread = 0; + + pData->bWaveShutdown = AL_FALSE; + + // Release the wave buffers + for(i = 0;i < 4;i++) + { + waveOutUnprepareHeader(pData->hWaveHandle.Out, &pData->WaveBuffer[i], sizeof(WAVEHDR)); + if(i == 0) buffer = pData->WaveBuffer[i].lpData; + pData->WaveBuffer[i].lpData = NULL; + } + free(buffer); +} + + +static ALCboolean WinMMOpenCapture(ALCdevice *pDevice, const ALCchar *deviceName) +{ + WAVEFORMATEX wfexCaptureFormat; + DWORD ulCapturedDataSize; + WinMMData *pData = NULL; + UINT lDeviceID = 0; + ALbyte *BufferData; + ALint lBufferSize; + MMRESULT res; + ALuint i; + + if(!CaptureDeviceList) + ProbeCaptureDevices(); + + // Find the Device ID matching the deviceName if valid + if(deviceName) + { + for(i = 0;i < NumCaptureDevices;i++) + { + if(CaptureDeviceList[i] && + strcmp(deviceName, CaptureDeviceList[i]) == 0) + { + lDeviceID = i; + break; + } + } + } + else + { + for(i = 0;i < NumCaptureDevices;i++) + { + if(CaptureDeviceList[i]) + { + lDeviceID = i; + break; + } + } + } + if(i == NumCaptureDevices) + return ALC_FALSE; + + pData = calloc(1, sizeof(*pData)); + if(!pData) + { + alcSetError(pDevice, ALC_OUT_OF_MEMORY); + return ALC_FALSE; + } + pDevice->ExtraData = pData; + + if((pDevice->FmtChans != DevFmtMono && pDevice->FmtChans != DevFmtStereo) || + (pDevice->FmtType != DevFmtUByte && pDevice->FmtType != DevFmtShort)) + { + alcSetError(pDevice, ALC_INVALID_ENUM); + goto failure; + } + + memset(&wfexCaptureFormat, 0, sizeof(WAVEFORMATEX)); + wfexCaptureFormat.wFormatTag = WAVE_FORMAT_PCM; + wfexCaptureFormat.nChannels = ChannelsFromDevFmt(pDevice->FmtChans); + wfexCaptureFormat.wBitsPerSample = BytesFromDevFmt(pDevice->FmtType) * 8; + wfexCaptureFormat.nBlockAlign = wfexCaptureFormat.wBitsPerSample * + wfexCaptureFormat.nChannels / 8; + wfexCaptureFormat.nSamplesPerSec = pDevice->Frequency; + wfexCaptureFormat.nAvgBytesPerSec = wfexCaptureFormat.nSamplesPerSec * + wfexCaptureFormat.nBlockAlign; + wfexCaptureFormat.cbSize = 0; + + if((res=waveInOpen(&pData->hWaveHandle.In, lDeviceID, &wfexCaptureFormat, (DWORD_PTR)&WaveInProc, (DWORD_PTR)pDevice, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR) + { + ERR("waveInOpen failed: %u\n", res); + goto failure; + } + + pData->hWaveThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if(pData->hWaveThreadEvent == NULL) + { + ERR("CreateEvent failed: %lu\n", GetLastError()); + goto failure; + } + + pData->Frequency = pDevice->Frequency; + + // Allocate circular memory buffer for the captured audio + ulCapturedDataSize = pDevice->UpdateSize*pDevice->NumUpdates; + + // Make sure circular buffer is at least 100ms in size + if(ulCapturedDataSize < (wfexCaptureFormat.nSamplesPerSec / 10)) + ulCapturedDataSize = wfexCaptureFormat.nSamplesPerSec / 10; + + pData->pRing = CreateRingBuffer(wfexCaptureFormat.nBlockAlign, ulCapturedDataSize); + if(!pData->pRing) + goto failure; + + pData->lWaveBuffersCommitted = 0; + + // Create 4 Buffers of 50ms each + lBufferSize = wfexCaptureFormat.nAvgBytesPerSec / 20; + lBufferSize -= (lBufferSize % wfexCaptureFormat.nBlockAlign); + + BufferData = calloc(4, lBufferSize); + if(!BufferData) + goto failure; + + for(i = 0;i < 4;i++) + { + memset(&pData->WaveBuffer[i], 0, sizeof(WAVEHDR)); + pData->WaveBuffer[i].dwBufferLength = lBufferSize; + pData->WaveBuffer[i].lpData = ((i==0) ? (LPSTR)BufferData : + (pData->WaveBuffer[i-1].lpData + + pData->WaveBuffer[i-1].dwBufferLength)); + pData->WaveBuffer[i].dwFlags = 0; + pData->WaveBuffer[i].dwLoops = 0; + waveInPrepareHeader(pData->hWaveHandle.In, &pData->WaveBuffer[i], sizeof(WAVEHDR)); + waveInAddBuffer(pData->hWaveHandle.In, &pData->WaveBuffer[i], sizeof(WAVEHDR)); + InterlockedIncrement(&pData->lWaveBuffersCommitted); + } + + pData->hWaveThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CaptureThreadProc, (LPVOID)pDevice, 0, &pData->ulWaveThreadID); + if (pData->hWaveThread == NULL) + goto failure; + + pDevice->szDeviceName = strdup(CaptureDeviceList[lDeviceID]); + return ALC_TRUE; + +failure: + if(pData->hWaveThread) + CloseHandle(pData->hWaveThread); + + for(i = 0;i < 4;i++) + { + if(pData->WaveBuffer[i].lpData) + { + waveInUnprepareHeader(pData->hWaveHandle.In, &pData->WaveBuffer[i], sizeof(WAVEHDR)); + if(i == 0) + free(pData->WaveBuffer[i].lpData); + } + } + + if(pData->pRing) + DestroyRingBuffer(pData->pRing); + + if(pData->hWaveThreadEvent) + CloseHandle(pData->hWaveThreadEvent); + + if(pData->hWaveHandle.In) + waveInClose(pData->hWaveHandle.In); + + free(pData); + pDevice->ExtraData = NULL; + return ALC_FALSE; +} + +static void WinMMCloseCapture(ALCdevice *pDevice) +{ + WinMMData *pData = (WinMMData*)pDevice->ExtraData; + void *buffer = NULL; + int i; + + // Call waveOutReset to shutdown wave device + pData->bWaveShutdown = AL_TRUE; + waveInReset(pData->hWaveHandle.In); + + // Wait for signal that Wave Thread has been destroyed + WaitForSingleObjectEx(pData->hWaveThreadEvent, 5000, FALSE); + + CloseHandle(pData->hWaveThread); + pData->hWaveThread = 0; + + // Release the wave buffers + for(i = 0;i < 4;i++) + { + waveInUnprepareHeader(pData->hWaveHandle.In, &pData->WaveBuffer[i], sizeof(WAVEHDR)); + if(i == 0) buffer = pData->WaveBuffer[i].lpData; + pData->WaveBuffer[i].lpData = NULL; + } + free(buffer); + + DestroyRingBuffer(pData->pRing); + pData->pRing = NULL; + + // Close the Wave device + CloseHandle(pData->hWaveThreadEvent); + pData->hWaveThreadEvent = 0; + + waveInClose(pData->hWaveHandle.In); + pData->hWaveHandle.In = 0; + + free(pData); + pDevice->ExtraData = NULL; +} + +static void WinMMStartCapture(ALCdevice *pDevice) +{ + WinMMData *pData = (WinMMData*)pDevice->ExtraData; + waveInStart(pData->hWaveHandle.In); +} + +static void WinMMStopCapture(ALCdevice *pDevice) +{ + WinMMData *pData = (WinMMData*)pDevice->ExtraData; + waveInStop(pData->hWaveHandle.In); +} + +static ALCuint WinMMAvailableSamples(ALCdevice *pDevice) +{ + WinMMData *pData = (WinMMData*)pDevice->ExtraData; + return RingBufferSize(pData->pRing); +} + +static void WinMMCaptureSamples(ALCdevice *pDevice, ALCvoid *pBuffer, ALCuint lSamples) +{ + WinMMData *pData = (WinMMData*)pDevice->ExtraData; + + if(WinMMAvailableSamples(pDevice) >= lSamples) + ReadRingBuffer(pData->pRing, pBuffer, lSamples); + else + alcSetError(pDevice, ALC_INVALID_VALUE); +} + + +static const BackendFuncs WinMMFuncs = { + WinMMOpenPlayback, + WinMMClosePlayback, + WinMMResetPlayback, + WinMMStopPlayback, + WinMMOpenCapture, + WinMMCloseCapture, + WinMMStartCapture, + WinMMStopCapture, + WinMMCaptureSamples, + WinMMAvailableSamples +}; + +ALCboolean alcWinMMInit(BackendFuncs *FuncList) +{ + *FuncList = WinMMFuncs; + return ALC_TRUE; +} + +void alcWinMMDeinit() +{ + ALuint lLoop; + + for(lLoop = 0;lLoop < NumPlaybackDevices;lLoop++) + free(PlaybackDeviceList[lLoop]); + free(PlaybackDeviceList); + PlaybackDeviceList = NULL; + + NumPlaybackDevices = 0; + + + for(lLoop = 0; lLoop < NumCaptureDevices; lLoop++) + free(CaptureDeviceList[lLoop]); + free(CaptureDeviceList); + CaptureDeviceList = NULL; + + NumCaptureDevices = 0; +} + +void alcWinMMProbe(enum DevProbe type) +{ + ALuint i; + + switch(type) + { + case DEVICE_PROBE: + ProbePlaybackDevices(); + if(NumPlaybackDevices > 0) + AppendDeviceList(woDefault); + break; + + case ALL_DEVICE_PROBE: + ProbePlaybackDevices(); + if(NumPlaybackDevices > 0) + AppendAllDeviceList(woDefault); + for(i = 0;i < NumPlaybackDevices;i++) + { + if(PlaybackDeviceList[i]) + AppendAllDeviceList(PlaybackDeviceList[i]); + } + break; + + case CAPTURE_DEVICE_PROBE: + ProbeCaptureDevices(); + for(i = 0;i < NumCaptureDevices;i++) + { + if(CaptureDeviceList[i]) + AppendCaptureDeviceList(CaptureDeviceList[i]); + } + break; + } +} |