Branch data Line data Source code
1 : : /* GStreamer - AirPort Express Audio Sink -
2 : : *
3 : : * Remote Audio Access Protocol (RAOP) as used in Apple iTunes to stream music to the Airport Express (ApEx) -
4 : : * RAOP is based on the Real Time Streaming Protocol (RTSP) but with an extra challenge-response RSA based authentication step.
5 : : *
6 : : * RAW PCM input only as defined by the following GST_STATIC_PAD_TEMPLATE
7 : : *
8 : : * Copyright (C) 2008 Jérémie Bernard [GRemi] <gremimail@gmail.com>
9 : : *
10 : : * gstapexsink.c
11 : : *
12 : : * This library is free software; you can redistribute it and/or
13 : : * modify it under the terms of the GNU Library General Public
14 : : * License as published by the Free Software Foundation; either
15 : : * version 2 of the License, or (at your option) any later version.
16 : : *
17 : : * This library is distributed in the hope that it will be useful,
18 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 : : * Library General Public License for more details.
21 : : *
22 : : * You should have received a copy of the GNU Library General Public
23 : : * License along with this library; if not, write to the
24 : : * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 : : * Boston, MA 02111-1307, USA.
26 : : *
27 : : */
28 : :
29 : : #ifdef HAVE_CONFIG_H
30 : : #include "config.h"
31 : : #endif
32 : :
33 : : #include <string.h>
34 : :
35 : : #include "gstapexsink.h"
36 : :
37 : : GST_DEBUG_CATEGORY_STATIC (apexsink_debug);
38 : : #define GST_CAT_DEFAULT apexsink_debug
39 : :
40 : : static GstStaticPadTemplate gst_apexsink_sink_factory = GST_STATIC_PAD_TEMPLATE
41 : : ("sink",
42 : : GST_PAD_SINK,
43 : : GST_PAD_ALWAYS,
44 : : GST_STATIC_CAPS
45 : : (GST_APEX_RAOP_INPUT_TYPE ","
46 : : "width = (int) " GST_APEX_RAOP_INPUT_WIDTH ","
47 : : "depth = (int) " GST_APEX_RAOP_INPUT_DEPTH ","
48 : : "endianness = (int) " GST_APEX_RAOP_INPUT_ENDIAN ","
49 : : "channels = (int) " GST_APEX_RAOP_INPUT_CHANNELS ","
50 : : "rate = (int) " GST_APEX_RAOP_INPUT_BIT_RATE ","
51 : : "signed = (boolean) " GST_APEX_RAOP_INPUT_SIGNED)
52 : : );
53 : :
54 : :
55 : : enum
56 : : {
57 : : APEX_PROP_HOST = 1,
58 : : APEX_PROP_PORT,
59 : : APEX_PROP_VOLUME,
60 : : APEX_PROP_JACK_TYPE,
61 : : APEX_PROP_JACK_STATUS,
62 : : };
63 : :
64 : : #define DEFAULT_APEX_HOST ""
65 : : #define DEFAULT_APEX_PORT 5000
66 : : #define DEFAULT_APEX_VOLUME 1.0
67 : : #define DEFAULT_APEX_JACK_TYPE GST_APEX_JACK_TYPE_UNDEFINED
68 : : #define DEFAULT_APEX_JACK_STATUS GST_APEX_JACK_STATUS_UNDEFINED
69 : :
70 : : /* genum apex jack resolution */
71 : : GType
72 : 3 : gst_apexsink_jackstatus_get_type (void)
73 : : {
74 : : static GType jackstatus_type = 0;
75 : : static GEnumValue jackstatus[] = {
76 : : {GST_APEX_JACK_STATUS_UNDEFINED, "GST_APEX_JACK_STATUS_UNDEFINED",
77 : : "Jack status undefined"},
78 : : {GST_APEX_JACK_STATUS_DISCONNECTED, "GST_APEX_JACK_STATUS_DISCONNECTED",
79 : : "Jack disconnected"},
80 : : {GST_APEX_JACK_STATUS_CONNECTED, "GST_APEX_JACK_STATUS_CONNECTED",
81 : : "Jack connected"},
82 : : {0, NULL, NULL},
83 : : };
84 : :
85 [ + - ]: 3 : if (!jackstatus_type) {
86 : 3 : jackstatus_type = g_enum_register_static ("GstApExJackStatus", jackstatus);
87 : : }
88 : :
89 : 3 : return jackstatus_type;
90 : : }
91 : :
92 : : GType
93 : 3 : gst_apexsink_jacktype_get_type (void)
94 : : {
95 : : static GType jacktype_type = 0;
96 : : static GEnumValue jacktype[] = {
97 : : {GST_APEX_JACK_TYPE_UNDEFINED, "GST_APEX_JACK_TYPE_UNDEFINED",
98 : : "Undefined jack type"},
99 : : {GST_APEX_JACK_TYPE_ANALOG, "GST_APEX_JACK_TYPE_ANALOG", "Analog jack"},
100 : : {GST_APEX_JACK_TYPE_DIGITAL, "GST_APEX_JACK_TYPE_DIGITAL", "Digital jack"},
101 : : {0, NULL, NULL},
102 : : };
103 : :
104 [ + - ]: 3 : if (!jacktype_type) {
105 : 3 : jacktype_type = g_enum_register_static ("GstApExJackType", jacktype);
106 : : }
107 : :
108 : 3 : return jacktype_type;
109 : : }
110 : :
111 : :
112 : : static void gst_apexsink_set_property (GObject * object, guint prop_id,
113 : : const GValue * value, GParamSpec * pspec);
114 : : static void gst_apexsink_get_property (GObject * object, guint prop_id,
115 : : GValue * value, GParamSpec * pspec);
116 : : static void gst_apexsink_finalise (GObject * object);
117 : :
118 : : static gboolean gst_apexsink_open (GstAudioSink * asink);
119 : : static gboolean gst_apexsink_prepare (GstAudioSink * asink,
120 : : GstRingBufferSpec * spec);
121 : : static guint gst_apexsink_write (GstAudioSink * asink, gpointer data,
122 : : guint length);
123 : : static gboolean gst_apexsink_unprepare (GstAudioSink * asink);
124 : : static guint gst_apexsink_delay (GstAudioSink * asink);
125 : : static void gst_apexsink_reset (GstAudioSink * asink);
126 : : static gboolean gst_apexsink_close (GstAudioSink * asink);
127 : :
128 : : /* mixer interface standard api */
129 : : static void gst_apexsink_interfaces_init (GType type);
130 : : static void gst_apexsink_implements_interface_init (GstImplementsInterfaceClass
131 : : * iface);
132 : : static void gst_apexsink_mixer_interface_init (GstMixerClass * iface);
133 : :
134 : : static gboolean gst_apexsink_interface_supported (GstImplementsInterface *
135 : : iface, GType iface_type);
136 : : static const GList *gst_apexsink_mixer_list_tracks (GstMixer * mixer);
137 : : static void gst_apexsink_mixer_set_volume (GstMixer * mixer,
138 : : GstMixerTrack * track, gint * volumes);
139 : : static void gst_apexsink_mixer_get_volume (GstMixer * mixer,
140 : : GstMixerTrack * track, gint * volumes);
141 : :
142 [ + + ]: 12 : GST_BOILERPLATE_FULL (GstApExSink, gst_apexsink, GstAudioSink,
143 : 12 : GST_TYPE_AUDIO_SINK, gst_apexsink_interfaces_init);
144 : :
145 : : /* apex sink interface(s) stuff */
146 : : static void
147 : 3 : gst_apexsink_interfaces_init (GType type)
148 : : {
149 : : static const GInterfaceInfo implements_interface_info =
150 : : { (GInterfaceInitFunc) gst_apexsink_implements_interface_init, NULL,
151 : : NULL
152 : : };
153 : : static const GInterfaceInfo mixer_interface_info =
154 : : { (GInterfaceInitFunc) gst_apexsink_mixer_interface_init, NULL, NULL };
155 : :
156 : 3 : g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE,
157 : : &implements_interface_info);
158 : 3 : g_type_add_interface_static (type, GST_TYPE_MIXER, &mixer_interface_info);
159 : 3 : }
160 : :
161 : : static void
162 : 3 : gst_apexsink_implements_interface_init (GstImplementsInterfaceClass * iface)
163 : : {
164 : 3 : iface->supported = gst_apexsink_interface_supported;
165 : 3 : }
166 : :
167 : : static void
168 : 3 : gst_apexsink_mixer_interface_init (GstMixerClass * iface)
169 : : {
170 : 3 : GST_MIXER_TYPE (iface) = GST_MIXER_SOFTWARE;
171 : :
172 : 3 : iface->list_tracks = gst_apexsink_mixer_list_tracks;
173 : 3 : iface->set_volume = gst_apexsink_mixer_set_volume;
174 : 3 : iface->get_volume = gst_apexsink_mixer_get_volume;
175 : 3 : }
176 : :
177 : : static gboolean
178 : 0 : gst_apexsink_interface_supported (GstImplementsInterface * iface,
179 : : GType iface_type)
180 : : {
181 [ # # ]: 0 : g_return_val_if_fail (iface_type == GST_TYPE_MIXER, FALSE);
182 : :
183 : 0 : return TRUE;
184 : : }
185 : :
186 : : static const GList *
187 : 0 : gst_apexsink_mixer_list_tracks (GstMixer * mixer)
188 : : {
189 : 0 : GstApExSink *apexsink = GST_APEX_SINK (mixer);
190 : :
191 : 0 : return apexsink->tracks;
192 : : }
193 : :
194 : : static void
195 : 0 : gst_apexsink_mixer_set_volume (GstMixer * mixer, GstMixerTrack * track,
196 : : gint * volumes)
197 : : {
198 : 0 : GstApExSink *apexsink = GST_APEX_SINK (mixer);
199 : :
200 : 0 : apexsink->volume = volumes[0];
201 : :
202 [ # # ]: 0 : if (apexsink->gst_apexraop != NULL)
203 : 0 : gst_apexraop_set_volume (apexsink->gst_apexraop, apexsink->volume);
204 : 0 : }
205 : :
206 : : static void
207 : 0 : gst_apexsink_mixer_get_volume (GstMixer * mixer, GstMixerTrack * track,
208 : : gint * volumes)
209 : : {
210 : 0 : GstApExSink *apexsink = GST_APEX_SINK (mixer);
211 : :
212 : 0 : volumes[0] = apexsink->volume;
213 : 0 : }
214 : :
215 : : /* sink base init */
216 : : static void
217 : 3 : gst_apexsink_base_init (gpointer g_class)
218 : : {
219 : 3 : GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
220 : :
221 : 3 : gst_element_class_set_details_simple (element_class,
222 : : "Apple AirPort Express Audio Sink", "Sink/Audio/Wireless",
223 : : "Output stream to an AirPort Express",
224 : : "Jérémie Bernard [GRemi] <gremimail@gmail.com>");
225 : 3 : gst_element_class_add_pad_template (element_class,
226 : : gst_static_pad_template_get (&gst_apexsink_sink_factory));
227 : 3 : }
228 : :
229 : : /* sink class init */
230 : : static void
231 : 3 : gst_apexsink_class_init (GstApExSinkClass * klass)
232 : : {
233 [ + - ]: 3 : GST_DEBUG_CATEGORY_INIT (apexsink_debug, GST_APEX_SINK_NAME, 0,
234 : : "AirPort Express sink");
235 : :
236 : 3 : parent_class = g_type_class_peek_parent (klass);
237 : :
238 : 3 : ((GObjectClass *) klass)->get_property =
239 : 3 : GST_DEBUG_FUNCPTR (gst_apexsink_get_property);
240 : 3 : ((GObjectClass *) klass)->set_property =
241 : 3 : GST_DEBUG_FUNCPTR (gst_apexsink_set_property);
242 : 3 : ((GObjectClass *) klass)->finalize =
243 : 3 : GST_DEBUG_FUNCPTR (gst_apexsink_finalise);
244 : :
245 : 3 : ((GstAudioSinkClass *) klass)->open = GST_DEBUG_FUNCPTR (gst_apexsink_open);
246 : 3 : ((GstAudioSinkClass *) klass)->prepare =
247 : 3 : GST_DEBUG_FUNCPTR (gst_apexsink_prepare);
248 : 3 : ((GstAudioSinkClass *) klass)->write = GST_DEBUG_FUNCPTR (gst_apexsink_write);
249 : 3 : ((GstAudioSinkClass *) klass)->unprepare =
250 : 3 : GST_DEBUG_FUNCPTR (gst_apexsink_unprepare);
251 : 3 : ((GstAudioSinkClass *) klass)->delay = GST_DEBUG_FUNCPTR (gst_apexsink_delay);
252 : 3 : ((GstAudioSinkClass *) klass)->reset = GST_DEBUG_FUNCPTR (gst_apexsink_reset);
253 : 3 : ((GstAudioSinkClass *) klass)->close = GST_DEBUG_FUNCPTR (gst_apexsink_close);
254 : :
255 : 3 : g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_HOST,
256 : : g_param_spec_string ("host", "Host", "AirPort Express target host",
257 : : DEFAULT_APEX_HOST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
258 : 3 : g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_PORT,
259 : : g_param_spec_uint ("port", "Port", "AirPort Express target port", 0,
260 : : 32000, DEFAULT_APEX_PORT,
261 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
262 : : /* we need to expose the volume as a double for playbin2. Internally we keep
263 : : * it as an int between 0 and 100, where 75 corresponds to 1.0.
264 : : * FIXME we should store the volume as a double. */
265 : 3 : g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_VOLUME,
266 : : g_param_spec_double ("volume", "Volume", "AirPort Express target volume",
267 : : 0.0, 10.0, DEFAULT_APEX_VOLUME,
268 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
269 : 3 : g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_JACK_TYPE,
270 : : g_param_spec_enum ("jack-type", "Jack Type",
271 : : "AirPort Express connected jack type", GST_APEX_SINK_JACKTYPE_TYPE,
272 : : DEFAULT_APEX_JACK_TYPE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
273 : 3 : g_object_class_install_property ((GObjectClass *) klass,
274 : : APEX_PROP_JACK_STATUS, g_param_spec_enum ("jack-status", "Jack Status",
275 : : "AirPort Express jack connection status",
276 : : GST_APEX_SINK_JACKSTATUS_TYPE, DEFAULT_APEX_JACK_STATUS,
277 : : G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
278 : 3 : }
279 : :
280 : : /* sink plugin instance init */
281 : : static void
282 : 1 : gst_apexsink_init (GstApExSink * apexsink, GstApExSinkClass * g_class)
283 : : {
284 : 1 : GstMixerTrack *track = NULL;
285 : :
286 : 1 : track = g_object_new (GST_TYPE_MIXER_TRACK, NULL);
287 : 1 : track->label = g_strdup ("Airport Express");
288 : 1 : track->num_channels = GST_APEX_RAOP_CHANNELS;
289 : 1 : track->min_volume = 0;
290 : 1 : track->max_volume = 100;
291 : 1 : track->flags = GST_MIXER_TRACK_OUTPUT;
292 : :
293 : 1 : apexsink->host = g_strdup (DEFAULT_APEX_HOST);
294 : 1 : apexsink->port = DEFAULT_APEX_PORT;
295 : 1 : apexsink->volume = CLAMP (DEFAULT_APEX_VOLUME * 75, 0, 100);
296 : 1 : apexsink->gst_apexraop = NULL;
297 : 1 : apexsink->tracks = g_list_append (apexsink->tracks, track);
298 : :
299 [ - + ]: 1 : GST_INFO_OBJECT (apexsink,
300 : : "ApEx sink default initialization, target=\"%s\", port=\"%d\", volume=\"%d%%\"",
301 : : apexsink->host, apexsink->port, apexsink->volume);
302 : 1 : }
303 : :
304 : : /* apex sink set property */
305 : : static void
306 : 0 : gst_apexsink_set_property (GObject * object, guint prop_id,
307 : : const GValue * value, GParamSpec * pspec)
308 : : {
309 : 0 : GstApExSink *sink = GST_APEX_SINK (object);
310 : :
311 [ # # # # ]: 0 : switch (prop_id) {
312 : : case APEX_PROP_HOST:
313 [ # # ]: 0 : if (sink->gst_apexraop == NULL) {
314 : 0 : g_free (sink->host);
315 : 0 : sink->host = g_value_dup_string (value);
316 : :
317 [ # # ]: 0 : GST_INFO_OBJECT (sink, "ApEx sink target set to \"%s\"", sink->host);
318 : : } else {
319 : 0 : G_OBJECT_WARN_INVALID_PSPEC (object, "host", prop_id, pspec);
320 : : }
321 : 0 : break;
322 : : case APEX_PROP_PORT:
323 [ # # ]: 0 : if (sink->gst_apexraop == NULL) {
324 : 0 : sink->port = g_value_get_uint (value);
325 : :
326 [ # # ]: 0 : GST_INFO_OBJECT (sink, "ApEx port set to \"%d\"", sink->port);
327 : : } else {
328 : 0 : G_OBJECT_WARN_INVALID_PSPEC (object, "port", prop_id, pspec);
329 : : }
330 : 0 : break;
331 : : case APEX_PROP_VOLUME:
332 : : {
333 : : gdouble volume;
334 : :
335 : 0 : volume = g_value_get_double (value);
336 : 0 : volume *= 75.0;
337 : :
338 [ # # ][ # # ]: 0 : sink->volume = CLAMP (volume, 0, 100);
339 : :
340 [ # # ]: 0 : if (sink->gst_apexraop != NULL)
341 : 0 : gst_apexraop_set_volume (sink->gst_apexraop, sink->volume);
342 : :
343 [ # # ]: 0 : GST_INFO_OBJECT (sink, "ApEx volume set to \"%d%%\"", sink->volume);
344 : 0 : break;
345 : : }
346 : : default:
347 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
348 : 0 : break;
349 : : }
350 : 0 : }
351 : :
352 : : /* apex sink get property */
353 : : static void
354 : 5 : gst_apexsink_get_property (GObject * object, guint prop_id, GValue * value,
355 : : GParamSpec * pspec)
356 : : {
357 : 5 : GstApExSink *sink = GST_APEX_SINK (object);
358 : :
359 [ + + + + : 5 : switch (prop_id) {
+ - ]
360 : : case APEX_PROP_HOST:
361 : 1 : g_value_set_string (value, sink->host);
362 : 1 : break;
363 : : case APEX_PROP_PORT:
364 : 1 : g_value_set_uint (value, sink->port);
365 : 1 : break;
366 : : case APEX_PROP_VOLUME:
367 : 1 : g_value_set_double (value, ((gdouble) sink->volume) / 75.0);
368 : 1 : break;
369 : : case APEX_PROP_JACK_TYPE:
370 : 1 : g_value_set_enum (value, gst_apexraop_get_jacktype (sink->gst_apexraop));
371 : 1 : break;
372 : : case APEX_PROP_JACK_STATUS:
373 : 1 : g_value_set_enum (value,
374 : 1 : gst_apexraop_get_jackstatus (sink->gst_apexraop));
375 : 1 : break;
376 : : default:
377 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
378 : 0 : break;
379 : : }
380 : 5 : }
381 : :
382 : : /* apex sink finalize */
383 : : static void
384 : 1 : gst_apexsink_finalise (GObject * object)
385 : : {
386 : 1 : GstApExSink *sink = GST_APEX_SINK (object);
387 : :
388 [ + - ]: 1 : if (sink->tracks) {
389 : 1 : g_list_foreach (sink->tracks, (GFunc) g_object_unref, NULL);
390 : 1 : g_list_free (sink->tracks);
391 : 1 : sink->tracks = NULL;
392 : : }
393 : :
394 : 1 : g_free (sink->host);
395 : :
396 : 1 : G_OBJECT_CLASS (parent_class)->finalize (object);
397 : 1 : }
398 : :
399 : : /* sink open : open the device */
400 : : static gboolean
401 : 0 : gst_apexsink_open (GstAudioSink * asink)
402 : : {
403 : : int res;
404 : 0 : GstApExSink *apexsink = (GstApExSink *) asink;
405 : :
406 : 0 : apexsink->gst_apexraop = gst_apexraop_new (apexsink->host, apexsink->port);
407 : :
408 [ # # ]: 0 : if ((res = gst_apexraop_connect (apexsink->gst_apexraop)) != GST_RTSP_STS_OK) {
409 [ # # ]: 0 : GST_ERROR_OBJECT (apexsink,
410 : : "%s : network or RAOP failure, connection refused or timeout, RTSP code=%d",
411 : : apexsink->host, res);
412 : 0 : return FALSE;
413 : : }
414 : :
415 [ # # ]: 0 : GST_INFO_OBJECT (apexsink,
416 : : "OPEN : ApEx sink successfully connected to \"%s:%d\", ANNOUNCE, SETUP and RECORD requests performed",
417 : : apexsink->host, apexsink->port);
418 : :
419 [ # # # ]: 0 : switch (gst_apexraop_get_jackstatus (apexsink->gst_apexraop)) {
420 : : case GST_APEX_JACK_STATUS_CONNECTED:
421 [ # # ]: 0 : GST_INFO_OBJECT (apexsink, "OPEN : ApEx jack is connected");
422 : 0 : break;
423 : : case GST_APEX_JACK_STATUS_DISCONNECTED:
424 [ # # ]: 0 : GST_WARNING_OBJECT (apexsink, "OPEN : ApEx jack is disconnected !");
425 : 0 : break;
426 : : default:
427 [ # # ]: 0 : GST_WARNING_OBJECT (apexsink, "OPEN : ApEx jack status is undefined !");
428 : 0 : break;
429 : : }
430 : :
431 [ # # # ]: 0 : switch (gst_apexraop_get_jacktype (apexsink->gst_apexraop)) {
432 : : case GST_APEX_JACK_TYPE_ANALOG:
433 [ # # ]: 0 : GST_INFO_OBJECT (apexsink, "OPEN : ApEx jack type is analog");
434 : 0 : break;
435 : : case GST_APEX_JACK_TYPE_DIGITAL:
436 [ # # ]: 0 : GST_INFO_OBJECT (apexsink, "OPEN : ApEx jack type is digital");
437 : 0 : break;
438 : : default:
439 [ # # ]: 0 : GST_WARNING_OBJECT (apexsink, "OPEN : ApEx jack type is undefined !");
440 : 0 : break;
441 : : }
442 : :
443 [ # # ]: 0 : if ((res =
444 : 0 : gst_apexraop_set_volume (apexsink->gst_apexraop,
445 : : apexsink->volume)) != GST_RTSP_STS_OK) {
446 [ # # ]: 0 : GST_WARNING_OBJECT (apexsink,
447 : : "%s : could not set initial volume to \"%d%%\", RTSP code=%d",
448 : : apexsink->host, apexsink->volume, res);
449 : : } else {
450 [ # # ]: 0 : GST_INFO_OBJECT (apexsink,
451 : : "OPEN : ApEx sink successfully set volume to \"%d%%\"",
452 : : apexsink->volume);
453 : : }
454 : :
455 : 0 : return TRUE;
456 : : }
457 : :
458 : : /* prepare sink : configure the device with the specified format */
459 : : static gboolean
460 : 0 : gst_apexsink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec)
461 : : {
462 : 0 : GstApExSink *apexsink = (GstApExSink *) asink;
463 : :
464 : 0 : apexsink->latency_time = spec->latency_time;
465 : :
466 : 0 : spec->segsize =
467 : : GST_APEX_RAOP_SAMPLES_PER_FRAME * GST_APEX_RAOP_BYTES_PER_SAMPLE;
468 : 0 : spec->segtotal = 1;
469 : :
470 : 0 : memset (spec->silence_sample, 0, sizeof (spec->silence_sample));
471 : :
472 [ # # ]: 0 : GST_INFO_OBJECT (apexsink,
473 : : "PREPARE : ApEx sink ready to stream at %dHz, %d bytes per sample, %d channels, %d bytes segments (%dkB/s)",
474 : : spec->rate, spec->bytes_per_sample, spec->channels, spec->segsize,
475 : : spec->rate * spec->bytes_per_sample / 1000);
476 : :
477 : 0 : return TRUE;
478 : : }
479 : :
480 : : /* sink write : write samples to the device */
481 : : static guint
482 : 0 : gst_apexsink_write (GstAudioSink * asink, gpointer data, guint length)
483 : : {
484 : 0 : GstApExSink *apexsink = (GstApExSink *) asink;
485 : :
486 [ # # ]: 0 : if (gst_apexraop_write (apexsink->gst_apexraop, data, length) != length) {
487 [ # # ]: 0 : GST_INFO_OBJECT (apexsink,
488 : : "WRITE : %d bytes not fully sended, skipping frame samples...", length);
489 : : } else {
490 [ # # ]: 0 : GST_INFO_OBJECT (apexsink, "WRITE : %d bytes sent", length);
491 : :
492 : : /* FIXME, sleeping is ugly and not interruptible */
493 : 0 : usleep ((gulong) ((length * 1000000.) / (GST_APEX_RAOP_BITRATE *
494 : 0 : GST_APEX_RAOP_BYTES_PER_SAMPLE) - apexsink->latency_time));
495 : : }
496 : :
497 : 0 : return length;
498 : : }
499 : :
500 : : /* unprepare sink : undo operations done by prepare */
501 : : static gboolean
502 : 0 : gst_apexsink_unprepare (GstAudioSink * asink)
503 : : {
504 [ # # ]: 0 : GST_INFO_OBJECT (asink, "UNPREPARE");
505 : :
506 : 0 : return TRUE;
507 : : }
508 : :
509 : : /* delay sink : get the estimated number of samples written but not played yet by the device */
510 : : static guint
511 : 0 : gst_apexsink_delay (GstAudioSink * asink)
512 : : {
513 [ # # ]: 0 : GST_LOG_OBJECT (asink, "DELAY");
514 : :
515 : 0 : return 0;
516 : : }
517 : :
518 : : /* reset sink : unblock writes and flush the device */
519 : : static void
520 : 0 : gst_apexsink_reset (GstAudioSink * asink)
521 : : {
522 : : int res;
523 : 0 : GstApExSink *apexsink = (GstApExSink *) asink;
524 : :
525 [ # # ]: 0 : GST_INFO_OBJECT (apexsink, "RESET : flushing buffer...");
526 : :
527 [ # # ]: 0 : if ((res = gst_apexraop_flush (apexsink->gst_apexraop)) == GST_RTSP_STS_OK) {
528 [ # # ]: 0 : GST_INFO_OBJECT (apexsink, "RESET : ApEx buffer flush success");
529 : : } else {
530 [ # # ]: 0 : GST_WARNING_OBJECT (apexsink,
531 : : "RESET : could not flush ApEx buffer, RTSP code=%d", res);
532 : : }
533 : 0 : }
534 : :
535 : : /* sink close : close the device */
536 : : static gboolean
537 : 0 : gst_apexsink_close (GstAudioSink * asink)
538 : : {
539 : 0 : GstApExSink *apexsink = (GstApExSink *) asink;
540 : :
541 : 0 : gst_apexraop_close (apexsink->gst_apexraop);
542 : 0 : gst_apexraop_free (apexsink->gst_apexraop);
543 : :
544 [ # # ]: 0 : GST_INFO_OBJECT (apexsink, "CLOSE : ApEx sink closed connection");
545 : :
546 : 0 : return TRUE;
547 : : }
|