From 8b6a5d19d01ddf0657a64634ccd66b6fde66b735 Mon Sep 17 00:00:00 2001 From: Serge Ziryukin Date: Mon, 31 Aug 2009 10:26:22 +0300 Subject: openal output plugin --- Makefile.am | 4 + configure.ac | 25 +++++ src/output/openal_plugin.c | 267 +++++++++++++++++++++++++++++++++++++++++++++ src/output_list.c | 4 + 4 files changed, 300 insertions(+) create mode 100644 src/output/openal_plugin.c diff --git a/Makefile.am b/Makefile.am index b017d970a..5a6a8dafa 100644 --- a/Makefile.am +++ b/Makefile.am @@ -590,6 +590,10 @@ OUTPUT_SRC += src/output/oss_plugin.c MIXER_SRC += src/mixer/oss_mixer.c endif +if HAVE_OPENAL +OUTPUT_SRC += src/output/openal_plugin.c +endif + if HAVE_OSX OUTPUT_SRC += src/output/osx_plugin.c endif diff --git a/configure.ac b/configure.ac index e730c6977..b74fbf9b0 100644 --- a/configure.ac +++ b/configure.ac @@ -711,6 +711,11 @@ AC_ARG_ENABLE(oss, [disable OSS support (default: enable)]),, enable_oss=yes) +AC_ARG_ENABLE(openal, + AS_HELP_STRING([--enable-openal], + [enable OpenAL support (default: disable)]),, + enable_openal=no) + AC_ARG_ENABLE(pulse, AS_HELP_STRING([--enable-pulse], [enable support for the PulseAudio sound server]),, @@ -779,6 +784,19 @@ fi AM_CONDITIONAL(HAVE_OSS, test x$enable_oss = xyes) +if test x$enable_openal = xyes; then + PKG_CHECK_MODULES([OPENAL], [openal], + AC_DEFINE(HAVE_OPENAL, 1, [Define for OpenAL support]), + enable_openal=no) +fi + +if test x$enable_openal = xyes; then + MPD_CFLAGS="$MPD_CFLAGS $OPENAL_CFLAGS" + MPD_LIBS="$MPD_LIBS $OPENAL_LIBS" +fi + +AM_CONDITIONAL(HAVE_OPENAL, test x$enable_openal = xyes) + if test x$enable_fifo = xyes; then AC_CHECK_FUNC([mkfifo], [enable_fifo=yes;AC_DEFINE([HAVE_FIFO], 1, @@ -1297,6 +1315,12 @@ else echo " OSS support ...................disabled" fi +if test x$enable_openal = xyes; then + echo " OpenAL support ................enabled" +else + echo " OpenAL support ................disabled" +fi + if test x$enable_osx = xyes; then echo " OS X support ..................enabled" else @@ -1338,6 +1362,7 @@ echo "" if test x$enable_ao = xno && test x$enable_oss = xno && + test x$enable_openal = xno && test x$enable_shout = xno && test x$enable_recorder_output = xno && test x$enable_httpd_output = xno && diff --git a/src/output/openal_plugin.c b/src/output/openal_plugin.c new file mode 100644 index 000000000..2d581ebbf --- /dev/null +++ b/src/output/openal_plugin.c @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../output_api.h" +#include "../timer.h" + +#include + +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "openal" + +/* should be enough for buffer size = 2048 */ +#define NUM_BUFFERS 16 + +struct openal_data { + const char *device_name; + ALCdevice *device; + ALCcontext *context; + Timer *timer; + ALuint buffers[NUM_BUFFERS]; + int filled; + ALuint source; + ALenum format; + ALuint frequency; +}; + +static inline GQuark +openal_output_quark(void) +{ + return g_quark_from_static_string("openal_output"); +} + +static ALenum +openal_audio_format(struct audio_format *audio_format) +{ + /* Only 8 and 16 bit samples are supported */ + if (audio_format->bits != 16 && audio_format->bits != 8) + audio_format->bits = 16; + + switch (audio_format->bits) + { + case 16: + if (audio_format->channels == 2) + return AL_FORMAT_STEREO16; + if (audio_format->channels == 1) + return AL_FORMAT_MONO16; + break; + + case 8: + if (audio_format->channels == 2) + return AL_FORMAT_STEREO8; + if (audio_format->channels == 1) + return AL_FORMAT_MONO8; + break; + } + + return 0; +} + +static bool +openal_setup_context(struct openal_data *od, + GError **error) +{ + od->device = alcOpenDevice(od->device_name); + + if (od->device == NULL) { + g_set_error(error, openal_output_quark(), 0, + "Error opening OpenAL device \"%s\"\n", + od->device_name); + return false; + } + + od->context = alcCreateContext(od->device, NULL); + + if (od->context == NULL) { + g_set_error(error, openal_output_quark(), 0, + "Error creating context for \"%s\"\n", + od->device_name); + alcCloseDevice(od->device); + return false; + } + + return true; +} + +static void +openal_unqueue_buffers(struct openal_data *od) +{ + ALint num; + ALuint buffer; + + alGetSourcei(od->source, AL_BUFFERS_QUEUED, &num); + + while (num--) { + alSourceUnqueueBuffers(od->source, 1, &buffer); + } +} + +static void * +openal_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, + G_GNUC_UNUSED GError **error) +{ + const char *device_name = config_get_block_string(param, "device", NULL); + struct openal_data *od; + + od = g_new(struct openal_data, 1); + od->device_name = device_name; + + if (device_name == NULL) { + device_name = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); + } + + return od; +} + +static void +openal_finish(void *data) +{ + struct openal_data *od = data; + + g_free(od); +} + +static bool +openal_open(void *data, struct audio_format *audio_format, + GError **error) +{ + struct openal_data *od = data; + + od->format = openal_audio_format(audio_format); + + if (!od->format) { + g_set_error(error, openal_output_quark(), 0, + "Unsupported audio format (%i channels, %i bps)", + audio_format->channels, + audio_format->bits); + return false; + } + + if (!openal_setup_context(od, error)) { + return false; + } + + alcMakeContextCurrent(od->context); + alGenBuffers(NUM_BUFFERS, od->buffers); + + if (alGetError() != AL_NO_ERROR) { + g_set_error(error, openal_output_quark(), 0, + "Failed to generate buffers"); + return false; + } + + alGenSources(1, &od->source); + + if (alGetError() != AL_NO_ERROR) { + g_set_error(error, openal_output_quark(), 0, + "Failed to generate source"); + alDeleteBuffers(NUM_BUFFERS, od->buffers); + return false; + } + + od->filled = 0; + od->timer = timer_new(audio_format); + od->frequency = audio_format->sample_rate; + + return true; +} + +static void +openal_close(void *data) +{ + struct openal_data *od = data; + + timer_free(od->timer); + alcMakeContextCurrent(od->context); + alDeleteSources(1, &od->source); + alDeleteBuffers(NUM_BUFFERS, od->buffers); + alcDestroyContext(od->context); + alcCloseDevice(od->device); +} + +static size_t +openal_play(void *data, const void *chunk, size_t size, + G_GNUC_UNUSED GError **error) +{ + struct openal_data *od = data; + ALuint buffer; + ALint num, state; + + if (alcGetCurrentContext() != od->context) { + alcMakeContextCurrent(od->context); + } + + alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num); + + if (od->filled < NUM_BUFFERS) { + /* fill all buffers */ + buffer = od->buffers[od->filled]; + od->filled++; + } else { + /* wait for processed buffer */ + while (num < 1) { + if (!od->timer->started) { + timer_start(od->timer); + } else { + timer_sync(od->timer); + } + + timer_add(od->timer, size); + + alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num); + } + + alSourceUnqueueBuffers(od->source, 1, &buffer); + } + + alBufferData(buffer, od->format, chunk, size, od->frequency); + alSourceQueueBuffers(od->source, 1, &buffer); + alGetSourcei(od->source, AL_SOURCE_STATE, &state); + + if (state != AL_PLAYING) { + alSourcePlay(od->source); + } + + return size; +} + +static void +openal_cancel(void *data) +{ + struct openal_data *od = data; + + od->filled = 0; + alcMakeContextCurrent(od->context); + alSourceStop(od->source); + openal_unqueue_buffers(od); +} + +const struct audio_output_plugin openal_output_plugin = { + .name = "openal", + .init = openal_init, + .finish = openal_finish, + .open = openal_open, + .close = openal_close, + .play = openal_play, + .cancel = openal_cancel, +}; diff --git a/src/output_list.c b/src/output_list.c index 74a9be81c..476701a1a 100644 --- a/src/output_list.c +++ b/src/output_list.c @@ -28,6 +28,7 @@ extern const struct audio_output_plugin pipe_output_plugin; extern const struct audio_output_plugin alsaPlugin; extern const struct audio_output_plugin ao_output_plugin; extern const struct audio_output_plugin oss_output_plugin; +extern const struct audio_output_plugin openal_output_plugin; extern const struct audio_output_plugin osxPlugin; extern const struct audio_output_plugin solaris_output_plugin; extern const struct audio_output_plugin pulse_plugin; @@ -56,6 +57,9 @@ const struct audio_output_plugin *audio_output_plugins[] = { #ifdef HAVE_OSS &oss_output_plugin, #endif +#ifdef HAVE_OPENAL + &openal_output_plugin, +#endif #ifdef HAVE_OSX &osxPlugin, #endif -- cgit v1.2.3