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
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.
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 */
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. */
55 /* Size of one of our audio samples, in bytes */
62 stream_write_callback (pa_stream
*pa
,
66 Stream
*stream
= data
;
68 unsigned int samples
= bytes
/ SAMPLESIZE
/ CHANNELS
;
74 /* Adjust to our rounded-down number */
75 bytes
= samples
* SAMPLESIZE
* CHANNELS
;
77 frag
= malloc (bytes
);
79 g_printerr ("Failed to allocate fragment of size %d\n", (int)bytes
);
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
,
89 /* Send the new fragment out the PA stream */
90 err
= pa_stream_write (pa
, frag
, bytes
, NULL
, 0, PA_SEEK_RELATIVE
);
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
;
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
);
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
) {
125 o
= pa_stream_drain (stream
->pa
, stream_drain_complete
, stream
);
127 pa_operation_unref (o
);
130 g_printerr("PA stream drain failed: %s\n",
131 pa_strerror(pa_context_errno(stream
->sound
->pa
)));
134 g_object_unref (stream
->audio
);
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
:
148 case PA_STREAM_FAILED
:
149 g_printerr("PA stream failed: %s\n",
150 pa_strerror(pa_context_errno(pa_stream_get_context(pa
))));
157 swfdec_playback_stream_open (SwfdecPlayback
*sound
, SwfdecAudio
*audio
)
160 pa_sample_spec spec
= {
161 .format
= PA_SAMPLE_S16LE
,
163 .channels
= CHANNELS
,
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
)
179 /* Create our stream */
180 stream
->pa
= pa_stream_new(sound
->pa
,
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
);
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 */,
204 NULL
/* Don't sync to any stream */
207 g_printerr ("Failed to connect PA stream: %s\n",
208 pa_strerror(pa_context_errno(sound
->pa
)));
209 swfdec_playback_stream_close(stream
);
217 advance_before (SwfdecPlayer
*player
, guint msecs
, guint audio_samples
, gpointer data
)
219 SwfdecPlayback
*sound
= data
;
222 for (walk
= sound
->streams
; walk
; walk
= walk
->next
) {
223 Stream
*stream
= walk
->data
;
224 if (audio_samples
>= stream
->offset
) {
227 stream
->offset
-= audio_samples
;
233 audio_added (SwfdecPlayer
*player
, SwfdecAudio
*audio
, SwfdecPlayback
*sound
)
235 swfdec_playback_stream_open (sound
, audio
);
239 audio_removed (SwfdecPlayer
*player
, SwfdecAudio
*audio
, SwfdecPlayback
*sound
)
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
);
250 g_assert_not_reached ();
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
);
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
:
277 swfdec_playback_open (SwfdecPlayer
*player
, GMainContext
*context
)
279 SwfdecPlayback
*sound
;
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
;
316 context_drain_complete (pa_context
*pa
, void *data
)
318 pa_context_disconnect (pa
);
319 pa_context_unref (pa
);
323 swfdec_playback_close (SwfdecPlayback
*sound
)
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 (); \
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
);
344 pa_context_disconnect (sound
->pa
);
345 pa_context_unref (sound
->pa
);
347 pa_operation_unref (op
);
349 pa_glib_mainloop_free (sound
->pa_mainloop
);
352 g_main_context_unref (sound
->context
);