Add a test for addProperty behaviour in scope chain objects
[swfdec.git] / swfdec-gtk / swfdec_playback_pulse.c
blob5aa85f07c916cdc5a6777293cb310f1f2a251091
1 /* Swfdec
2 * Copyright © 2006 Benjamin Otte <otte@gnome.org>
3 * Copyright © 2007 Eric Anholt <eric@anholt.net>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301 USA
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
25 #include <stdlib.h>
26 #include <string.h>
28 #include "swfdec_playback.h"
29 #include "pulse/pulseaudio.h"
30 #include "pulse/glib-mainloop.h"
32 /** @file Implements swfdec audio playback by dumping swfdec streams out
33 * using pulseaudio streams.
36 /*** DEFINITIONS ***/
38 struct _SwfdecPlayback {
39 SwfdecPlayer * player;
40 GList * streams; /* all Stream objects */
41 GMainContext * context; /* glib context we work in */
42 pa_glib_mainloop * pa_mainloop; /* PA to glib mainloop connection */
43 pa_context * pa; /* PA context for sound rendering */
46 typedef struct {
47 SwfdecPlayback * sound; /* reference to sound object */
48 SwfdecAudio * audio; /* the audio we play back */
49 guint offset; /* offset into sound */
50 pa_stream * pa; /* PA stream */
51 pa_cvolume volume; /* Volume control. Not yet used. */
52 gboolean no_more;
53 } Stream;
55 /* Size of one of our audio samples, in bytes */
56 #define SAMPLESIZE 2
57 #define CHANNELS 2
59 /*** STREAMS ***/
61 static void
62 stream_write_callback (pa_stream *pa,
63 size_t bytes,
64 void *data)
66 Stream *stream = data;
67 char *frag;
68 unsigned int samples = bytes / SAMPLESIZE / CHANNELS;
69 int err;
71 if (stream->no_more)
72 return;
74 /* Adjust to our rounded-down number */
75 bytes = samples * SAMPLESIZE * CHANNELS;
77 frag = malloc (bytes);
78 if (frag == NULL) {
79 g_printerr ("Failed to allocate fragment of size %d\n", (int)bytes);
80 return;
83 /* Set up our fragment and render swfdec's audio into it. The swfdec audio
84 * decoder renders deltas from the existing data in the fragment.
86 swfdec_audio_render (stream->audio, (gint16 *)frag, stream->offset,
87 samples);
89 /* Send the new fragment out the PA stream */
90 err = pa_stream_write (pa, frag, bytes, NULL, 0, PA_SEEK_RELATIVE);
91 if (err != 0) {
92 g_printerr ("Failed to write fragment to PA stream: %s\n",
93 pa_strerror(pa_context_errno(stream->sound->pa)));
96 /* Advance playback pointer */
97 stream->offset += samples;
99 free(frag);
102 static void
103 stream_drain_complete (pa_stream *pa, int success, void *data)
105 Stream *stream = data;
107 pa_stream_disconnect (stream->pa);
108 pa_stream_unref (stream->pa);
109 g_object_unref (stream->audio);
110 g_free (stream);
113 static void
114 swfdec_playback_stream_close (Stream *stream)
116 /* Pull it off of the active stream list. */
117 stream->sound->streams = g_list_remove (stream->sound->streams, stream);
119 /* If we have created a PA stream, defer freeing until we drain it. */
120 if (stream->pa != NULL) {
121 pa_operation *o;
123 stream->no_more = 1;
125 o = pa_stream_drain (stream->pa, stream_drain_complete, stream);
126 if (o != NULL) {
127 pa_operation_unref (o);
128 return;
129 } else {
130 g_printerr("PA stream drain failed: %s\n",
131 pa_strerror(pa_context_errno(stream->sound->pa)));
134 g_object_unref (stream->audio);
135 g_free (stream);
138 static void
139 stream_state_callback (pa_stream *pa, void *data)
141 switch (pa_stream_get_state(pa)) {
142 case PA_STREAM_CREATING:
143 case PA_STREAM_TERMINATED:
144 case PA_STREAM_READY:
145 case PA_STREAM_UNCONNECTED:
146 break;
148 case PA_STREAM_FAILED:
149 g_printerr("PA stream failed: %s\n",
150 pa_strerror(pa_context_errno(pa_stream_get_context(pa))));
151 default:
152 break;
156 static void
157 swfdec_playback_stream_open (SwfdecPlayback *sound, SwfdecAudio *audio)
159 Stream *stream;
160 pa_sample_spec spec = {
161 .format = PA_SAMPLE_S16LE,
162 .rate = 44100,
163 .channels = CHANNELS,
165 int err;
167 stream = g_new0 (Stream, 1);
168 stream->sound = sound;
169 stream->audio = g_object_ref (audio);
170 sound->streams = g_list_prepend (sound->streams, stream);
172 /* If we failed to initialize the context, don't try to create the stream.
173 * We still have to get put in the list, because swfdec_playback.c expects
174 * to find it in the list for removal later.
176 if (sound->pa == NULL)
177 return;
179 /* Create our stream */
180 stream->pa = pa_stream_new(sound->pa,
181 "swfdec stream",
182 &spec,
183 NULL /* Default channel map */
185 if (stream->pa == NULL) {
186 g_printerr("Failed to create PA stream\n");
187 swfdec_playback_stream_close(stream);
188 return;
191 /* Start at default volume */
192 pa_cvolume_set(&stream->volume, CHANNELS, PA_VOLUME_NORM);
194 /* Hook up our stream write callback for when new data is needed */
195 pa_stream_set_state_callback(stream->pa, stream_state_callback, stream);
196 pa_stream_set_write_callback(stream->pa, stream_write_callback, stream);
198 /* Connect it up as a playback stream. */
199 err = pa_stream_connect_playback(stream->pa,
200 NULL, /* Default device */
201 NULL /* Default buffering */,
202 0, /* No flags */
203 &stream->volume,
204 NULL /* Don't sync to any stream */
206 if (err != 0) {
207 g_printerr ("Failed to connect PA stream: %s\n",
208 pa_strerror(pa_context_errno(sound->pa)));
209 swfdec_playback_stream_close(stream);
210 return;
214 /*** SOUND ***/
216 static void
217 advance_before (SwfdecPlayer *player, guint msecs, guint audio_samples, gpointer data)
219 SwfdecPlayback *sound = data;
220 GList *walk;
222 for (walk = sound->streams; walk; walk = walk->next) {
223 Stream *stream = walk->data;
224 if (audio_samples >= stream->offset) {
225 stream->offset = 0;
226 } else {
227 stream->offset -= audio_samples;
232 static void
233 audio_added (SwfdecPlayer *player, SwfdecAudio *audio, SwfdecPlayback *sound)
235 swfdec_playback_stream_open (sound, audio);
238 static void
239 audio_removed (SwfdecPlayer *player, SwfdecAudio *audio, SwfdecPlayback *sound)
241 GList *walk;
243 for (walk = sound->streams; walk; walk = walk->next) {
244 Stream *stream = walk->data;
245 if (stream->audio == audio) {
246 swfdec_playback_stream_close (stream);
247 return;
250 g_assert_not_reached ();
252 static void
253 context_state_callback (pa_context *pa, void *data)
255 SwfdecPlayback *sound = data;
257 switch (pa_context_get_state(pa)) {
258 case PA_CONTEXT_FAILED:
259 g_printerr ("PA context failed\n");
260 pa_context_unref (pa);
261 sound->pa = NULL;
262 break;
264 default:
265 case PA_CONTEXT_TERMINATED:
266 case PA_CONTEXT_UNCONNECTED:
267 case PA_CONTEXT_CONNECTING:
268 case PA_CONTEXT_AUTHORIZING:
269 case PA_CONTEXT_SETTING_NAME:
270 case PA_CONTEXT_READY:
271 break;
276 SwfdecPlayback *
277 swfdec_playback_open (SwfdecPlayer *player, GMainContext *context)
279 SwfdecPlayback *sound;
280 const GList *walk;
281 pa_mainloop_api *pa_api;
283 g_return_val_if_fail (SWFDEC_IS_PLAYER (player), NULL);
284 g_return_val_if_fail (context != NULL, NULL);
286 sound = g_new0 (SwfdecPlayback, 1);
287 sound->player = player;
288 g_signal_connect (player, "advance", G_CALLBACK (advance_before), sound);
289 g_signal_connect (player, "audio-added", G_CALLBACK (audio_added), sound);
290 g_signal_connect (player, "audio-removed", G_CALLBACK (audio_removed), sound);
292 /* Create our mainloop attachment to glib. XXX: I hope this means we don't
293 * have to run the main loop using pa functions.
295 sound->pa_mainloop = pa_glib_mainloop_new (context);
296 pa_api = pa_glib_mainloop_get_api (sound->pa_mainloop);
298 sound->pa = pa_context_new (pa_api, "swfdec");
300 pa_context_set_state_callback (sound->pa, context_state_callback, sound);
301 pa_context_connect (sound->pa,
302 NULL, /* default server */
303 0, /* default flags */
304 NULL /* spawning api */
307 for (walk = swfdec_player_get_audio (player); walk; walk = walk->next) {
308 swfdec_playback_stream_open (sound, walk->data);
310 g_main_context_ref (context);
311 sound->context = context;
312 return sound;
315 static void
316 context_drain_complete (pa_context *pa, void *data)
318 pa_context_disconnect (pa);
319 pa_context_unref (pa);
322 void
323 swfdec_playback_close (SwfdecPlayback *sound)
325 pa_operation *op;
327 #define REMOVE_HANDLER_FULL(obj,func,data,count) G_STMT_START {\
328 if (g_signal_handlers_disconnect_by_func ((obj), \
329 G_CALLBACK (func), (data)) != (count)) { \
330 g_assert_not_reached (); \
332 } G_STMT_END
333 #define REMOVE_HANDLER(obj,func,data) REMOVE_HANDLER_FULL (obj, func, data, 1)
335 while (sound->streams)
336 swfdec_playback_stream_close (sound->streams->data);
337 REMOVE_HANDLER (sound->player, advance_before, sound);
338 REMOVE_HANDLER (sound->player, audio_added, sound);
339 REMOVE_HANDLER (sound->player, audio_removed, sound);
341 if (sound->pa != NULL) {
342 op = pa_context_drain (sound->pa, context_drain_complete, NULL);
343 if (op == NULL) {
344 pa_context_disconnect (sound->pa);
345 pa_context_unref (sound->pa);
346 } else {
347 pa_operation_unref (op);
349 pa_glib_mainloop_free (sound->pa_mainloop);
352 g_main_context_unref (sound->context);
353 g_free (sound);