1 /* GStreamer - AirPort Express Audio Sink -
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.
6 * RAW PCM input only as defined by the following GST_STATIC_PAD_TEMPLATE
8 * Copyright (C) 2008 Jérémie Bernard [GRemi] <gremimail@gmail.com>
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.
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.
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.
35 #include "gstapexsink.h"
37 GST_DEBUG_CATEGORY_STATIC (apexsink_debug);
38 #define GST_CAT_DEFAULT apexsink_debug
40 static GstStaticPadTemplate gst_apexsink_sink_factory = GST_STATIC_PAD_TEMPLATE
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)
61 APEX_PROP_JACK_STATUS,
63 APEX_PROP_TRANSPORT_PROTOCOL,
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
74 /* genum apex jack resolution */
76 gst_apexsink_jackstatus_get_type (void)
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",
84 {GST_APEX_JACK_STATUS_CONNECTED, "GST_APEX_JACK_STATUS_CONNECTED",
89 if (!jackstatus_type) {
90 jackstatus_type = g_enum_register_static ("GstApExJackStatus", jackstatus);
93 return jackstatus_type;
97 gst_apexsink_jacktype_get_type (void)
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"},
108 if (!jacktype_type) {
109 jacktype_type = g_enum_register_static ("GstApExJackType", jacktype);
112 return jacktype_type;
116 gst_apexsink_generation_get_type (void)
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)"},
127 if (!generation_type) {
128 generation_type = g_enum_register_static ("GstApExGeneration", generation);
131 return generation_type;
135 gst_apexsink_transport_protocol_get_type (void)
137 static GType transport_protocol_type = 0;
138 static GEnumValue transport_protocol[] = {
139 {GST_APEX_TCP, "tcp", "TCP"},
140 {GST_APEX_UDP, "udp", "UDP"},
144 if (!transport_protocol_type) {
145 transport_protocol_type =
146 g_enum_register_static ("GstApExTransportProtocol", transport_protocol);
149 return transport_protocol_type;
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);
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,
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);
171 /* mixer interface standard api */
172 static void gst_apexsink_interfaces_init (GType type);
173 static void gst_apexsink_implements_interface_init (GstImplementsInterfaceClass
175 static void gst_apexsink_mixer_interface_init (GstMixerInterface * iface);
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);
185 GST_BOILERPLATE_FULL (GstApExSink, gst_apexsink, GstAudioSink,
186 GST_TYPE_AUDIO_SINK, gst_apexsink_interfaces_init);
188 /* apex sink interface(s) stuff */
190 gst_apexsink_interfaces_init (GType type)
192 static const GInterfaceInfo implements_interface_info =
193 { (GInterfaceInitFunc) gst_apexsink_implements_interface_init, NULL,
196 static const GInterfaceInfo mixer_interface_info =
197 { (GInterfaceInitFunc) gst_apexsink_mixer_interface_init, NULL, NULL };
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);
205 gst_apexsink_implements_interface_init (GstImplementsInterfaceClass * iface)
207 iface->supported = gst_apexsink_interface_supported;
211 gst_apexsink_mixer_interface_init (GstMixerInterface * iface)
213 GST_MIXER_TYPE (iface) = GST_MIXER_SOFTWARE;
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;
221 gst_apexsink_interface_supported (GstImplementsInterface * iface,
224 g_return_val_if_fail (iface_type == GST_TYPE_MIXER, FALSE);
230 gst_apexsink_mixer_list_tracks (GstMixer * mixer)
232 GstApExSink *apexsink = GST_APEX_SINK (mixer);
234 return apexsink->tracks;
238 gst_apexsink_mixer_set_volume (GstMixer * mixer, GstMixerTrack * track,
241 GstApExSink *apexsink = GST_APEX_SINK (mixer);
243 apexsink->volume = volumes[0];
245 if (apexsink->gst_apexraop != NULL)
246 gst_apexraop_set_volume (apexsink->gst_apexraop, apexsink->volume);
250 gst_apexsink_mixer_get_volume (GstMixer * mixer, GstMixerTrack * track,
253 GstApExSink *apexsink = GST_APEX_SINK (mixer);
255 volumes[0] = apexsink->volume;
260 gst_apexsink_base_init (gpointer g_class)
262 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
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));
272 /* sink class init */
274 gst_apexsink_class_init (GstApExSinkClass * klass)
276 GST_DEBUG_CATEGORY_INIT (apexsink_debug, GST_APEX_SINK_NAME, 0,
277 "AirPort Express sink");
279 parent_class = g_type_class_peek_parent (klass);
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);
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);
298 ((GstElementClass *) klass)->change_state =
299 GST_DEBUG_FUNCPTR (gst_apexsink_change_state);
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));
337 /* sink plugin instance init */
339 gst_apexsink_init (GstApExSink * apexsink, GstApExSinkClass * g_class)
341 GstMixerTrack *track = NULL;
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;
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;
358 GST_INFO_OBJECT (apexsink,
359 "ApEx sink default initialization, target=\"%s\", port=\"%d\", volume=\"%d%%\"",
360 apexsink->host, apexsink->port, apexsink->volume);
363 /* apex sink set property */
365 gst_apexsink_set_property (GObject * object, guint prop_id,
366 const GValue * value, GParamSpec * pspec)
368 GstApExSink *sink = GST_APEX_SINK (object);
372 if (sink->gst_apexraop == NULL) {
374 sink->host = g_value_dup_string (value);
376 GST_INFO_OBJECT (sink, "ApEx sink target set to \"%s\"", sink->host);
378 G_OBJECT_WARN_INVALID_PSPEC (object, "host", prop_id, pspec);
382 if (sink->gst_apexraop == NULL) {
383 sink->port = g_value_get_uint (value);
385 GST_INFO_OBJECT (sink, "ApEx port set to \"%d\"", sink->port);
387 G_OBJECT_WARN_INVALID_PSPEC (object, "port", prop_id, pspec);
390 case APEX_PROP_VOLUME:
394 volume = g_value_get_double (value);
397 sink->volume = CLAMP (volume, 0, 100);
399 if (sink->gst_apexraop != NULL)
400 gst_apexraop_set_volume (sink->gst_apexraop, sink->volume);
402 GST_INFO_OBJECT (sink, "ApEx volume set to \"%d%%\"", sink->volume);
405 case APEX_PROP_GENERATION:
406 if (sink->gst_apexraop == NULL) {
407 sink->generation = g_value_get_enum (value);
409 GST_INFO_OBJECT (sink, "ApEx generation set to \"%d\"",
412 GST_WARNING_OBJECT (sink,
413 "SET-PROPERTY : generation property may not be set when apexsink opened !");
416 case APEX_PROP_TRANSPORT_PROTOCOL:
417 if (sink->gst_apexraop == NULL) {
418 sink->transport_protocol = g_value_get_enum (value);
420 GST_INFO_OBJECT (sink, "ApEx transport protocol set to \"%d\"",
421 sink->transport_protocol);
423 GST_WARNING_OBJECT (sink,
424 "SET-PROPERTY : transport protocol property may not be set when apexsink opened !");
428 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
433 /* apex sink get property */
435 gst_apexsink_get_property (GObject * object, guint prop_id, GValue * value,
438 GstApExSink *sink = GST_APEX_SINK (object);
442 g_value_set_string (value, sink->host);
445 g_value_set_uint (value, sink->port);
447 case APEX_PROP_VOLUME:
448 g_value_set_double (value, ((gdouble) sink->volume) / 75.0);
450 case APEX_PROP_JACK_TYPE:
451 g_value_set_enum (value, gst_apexraop_get_jacktype (sink->gst_apexraop));
453 case APEX_PROP_JACK_STATUS:
454 g_value_set_enum (value,
455 gst_apexraop_get_jackstatus (sink->gst_apexraop));
457 case APEX_PROP_GENERATION:
458 g_value_set_enum (value,
459 gst_apexraop_get_generation (sink->gst_apexraop));
461 case APEX_PROP_TRANSPORT_PROTOCOL:
462 g_value_set_enum (value,
463 gst_apexraop_get_transport_protocol (sink->gst_apexraop));
466 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
471 /* apex sink finalize */
473 gst_apexsink_finalise (GObject * object)
475 GstApExSink *sink = GST_APEX_SINK (object);
478 g_list_foreach (sink->tracks, (GFunc) g_object_unref, NULL);
479 g_list_free (sink->tracks);
483 gst_object_unref (sink->clock);
487 G_OBJECT_CLASS (parent_class)->finalize (object);
490 /* sink open : open the device */
492 gst_apexsink_open (GstAudioSink * asink)
495 GstApExSink *apexsink = (GstApExSink *) asink;
497 apexsink->gst_apexraop = gst_apexraop_new (apexsink->host,
498 apexsink->port, apexsink->generation, apexsink->transport_protocol);
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);
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);
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");
515 case GST_APEX_JACK_STATUS_DISCONNECTED:
516 GST_WARNING_OBJECT (apexsink, "OPEN : ApEx jack is disconnected !");
519 GST_WARNING_OBJECT (apexsink, "OPEN : ApEx jack status is undefined !");
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");
527 case GST_APEX_JACK_TYPE_DIGITAL:
528 GST_INFO_OBJECT (apexsink, "OPEN : ApEx jack type is digital");
531 GST_WARNING_OBJECT (apexsink, "OPEN : ApEx jack type is undefined !");
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);
542 GST_INFO_OBJECT (apexsink,
543 "OPEN : ApEx sink successfully set volume to \"%d%%\"",
550 /* prepare sink : configure the device with the specified format */
552 gst_apexsink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec)
554 GstApExSink *apexsink = (GstApExSink *) asink;
555 GstApExGeneration gen = gst_apexraop_get_generation (apexsink->gst_apexraop);
557 apexsink->latency_time = spec->latency_time;
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;
564 memset (spec->silence_sample, 0, sizeof (spec->silence_sample));
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);
574 /* sink write : write samples to the device */
576 gst_apexsink_write (GstAudioSink * asink, gpointer data, guint length)
579 GstApExSink *apexsink = (GstApExSink *) asink;
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,
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).
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;
605 /* unprepare sink : undo operations done by prepare */
607 gst_apexsink_unprepare (GstAudioSink * asink)
609 GST_INFO_OBJECT (asink, "UNPREPARE");
614 /* delay sink : get the estimated number of samples written but not played yet by the device */
616 gst_apexsink_delay (GstAudioSink * asink)
618 GST_LOG_OBJECT (asink, "DELAY");
623 /* reset sink : unblock writes and flush the device */
625 gst_apexsink_reset (GstAudioSink * asink)
628 GstApExSink *apexsink = (GstApExSink *) asink;
630 GST_INFO_OBJECT (apexsink, "RESET : flushing buffer...");
632 if ((res = gst_apexraop_flush (apexsink->gst_apexraop)) == GST_RTSP_STS_OK) {
633 GST_INFO_OBJECT (apexsink, "RESET : ApEx buffer flush success");
635 GST_WARNING_OBJECT (apexsink,
636 "RESET : could not flush ApEx buffer, RTSP code=%d", res);
640 /* sink close : close the device */
642 gst_apexsink_close (GstAudioSink * asink)
644 GstApExSink *apexsink = (GstApExSink *) asink;
646 gst_apexraop_close (apexsink->gst_apexraop);
647 gst_apexraop_free (apexsink->gst_apexraop);
649 GST_INFO_OBJECT (apexsink, "CLOSE : ApEx sink closed connection");
654 static GstStateChangeReturn
655 gst_apexsink_change_state (GstElement * element, GstStateChange transition)
657 GstApExSink *apexsink = (GstApExSink *) element;
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;
664 return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);