aboutsummaryrefslogtreecommitdiffstats
path: root/Alc/backends
diff options
context:
space:
mode:
Diffstat (limited to 'Alc/backends')
-rw-r--r--Alc/backends/alsa.c1138
-rw-r--r--Alc/backends/coreaudio.c719
-rw-r--r--Alc/backends/dsound.c627
-rw-r--r--Alc/backends/loopback.c77
-rw-r--r--Alc/backends/mmdevapi.c775
-rw-r--r--Alc/backends/null.c164
-rw-r--r--Alc/backends/opensl.c425
-rw-r--r--Alc/backends/oss.c536
-rw-r--r--Alc/backends/portaudio.c449
-rw-r--r--Alc/backends/pulseaudio.c1430
-rw-r--r--Alc/backends/sndio.c381
-rw-r--r--Alc/backends/solaris.c282
-rw-r--r--Alc/backends/wave.c355
-rw-r--r--Alc/backends/winmm.c780
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;
+ }
+}