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
30 #include "gsta2dpsink.h"
31
32 #include <gst/rtp/gstrtpbasepayload.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
39 #define DEFAULT_AUTOCONNECT TRUE
40
41 enum
42 {
43 PROP_0,
44 PROP_DEVICE,
45 PROP_AUTOCONNECT,
46 PROP_TRANSPORT
47 };
48
49 #define parent_class gst_a2dp_sink_parent_class
50 G_DEFINE_TYPE (GstA2dpSink, gst_a2dp_sink, GST_TYPE_BIN);
51
52 static GstStaticPadTemplate gst_a2dp_sink_factory =
53 GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
54 GST_STATIC_CAPS ("audio/x-sbc, "
55 "rate = (int) { 16000, 32000, 44100, 48000 }, "
56 "channels = (int) [ 1, 2 ], "
57 "channel-mode = (string) { mono, dual, stereo, joint }, "
58 "blocks = (int) { 4, 8, 12, 16 }, "
59 "subbands = (int) { 4, 8 }, "
60 "allocation-method = (string) { snr, loudness }, "
61 "bitpool = (int) [ 2, " TEMPLATE_MAX_BITPOOL_STR " ]; " "audio/mpeg"));
62
63 static gboolean gst_a2dp_sink_handle_event (GstPad * pad,
64 GstObject * pad_parent, GstEvent * event);
65 static gboolean gst_a2dp_sink_query (GstPad * pad, GstObject * parent,
66 GstQuery * query);
67 static GstCaps *gst_a2dp_sink_get_caps (GstA2dpSink * self);
68
69 /*
70 * Helper function to create elements, add to the bin and link it
71 * to another element.
72 */
73 static GstElement *
gst_a2dp_sink_init_element(GstA2dpSink * self,const gchar * elementname,const gchar * name)74 gst_a2dp_sink_init_element (GstA2dpSink * self, const gchar * elementname,
75 const gchar * name)
76 {
77 GstElement *element;
78 GstPad *sinkpad;
79
80 GST_LOG_OBJECT (self, "Initializing %s", elementname);
81
82 element = gst_element_factory_make (elementname, name);
83 if (element == NULL) {
84 GST_DEBUG_OBJECT (self, "Couldn't create %s", elementname);
85 return NULL;
86 }
87
88 if (!gst_bin_add (GST_BIN (self), element)) {
89 GST_DEBUG_OBJECT (self, "failed to add %s to the bin", elementname);
90 goto cleanup_and_fail;
91 }
92
93 sinkpad = gst_element_get_static_pad (element, "sink");
94 if (!gst_ghost_pad_set_target (GST_GHOST_PAD (self->ghostpad), sinkpad)) {
95 GST_ERROR_OBJECT (self, "Failed to set target for ghost pad");
96 goto remove_element_and_fail;
97 }
98
99 if (!gst_element_sync_state_with_parent (element)) {
100 GST_DEBUG_OBJECT (self, "%s failed to go to playing", elementname);
101 goto remove_element_and_fail;
102 }
103
104 return element;
105
106 remove_element_and_fail:
107 gst_element_set_state (element, GST_STATE_NULL);
108 gst_bin_remove (GST_BIN (self), element);
109 return NULL;
110
111 cleanup_and_fail:
112 g_object_unref (G_OBJECT (element));
113
114 return NULL;
115 }
116
117 static void
gst_a2dp_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)118 gst_a2dp_sink_set_property (GObject * object, guint prop_id,
119 const GValue * value, GParamSpec * pspec)
120 {
121 GstA2dpSink *self = GST_A2DP_SINK (object);
122
123 switch (prop_id) {
124 case PROP_DEVICE:
125 if (self->sink != NULL)
126 gst_avdtp_sink_set_device (self->sink, g_value_get_string (value));
127
128 g_free (self->device);
129 self->device = g_value_dup_string (value);
130 break;
131
132 case PROP_TRANSPORT:
133 if (self->sink != NULL)
134 gst_avdtp_sink_set_transport (self->sink, g_value_get_string (value));
135
136 g_free (self->transport);
137 self->transport = g_value_dup_string (value);
138 break;
139
140 case PROP_AUTOCONNECT:
141 self->autoconnect = g_value_get_boolean (value);
142
143 if (self->sink != NULL)
144 g_object_set (G_OBJECT (self->sink), "auto-connect",
145 self->autoconnect, NULL);
146 break;
147
148 default:
149 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
150 break;
151 }
152 }
153
154 static void
gst_a2dp_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)155 gst_a2dp_sink_get_property (GObject * object, guint prop_id,
156 GValue * value, GParamSpec * pspec)
157 {
158 GstA2dpSink *self = GST_A2DP_SINK (object);
159 gchar *device, *transport;
160
161 switch (prop_id) {
162 case PROP_DEVICE:
163 if (self->sink != NULL) {
164 device = gst_avdtp_sink_get_device (self->sink);
165 if (device != NULL)
166 g_value_take_string (value, device);
167 }
168 break;
169 case PROP_AUTOCONNECT:
170 if (self->sink != NULL)
171 g_object_get (G_OBJECT (self->sink), "auto-connect",
172 &self->autoconnect, NULL);
173
174 g_value_set_boolean (value, self->autoconnect);
175 break;
176 case PROP_TRANSPORT:
177 if (self->sink != NULL) {
178 transport = gst_avdtp_sink_get_transport (self->sink);
179 if (transport != NULL)
180 g_value_take_string (value, transport);
181 }
182 break;
183 default:
184 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
185 break;
186 }
187 }
188
189 static gboolean
gst_a2dp_sink_init_ghost_pad(GstA2dpSink * self)190 gst_a2dp_sink_init_ghost_pad (GstA2dpSink * self)
191 {
192 GstPadTemplate *templ;
193
194 /* now we add a ghostpad */
195 templ = gst_static_pad_template_get (&gst_a2dp_sink_factory);
196 self->ghostpad = gst_ghost_pad_new_no_target_from_template ("sink", templ);
197 g_object_unref (templ);
198
199 /* the getcaps of our ghostpad must reflect the device caps */
200 gst_pad_set_query_function (self->ghostpad, gst_a2dp_sink_query);
201
202 gst_pad_set_event_function (self->ghostpad, gst_a2dp_sink_handle_event);
203
204 if (!gst_element_add_pad (GST_ELEMENT (self), self->ghostpad))
205 GST_ERROR_OBJECT (self, "failed to add ghostpad");
206
207 return TRUE;
208 }
209
210 static void
gst_a2dp_sink_remove_dynamic_elements(GstA2dpSink * self)211 gst_a2dp_sink_remove_dynamic_elements (GstA2dpSink * self)
212 {
213 if (self->rtp) {
214 GST_LOG_OBJECT (self, "removing rtp element from the bin");
215 if (!gst_bin_remove (GST_BIN (self), GST_ELEMENT (self->rtp)))
216 GST_WARNING_OBJECT (self, "failed to remove rtp " "element from bin");
217 else
218 self->rtp = NULL;
219 }
220 }
221
222 static GstStateChangeReturn
gst_a2dp_sink_change_state(GstElement * element,GstStateChange transition)223 gst_a2dp_sink_change_state (GstElement * element, GstStateChange transition)
224 {
225 GstStateChangeReturn ret;
226 GstA2dpSink *self = GST_A2DP_SINK (element);
227
228 switch (transition) {
229 case GST_STATE_CHANGE_READY_TO_PAUSED:
230 self->taglist = gst_tag_list_new_empty ();
231 break;
232
233 case GST_STATE_CHANGE_NULL_TO_READY:
234 if (self->device != NULL)
235 gst_avdtp_sink_set_device (self->sink, self->device);
236
237 if (self->transport != NULL)
238 gst_avdtp_sink_set_transport (self->sink, self->transport);
239
240 g_object_set (G_OBJECT (self->sink), "auto-connect",
241 self->autoconnect, NULL);
242
243 break;
244
245 default:
246 break;
247 }
248
249 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
250
251 switch (transition) {
252 case GST_STATE_CHANGE_PAUSED_TO_READY:
253 if (self->taglist) {
254 gst_tag_list_unref (self->taglist);
255 self->taglist = NULL;
256 }
257 break;
258
259 case GST_STATE_CHANGE_READY_TO_NULL:
260 gst_a2dp_sink_remove_dynamic_elements (self);
261
262 break;
263 default:
264 break;
265 }
266
267 return ret;
268 }
269
270 static void
gst_a2dp_sink_class_init(GstA2dpSinkClass * klass)271 gst_a2dp_sink_class_init (GstA2dpSinkClass * klass)
272 {
273 GObjectClass *object_class = G_OBJECT_CLASS (klass);
274 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
275
276 parent_class = g_type_class_peek_parent (klass);
277
278 object_class->set_property = GST_DEBUG_FUNCPTR (gst_a2dp_sink_set_property);
279 object_class->get_property = GST_DEBUG_FUNCPTR (gst_a2dp_sink_get_property);
280
281 element_class->change_state = GST_DEBUG_FUNCPTR (gst_a2dp_sink_change_state);
282
283 g_object_class_install_property (object_class, PROP_DEVICE,
284 g_param_spec_string ("device", "Device",
285 "Bluetooth remote device address", NULL, G_PARAM_READWRITE));
286
287 g_object_class_install_property (object_class, PROP_AUTOCONNECT,
288 g_param_spec_boolean ("auto-connect", "Auto-connect",
289 "Automatically attempt to connect to device",
290 DEFAULT_AUTOCONNECT, G_PARAM_READWRITE));
291
292 g_object_class_install_property (object_class, PROP_TRANSPORT,
293 g_param_spec_string ("transport", "Transport",
294 "Use configured transport", NULL, G_PARAM_READWRITE));
295
296 gst_element_class_set_static_metadata (element_class, "Bluetooth A2DP sink",
297 "Sink/Audio", "Plays audio to an A2DP device",
298 "Marcel Holtmann <marcel@holtmann.org>");
299
300 GST_DEBUG_CATEGORY_INIT (gst_a2dp_sink_debug, "a2dpsink", 0,
301 "A2DP sink element");
302
303 gst_element_class_add_static_pad_template (element_class,
304 &gst_a2dp_sink_factory);
305 }
306
307 GstCaps *
gst_a2dp_sink_get_device_caps(GstA2dpSink * self)308 gst_a2dp_sink_get_device_caps (GstA2dpSink * self)
309 {
310 return gst_avdtp_sink_get_device_caps (self->sink);
311 }
312
313 static GstCaps *
gst_a2dp_sink_get_caps(GstA2dpSink * self)314 gst_a2dp_sink_get_caps (GstA2dpSink * self)
315 {
316 GstCaps *caps = NULL;
317
318 if (self->sink != NULL) {
319 GST_LOG_OBJECT (self, "Getting device caps");
320 caps = gst_a2dp_sink_get_device_caps (self);
321 }
322
323 if (!caps)
324 caps = gst_static_pad_template_get_caps (&gst_a2dp_sink_factory);
325
326 return caps;
327 }
328
329 static gboolean
gst_a2dp_sink_init_avdtp_sink(GstA2dpSink * self)330 gst_a2dp_sink_init_avdtp_sink (GstA2dpSink * self)
331 {
332 GstElement *sink;
333
334 if (self->sink == NULL)
335 sink = gst_element_factory_make ("avdtpsink", "avdtpsink");
336 else
337 sink = GST_ELEMENT (self->sink);
338
339 if (sink == NULL) {
340 GST_ERROR_OBJECT (self, "Couldn't create avdtpsink");
341 return FALSE;
342 }
343
344 if (!gst_bin_add (GST_BIN (self), sink)) {
345 GST_ERROR_OBJECT (self, "failed to add avdtpsink " "to the bin");
346 goto cleanup_and_fail;
347 }
348
349 self->sink = GST_AVDTP_SINK (sink);
350 g_object_set (G_OBJECT (self->sink), "device", self->device, NULL);
351 g_object_set (G_OBJECT (self->sink), "transport", self->transport, NULL);
352
353 gst_element_sync_state_with_parent (sink);
354
355 return TRUE;
356
357 cleanup_and_fail:
358 if (sink != NULL)
359 g_object_unref (G_OBJECT (sink));
360
361 return FALSE;
362 }
363
364 static gboolean
gst_a2dp_sink_init_rtp_sbc_element(GstA2dpSink * self)365 gst_a2dp_sink_init_rtp_sbc_element (GstA2dpSink * self)
366 {
367 GstElement *rtppay;
368
369 /* if we already have a rtp, we don't need a new one */
370 if (self->rtp != NULL)
371 return TRUE;
372
373 rtppay = gst_a2dp_sink_init_element (self, "rtpsbcpay", "rtp");
374 if (rtppay == NULL)
375 return FALSE;
376
377 self->rtp = rtppay;
378 g_object_set (self->rtp, "min-frames", -1, NULL);
379
380 gst_element_set_state (rtppay, GST_STATE_PAUSED);
381
382 return TRUE;
383 }
384
385 static gboolean
gst_a2dp_sink_init_rtp_mpeg_element(GstA2dpSink * self)386 gst_a2dp_sink_init_rtp_mpeg_element (GstA2dpSink * self)
387 {
388 GstElement *rtppay;
389
390 /* check if we don't need a new rtp */
391 if (self->rtp)
392 return TRUE;
393
394 GST_LOG_OBJECT (self, "Initializing rtp mpeg element");
395
396 rtppay = gst_a2dp_sink_init_element (self, "rtpmpapay", "rtp");
397 if (rtppay == NULL)
398 return FALSE;
399
400 self->rtp = rtppay;
401
402 gst_element_set_state (rtppay, GST_STATE_PAUSED);
403
404 return TRUE;
405 }
406
407 static gboolean
gst_a2dp_sink_init_dynamic_elements(GstA2dpSink * self,GstCaps * caps)408 gst_a2dp_sink_init_dynamic_elements (GstA2dpSink * self, GstCaps * caps)
409 {
410 GstStructure *structure;
411 GstEvent *event;
412 gboolean crc;
413 gchar *mode = NULL;
414
415 structure = gst_caps_get_structure (caps, 0);
416
417 /* first, we need to create our rtp payloader */
418 if (gst_structure_has_name (structure, "audio/x-sbc")) {
419 GST_LOG_OBJECT (self, "sbc media received");
420 if (!gst_a2dp_sink_init_rtp_sbc_element (self))
421 return FALSE;
422 } else if (gst_structure_has_name (structure, "audio/mpeg")) {
423 GST_LOG_OBJECT (self, "mp3 media received");
424 if (!gst_a2dp_sink_init_rtp_mpeg_element (self))
425 return FALSE;
426 } else {
427 GST_ERROR_OBJECT (self, "Unexpected media type");
428 return FALSE;
429 }
430
431 if (!gst_element_link (GST_ELEMENT (self->rtp), GST_ELEMENT (self->sink))) {
432 GST_ERROR_OBJECT (self, "couldn't link rtpsbcpay " "to avdtpsink");
433 return FALSE;
434 }
435
436 /* check if we should push the taglist FIXME should we push this?
437 * we can send the tags directly if needed */
438 if (self->taglist != NULL && gst_structure_has_name (structure, "audio/mpeg")) {
439
440 event = gst_event_new_tag (self->taglist);
441
442 /* send directly the crc */
443 if (gst_tag_list_get_boolean (self->taglist, "has-crc", &crc))
444 gst_avdtp_sink_set_crc (self->sink, crc);
445
446 if (gst_tag_list_get_string (self->taglist, "channel-mode", &mode))
447 gst_avdtp_sink_set_channel_mode (self->sink, mode);
448
449 gst_pad_send_event (self->ghostpad, event);
450
451 self->taglist = NULL;
452 g_free (mode);
453 }
454
455 g_object_set (self->rtp, "mtu",
456 gst_avdtp_sink_get_link_mtu (self->sink), NULL);
457
458 return TRUE;
459 }
460
461 static gboolean
gst_a2dp_sink_handle_event(GstPad * pad,GstObject * pad_parent,GstEvent * event)462 gst_a2dp_sink_handle_event (GstPad * pad, GstObject * pad_parent,
463 GstEvent * event)
464 {
465 GstA2dpSink *self;
466 GstTagList *taglist = NULL;
467
468 self = GST_A2DP_SINK (pad_parent);
469
470 if (GST_EVENT_TYPE (event) == GST_EVENT_TAG) {
471 if (self->taglist == NULL)
472 gst_event_parse_tag (event, &self->taglist);
473 else {
474 gst_event_parse_tag (event, &taglist);
475 gst_tag_list_insert (self->taglist, taglist, GST_TAG_MERGE_REPLACE);
476 }
477 } else if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS) {
478 GstCaps *caps = NULL;
479
480 gst_event_parse_caps (event, &caps);
481 gst_a2dp_sink_init_dynamic_elements (self, caps);
482 }
483
484 return gst_pad_event_default (pad, pad_parent, event);
485 }
486
487 static gboolean
gst_a2dp_sink_query(GstPad * pad,GstObject * parent,GstQuery * query)488 gst_a2dp_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
489 {
490 GstA2dpSink *sink = GST_A2DP_SINK (parent);
491 gboolean ret;
492
493 if (GST_QUERY_TYPE (query) == GST_QUERY_CAPS) {
494 GstCaps *caps;
495
496 caps = gst_a2dp_sink_get_caps (sink);
497 gst_query_set_caps_result (query, caps);
498 gst_caps_unref (caps);
499 ret = TRUE;
500 } else {
501 ret = gst_pad_query_default (pad, parent, query);
502 }
503
504 return ret;
505 }
506
507 static void
gst_a2dp_sink_init(GstA2dpSink * self)508 gst_a2dp_sink_init (GstA2dpSink * self)
509 {
510 self->sink = NULL;
511 self->rtp = NULL;
512 self->device = NULL;
513 self->transport = NULL;
514 self->autoconnect = DEFAULT_AUTOCONNECT;
515 self->taglist = NULL;
516 self->ghostpad = NULL;
517
518 gst_a2dp_sink_init_ghost_pad (self);
519
520 gst_a2dp_sink_init_avdtp_sink (self);
521 }
522