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