diff --git a/configure.ac b/configure.ac index dc68eae..32d14dc 100644 --- a/configure.ac +++ b/configure.ac @@ -134,7 +134,7 @@ dnl dnl audio backend dnl AC_ARG_WITH(audio, - [AC_HELP_STRING([--with-audio=@<:@auto/pulse/none@:>@], + [AC_HELP_STRING([--with-audio=@<:@auto/pulse/oss/none@:>@], [audio backend to use])],, [with_audio=alsa]) @@ -169,6 +169,14 @@ if test "$with_audio" = "pulse"; then fi fi +dnl Assume OSS is available if ALSA wasn't found and we're "auto". +if test "$with_audio" = "auto" -o "$with_audio" = "oss"; then + with_audio="oss" + AUDIO_CFLAGS= + AUDIO_LIBS= + AUDIO_TYPE=oss +fi + dnl If all else fails, fall back to none. if test "$with_audio" = "none"; then AUDIO_CFLAGS= diff --git a/swfdec-gtk/swfdec_playback_oss.c b/swfdec-gtk/swfdec_playback_oss.c new file mode 100644 index 0000000..af07485 --- /dev/null +++ b/swfdec-gtk/swfdec_playback_oss.c @@ -0,0 +1,265 @@ +/* Swfdec + * Copyright © 2006 Benjamin Otte + * Copyright © 2007 Eric Anholt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include "swfdec_playback.h" + +/** @file Implements swfdec audio playback by opening /dev/dsp per stream + * and playing out through that. + * + * Allowing multiple access to /dev/dsp is not required by the OSS API spec, + * but FreeBSD's sound system lets you, which is what this file was written + * for. + */ + +/*** DEFINITIONS ***/ + +struct _SwfdecPlayback { + SwfdecPlayer * player; + GList * streams; /* all Stream objects */ + GMainContext * context; /* context we work in */ +}; + +typedef struct { + SwfdecPlayback * sound; /* reference to sound object */ + SwfdecAudio * audio; /* the audio we play back */ + int dsp_fd; + int fragsize; /* Audio fragment size */ + GSource * source; /* source for writing data */ + guint offset; /* offset into sound */ +} Stream; + +/* Size of one of our audio samples, in bytes */ +#define SAMPLESIZE 2 +#define CHANNELS 2 + +/*** STREAMS ***/ + +static gboolean +handle_stream (GIOChannel *source, GIOCondition cond, gpointer data) +{ + Stream *stream = data; + char *frag = malloc(stream->fragsize); + + if (frag == NULL) { + g_printerr ("Failed to allocate fragment of size %d\n", + stream->fragsize); + return FALSE; + } + + while (TRUE) { + int ret; + audio_buf_info spaceinfo; + + ret = ioctl(stream->dsp_fd, SNDCTL_DSP_GETOSPACE, &spaceinfo); + if (ret == -1) { + g_printerr ("Failed to get output buffer availability\n"); + free(frag); + return FALSE; + } + + if (spaceinfo.fragments == 0) + break; + + memset (frag, 0, stream->fragsize); + swfdec_audio_render (stream->audio, (gint16 *)frag, stream->offset, + stream->fragsize / SAMPLESIZE / CHANNELS); + + ret = write (stream->dsp_fd, frag, stream->fragsize); + if (ret != stream->fragsize) { + g_printerr ("Failed to write fragment\n"); + free(frag); + return FALSE; + } + + stream->offset += stream->fragsize / SAMPLESIZE / CHANNELS; + } + + free(frag); + + return TRUE; +} + +static void +swfdec_playback_stream_open (SwfdecPlayback *sound, SwfdecAudio *audio) +{ + GIOChannel *channel; + Stream *stream; + guint rate; + int dsp_fd, ret, format, channels, fragsize; + + dsp_fd = open("/dev/dsp", O_WRONLY | O_NONBLOCK); + if (dsp_fd == -1) { + g_printerr ("Failed to open /dev/dsp\n"); + return; + } + + format = AFMT_S16_LE; + ret = ioctl(dsp_fd, SNDCTL_DSP_SETFMT, &format); + if (ret == -1) { + g_printerr ("Failed to set sound format\n"); + close(dsp_fd); + return; + } + + channels = 2; + ret = ioctl(dsp_fd, SNDCTL_DSP_CHANNELS, &channels); + if (ret == -1) { + g_printerr ("Failed to set stereo\n"); + close(dsp_fd); + return; + } + + rate = 44100; + ret = ioctl(dsp_fd, SNDCTL_DSP_SPEED, &rate); + if (ret == -1) { + g_printerr ("Failed to set rate\n"); + close(dsp_fd); + return; + } + + ret = ioctl(dsp_fd, SNDCTL_DSP_GETBLKSIZE, &fragsize); + if (ret == -1) { + g_printerr ("Failed to get fragment size\n"); + close(dsp_fd); + return; + } + + stream = g_new0 (Stream, 1); + stream->sound = sound; + stream->audio = g_object_ref (audio); + stream->dsp_fd = dsp_fd; + stream->fragsize = fragsize; + sound->streams = g_list_prepend (sound->streams, stream); + + channel = g_io_channel_unix_new (stream->dsp_fd); + stream->source = g_io_create_watch (channel, G_IO_OUT); + g_source_set_priority (stream->source, G_PRIORITY_HIGH); + g_source_set_callback (stream->source, (GSourceFunc) handle_stream, stream, + NULL); + g_io_channel_unref (channel); + g_source_attach (stream->source, stream->sound->context); + + return; +} + +static void +swfdec_playback_stream_close (Stream *stream) +{ + close (stream->dsp_fd); + g_source_destroy (stream->source); + g_source_unref (stream->source); + stream->sound->streams = g_list_remove (stream->sound->streams, stream); + g_object_unref (stream->audio); + g_free (stream); +} + +/*** SOUND ***/ + +static void +advance_before (SwfdecPlayer *player, guint msecs, guint audio_samples, gpointer data) +{ + SwfdecPlayback *sound = data; + GList *walk; + + for (walk = sound->streams; walk; walk = walk->next) { + Stream *stream = walk->data; + if (audio_samples >= stream->offset) { + stream->offset = 0; + } else { + stream->offset -= audio_samples; + } + } +} + +static void +audio_added (SwfdecPlayer *player, SwfdecAudio *audio, SwfdecPlayback *sound) +{ + swfdec_playback_stream_open (sound, audio); +} + +static void +audio_removed (SwfdecPlayer *player, SwfdecAudio *audio, SwfdecPlayback *sound) +{ + GList *walk; + + for (walk = sound->streams; walk; walk = walk->next) { + Stream *stream = walk->data; + if (stream->audio == audio) { + swfdec_playback_stream_close (stream); + return; + } + } + g_assert_not_reached (); +} + +SwfdecPlayback * +swfdec_playback_open (SwfdecPlayer *player, GMainContext *context) +{ + SwfdecPlayback *sound; + const GList *walk; + + g_return_val_if_fail (SWFDEC_IS_PLAYER (player), NULL); + g_return_val_if_fail (context != NULL, NULL); + + sound = g_new0 (SwfdecPlayback, 1); + sound->player = player; + g_signal_connect (player, "advance", G_CALLBACK (advance_before), sound); + g_signal_connect (player, "audio-added", G_CALLBACK (audio_added), sound); + g_signal_connect (player, "audio-removed", G_CALLBACK (audio_removed), sound); + for (walk = swfdec_player_get_audio (player); walk; walk = walk->next) { + swfdec_playback_stream_open (sound, walk->data); + } + g_main_context_ref (context); + sound->context = context; + return sound; +} + +void +swfdec_playback_close (SwfdecPlayback *sound) +{ +#define REMOVE_HANDLER_FULL(obj,func,data,count) G_STMT_START {\ + if (g_signal_handlers_disconnect_by_func ((obj), \ + G_CALLBACK (func), (data)) != (count)) { \ + g_assert_not_reached (); \ + } \ +} G_STMT_END +#define REMOVE_HANDLER(obj,func,data) REMOVE_HANDLER_FULL (obj, func, data, 1) + + while (sound->streams) + swfdec_playback_stream_close (sound->streams->data); + REMOVE_HANDLER (sound->player, advance_before, sound); + REMOVE_HANDLER (sound->player, audio_added, sound); + REMOVE_HANDLER (sound->player, audio_removed, sound); + g_main_context_unref (sound->context); + g_free (sound); +} + +