1 /* 2 * 3 * BlueZ - Bluetooth protocol stack for Linux 4 * 5 * Copyright (C) 2004-2010 Marcel Holtmann <marcel (at) 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 GST_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 42 enum { 43 PROP_0, 44 PROP_DEVICE, 45 PROP_AUTOCONNECT, 46 PROP_TRANSPORT 47 }; 48 49 GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBin, GST_TYPE_BIN); 50 51 static 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 (at) holtmann.org>"); 56 57 static 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 71 static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event); 72 static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps); 73 static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad); 74 static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self); 75 static gboolean gst_a2dp_sink_init_fakesink(GstA2dpSink *self); 76 static gboolean gst_a2dp_sink_remove_fakesink(GstA2dpSink *self); 77 78 static 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 87 static GstState gst_a2dp_sink_get_state(GstA2dpSink *self) 88 { 89 GstState current, pending; 90 91 gst_element_get_state(GST_ELEMENT(self), ¤t, &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 */ 102 static 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 140 remove_element_and_fail: 141 gst_element_set_state(element, GST_STATE_NULL); 142 gst_bin_remove(GST_BIN(self), element); 143 return NULL; 144 145 cleanup_and_fail: 146 g_object_unref(G_OBJECT(element)); 147 148 return NULL; 149 } 150 151 static 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 161 static 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 201 static 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 235 static 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 266 static 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 278 static 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 360 static 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 397 GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self) 398 { 399 return gst_avdtp_sink_get_device_caps(self->sink); 400 } 401 402 static 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 426 static 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 471 remove_element_and_fail: 472 gst_element_set_state(sink, GST_STATE_NULL); 473 gst_bin_remove(GST_BIN(self), sink); 474 return FALSE; 475 476 cleanup_and_fail: 477 if (sink != NULL) 478 g_object_unref(G_OBJECT(sink)); 479 480 return FALSE; 481 } 482 483 static 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 504 static 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 529 static 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 597 static 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 */ 612 static 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 644 static 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 658 failed: 659 GST_ERROR_OBJECT(self, "Failed to initialize caps filter"); 660 return FALSE; 661 } 662 663 static 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 679 static 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 696 static 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 725 gboolean 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