diff --git a/ext/apexsink/gstapexraop.c b/ext/apexsink/gstapexraop.c index 4d07674b0d..04c49a2b3d 100644 --- a/ext/apexsink/gstapexraop.c +++ b/ext/apexsink/gstapexraop.c @@ -52,14 +52,15 @@ const static gchar GST_APEX_RAOP_RSA_PUBLIC_EXP[] = "AQAB"; const static gchar GST_APEX_RAOP_USER_AGENT[] = "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"; -const static guchar GST_APEX_RAOP_FRAME_HEADER[] = { +const static guchar GST_APEX_RAOP_FRAME_HEADER[] = { // Used by gen. 1 0x24, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; -const static int GST_APEX_RAOP_FRAME_HEADER_SIZE = 16; +const static int GST_APEX_RAOP_FRAME_HEADER_SIZE = 16; // Used by gen. 1 +const static int GST_APEX_RTP_FRAME_HEADER_SIZE = 12; // Used by gen. 2 const static int GST_APEX_RAOP_ALAC_HEADER_SIZE = 3; @@ -121,6 +122,9 @@ typedef struct GstApExJackType jack_type; /* APEX connected jack type, once ANNOUNCE performed */ GstApExJackStatus jack_status; /* APEX connected jack status, once ANNOUNCE performed */ + GstApExGeneration generation; /* Different devices accept different audio streams */ + GstApExTransportProtocol transport_protocol; /* For media stream, not RAOP/RTSP */ + gchar *host; /* APEX target ip */ guint ctrl_port; /* APEX target control port */ guint data_port; /* APEX negotiated data port, once SETUP performed */ @@ -130,12 +134,18 @@ typedef struct int data_sd; /* data socket */ struct sockaddr_in data_sd_in; + + short rtp_seq_num; /* RTP sequence number, used by gen. 2 */ + int rtp_timestamp; /* RTP timestamp, used by gen. 2 */ } _GstApExRAOP; /* raop apex struct allocation */ GstApExRAOP * -gst_apexraop_new (const gchar * host, const guint16 port) +gst_apexraop_new (const gchar * host, + const guint16 port, + const GstApExGeneration generation, + const GstApExTransportProtocol transport_protocol) { _GstApExRAOP *apexraop; @@ -146,6 +156,10 @@ gst_apexraop_new (const gchar * host, const guint16 port) apexraop->ua = g_strdup (GST_APEX_RAOP_USER_AGENT); apexraop->jack_type = GST_APEX_JACK_TYPE_UNDEFINED; apexraop->jack_status = GST_APEX_JACK_STATUS_DISCONNECTED; + apexraop->generation = generation; + apexraop->transport_protocol = transport_protocol; + apexraop->rtp_seq_num = 0; + apexraop->rtp_timestamp = 0; return (GstApExRAOP *) apexraop; } @@ -311,7 +325,9 @@ gst_apexraop_connect (GstApExRAOP * con) conn->url_abspath, inaddr, conn->host, - GST_APEX_RAOP_SAMPLES_PER_FRAME, + conn->generation == GST_APEX_GENERATION_ONE + ? GST_APEX_RAOP_V1_SAMPLES_PER_FRAME + : GST_APEX_RAOP_V2_SAMPLES_PER_FRAME, GST_APEX_RAOP_BYTES_PER_CHANNEL * 8, GST_APEX_RAOP_CHANNELS, GST_APEX_RAOP_BITRATE, ky, iv); @@ -451,8 +467,14 @@ gst_apexraop_connect (GstApExRAOP * con) if (res != GST_RTSP_STS_OK) return res; - if ((conn->data_sd = socket (AF_INET, SOCK_STREAM, 0)) < 0) - return GST_RTSP_STS_DESTINATION_UNREACHABLE; + if (conn->transport_protocol == GST_APEX_TCP) { + if ((conn->data_sd = socket (AF_INET, SOCK_STREAM, 0)) < 0) + return GST_RTSP_STS_DESTINATION_UNREACHABLE; + } else if (conn->transport_protocol == GST_APEX_UDP) { + if ((conn->data_sd = socket (AF_INET, SOCK_DGRAM, 0)) < 0) + return GST_RTSP_STS_DESTINATION_UNREACHABLE; + } else + return GST_RTSP_STS_METHOD_NOT_ALLOWED; conn->data_sd_in.sin_family = AF_INET; conn->data_sd_in.sin_port = htons (conn->data_port); @@ -495,6 +517,34 @@ gst_apexraop_get_jackstatus (GstApExRAOP * con) return conn->jack_status; } +/* raop apex generation access */ +GstApExGeneration +gst_apexraop_get_generation (GstApExRAOP * con) +{ + _GstApExRAOP *conn; + + conn = (_GstApExRAOP *) con; + + if (!conn) + return GST_APEX_GENERATION_ONE; + + return conn->generation; +} + +/* raop apex transport protocol access */ +GstApExTransportProtocol +gst_apexraop_get_transport_protocol (GstApExRAOP * con) +{ + _GstApExRAOP *conn; + + conn = (_GstApExRAOP *) con; + + if (!conn) + return GST_APEX_TCP; + + return conn->transport_protocol; +} + /* raop apex sockets close */ void gst_apexraop_close (GstApExRAOP * con) @@ -628,25 +678,49 @@ gst_apexraop_write (GstApExRAOP * con, gpointer rawdata, guint length) gushort len; gint bit_offset, byte_offset, i, out_len, res; EVP_CIPHER_CTX aes_ctx; - _GstApExRAOP *conn; - - conn = (_GstApExRAOP *) con; + _GstApExRAOP *conn = (_GstApExRAOP *) con; + const int frame_header_size = conn->generation == GST_APEX_GENERATION_ONE + ? GST_APEX_RAOP_FRAME_HEADER_SIZE : GST_APEX_RTP_FRAME_HEADER_SIZE; buffer = - (guchar *) g_malloc0 (GST_APEX_RAOP_FRAME_HEADER_SIZE + + (guchar *) g_malloc0 (frame_header_size + GST_APEX_RAOP_ALAC_HEADER_SIZE + length); - memcpy (buffer, GST_APEX_RAOP_FRAME_HEADER, GST_APEX_RAOP_FRAME_HEADER_SIZE); + if (conn->generation == GST_APEX_GENERATION_ONE) { + g_assert (frame_header_size == GST_APEX_RAOP_FRAME_HEADER_SIZE); + memcpy (buffer, GST_APEX_RAOP_FRAME_HEADER, frame_header_size); - len = - length + GST_APEX_RAOP_FRAME_HEADER_SIZE + - GST_APEX_RAOP_ALAC_HEADER_SIZE - 4; - buffer[2] = len >> 8; - buffer[3] = len & 0xff; + len = length + frame_header_size + GST_APEX_RAOP_ALAC_HEADER_SIZE - 4; + + buffer[2] = len >> 8; + buffer[3] = len & 0xff; + } else { + /* Gen. 2 uses RTP-like header (RFC 3550). */ + short network_seq_num; + int network_timestamp, unknown_const; + static gboolean first = TRUE; + + buffer[0] = 0x80; + if (first) { + buffer[1] = 0xe0; + first = FALSE; + } else + buffer[1] = 0x60; + + network_seq_num = htons (conn->rtp_seq_num++); + memcpy (buffer + 2, &network_seq_num, 2); + + network_timestamp = htons (conn->rtp_timestamp); + memcpy (buffer + 4, &network_timestamp, 4); + conn->rtp_timestamp += GST_APEX_RAOP_V2_SAMPLES_PER_FRAME; + + unknown_const = 0xdeadbeef; + memcpy (buffer + 8, &unknown_const, 4); + } bit_offset = 0; byte_offset = 0; - frame_data = buffer + GST_APEX_RAOP_FRAME_HEADER_SIZE; + frame_data = buffer + frame_header_size; gst_apexraop_write_bits (frame_data, 1, 3, &bit_offset, &byte_offset); /* channels, 0 mono, 1 stereo */ gst_apexraop_write_bits (frame_data, 0, 4, &bit_offset, &byte_offset); /* unknown */ @@ -673,16 +747,14 @@ gst_apexraop_write (GstApExRAOP * con, gpointer rawdata, guint length) res = gst_apexraop_send (conn->data_sd, buffer, - GST_APEX_RAOP_FRAME_HEADER_SIZE + GST_APEX_RAOP_ALAC_HEADER_SIZE + - length); + frame_header_size + GST_APEX_RAOP_ALAC_HEADER_SIZE + length); g_free (buffer); return (guint) ((res >= - (GST_APEX_RAOP_FRAME_HEADER_SIZE + + (frame_header_size + GST_APEX_RAOP_ALAC_HEADER_SIZE)) ? (res - - GST_APEX_RAOP_FRAME_HEADER_SIZE - - GST_APEX_RAOP_ALAC_HEADER_SIZE) : 0); + frame_header_size - GST_APEX_RAOP_ALAC_HEADER_SIZE) : 0); } /* raop apex buffer flush */ @@ -701,10 +773,13 @@ gst_apexraop_flush (GstApExRAOP * con) "Client-Instance: %s\r\n" "User-Agent: %s\r\n" "Session: %s\r\n" - "RTP-Info: seq=0;rtptime=0\r\n" + "RTP-Info: seq=%d;rtptime=%d\r\n" "\r\n", conn->host, - conn->url_abspath, ++conn->cseq, conn->cid, conn->ua, conn->session); + conn->url_abspath, + ++conn->cseq, + conn->cid, + conn->ua, conn->session, conn->rtp_seq_num, conn->rtp_timestamp); if (gst_apexraop_send (conn->ctrl_sd, hreq, strlen (hreq)) <= 0) return GST_RTSP_STS_GONE; diff --git a/ext/apexsink/gstapexraop.h b/ext/apexsink/gstapexraop.h index fe1ba41b7c..462118e231 100644 --- a/ext/apexsink/gstapexraop.h +++ b/ext/apexsink/gstapexraop.h @@ -48,7 +48,8 @@ G_BEGIN_DECLS /* raop fixed parameters */ #define GST_APEX_RAOP_BITRATE 44100 -#define GST_APEX_RAOP_SAMPLES_PER_FRAME 4096 +#define GST_APEX_RAOP_V1_SAMPLES_PER_FRAME 4096 +#define GST_APEX_RAOP_V2_SAMPLES_PER_FRAME 352 #define GST_APEX_RAOP_BYTES_PER_CHANNEL 2 #define GST_APEX_RAOP_CHANNELS 2 #define GST_APEX_RAOP_BYTES_PER_SAMPLE (GST_APEX_RAOP_CHANNELS * GST_APEX_RAOP_BYTES_PER_CHANNEL) @@ -78,13 +79,30 @@ typedef enum } GstApExJackStatus; +typedef enum +{ + GST_APEX_GENERATION_ONE = 1, + GST_APEX_GENERATION_TWO, +} +GstApExGeneration; + +typedef enum +{ + GST_APEX_TCP = 0, + GST_APEX_UDP, +} +GstApExTransportProtocol; + /* raop context handle */ typedef struct { } GstApExRAOP; /* host might be null and port might be 0 while instanciating */ -GstApExRAOP *gst_apexraop_new (const gchar * host, const guint16 port); +GstApExRAOP *gst_apexraop_new (const gchar * host, + const guint16 port, + const GstApExGeneration generation, + const GstApExTransportProtocol transport_protocol); void gst_apexraop_free (GstApExRAOP * conn); /* must not be connected yet while setting the host target */ @@ -118,6 +136,12 @@ GstRTSPStatusCode gst_apexraop_flush (GstApExRAOP * conn); GstApExJackType gst_apexraop_get_jacktype (GstApExRAOP * conn); GstApExJackStatus gst_apexraop_get_jackstatus (GstApExRAOP * conn); +/* retrieve the generation */ +GstApExGeneration gst_apexraop_get_generation (GstApExRAOP * conn); + +/* retrieve the transport protocol */ +GstApExTransportProtocol gst_apexraop_get_transport_protocol (GstApExRAOP * conn); + G_END_DECLS #endif diff --git a/ext/apexsink/gstapexsink.c b/ext/apexsink/gstapexsink.c index 8daaa23fc3..fc64db83a4 100644 --- a/ext/apexsink/gstapexsink.c +++ b/ext/apexsink/gstapexsink.c @@ -59,6 +59,8 @@ enum APEX_PROP_VOLUME, APEX_PROP_JACK_TYPE, APEX_PROP_JACK_STATUS, + APEX_PROP_GENERATION, + APEX_PROP_TRANSPORT_PROTOCOL, }; #define DEFAULT_APEX_HOST "" @@ -66,6 +68,8 @@ enum #define DEFAULT_APEX_VOLUME 1.0 #define DEFAULT_APEX_JACK_TYPE GST_APEX_JACK_TYPE_UNDEFINED #define DEFAULT_APEX_JACK_STATUS GST_APEX_JACK_STATUS_UNDEFINED +#define DEFAULT_APEX_GENERATION GST_APEX_GENERATION_ONE +#define DEFAULT_APEX_TRANSPORT_PROTOCOL GST_APEX_TCP /* genum apex jack resolution */ GType @@ -108,6 +112,43 @@ gst_apexsink_jacktype_get_type (void) return jacktype_type; } +GType +gst_apexsink_generation_get_type (void) +{ + static GType generation_type = 0; + static GEnumValue generation[] = { + {GST_APEX_GENERATION_ONE, "generation-one", + "First generation (e.g., original AirPort Express)"}, + {GST_APEX_GENERATION_TWO, "generation-two", + "Second generation (e.g., Apple TV v2)"}, + {0, NULL, NULL}, + }; + + if (!generation_type) { + generation_type = g_enum_register_static ("GstApExGeneration", generation); + } + + return generation_type; +} + +GType +gst_apexsink_transport_protocol_get_type (void) +{ + static GType transport_protocol_type = 0; + static GEnumValue transport_protocol[] = { + {GST_APEX_TCP, "tcp", "TCP"}, + {GST_APEX_UDP, "udp", "UDP"}, + {0, NULL, NULL}, + }; + + if (!transport_protocol_type) { + transport_protocol_type = + g_enum_register_static ("GstApExTransportProtocol", transport_protocol); + } + + return transport_protocol_type; +} + static void gst_apexsink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); @@ -124,6 +165,8 @@ static gboolean gst_apexsink_unprepare (GstAudioSink * asink); static guint gst_apexsink_delay (GstAudioSink * asink); static void gst_apexsink_reset (GstAudioSink * asink); static gboolean gst_apexsink_close (GstAudioSink * asink); +static GstStateChangeReturn gst_apexsink_change_state (GstElement * element, + GstStateChange transition); /* mixer interface standard api */ static void gst_apexsink_interfaces_init (GType type); @@ -252,6 +295,9 @@ gst_apexsink_class_init (GstApExSinkClass * klass) ((GstAudioSinkClass *) klass)->reset = GST_DEBUG_FUNCPTR (gst_apexsink_reset); ((GstAudioSinkClass *) klass)->close = GST_DEBUG_FUNCPTR (gst_apexsink_close); + ((GstElementClass *) klass)->change_state = + GST_DEBUG_FUNCPTR (gst_apexsink_change_state); + g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_HOST, g_param_spec_string ("host", "Host", "AirPort Express target host", DEFAULT_APEX_HOST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); @@ -275,6 +321,17 @@ gst_apexsink_class_init (GstApExSinkClass * klass) "AirPort Express jack connection status", GST_APEX_SINK_JACKSTATUS_TYPE, DEFAULT_APEX_JACK_STATUS, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property ((GObjectClass *) klass, + APEX_PROP_GENERATION, g_param_spec_enum ("generation", "Generation", + "AirPort device generation", + GST_APEX_SINK_GENERATION_TYPE, DEFAULT_APEX_GENERATION, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property ((GObjectClass *) klass, + APEX_PROP_TRANSPORT_PROTOCOL, g_param_spec_enum ("transport-protocol", + "Transport Protocol", "AirPort transport protocol", + GST_APEX_SINK_TRANSPORT_PROTOCOL_TYPE, + DEFAULT_APEX_TRANSPORT_PROTOCOL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } /* sink plugin instance init */ @@ -295,6 +352,8 @@ gst_apexsink_init (GstApExSink * apexsink, GstApExSinkClass * g_class) apexsink->volume = CLAMP (DEFAULT_APEX_VOLUME * 75, 0, 100); apexsink->gst_apexraop = NULL; apexsink->tracks = g_list_append (apexsink->tracks, track); + apexsink->clock = gst_system_clock_obtain (); + apexsink->clock_id = NULL; GST_INFO_OBJECT (apexsink, "ApEx sink default initialization, target=\"%s\", port=\"%d\", volume=\"%d%%\"", @@ -343,6 +402,28 @@ gst_apexsink_set_property (GObject * object, guint prop_id, GST_INFO_OBJECT (sink, "ApEx volume set to \"%d%%\"", sink->volume); break; } + case APEX_PROP_GENERATION: + if (sink->gst_apexraop == NULL) { + sink->generation = g_value_get_enum (value); + + GST_INFO_OBJECT (sink, "ApEx generation set to \"%d\"", + sink->generation); + } else { + GST_WARNING_OBJECT (sink, + "SET-PROPERTY : generation property may not be set when apexsink opened !"); + } + break; + case APEX_PROP_TRANSPORT_PROTOCOL: + if (sink->gst_apexraop == NULL) { + sink->transport_protocol = g_value_get_enum (value); + + GST_INFO_OBJECT (sink, "ApEx transport protocol set to \"%d\"", + sink->transport_protocol); + } else { + GST_WARNING_OBJECT (sink, + "SET-PROPERTY : transport protocol property may not be set when apexsink opened !"); + } + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -373,6 +454,14 @@ gst_apexsink_get_property (GObject * object, guint prop_id, GValue * value, g_value_set_enum (value, gst_apexraop_get_jackstatus (sink->gst_apexraop)); break; + case APEX_PROP_GENERATION: + g_value_set_enum (value, + gst_apexraop_get_generation (sink->gst_apexraop)); + break; + case APEX_PROP_TRANSPORT_PROTOCOL: + g_value_set_enum (value, + gst_apexraop_get_transport_protocol (sink->gst_apexraop)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -391,6 +480,8 @@ gst_apexsink_finalise (GObject * object) sink->tracks = NULL; } + gst_object_unref (sink->clock); + g_free (sink->host); G_OBJECT_CLASS (parent_class)->finalize (object); @@ -403,7 +494,8 @@ gst_apexsink_open (GstAudioSink * asink) int res; GstApExSink *apexsink = (GstApExSink *) asink; - apexsink->gst_apexraop = gst_apexraop_new (apexsink->host, apexsink->port); + apexsink->gst_apexraop = gst_apexraop_new (apexsink->host, + apexsink->port, apexsink->generation, apexsink->transport_protocol); if ((res = gst_apexraop_connect (apexsink->gst_apexraop)) != GST_RTSP_STS_OK) { GST_ERROR_OBJECT (apexsink, @@ -460,12 +552,14 @@ static gboolean gst_apexsink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec) { GstApExSink *apexsink = (GstApExSink *) asink; + GstApExGeneration gen = gst_apexraop_get_generation (apexsink->gst_apexraop); apexsink->latency_time = spec->latency_time; - spec->segsize = - GST_APEX_RAOP_SAMPLES_PER_FRAME * GST_APEX_RAOP_BYTES_PER_SAMPLE; - spec->segtotal = 1; + spec->segsize = gen == GST_APEX_GENERATION_ONE + ? GST_APEX_RAOP_V1_SAMPLES_PER_FRAME * GST_APEX_RAOP_BYTES_PER_SAMPLE + : GST_APEX_RAOP_V2_SAMPLES_PER_FRAME * GST_APEX_RAOP_BYTES_PER_SAMPLE; + spec->segtotal = 2; memset (spec->silence_sample, 0, sizeof (spec->silence_sample)); @@ -481,17 +575,28 @@ gst_apexsink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec) static guint gst_apexsink_write (GstAudioSink * asink, gpointer data, guint length) { + guint written; GstApExSink *apexsink = (GstApExSink *) asink; - if (gst_apexraop_write (apexsink->gst_apexraop, data, length) != length) { + if ((written = + gst_apexraop_write (apexsink->gst_apexraop, data, + length)) != length) { GST_INFO_OBJECT (apexsink, - "WRITE : %d bytes not fully sended, skipping frame samples...", length); + "WRITE : %d of %d bytes sent, skipping frame samples...", written, + length); } else { GST_INFO_OBJECT (apexsink, "WRITE : %d bytes sent", length); - - /* FIXME, sleeping is ugly and not interruptible */ - usleep ((gulong) ((length * 1000000.) / (GST_APEX_RAOP_BITRATE * - GST_APEX_RAOP_BYTES_PER_SAMPLE) - apexsink->latency_time)); + /* NOTE, previous calculation subtracted apexsink->latency_time from this; + * however, the value below is less than apexsink->latency_time for generation 2. + * In this case, the number went negative (actualy wrapped around into a big number). + */ + apexsink->clock_id = gst_clock_new_single_shot_id (apexsink->clock, + (GstClockTime) (gst_clock_get_time (apexsink->clock) + + ((length * 1000000000.) + / (GST_APEX_RAOP_BITRATE * GST_APEX_RAOP_BYTES_PER_SAMPLE)))); + gst_clock_id_wait (apexsink->clock_id, NULL); + gst_clock_id_unref (apexsink->clock_id); + apexsink->clock_id = NULL; } return length; @@ -545,3 +650,16 @@ gst_apexsink_close (GstAudioSink * asink) return TRUE; } + +static GstStateChangeReturn +gst_apexsink_change_state (GstElement * element, GstStateChange transition) +{ + GstApExSink *apexsink = (GstApExSink *) element; + + if (apexsink->clock_id && transition == GST_STATE_CHANGE_PAUSED_TO_READY) { + gst_clock_id_unschedule (apexsink->clock_id); + gst_clock_id_unref (apexsink->clock_id); + apexsink->clock_id = NULL; + } + return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); +} diff --git a/ext/apexsink/gstapexsink.h b/ext/apexsink/gstapexsink.h index 11d2f1ef91..ad2b299e35 100644 --- a/ext/apexsink/gstapexsink.h +++ b/ext/apexsink/gstapexsink.h @@ -46,6 +46,8 @@ G_BEGIN_DECLS #define GST_APEX_SINK_NAME "apexsink" #define GST_APEX_SINK_JACKTYPE_TYPE (gst_apexsink_jacktype_get_type()) #define GST_APEX_SINK_JACKSTATUS_TYPE (gst_apexsink_jackstatus_get_type()) +#define GST_APEX_SINK_GENERATION_TYPE (gst_apexsink_generation_get_type()) +#define GST_APEX_SINK_TRANSPORT_PROTOCOL_TYPE (gst_apexsink_transport_protocol_get_type()) /* ApEx classes declaration */ typedef struct _GstApExSink GstApExSink; typedef struct _GstApExSinkClass GstApExSinkClass; @@ -59,10 +61,19 @@ struct _GstApExSink gchar *host; guint port; guint volume; + GstApExGeneration generation; + GstApExTransportProtocol transport_protocol; - /* private attributes : latency time local copy, tracks list of the mixer interface */ + /* private attributes : + * latency time local copy + * tracks list of the mixer interface + * clock for sleeping + * clock ID for sleeping / canceling sleep + */ guint64 latency_time; GList *tracks; + GstClock *clock; + GstClockID clock_id; /* private apex client */ GstApExRAOP *gst_apexraop; @@ -73,9 +84,11 @@ struct _GstApExSinkClass GstAudioSinkClass parent_class; }; -/* genum jack access */ +/* genums */ GType gst_apexsink_jackstatus_get_type (void); GType gst_apexsink_jacktype_get_type (void); +GType gst_apexsink_generation_get_type (void); +GType gst_apexsink_transport_protocol_get_type (void); /* audio sink standard api */ GType gst_apexsink_get_type (void);