replace some playbin2 -> playbin
[platform/upstream/gstreamer.git] / ext / apexsink / gstapexsink.c
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   APEX_PROP_GENERATION,
63   APEX_PROP_TRANSPORT_PROTOCOL,
64 };
65
66 #define DEFAULT_APEX_HOST               ""
67 #define DEFAULT_APEX_PORT               5000
68 #define DEFAULT_APEX_VOLUME             1.0
69 #define DEFAULT_APEX_JACK_TYPE          GST_APEX_JACK_TYPE_UNDEFINED
70 #define DEFAULT_APEX_JACK_STATUS        GST_APEX_JACK_STATUS_UNDEFINED
71 #define DEFAULT_APEX_GENERATION         GST_APEX_GENERATION_ONE
72 #define DEFAULT_APEX_TRANSPORT_PROTOCOL GST_APEX_TCP
73
74 /* genum apex jack resolution */
75 GType
76 gst_apexsink_jackstatus_get_type (void)
77 {
78   static GType jackstatus_type = 0;
79   static GEnumValue jackstatus[] = {
80     {GST_APEX_JACK_STATUS_UNDEFINED, "GST_APEX_JACK_STATUS_UNDEFINED",
81         "Jack status undefined"},
82     {GST_APEX_JACK_STATUS_DISCONNECTED, "GST_APEX_JACK_STATUS_DISCONNECTED",
83         "Jack disconnected"},
84     {GST_APEX_JACK_STATUS_CONNECTED, "GST_APEX_JACK_STATUS_CONNECTED",
85         "Jack connected"},
86     {0, NULL, NULL},
87   };
88
89   if (!jackstatus_type) {
90     jackstatus_type = g_enum_register_static ("GstApExJackStatus", jackstatus);
91   }
92
93   return jackstatus_type;
94 }
95
96 GType
97 gst_apexsink_jacktype_get_type (void)
98 {
99   static GType jacktype_type = 0;
100   static GEnumValue jacktype[] = {
101     {GST_APEX_JACK_TYPE_UNDEFINED, "GST_APEX_JACK_TYPE_UNDEFINED",
102         "Undefined jack type"},
103     {GST_APEX_JACK_TYPE_ANALOG, "GST_APEX_JACK_TYPE_ANALOG", "Analog jack"},
104     {GST_APEX_JACK_TYPE_DIGITAL, "GST_APEX_JACK_TYPE_DIGITAL", "Digital jack"},
105     {0, NULL, NULL},
106   };
107
108   if (!jacktype_type) {
109     jacktype_type = g_enum_register_static ("GstApExJackType", jacktype);
110   }
111
112   return jacktype_type;
113 }
114
115 GType
116 gst_apexsink_generation_get_type (void)
117 {
118   static GType generation_type = 0;
119   static GEnumValue generation[] = {
120     {GST_APEX_GENERATION_ONE, "generation-one",
121         "First generation (e.g., original AirPort Express)"},
122     {GST_APEX_GENERATION_TWO, "generation-two",
123         "Second generation (e.g., Apple TV v2)"},
124     {0, NULL, NULL},
125   };
126
127   if (!generation_type) {
128     generation_type = g_enum_register_static ("GstApExGeneration", generation);
129   }
130
131   return generation_type;
132 }
133
134 GType
135 gst_apexsink_transport_protocol_get_type (void)
136 {
137   static GType transport_protocol_type = 0;
138   static GEnumValue transport_protocol[] = {
139     {GST_APEX_TCP, "tcp", "TCP"},
140     {GST_APEX_UDP, "udp", "UDP"},
141     {0, NULL, NULL},
142   };
143
144   if (!transport_protocol_type) {
145     transport_protocol_type =
146         g_enum_register_static ("GstApExTransportProtocol", transport_protocol);
147   }
148
149   return transport_protocol_type;
150 }
151
152
153 static void gst_apexsink_set_property (GObject * object, guint prop_id,
154     const GValue * value, GParamSpec * pspec);
155 static void gst_apexsink_get_property (GObject * object, guint prop_id,
156     GValue * value, GParamSpec * pspec);
157 static void gst_apexsink_finalise (GObject * object);
158
159 static gboolean gst_apexsink_open (GstAudioSink * asink);
160 static gboolean gst_apexsink_prepare (GstAudioSink * asink,
161     GstRingBufferSpec * spec);
162 static guint gst_apexsink_write (GstAudioSink * asink, gpointer data,
163     guint length);
164 static gboolean gst_apexsink_unprepare (GstAudioSink * asink);
165 static guint gst_apexsink_delay (GstAudioSink * asink);
166 static void gst_apexsink_reset (GstAudioSink * asink);
167 static gboolean gst_apexsink_close (GstAudioSink * asink);
168 static GstStateChangeReturn gst_apexsink_change_state (GstElement * element,
169     GstStateChange transition);
170
171 /* mixer interface standard api */
172 static void gst_apexsink_interfaces_init (GType type);
173 static void gst_apexsink_implements_interface_init (GstImplementsInterfaceClass
174     * iface);
175 static void gst_apexsink_mixer_interface_init (GstMixerInterface * iface);
176
177 static gboolean gst_apexsink_interface_supported (GstImplementsInterface *
178     iface, GType iface_type);
179 static const GList *gst_apexsink_mixer_list_tracks (GstMixer * mixer);
180 static void gst_apexsink_mixer_set_volume (GstMixer * mixer,
181     GstMixerTrack * track, gint * volumes);
182 static void gst_apexsink_mixer_get_volume (GstMixer * mixer,
183     GstMixerTrack * track, gint * volumes);
184
185 GST_BOILERPLATE_FULL (GstApExSink, gst_apexsink, GstAudioSink,
186     GST_TYPE_AUDIO_SINK, gst_apexsink_interfaces_init);
187
188 /* apex sink interface(s) stuff */
189 static void
190 gst_apexsink_interfaces_init (GType type)
191 {
192   static const GInterfaceInfo implements_interface_info =
193       { (GInterfaceInitFunc) gst_apexsink_implements_interface_init, NULL,
194     NULL
195   };
196   static const GInterfaceInfo mixer_interface_info =
197       { (GInterfaceInitFunc) gst_apexsink_mixer_interface_init, NULL, NULL };
198
199   g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE,
200       &implements_interface_info);
201   g_type_add_interface_static (type, GST_TYPE_MIXER, &mixer_interface_info);
202 }
203
204 static void
205 gst_apexsink_implements_interface_init (GstImplementsInterfaceClass * iface)
206 {
207   iface->supported = gst_apexsink_interface_supported;
208 }
209
210 static void
211 gst_apexsink_mixer_interface_init (GstMixerInterface * iface)
212 {
213   GST_MIXER_TYPE (iface) = GST_MIXER_SOFTWARE;
214
215   iface->list_tracks = gst_apexsink_mixer_list_tracks;
216   iface->set_volume = gst_apexsink_mixer_set_volume;
217   iface->get_volume = gst_apexsink_mixer_get_volume;
218 }
219
220 static gboolean
221 gst_apexsink_interface_supported (GstImplementsInterface * iface,
222     GType iface_type)
223 {
224   g_return_val_if_fail (iface_type == GST_TYPE_MIXER, FALSE);
225
226   return TRUE;
227 }
228
229 static const GList *
230 gst_apexsink_mixer_list_tracks (GstMixer * mixer)
231 {
232   GstApExSink *apexsink = GST_APEX_SINK (mixer);
233
234   return apexsink->tracks;
235 }
236
237 static void
238 gst_apexsink_mixer_set_volume (GstMixer * mixer, GstMixerTrack * track,
239     gint * volumes)
240 {
241   GstApExSink *apexsink = GST_APEX_SINK (mixer);
242
243   apexsink->volume = volumes[0];
244
245   if (apexsink->gst_apexraop != NULL)
246     gst_apexraop_set_volume (apexsink->gst_apexraop, apexsink->volume);
247 }
248
249 static void
250 gst_apexsink_mixer_get_volume (GstMixer * mixer, GstMixerTrack * track,
251     gint * volumes)
252 {
253   GstApExSink *apexsink = GST_APEX_SINK (mixer);
254
255   volumes[0] = apexsink->volume;
256 }
257
258 /* sink base init */
259 static void
260 gst_apexsink_base_init (gpointer g_class)
261 {
262   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
263
264   gst_element_class_set_metadata (element_class,
265       "Apple AirPort Express Audio Sink", "Sink/Audio/Wireless",
266       "Output stream to an AirPort Express",
267       "Jérémie Bernard [GRemi] <gremimail@gmail.com>");
268   gst_element_class_add_pad_template (element_class,
269       gst_static_pad_template_get (&gst_apexsink_sink_factory));
270 }
271
272 /* sink class init */
273 static void
274 gst_apexsink_class_init (GstApExSinkClass * klass)
275 {
276   GST_DEBUG_CATEGORY_INIT (apexsink_debug, GST_APEX_SINK_NAME, 0,
277       "AirPort Express sink");
278
279   parent_class = g_type_class_peek_parent (klass);
280
281   ((GObjectClass *) klass)->get_property =
282       GST_DEBUG_FUNCPTR (gst_apexsink_get_property);
283   ((GObjectClass *) klass)->set_property =
284       GST_DEBUG_FUNCPTR (gst_apexsink_set_property);
285   ((GObjectClass *) klass)->finalize =
286       GST_DEBUG_FUNCPTR (gst_apexsink_finalise);
287
288   ((GstAudioSinkClass *) klass)->open = GST_DEBUG_FUNCPTR (gst_apexsink_open);
289   ((GstAudioSinkClass *) klass)->prepare =
290       GST_DEBUG_FUNCPTR (gst_apexsink_prepare);
291   ((GstAudioSinkClass *) klass)->write = GST_DEBUG_FUNCPTR (gst_apexsink_write);
292   ((GstAudioSinkClass *) klass)->unprepare =
293       GST_DEBUG_FUNCPTR (gst_apexsink_unprepare);
294   ((GstAudioSinkClass *) klass)->delay = GST_DEBUG_FUNCPTR (gst_apexsink_delay);
295   ((GstAudioSinkClass *) klass)->reset = GST_DEBUG_FUNCPTR (gst_apexsink_reset);
296   ((GstAudioSinkClass *) klass)->close = GST_DEBUG_FUNCPTR (gst_apexsink_close);
297
298   ((GstElementClass *) klass)->change_state =
299       GST_DEBUG_FUNCPTR (gst_apexsink_change_state);
300
301   g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_HOST,
302       g_param_spec_string ("host", "Host", "AirPort Express target host",
303           DEFAULT_APEX_HOST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
304   g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_PORT,
305       g_param_spec_uint ("port", "Port", "AirPort Express target port", 0,
306           32000, DEFAULT_APEX_PORT,
307           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
308   /* we need to expose the volume as a double for playbin. Internally we keep
309    * it as an int between 0 and 100, where 75 corresponds to 1.0.
310    * FIXME we should store the volume as a double. */
311   g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_VOLUME,
312       g_param_spec_double ("volume", "Volume", "AirPort Express target volume",
313           0.0, 10.0, DEFAULT_APEX_VOLUME,
314           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
315   g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_JACK_TYPE,
316       g_param_spec_enum ("jack-type", "Jack Type",
317           "AirPort Express connected jack type", GST_APEX_SINK_JACKTYPE_TYPE,
318           DEFAULT_APEX_JACK_TYPE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
319   g_object_class_install_property ((GObjectClass *) klass,
320       APEX_PROP_JACK_STATUS, g_param_spec_enum ("jack-status", "Jack Status",
321           "AirPort Express jack connection status",
322           GST_APEX_SINK_JACKSTATUS_TYPE, DEFAULT_APEX_JACK_STATUS,
323           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
324   g_object_class_install_property ((GObjectClass *) klass,
325       APEX_PROP_GENERATION, g_param_spec_enum ("generation", "Generation",
326           "AirPort device generation",
327           GST_APEX_SINK_GENERATION_TYPE, DEFAULT_APEX_GENERATION,
328           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
329   g_object_class_install_property ((GObjectClass *) klass,
330       APEX_PROP_TRANSPORT_PROTOCOL, g_param_spec_enum ("transport-protocol",
331           "Transport Protocol", "AirPort transport protocol",
332           GST_APEX_SINK_TRANSPORT_PROTOCOL_TYPE,
333           DEFAULT_APEX_TRANSPORT_PROTOCOL,
334           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
335 }
336
337 /* sink plugin instance init */
338 static void
339 gst_apexsink_init (GstApExSink * apexsink, GstApExSinkClass * g_class)
340 {
341   GstMixerTrack *track = NULL;
342
343   track = g_object_new (GST_TYPE_MIXER_TRACK, NULL);
344   track->label = g_strdup ("Airport Express");
345   track->num_channels = GST_APEX_RAOP_CHANNELS;
346   track->min_volume = 0;
347   track->max_volume = 100;
348   track->flags = GST_MIXER_TRACK_OUTPUT;
349
350   apexsink->host = g_strdup (DEFAULT_APEX_HOST);
351   apexsink->port = DEFAULT_APEX_PORT;
352   apexsink->volume = CLAMP (DEFAULT_APEX_VOLUME * 75, 0, 100);
353   apexsink->gst_apexraop = NULL;
354   apexsink->tracks = g_list_append (apexsink->tracks, track);
355   apexsink->clock = gst_system_clock_obtain ();
356   apexsink->clock_id = NULL;
357
358   GST_INFO_OBJECT (apexsink,
359       "ApEx sink default initialization, target=\"%s\", port=\"%d\", volume=\"%d%%\"",
360       apexsink->host, apexsink->port, apexsink->volume);
361 }
362
363 /* apex sink set property */
364 static void
365 gst_apexsink_set_property (GObject * object, guint prop_id,
366     const GValue * value, GParamSpec * pspec)
367 {
368   GstApExSink *sink = GST_APEX_SINK (object);
369
370   switch (prop_id) {
371     case APEX_PROP_HOST:
372       if (sink->gst_apexraop == NULL) {
373         g_free (sink->host);
374         sink->host = g_value_dup_string (value);
375
376         GST_INFO_OBJECT (sink, "ApEx sink target set to \"%s\"", sink->host);
377       } else {
378         G_OBJECT_WARN_INVALID_PSPEC (object, "host", prop_id, pspec);
379       }
380       break;
381     case APEX_PROP_PORT:
382       if (sink->gst_apexraop == NULL) {
383         sink->port = g_value_get_uint (value);
384
385         GST_INFO_OBJECT (sink, "ApEx port set to \"%d\"", sink->port);
386       } else {
387         G_OBJECT_WARN_INVALID_PSPEC (object, "port", prop_id, pspec);
388       }
389       break;
390     case APEX_PROP_VOLUME:
391     {
392       gdouble volume;
393
394       volume = g_value_get_double (value);
395       volume *= 75.0;
396
397       sink->volume = CLAMP (volume, 0, 100);
398
399       if (sink->gst_apexraop != NULL)
400         gst_apexraop_set_volume (sink->gst_apexraop, sink->volume);
401
402       GST_INFO_OBJECT (sink, "ApEx volume set to \"%d%%\"", sink->volume);
403       break;
404     }
405     case APEX_PROP_GENERATION:
406       if (sink->gst_apexraop == NULL) {
407         sink->generation = g_value_get_enum (value);
408
409         GST_INFO_OBJECT (sink, "ApEx generation set to \"%d\"",
410             sink->generation);
411       } else {
412         GST_WARNING_OBJECT (sink,
413             "SET-PROPERTY : generation property may not be set when apexsink opened !");
414       }
415       break;
416     case APEX_PROP_TRANSPORT_PROTOCOL:
417       if (sink->gst_apexraop == NULL) {
418         sink->transport_protocol = g_value_get_enum (value);
419
420         GST_INFO_OBJECT (sink, "ApEx transport protocol set to \"%d\"",
421             sink->transport_protocol);
422       } else {
423         GST_WARNING_OBJECT (sink,
424             "SET-PROPERTY : transport protocol property may not be set when apexsink opened !");
425       }
426       break;
427     default:
428       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
429       break;
430   }
431 }
432
433 /* apex sink get property */
434 static void
435 gst_apexsink_get_property (GObject * object, guint prop_id, GValue * value,
436     GParamSpec * pspec)
437 {
438   GstApExSink *sink = GST_APEX_SINK (object);
439
440   switch (prop_id) {
441     case APEX_PROP_HOST:
442       g_value_set_string (value, sink->host);
443       break;
444     case APEX_PROP_PORT:
445       g_value_set_uint (value, sink->port);
446       break;
447     case APEX_PROP_VOLUME:
448       g_value_set_double (value, ((gdouble) sink->volume) / 75.0);
449       break;
450     case APEX_PROP_JACK_TYPE:
451       g_value_set_enum (value, gst_apexraop_get_jacktype (sink->gst_apexraop));
452       break;
453     case APEX_PROP_JACK_STATUS:
454       g_value_set_enum (value,
455           gst_apexraop_get_jackstatus (sink->gst_apexraop));
456       break;
457     case APEX_PROP_GENERATION:
458       g_value_set_enum (value,
459           gst_apexraop_get_generation (sink->gst_apexraop));
460       break;
461     case APEX_PROP_TRANSPORT_PROTOCOL:
462       g_value_set_enum (value,
463           gst_apexraop_get_transport_protocol (sink->gst_apexraop));
464       break;
465     default:
466       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
467       break;
468   }
469 }
470
471 /* apex sink finalize */
472 static void
473 gst_apexsink_finalise (GObject * object)
474 {
475   GstApExSink *sink = GST_APEX_SINK (object);
476
477   if (sink->tracks) {
478     g_list_foreach (sink->tracks, (GFunc) g_object_unref, NULL);
479     g_list_free (sink->tracks);
480     sink->tracks = NULL;
481   }
482
483   gst_object_unref (sink->clock);
484
485   g_free (sink->host);
486
487   G_OBJECT_CLASS (parent_class)->finalize (object);
488 }
489
490 /* sink open : open the device */
491 static gboolean
492 gst_apexsink_open (GstAudioSink * asink)
493 {
494   int res;
495   GstApExSink *apexsink = (GstApExSink *) asink;
496
497   apexsink->gst_apexraop = gst_apexraop_new (apexsink->host,
498       apexsink->port, apexsink->generation, apexsink->transport_protocol);
499
500   if ((res = gst_apexraop_connect (apexsink->gst_apexraop)) != GST_RTSP_STS_OK) {
501     GST_ERROR_OBJECT (apexsink,
502         "%s : network or RAOP failure, connection refused or timeout, RTSP code=%d",
503         apexsink->host, res);
504     return FALSE;
505   }
506
507   GST_INFO_OBJECT (apexsink,
508       "OPEN : ApEx sink successfully connected to \"%s:%d\", ANNOUNCE, SETUP and RECORD requests performed",
509       apexsink->host, apexsink->port);
510
511   switch (gst_apexraop_get_jackstatus (apexsink->gst_apexraop)) {
512     case GST_APEX_JACK_STATUS_CONNECTED:
513       GST_INFO_OBJECT (apexsink, "OPEN : ApEx jack is connected");
514       break;
515     case GST_APEX_JACK_STATUS_DISCONNECTED:
516       GST_WARNING_OBJECT (apexsink, "OPEN : ApEx jack is disconnected !");
517       break;
518     default:
519       GST_WARNING_OBJECT (apexsink, "OPEN : ApEx jack status is undefined !");
520       break;
521   }
522
523   switch (gst_apexraop_get_jacktype (apexsink->gst_apexraop)) {
524     case GST_APEX_JACK_TYPE_ANALOG:
525       GST_INFO_OBJECT (apexsink, "OPEN : ApEx jack type is analog");
526       break;
527     case GST_APEX_JACK_TYPE_DIGITAL:
528       GST_INFO_OBJECT (apexsink, "OPEN : ApEx jack type is digital");
529       break;
530     default:
531       GST_WARNING_OBJECT (apexsink, "OPEN : ApEx jack type is undefined !");
532       break;
533   }
534
535   if ((res =
536           gst_apexraop_set_volume (apexsink->gst_apexraop,
537               apexsink->volume)) != GST_RTSP_STS_OK) {
538     GST_WARNING_OBJECT (apexsink,
539         "%s : could not set initial volume to \"%d%%\", RTSP code=%d",
540         apexsink->host, apexsink->volume, res);
541   } else {
542     GST_INFO_OBJECT (apexsink,
543         "OPEN : ApEx sink successfully set volume to \"%d%%\"",
544         apexsink->volume);
545   }
546
547   return TRUE;
548 }
549
550 /* prepare sink : configure the device with the specified format */
551 static gboolean
552 gst_apexsink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec)
553 {
554   GstApExSink *apexsink = (GstApExSink *) asink;
555   GstApExGeneration gen = gst_apexraop_get_generation (apexsink->gst_apexraop);
556
557   apexsink->latency_time = spec->latency_time;
558
559   spec->segsize = gen == GST_APEX_GENERATION_ONE
560       ? GST_APEX_RAOP_V1_SAMPLES_PER_FRAME * GST_APEX_RAOP_BYTES_PER_SAMPLE
561       : GST_APEX_RAOP_V2_SAMPLES_PER_FRAME * GST_APEX_RAOP_BYTES_PER_SAMPLE;
562   spec->segtotal = 2;
563
564   memset (spec->silence_sample, 0, sizeof (spec->silence_sample));
565
566   GST_INFO_OBJECT (apexsink,
567       "PREPARE : ApEx sink ready to stream at %dHz, %d bytes per sample, %d channels, %d bytes segments (%dkB/s)",
568       spec->rate, spec->bytes_per_sample, spec->channels, spec->segsize,
569       spec->rate * spec->bytes_per_sample / 1000);
570
571   return TRUE;
572 }
573
574 /* sink write : write samples to the device */
575 static guint
576 gst_apexsink_write (GstAudioSink * asink, gpointer data, guint length)
577 {
578   guint written;
579   GstApExSink *apexsink = (GstApExSink *) asink;
580
581   if ((written =
582           gst_apexraop_write (apexsink->gst_apexraop, data,
583               length)) != length) {
584     GST_INFO_OBJECT (apexsink,
585         "WRITE : %d of %d bytes sent, skipping frame samples...", written,
586         length);
587   } else {
588     GST_INFO_OBJECT (apexsink, "WRITE : %d bytes sent", length);
589     /* NOTE, previous calculation subtracted apexsink->latency_time from this;
590      * however, the value below is less than apexsink->latency_time for generation 2.
591      * In this case, the number went negative (actualy wrapped around into a big number).
592      */
593     apexsink->clock_id = gst_clock_new_single_shot_id (apexsink->clock,
594         (GstClockTime) (gst_clock_get_time (apexsink->clock) +
595             ((length * 1000000000.)
596                 / (GST_APEX_RAOP_BITRATE * GST_APEX_RAOP_BYTES_PER_SAMPLE))));
597     gst_clock_id_wait (apexsink->clock_id, NULL);
598     gst_clock_id_unref (apexsink->clock_id);
599     apexsink->clock_id = NULL;
600   }
601
602   return length;
603 }
604
605 /* unprepare sink : undo operations done by prepare */
606 static gboolean
607 gst_apexsink_unprepare (GstAudioSink * asink)
608 {
609   GST_INFO_OBJECT (asink, "UNPREPARE");
610
611   return TRUE;
612 }
613
614 /* delay sink : get the estimated number of samples written but not played yet by the device */
615 static guint
616 gst_apexsink_delay (GstAudioSink * asink)
617 {
618   GST_LOG_OBJECT (asink, "DELAY");
619
620   return 0;
621 }
622
623 /* reset sink : unblock writes and flush the device */
624 static void
625 gst_apexsink_reset (GstAudioSink * asink)
626 {
627   int res;
628   GstApExSink *apexsink = (GstApExSink *) asink;
629
630   GST_INFO_OBJECT (apexsink, "RESET : flushing buffer...");
631
632   if ((res = gst_apexraop_flush (apexsink->gst_apexraop)) == GST_RTSP_STS_OK) {
633     GST_INFO_OBJECT (apexsink, "RESET : ApEx buffer flush success");
634   } else {
635     GST_WARNING_OBJECT (apexsink,
636         "RESET : could not flush ApEx buffer, RTSP code=%d", res);
637   }
638 }
639
640 /* sink close : close the device */
641 static gboolean
642 gst_apexsink_close (GstAudioSink * asink)
643 {
644   GstApExSink *apexsink = (GstApExSink *) asink;
645
646   gst_apexraop_close (apexsink->gst_apexraop);
647   gst_apexraop_free (apexsink->gst_apexraop);
648
649   GST_INFO_OBJECT (apexsink, "CLOSE : ApEx sink closed connection");
650
651   return TRUE;
652 }
653
654 static GstStateChangeReturn
655 gst_apexsink_change_state (GstElement * element, GstStateChange transition)
656 {
657   GstApExSink *apexsink = (GstApExSink *) element;
658
659   if (apexsink->clock_id && transition == GST_STATE_CHANGE_PAUSED_TO_READY) {
660     gst_clock_id_unschedule (apexsink->clock_id);
661     gst_clock_id_unref (apexsink->clock_id);
662     apexsink->clock_id = NULL;
663   }
664   return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
665 }