1/*
2 *
3 *  BlueZ - Bluetooth protocol stack for Linux
4 *
5 *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
6 *
7 *
8 *  This library is free software; you can redistribute it and/or
9 *  modify it under the terms of the GNU Lesser General Public
10 *  License as published by the Free Software Foundation; either
11 *  version 2.1 of the License, or (at your option) any later version.
12 *
13 *  This library is distributed in the hope that it will be useful,
14 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 *  Lesser General Public License for more details.
17 *
18 *  You should have received a copy of the GNU Lesser General Public
19 *  License along with this library; if not, write to the Free Software
20 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21 *
22 */
23
24#ifdef HAVE_CONFIG_H
25#include <config.h>
26#endif
27
28#include <unistd.h>
29#include <pthread.h>
30
31#include "gstpragma.h"
32#include "gsta2dpsink.h"
33
34GST_DEBUG_CATEGORY_STATIC(gst_a2dp_sink_debug);
35#define GST_CAT_DEFAULT gst_a2dp_sink_debug
36
37#define A2DP_SBC_RTP_PAYLOAD_TYPE 1
38#define TEMPLATE_MAX_BITPOOL_STR "64"
39
40#define DEFAULT_AUTOCONNECT TRUE
41
42enum {
43	PROP_0,
44	PROP_DEVICE,
45	PROP_AUTOCONNECT,
46	PROP_TRANSPORT
47};
48
49GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBin, GST_TYPE_BIN);
50
51static const GstElementDetails gst_a2dp_sink_details =
52	GST_ELEMENT_DETAILS("Bluetooth A2DP sink",
53				"Sink/Audio",
54				"Plays audio to an A2DP device",
55				"Marcel Holtmann <marcel@holtmann.org>");
56
57static GstStaticPadTemplate gst_a2dp_sink_factory =
58	GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
59			GST_STATIC_CAPS("audio/x-sbc, "
60				"rate = (int) { 16000, 32000, 44100, 48000 }, "
61				"channels = (int) [ 1, 2 ], "
62				"mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, "
63				"blocks = (int) { 4, 8, 12, 16 }, "
64				"subbands = (int) { 4, 8 }, "
65				"allocation = (string) { \"snr\", \"loudness\" }, "
66				"bitpool = (int) [ 2, "
67				TEMPLATE_MAX_BITPOOL_STR " ]; "
68				"audio/mpeg"
69				));
70
71static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event);
72static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps);
73static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad);
74static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self);
75static gboolean gst_a2dp_sink_init_fakesink(GstA2dpSink *self);
76static gboolean gst_a2dp_sink_remove_fakesink(GstA2dpSink *self);
77
78static void gst_a2dp_sink_finalize(GObject *obj)
79{
80	GstA2dpSink *self = GST_A2DP_SINK(obj);
81
82	g_mutex_free(self->cb_mutex);
83
84	G_OBJECT_CLASS(parent_class)->finalize(obj);
85}
86
87static GstState gst_a2dp_sink_get_state(GstA2dpSink *self)
88{
89	GstState current, pending;
90
91	gst_element_get_state(GST_ELEMENT(self), &current, &pending, 0);
92	if (pending == GST_STATE_VOID_PENDING)
93		return current;
94
95	return pending;
96}
97
98/*
99 * Helper function to create elements, add to the bin and link it
100 * to another element.
101 */
102static GstElement *gst_a2dp_sink_init_element(GstA2dpSink *self,
103			const gchar *elementname, const gchar *name,
104			GstElement *link_to)
105{
106	GstElement *element;
107	GstState state;
108
109	GST_LOG_OBJECT(self, "Initializing %s", elementname);
110
111	element = gst_element_factory_make(elementname, name);
112	if (element == NULL) {
113		GST_DEBUG_OBJECT(self, "Couldn't create %s", elementname);
114		return NULL;
115	}
116
117	if (!gst_bin_add(GST_BIN(self), element)) {
118		GST_DEBUG_OBJECT(self, "failed to add %s to the bin",
119						elementname);
120		goto cleanup_and_fail;
121	}
122
123	state = gst_a2dp_sink_get_state(self);
124	if (gst_element_set_state(element, state) ==
125			GST_STATE_CHANGE_FAILURE) {
126		GST_DEBUG_OBJECT(self, "%s failed to go to playing",
127						elementname);
128		goto remove_element_and_fail;
129	}
130
131	if (link_to != NULL)
132		if (!gst_element_link(link_to, element)) {
133			GST_DEBUG_OBJECT(self, "couldn't link %s",
134					elementname);
135			goto remove_element_and_fail;
136		}
137
138	return element;
139
140remove_element_and_fail:
141	gst_element_set_state(element, GST_STATE_NULL);
142	gst_bin_remove(GST_BIN(self), element);
143	return NULL;
144
145cleanup_and_fail:
146	g_object_unref(G_OBJECT(element));
147
148	return NULL;
149}
150
151static void gst_a2dp_sink_base_init(gpointer g_class)
152{
153	GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
154
155	gst_element_class_set_details(element_class,
156		&gst_a2dp_sink_details);
157	gst_element_class_add_pad_template(element_class,
158		gst_static_pad_template_get(&gst_a2dp_sink_factory));
159}
160
161static void gst_a2dp_sink_set_property(GObject *object, guint prop_id,
162					const GValue *value, GParamSpec *pspec)
163{
164	GstA2dpSink *self = GST_A2DP_SINK(object);
165
166	switch (prop_id) {
167	case PROP_DEVICE:
168		if (self->sink != NULL)
169			gst_avdtp_sink_set_device(self->sink,
170				g_value_get_string(value));
171
172		if (self->device != NULL)
173			g_free(self->device);
174		self->device = g_value_dup_string(value);
175		break;
176
177	case PROP_TRANSPORT:
178		if (self->sink != NULL)
179			gst_avdtp_sink_set_transport(self->sink,
180				g_value_get_string(value));
181
182		if (self->transport != NULL)
183			g_free(self->transport);
184		self->transport = g_value_dup_string(value);
185		break;
186
187	case PROP_AUTOCONNECT:
188		self->autoconnect = g_value_get_boolean(value);
189
190		if (self->sink != NULL)
191			g_object_set(G_OBJECT(self->sink), "auto-connect",
192					self->autoconnect, NULL);
193		break;
194
195	default:
196		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
197		break;
198	}
199}
200
201static void gst_a2dp_sink_get_property(GObject *object, guint prop_id,
202					GValue *value, GParamSpec *pspec)
203{
204	GstA2dpSink *self = GST_A2DP_SINK(object);
205	gchar *device, *transport;
206
207	switch (prop_id) {
208	case PROP_DEVICE:
209		if (self->sink != NULL) {
210			device = gst_avdtp_sink_get_device(self->sink);
211			if (device != NULL)
212				g_value_take_string(value, device);
213		}
214		break;
215	case PROP_AUTOCONNECT:
216		if (self->sink != NULL)
217			g_object_get(G_OBJECT(self->sink), "auto-connect",
218				&self->autoconnect, NULL);
219
220		g_value_set_boolean(value, self->autoconnect);
221		break;
222	case PROP_TRANSPORT:
223		if (self->sink != NULL) {
224			transport = gst_avdtp_sink_get_transport(self->sink);
225			if (transport != NULL)
226				g_value_take_string(value, transport);
227		}
228		break;
229	default:
230		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
231		break;
232	}
233}
234
235static gboolean gst_a2dp_sink_init_ghost_pad(GstA2dpSink *self)
236{
237	GstPad *capsfilter_pad;
238
239	/* we search for the capsfilter sinkpad */
240	capsfilter_pad = gst_element_get_static_pad(self->capsfilter, "sink");
241
242	/* now we add a ghostpad */
243	self->ghostpad = GST_GHOST_PAD(gst_ghost_pad_new("sink",
244		capsfilter_pad));
245	g_object_unref(capsfilter_pad);
246
247	/* the getcaps of our ghostpad must reflect the device caps */
248	gst_pad_set_getcaps_function(GST_PAD(self->ghostpad),
249				gst_a2dp_sink_get_caps);
250	self->ghostpad_setcapsfunc = GST_PAD_SETCAPSFUNC(self->ghostpad);
251	gst_pad_set_setcaps_function(GST_PAD(self->ghostpad),
252			GST_DEBUG_FUNCPTR(gst_a2dp_sink_set_caps));
253
254	/* we need to handle events on our own and we also need the eventfunc
255	 * of the ghostpad for forwarding calls */
256	self->ghostpad_eventfunc = GST_PAD_EVENTFUNC(GST_PAD(self->ghostpad));
257	gst_pad_set_event_function(GST_PAD(self->ghostpad),
258			gst_a2dp_sink_handle_event);
259
260	if (!gst_element_add_pad(GST_ELEMENT(self), GST_PAD(self->ghostpad)))
261		GST_ERROR_OBJECT(self, "failed to add ghostpad");
262
263	return TRUE;
264}
265
266static void gst_a2dp_sink_remove_dynamic_elements(GstA2dpSink *self)
267{
268	if (self->rtp) {
269		GST_LOG_OBJECT(self, "removing rtp element from the bin");
270		if (!gst_bin_remove(GST_BIN(self), GST_ELEMENT(self->rtp)))
271			GST_WARNING_OBJECT(self, "failed to remove rtp "
272					"element from bin");
273		else
274			self->rtp = NULL;
275	}
276}
277
278static GstStateChangeReturn gst_a2dp_sink_change_state(GstElement *element,
279			GstStateChange transition)
280{
281	GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
282	GstA2dpSink *self = GST_A2DP_SINK(element);
283
284	switch (transition) {
285	case GST_STATE_CHANGE_READY_TO_PAUSED:
286		self->taglist = gst_tag_list_new();
287
288		gst_a2dp_sink_init_fakesink(self);
289		break;
290
291	case GST_STATE_CHANGE_NULL_TO_READY:
292		self->sink_is_in_bin = FALSE;
293		self->sink = GST_AVDTP_SINK(gst_element_factory_make(
294				"avdtpsink", "avdtpsink"));
295		if (self->sink == NULL) {
296			GST_WARNING_OBJECT(self, "failed to create avdtpsink");
297			return GST_STATE_CHANGE_FAILURE;
298		}
299
300		if (self->device != NULL)
301			gst_avdtp_sink_set_device(self->sink,
302					self->device);
303
304		if (self->transport != NULL)
305			gst_avdtp_sink_set_transport(self->sink,
306					self->transport);
307
308		g_object_set(G_OBJECT(self->sink), "auto-connect",
309					self->autoconnect, NULL);
310
311		ret = gst_element_set_state(GST_ELEMENT(self->sink),
312			GST_STATE_READY);
313		break;
314	default:
315		break;
316	}
317
318	if (ret == GST_STATE_CHANGE_FAILURE)
319		return ret;
320
321	ret = GST_ELEMENT_CLASS(parent_class)->change_state(element,
322								transition);
323
324	switch (transition) {
325	case GST_STATE_CHANGE_PAUSED_TO_READY:
326		if (self->taglist) {
327			gst_tag_list_free(self->taglist);
328			self->taglist = NULL;
329		}
330		if (self->newseg_event != NULL) {
331			gst_event_unref(self->newseg_event);
332			self->newseg_event = NULL;
333		}
334		gst_a2dp_sink_remove_fakesink(self);
335		break;
336
337	case GST_STATE_CHANGE_READY_TO_NULL:
338		if (self->sink_is_in_bin) {
339			if (!gst_bin_remove(GST_BIN(self),
340						GST_ELEMENT(self->sink)))
341				GST_WARNING_OBJECT(self, "Failed to remove "
342						"avdtpsink from bin");
343		} else if (self->sink != NULL) {
344			gst_element_set_state(GST_ELEMENT(self->sink),
345					GST_STATE_NULL);
346			g_object_unref(G_OBJECT(self->sink));
347		}
348
349		self->sink = NULL;
350
351		gst_a2dp_sink_remove_dynamic_elements(self);
352		break;
353	default:
354		break;
355	}
356
357	return ret;
358}
359
360static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass)
361{
362	GObjectClass *object_class = G_OBJECT_CLASS(klass);
363	GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
364
365	parent_class = g_type_class_peek_parent(klass);
366
367	object_class->set_property = GST_DEBUG_FUNCPTR(
368					gst_a2dp_sink_set_property);
369	object_class->get_property = GST_DEBUG_FUNCPTR(
370					gst_a2dp_sink_get_property);
371
372	object_class->finalize = GST_DEBUG_FUNCPTR(
373					gst_a2dp_sink_finalize);
374
375	element_class->change_state = GST_DEBUG_FUNCPTR(
376					gst_a2dp_sink_change_state);
377
378	g_object_class_install_property(object_class, PROP_DEVICE,
379			g_param_spec_string("device", "Device",
380			"Bluetooth remote device address",
381			NULL, G_PARAM_READWRITE));
382
383	g_object_class_install_property(object_class, PROP_AUTOCONNECT,
384			g_param_spec_boolean("auto-connect", "Auto-connect",
385			"Automatically attempt to connect to device",
386			DEFAULT_AUTOCONNECT, G_PARAM_READWRITE));
387
388	g_object_class_install_property(object_class, PROP_TRANSPORT,
389			g_param_spec_string("transport", "Transport",
390			"Use configured transport",
391			NULL, G_PARAM_READWRITE));
392
393	GST_DEBUG_CATEGORY_INIT(gst_a2dp_sink_debug, "a2dpsink", 0,
394				"A2DP sink element");
395}
396
397GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self)
398{
399	return gst_avdtp_sink_get_device_caps(self->sink);
400}
401
402static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad)
403{
404	GstCaps *caps;
405	GstCaps *caps_aux;
406	GstA2dpSink *self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
407
408	if (self->sink == NULL) {
409		GST_DEBUG_OBJECT(self, "a2dpsink isn't initialized "
410			"returning template caps");
411		caps = gst_static_pad_template_get_caps(
412				&gst_a2dp_sink_factory);
413	} else {
414		GST_LOG_OBJECT(self, "Getting device caps");
415		caps = gst_a2dp_sink_get_device_caps(self);
416		if (caps == NULL)
417			caps = gst_static_pad_template_get_caps(
418					&gst_a2dp_sink_factory);
419	}
420	caps_aux = gst_caps_copy(caps);
421	g_object_set(self->capsfilter, "caps", caps_aux, NULL);
422	gst_caps_unref(caps_aux);
423	return caps;
424}
425
426static gboolean gst_a2dp_sink_init_avdtp_sink(GstA2dpSink *self)
427{
428	GstElement *sink;
429
430	/* check if we don't need a new sink */
431	if (self->sink_is_in_bin)
432		return TRUE;
433
434	if (self->sink == NULL)
435		sink = gst_element_factory_make("avdtpsink", "avdtpsink");
436	else
437		sink = GST_ELEMENT(self->sink);
438
439	if (sink == NULL) {
440		GST_ERROR_OBJECT(self, "Couldn't create avdtpsink");
441		return FALSE;
442	}
443
444	if (!gst_bin_add(GST_BIN(self), sink)) {
445		GST_ERROR_OBJECT(self, "failed to add avdtpsink "
446			"to the bin");
447		goto cleanup_and_fail;
448	}
449
450	if (gst_element_set_state(sink, GST_STATE_READY) ==
451			GST_STATE_CHANGE_FAILURE) {
452		GST_ERROR_OBJECT(self, "avdtpsink failed to go to ready");
453		goto remove_element_and_fail;
454	}
455
456	if (!gst_element_link(GST_ELEMENT(self->rtp), sink)) {
457		GST_ERROR_OBJECT(self, "couldn't link rtpsbcpay "
458			"to avdtpsink");
459		goto remove_element_and_fail;
460	}
461
462	self->sink = GST_AVDTP_SINK(sink);
463	self->sink_is_in_bin = TRUE;
464	g_object_set(G_OBJECT(self->sink), "device", self->device, NULL);
465	g_object_set(G_OBJECT(self->sink), "transport", self->transport, NULL);
466
467	gst_element_set_state(sink, GST_STATE_PAUSED);
468
469	return TRUE;
470
471remove_element_and_fail:
472	gst_element_set_state(sink, GST_STATE_NULL);
473	gst_bin_remove(GST_BIN(self), sink);
474	return FALSE;
475
476cleanup_and_fail:
477	if (sink != NULL)
478		g_object_unref(G_OBJECT(sink));
479
480	return FALSE;
481}
482
483static gboolean gst_a2dp_sink_init_rtp_sbc_element(GstA2dpSink *self)
484{
485	GstElement *rtppay;
486
487	/* if we already have a rtp, we don't need a new one */
488	if (self->rtp != NULL)
489		return TRUE;
490
491	rtppay = gst_a2dp_sink_init_element(self, "rtpsbcpay", "rtp",
492						self->capsfilter);
493	if (rtppay == NULL)
494		return FALSE;
495
496	self->rtp = GST_BASE_RTP_PAYLOAD(rtppay);
497	g_object_set(G_OBJECT(self->rtp), "min-frames", -1, NULL);
498
499	gst_element_set_state(rtppay, GST_STATE_PAUSED);
500
501	return TRUE;
502}
503
504static gboolean gst_a2dp_sink_init_rtp_mpeg_element(GstA2dpSink *self)
505{
506	GstElement *rtppay;
507
508	/* check if we don't need a new rtp */
509	if (self->rtp)
510		return TRUE;
511
512	GST_LOG_OBJECT(self, "Initializing rtp mpeg element");
513	/* if capsfilter is not created then we can't have our rtp element */
514	if (self->capsfilter == NULL)
515		return FALSE;
516
517	rtppay = gst_a2dp_sink_init_element(self, "rtpmpapay", "rtp",
518					self->capsfilter);
519	if (rtppay == NULL)
520		return FALSE;
521
522	self->rtp = GST_BASE_RTP_PAYLOAD(rtppay);
523
524	gst_element_set_state(rtppay, GST_STATE_PAUSED);
525
526	return TRUE;
527}
528
529static gboolean gst_a2dp_sink_init_dynamic_elements(GstA2dpSink *self,
530						GstCaps *caps)
531{
532	GstStructure *structure;
533	GstEvent *event;
534	GstPad *capsfilterpad;
535	gboolean crc;
536	gchar *mode = NULL;
537
538	structure = gst_caps_get_structure(caps, 0);
539
540	/* before everything we need to remove fakesink */
541	gst_a2dp_sink_remove_fakesink(self);
542
543	/* first, we need to create our rtp payloader */
544	if (gst_structure_has_name(structure, "audio/x-sbc")) {
545		GST_LOG_OBJECT(self, "sbc media received");
546		if (!gst_a2dp_sink_init_rtp_sbc_element(self))
547			return FALSE;
548	} else if (gst_structure_has_name(structure, "audio/mpeg")) {
549		GST_LOG_OBJECT(self, "mp3 media received");
550		if (!gst_a2dp_sink_init_rtp_mpeg_element(self))
551			return FALSE;
552	} else {
553		GST_ERROR_OBJECT(self, "Unexpected media type");
554		return FALSE;
555	}
556
557	if (!gst_a2dp_sink_init_avdtp_sink(self))
558		return FALSE;
559
560	/* check if we should push the taglist FIXME should we push this?
561	 * we can send the tags directly if needed */
562	if (self->taglist != NULL &&
563			gst_structure_has_name(structure, "audio/mpeg")) {
564
565		event = gst_event_new_tag(self->taglist);
566
567		/* send directly the crc */
568		if (gst_tag_list_get_boolean(self->taglist, "has-crc", &crc))
569			gst_avdtp_sink_set_crc(self->sink, crc);
570
571		if (gst_tag_list_get_string(self->taglist, "channel-mode",
572				&mode))
573			gst_avdtp_sink_set_channel_mode(self->sink, mode);
574
575		capsfilterpad = gst_ghost_pad_get_target(self->ghostpad);
576		gst_pad_send_event(capsfilterpad, event);
577		self->taglist = NULL;
578		g_free(mode);
579	}
580
581	if (!gst_avdtp_sink_set_device_caps(self->sink, caps))
582		return FALSE;
583
584	g_object_set(G_OBJECT(self->rtp), "mtu",
585		gst_avdtp_sink_get_link_mtu(self->sink), NULL);
586
587	/* we forward our new segment here if we have one */
588	if (self->newseg_event) {
589		gst_pad_send_event(GST_BASE_RTP_PAYLOAD_SINKPAD(self->rtp),
590					self->newseg_event);
591		self->newseg_event = NULL;
592	}
593
594	return TRUE;
595}
596
597static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps)
598{
599	GstA2dpSink *self;
600
601	self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
602	GST_INFO_OBJECT(self, "setting caps");
603
604	/* now we know the caps */
605	gst_a2dp_sink_init_dynamic_elements(self, caps);
606
607	return self->ghostpad_setcapsfunc(GST_PAD(self->ghostpad), caps);
608}
609
610/* used for catching newsegment events while we don't have a sink, for
611 * later forwarding it to the sink */
612static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event)
613{
614	GstA2dpSink *self;
615	GstTagList *taglist = NULL;
616	GstObject *parent;
617
618	self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
619	parent = gst_element_get_parent(GST_ELEMENT(self->sink));
620
621	if (GST_EVENT_TYPE(event) == GST_EVENT_NEWSEGMENT &&
622			parent != GST_OBJECT_CAST(self)) {
623		if (self->newseg_event != NULL)
624			gst_event_unref(self->newseg_event);
625		self->newseg_event = gst_event_ref(event);
626
627	} else if (GST_EVENT_TYPE(event) == GST_EVENT_TAG &&
628			parent != GST_OBJECT_CAST(self)) {
629		if (self->taglist == NULL)
630			gst_event_parse_tag(event, &self->taglist);
631		else {
632			gst_event_parse_tag(event, &taglist);
633			gst_tag_list_insert(self->taglist, taglist,
634					GST_TAG_MERGE_REPLACE);
635		}
636	}
637
638	if (parent != NULL)
639		gst_object_unref(GST_OBJECT(parent));
640
641	return self->ghostpad_eventfunc(GST_PAD(self->ghostpad), event);
642}
643
644static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self)
645{
646	GstElement *element;
647
648	element = gst_element_factory_make("capsfilter", "filter");
649	if (element == NULL)
650		goto failed;
651
652	if (!gst_bin_add(GST_BIN(self), element))
653		goto failed;
654
655	self->capsfilter = element;
656	return TRUE;
657
658failed:
659	GST_ERROR_OBJECT(self, "Failed to initialize caps filter");
660	return FALSE;
661}
662
663static gboolean gst_a2dp_sink_init_fakesink(GstA2dpSink *self)
664{
665	if (self->fakesink != NULL)
666		return TRUE;
667
668	g_mutex_lock(self->cb_mutex);
669	self->fakesink = gst_a2dp_sink_init_element(self, "fakesink",
670			"fakesink", self->capsfilter);
671	g_mutex_unlock(self->cb_mutex);
672
673	if (!self->fakesink)
674		return FALSE;
675
676	return TRUE;
677}
678
679static gboolean gst_a2dp_sink_remove_fakesink(GstA2dpSink *self)
680{
681	g_mutex_lock(self->cb_mutex);
682
683	if (self->fakesink != NULL) {
684		gst_element_set_locked_state(self->fakesink, TRUE);
685		gst_element_set_state(self->fakesink, GST_STATE_NULL);
686
687		gst_bin_remove(GST_BIN(self), self->fakesink);
688		self->fakesink = NULL;
689	}
690
691	g_mutex_unlock(self->cb_mutex);
692
693	return TRUE;
694}
695
696static void gst_a2dp_sink_init(GstA2dpSink *self,
697			GstA2dpSinkClass *klass)
698{
699	self->sink = NULL;
700	self->fakesink = NULL;
701	self->rtp = NULL;
702	self->device = NULL;
703	self->transport = NULL;
704	self->autoconnect = DEFAULT_AUTOCONNECT;
705	self->capsfilter = NULL;
706	self->newseg_event = NULL;
707	self->taglist = NULL;
708	self->ghostpad = NULL;
709	self->sink_is_in_bin = FALSE;
710
711	self->cb_mutex = g_mutex_new();
712
713	/* we initialize our capsfilter */
714	gst_a2dp_sink_init_caps_filter(self);
715	g_object_set(self->capsfilter, "caps",
716		gst_static_pad_template_get_caps(&gst_a2dp_sink_factory),
717		NULL);
718
719	gst_a2dp_sink_init_fakesink(self);
720
721	gst_a2dp_sink_init_ghost_pad(self);
722
723}
724
725gboolean gst_a2dp_sink_plugin_init(GstPlugin *plugin)
726{
727	return gst_element_register(plugin, "a2dpsink",
728			GST_RANK_MARGINAL, GST_TYPE_A2DP_SINK);
729}
730
731