2 This file is part of PulseAudio.
4 Copyright 2008-2013 João Paulo Rechi Vita
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as
8 published by the Free Software Foundation; either version 2.1 of the
9 License, or (at your option) any later version.
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with PulseAudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
26 #include <pulse/xmalloc.h>
28 #include <pulsecore/core.h>
29 #include <pulsecore/core-util.h>
30 #include <pulsecore/dbus-shared.h>
31 #include <pulsecore/log.h>
32 #include <pulsecore/macro.h>
33 #include <pulsecore/refcnt.h>
34 #include <pulsecore/shared.h>
36 #include "a2dp-codecs.h"
38 #include "bluez5-util.h"
40 #define BLUEZ_SERVICE "org.bluez"
41 #define BLUEZ_MEDIA_ENDPOINT_INTERFACE BLUEZ_SERVICE ".MediaEndpoint1"
42 #define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1"
44 #define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource"
45 #define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink"
47 #define ENDPOINT_INTROSPECT_XML \
48 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
50 " <interface name=\"" BLUEZ_MEDIA_ENDPOINT_INTERFACE "\">" \
51 " <method name=\"SetConfiguration\">" \
52 " <arg name=\"transport\" direction=\"in\" type=\"o\"/>" \
53 " <arg name=\"properties\" direction=\"in\" type=\"ay\"/>" \
55 " <method name=\"SelectConfiguration\">" \
56 " <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>" \
57 " <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>" \
59 " <method name=\"ClearConfiguration\">" \
60 " <arg name=\"transport\" direction=\"in\" type=\"o\"/>" \
62 " <method name=\"Release\">" \
65 " <interface name=\"org.freedesktop.DBus.Introspectable\">" \
66 " <method name=\"Introspect\">" \
67 " <arg name=\"data\" type=\"s\" direction=\"out\"/>" \
72 struct pa_bluetooth_discovery
{
76 pa_dbus_connection
*connection
;
79 pa_hook hooks
[PA_BLUETOOTH_HOOK_MAX
];
82 pa_hashmap
*transports
;
85 pa_bluetooth_transport
*pa_bluetooth_transport_new(pa_bluetooth_device
*d
, const char *owner
, const char *path
,
86 pa_bluetooth_profile_t p
, const uint8_t *config
, size_t size
) {
87 pa_bluetooth_transport
*t
;
89 t
= pa_xnew0(pa_bluetooth_transport
, 1);
91 t
->owner
= pa_xstrdup(owner
);
92 t
->path
= pa_xstrdup(path
);
94 t
->config_size
= size
;
97 t
->config
= pa_xnew(uint8_t, size
);
98 memcpy(t
->config
, config
, size
);
101 pa_assert_se(pa_hashmap_put(d
->discovery
->transports
, t
->path
, t
) >= 0);
106 static const char *transport_state_to_string(pa_bluetooth_transport_state_t state
) {
108 case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
:
109 return "disconnected";
110 case PA_BLUETOOTH_TRANSPORT_STATE_IDLE
:
112 case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING
:
119 static void transport_state_changed(pa_bluetooth_transport
*t
, pa_bluetooth_transport_state_t state
) {
120 bool old_any_connected
;
124 if (t
->state
== state
)
127 old_any_connected
= pa_bluetooth_device_any_transport_connected(t
->device
);
129 pa_log_debug("Transport %s state changed from %s to %s",
130 t
->path
, transport_state_to_string(t
->state
), transport_state_to_string(state
));
133 if (state
== PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
)
134 t
->device
->transports
[t
->profile
] = NULL
;
136 pa_hook_fire(&t
->device
->discovery
->hooks
[PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED
], t
);
138 if (old_any_connected
!= pa_bluetooth_device_any_transport_connected(t
->device
))
139 pa_hook_fire(&t
->device
->discovery
->hooks
[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED
], t
->device
);
142 void pa_bluetooth_transport_put(pa_bluetooth_transport
*t
) {
143 transport_state_changed(t
, PA_BLUETOOTH_TRANSPORT_STATE_IDLE
);
146 void pa_bluetooth_transport_free(pa_bluetooth_transport
*t
) {
149 pa_hashmap_remove(t
->device
->discovery
->transports
, t
->path
);
156 static int bluez5_transport_acquire_cb(pa_bluetooth_transport
*t
, bool optional
, size_t *imtu
, size_t *omtu
) {
161 const char *method
= optional
? "TryAcquire" : "Acquire";
164 pa_assert(t
->device
);
165 pa_assert(t
->device
->discovery
);
167 pa_assert_se(m
= dbus_message_new_method_call(t
->owner
, t
->path
, BLUEZ_MEDIA_TRANSPORT_INTERFACE
, method
));
169 dbus_error_init(&err
);
171 r
= dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t
->device
->discovery
->connection
), m
, -1, &err
);
173 if (optional
&& pa_streq(err
.name
, "org.bluez.Error.NotAvailable"))
174 pa_log_info("Failed optional acquire of unavailable transport %s", t
->path
);
176 pa_log_error("Transport %s() failed for transport %s (%s)", method
, t
->path
, err
.message
);
178 dbus_error_free(&err
);
182 if (!dbus_message_get_args(r
, &err
, DBUS_TYPE_UNIX_FD
, &ret
, DBUS_TYPE_UINT16
, &i
, DBUS_TYPE_UINT16
, &o
,
183 DBUS_TYPE_INVALID
)) {
184 pa_log_error("Failed to parse %s() reply: %s", method
, err
.message
);
185 dbus_error_free(&err
);
197 dbus_message_unref(r
);
201 static void bluez5_transport_release_cb(pa_bluetooth_transport
*t
) {
206 pa_assert(t
->device
);
207 pa_assert(t
->device
->discovery
);
209 dbus_error_init(&err
);
211 if (t
->state
<= PA_BLUETOOTH_TRANSPORT_STATE_IDLE
) {
212 pa_log_info("Transport %s auto-released by BlueZ or already released", t
->path
);
216 pa_assert_se(m
= dbus_message_new_method_call(t
->owner
, t
->path
, BLUEZ_MEDIA_TRANSPORT_INTERFACE
, "Release"));
217 dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t
->device
->discovery
->connection
), m
, -1, &err
);
219 if (dbus_error_is_set(&err
)) {
220 pa_log_error("Failed to release transport %s: %s", t
->path
, err
.message
);
221 dbus_error_free(&err
);
223 pa_log_info("Transport %s released", t
->path
);
226 bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device
*d
) {
231 if (d
->device_info_valid
!= 1)
234 for (i
= 0; i
< PA_BLUETOOTH_PROFILE_COUNT
; i
++)
235 if (d
->transports
[i
] && d
->transports
[i
]->state
!= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
)
241 static pa_bluetooth_device
* device_create(pa_bluetooth_discovery
*y
, const char *path
) {
242 pa_bluetooth_device
*d
;
247 d
= pa_xnew0(pa_bluetooth_device
, 1);
249 d
->path
= pa_xstrdup(path
);
251 pa_hashmap_put(y
->devices
, d
->path
, d
);
256 pa_bluetooth_device
* pa_bluetooth_discovery_get_device_by_path(pa_bluetooth_discovery
*y
, const char *path
) {
257 pa_bluetooth_device
*d
;
260 pa_assert(PA_REFCNT_VALUE(y
) > 0);
263 if ((d
= pa_hashmap_get(y
->devices
, path
)))
264 if (d
->device_info_valid
== 1)
270 pa_bluetooth_device
* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_discovery
*y
, const char *remote
, const char *local
) {
271 pa_bluetooth_device
*d
;
275 pa_assert(PA_REFCNT_VALUE(y
) > 0);
279 while ((d
= pa_hashmap_iterate(y
->devices
, &state
, NULL
)))
280 if (pa_streq(d
->address
, remote
) && pa_streq(d
->adapter
->address
, local
))
281 return d
->device_info_valid
== 1 ? d
: NULL
;
286 static void device_free(pa_bluetooth_device
*d
) {
291 for (i
= 0; i
< PA_BLUETOOTH_PROFILE_COUNT
; i
++) {
292 pa_bluetooth_transport
*t
;
294 if (!(t
= d
->transports
[i
]))
297 transport_state_changed(t
, PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED
);
298 pa_bluetooth_transport_free(t
);
305 pa_xfree(d
->address
);
309 static void device_remove(pa_bluetooth_discovery
*y
, const char *path
) {
310 pa_bluetooth_device
*d
;
312 if (!(d
= pa_hashmap_remove(y
->devices
, path
)))
313 pa_log_warn("Unknown device removed %s", path
);
315 pa_log_debug("Device %s removed", path
);
320 static void device_remove_all(pa_bluetooth_discovery
*y
) {
321 pa_bluetooth_device
*d
;
325 while ((d
= pa_hashmap_steal_first(y
->devices
))) {
326 d
->device_info_valid
= -1;
327 pa_hook_fire(&y
->hooks
[PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED
], d
);
332 static pa_bluetooth_adapter
* adapter_create(pa_bluetooth_discovery
*y
, const char *path
) {
333 pa_bluetooth_adapter
*a
;
338 a
= pa_xnew0(pa_bluetooth_adapter
, 1);
340 a
->path
= pa_xstrdup(path
);
342 pa_hashmap_put(y
->adapters
, a
->path
, a
);
347 static void adapter_remove_all(pa_bluetooth_discovery
*y
) {
348 pa_bluetooth_adapter
*a
;
352 /* When this function is called all devices have already been freed */
354 while ((a
= pa_hashmap_steal_first(y
->adapters
))) {
356 pa_xfree(a
->address
);
361 pa_hook
* pa_bluetooth_discovery_hook(pa_bluetooth_discovery
*y
, pa_bluetooth_hook_t hook
) {
363 pa_assert(PA_REFCNT_VALUE(y
) > 0);
365 return &y
->hooks
[hook
];
368 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*m
, void *userdata
) {
369 pa_bluetooth_discovery
*y
;
374 pa_assert_se(y
= userdata
);
376 dbus_error_init(&err
);
378 if (dbus_message_is_signal(m
, "org.freedesktop.DBus", "NameOwnerChanged")) {
379 const char *name
, *old_owner
, *new_owner
;
381 if (!dbus_message_get_args(m
, &err
,
382 DBUS_TYPE_STRING
, &name
,
383 DBUS_TYPE_STRING
, &old_owner
,
384 DBUS_TYPE_STRING
, &new_owner
,
385 DBUS_TYPE_INVALID
)) {
386 pa_log_error("Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err
.message
);
390 if (pa_streq(name
, BLUEZ_SERVICE
)) {
391 if (old_owner
&& *old_owner
) {
392 pa_log_debug("Bluetooth daemon disappeared");
393 device_remove_all(y
);
394 adapter_remove_all(y
);
397 if (new_owner
&& *new_owner
) {
398 pa_log_debug("Bluetooth daemon appeared");
399 /* TODO: get managed objects */
403 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
407 dbus_error_free(&err
);
409 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
412 static uint8_t a2dp_default_bitpool(uint8_t freq
, uint8_t mode
) {
413 /* These bitpool values were chosen based on the A2DP spec recommendation */
415 case SBC_SAMPLING_FREQ_16000
:
416 case SBC_SAMPLING_FREQ_32000
:
419 case SBC_SAMPLING_FREQ_44100
:
422 case SBC_CHANNEL_MODE_MONO
:
423 case SBC_CHANNEL_MODE_DUAL_CHANNEL
:
426 case SBC_CHANNEL_MODE_STEREO
:
427 case SBC_CHANNEL_MODE_JOINT_STEREO
:
431 pa_log_warn("Invalid channel mode %u", mode
);
434 case SBC_SAMPLING_FREQ_48000
:
437 case SBC_CHANNEL_MODE_MONO
:
438 case SBC_CHANNEL_MODE_DUAL_CHANNEL
:
441 case SBC_CHANNEL_MODE_STEREO
:
442 case SBC_CHANNEL_MODE_JOINT_STEREO
:
446 pa_log_warn("Invalid channel mode %u", mode
);
450 pa_log_warn("Invalid sampling freq %u", freq
);
454 const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile
) {
456 case PA_BLUETOOTH_PROFILE_A2DP_SINK
:
458 case PA_BLUETOOTH_PROFILE_A2DP_SOURCE
:
459 return "a2dp_source";
460 case PA_BLUETOOTH_PROFILE_OFF
:
467 static DBusMessage
*endpoint_set_configuration(DBusConnection
*conn
, DBusMessage
*m
, void *userdata
) {
468 pa_bluetooth_discovery
*y
= userdata
;
469 pa_bluetooth_device
*d
;
470 pa_bluetooth_transport
*t
;
471 const char *sender
, *path
, *endpoint_path
, *dev_path
= NULL
, *uuid
= NULL
;
472 const uint8_t *config
= NULL
;
474 pa_bluetooth_profile_t p
= PA_BLUETOOTH_PROFILE_OFF
;
475 DBusMessageIter args
, props
;
478 if (!dbus_message_iter_init(m
, &args
) || !pa_streq(dbus_message_get_signature(m
), "oa{sv}")) {
479 pa_log_error("Invalid signature for method SetConfiguration()");
483 dbus_message_iter_get_basic(&args
, &path
);
485 if (pa_hashmap_get(y
->transports
, path
)) {
486 pa_log_error("Endpoint SetConfiguration(): Transport %s is already configured.", path
);
490 pa_assert_se(dbus_message_iter_next(&args
));
492 dbus_message_iter_recurse(&args
, &props
);
493 if (dbus_message_iter_get_arg_type(&props
) != DBUS_TYPE_DICT_ENTRY
)
496 /* Read transport properties */
497 while (dbus_message_iter_get_arg_type(&props
) == DBUS_TYPE_DICT_ENTRY
) {
499 DBusMessageIter value
, entry
;
502 dbus_message_iter_recurse(&props
, &entry
);
503 dbus_message_iter_get_basic(&entry
, &key
);
505 dbus_message_iter_next(&entry
);
506 dbus_message_iter_recurse(&entry
, &value
);
508 var
= dbus_message_iter_get_arg_type(&value
);
510 if (pa_streq(key
, "UUID")) {
511 if (var
!= DBUS_TYPE_STRING
) {
512 pa_log_error("Property %s of wrong type %c", key
, (char)var
);
516 dbus_message_iter_get_basic(&value
, &uuid
);
518 endpoint_path
= dbus_message_get_path(m
);
519 if (pa_streq(endpoint_path
, A2DP_SOURCE_ENDPOINT
)) {
520 if (pa_streq(uuid
, PA_BLUETOOTH_UUID_A2DP_SOURCE
))
521 p
= PA_BLUETOOTH_PROFILE_A2DP_SINK
;
522 } else if (pa_streq(endpoint_path
, A2DP_SINK_ENDPOINT
)) {
523 if (pa_streq(uuid
, PA_BLUETOOTH_UUID_A2DP_SINK
))
524 p
= PA_BLUETOOTH_PROFILE_A2DP_SOURCE
;
527 if (p
== PA_BLUETOOTH_PROFILE_OFF
) {
528 pa_log_error("UUID %s of transport %s incompatible with endpoint %s", uuid
, path
, endpoint_path
);
531 } else if (pa_streq(key
, "Device")) {
532 if (var
!= DBUS_TYPE_OBJECT_PATH
) {
533 pa_log_error("Property %s of wrong type %c", key
, (char)var
);
537 dbus_message_iter_get_basic(&value
, &dev_path
);
538 } else if (pa_streq(key
, "Configuration")) {
539 DBusMessageIter array
;
542 if (var
!= DBUS_TYPE_ARRAY
) {
543 pa_log_error("Property %s of wrong type %c", key
, (char)var
);
547 dbus_message_iter_recurse(&value
, &array
);
548 var
= dbus_message_iter_get_arg_type(&array
);
549 if (var
!= DBUS_TYPE_BYTE
) {
550 pa_log_error("%s is an array of wrong type %c", key
, (char)var
);
554 dbus_message_iter_get_fixed_array(&array
, &config
, &size
);
555 if (size
!= sizeof(a2dp_sbc_t
)) {
556 pa_log_error("Configuration array of invalid size");
560 c
= (a2dp_sbc_t
*) config
;
562 if (c
->frequency
!= SBC_SAMPLING_FREQ_16000
&& c
->frequency
!= SBC_SAMPLING_FREQ_32000
&&
563 c
->frequency
!= SBC_SAMPLING_FREQ_44100
&& c
->frequency
!= SBC_SAMPLING_FREQ_48000
) {
564 pa_log_error("Invalid sampling frequency in configuration");
568 if (c
->channel_mode
!= SBC_CHANNEL_MODE_MONO
&& c
->channel_mode
!= SBC_CHANNEL_MODE_DUAL_CHANNEL
&&
569 c
->channel_mode
!= SBC_CHANNEL_MODE_STEREO
&& c
->channel_mode
!= SBC_CHANNEL_MODE_JOINT_STEREO
) {
570 pa_log_error("Invalid channel mode in configuration");
574 if (c
->allocation_method
!= SBC_ALLOCATION_SNR
&& c
->allocation_method
!= SBC_ALLOCATION_LOUDNESS
) {
575 pa_log_error("Invalid allocation method in configuration");
579 if (c
->subbands
!= SBC_SUBBANDS_4
&& c
->subbands
!= SBC_SUBBANDS_8
) {
580 pa_log_error("Invalid SBC subbands in configuration");
584 if (c
->block_length
!= SBC_BLOCK_LENGTH_4
&& c
->block_length
!= SBC_BLOCK_LENGTH_8
&&
585 c
->block_length
!= SBC_BLOCK_LENGTH_12
&& c
->block_length
!= SBC_BLOCK_LENGTH_16
) {
586 pa_log_error("Invalid block length in configuration");
591 dbus_message_iter_next(&props
);
594 if ((d
= pa_hashmap_get(y
->devices
, dev_path
))) {
595 if (d
->device_info_valid
== -1) {
596 pa_log_error("Information about device %s is invalid", dev_path
);
600 /* InterfacesAdded signal is probably on it's way, device_info_valid is kept as 0. */
601 pa_log_warn("SetConfiguration() received for unknown device %s", dev_path
);
602 d
= device_create(y
, dev_path
);
605 if (d
->transports
[p
] != NULL
) {
606 pa_log_error("Cannot configure transport %s because profile %s is already used", path
, pa_bluetooth_profile_to_string(p
));
610 sender
= dbus_message_get_sender(m
);
612 pa_assert_se(r
= dbus_message_new_method_return(m
));
613 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y
->connection
), r
, NULL
));
614 dbus_message_unref(r
);
616 d
->transports
[p
] = t
= pa_bluetooth_transport_new(d
, sender
, path
, p
, config
, size
);
617 t
->acquire
= bluez5_transport_acquire_cb
;
618 t
->release
= bluez5_transport_release_cb
;
619 pa_bluetooth_transport_put(t
);
621 pa_log_debug("Transport %s available for profile %s", t
->path
, pa_bluetooth_profile_to_string(t
->profile
));
626 pa_log_error("Endpoint SetConfiguration(): invalid arguments");
629 pa_assert_se(r
= dbus_message_new_error(m
, "org.bluez.Error.InvalidArguments", "Unable to set configuration"));
633 static DBusMessage
*endpoint_select_configuration(DBusConnection
*conn
, DBusMessage
*m
, void *userdata
) {
634 pa_bluetooth_discovery
*y
= userdata
;
635 a2dp_sbc_t
*cap
, config
;
636 uint8_t *pconf
= (uint8_t *) &config
;
641 static const struct {
645 { 16000U, SBC_SAMPLING_FREQ_16000
},
646 { 32000U, SBC_SAMPLING_FREQ_32000
},
647 { 44100U, SBC_SAMPLING_FREQ_44100
},
648 { 48000U, SBC_SAMPLING_FREQ_48000
}
651 dbus_error_init(&err
);
653 if (!dbus_message_get_args(m
, &err
, DBUS_TYPE_ARRAY
, DBUS_TYPE_BYTE
, &cap
, &size
, DBUS_TYPE_INVALID
)) {
654 pa_log_error("Endpoint SelectConfiguration(): %s", err
.message
);
655 dbus_error_free(&err
);
659 if (size
!= sizeof(config
)) {
660 pa_log_error("Capabilities array has invalid size");
666 /* Find the lowest freq that is at least as high as the requested sampling rate */
667 for (i
= 0; (unsigned) i
< PA_ELEMENTSOF(freq_table
); i
++)
668 if (freq_table
[i
].rate
>= y
->core
->default_sample_spec
.rate
&& (cap
->frequency
& freq_table
[i
].cap
)) {
669 config
.frequency
= freq_table
[i
].cap
;
673 if ((unsigned) i
== PA_ELEMENTSOF(freq_table
)) {
674 for (--i
; i
>= 0; i
--) {
675 if (cap
->frequency
& freq_table
[i
].cap
) {
676 config
.frequency
= freq_table
[i
].cap
;
682 pa_log_error("Not suitable sample rate");
687 pa_assert((unsigned) i
< PA_ELEMENTSOF(freq_table
));
689 if (y
->core
->default_sample_spec
.channels
<= 1) {
690 if (cap
->channel_mode
& SBC_CHANNEL_MODE_MONO
)
691 config
.channel_mode
= SBC_CHANNEL_MODE_MONO
;
692 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_JOINT_STEREO
)
693 config
.channel_mode
= SBC_CHANNEL_MODE_JOINT_STEREO
;
694 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_STEREO
)
695 config
.channel_mode
= SBC_CHANNEL_MODE_STEREO
;
696 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_DUAL_CHANNEL
)
697 config
.channel_mode
= SBC_CHANNEL_MODE_DUAL_CHANNEL
;
699 pa_log_error("No supported channel modes");
704 if (y
->core
->default_sample_spec
.channels
>= 2) {
705 if (cap
->channel_mode
& SBC_CHANNEL_MODE_JOINT_STEREO
)
706 config
.channel_mode
= SBC_CHANNEL_MODE_JOINT_STEREO
;
707 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_STEREO
)
708 config
.channel_mode
= SBC_CHANNEL_MODE_STEREO
;
709 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_DUAL_CHANNEL
)
710 config
.channel_mode
= SBC_CHANNEL_MODE_DUAL_CHANNEL
;
711 else if (cap
->channel_mode
& SBC_CHANNEL_MODE_MONO
)
712 config
.channel_mode
= SBC_CHANNEL_MODE_MONO
;
714 pa_log_error("No supported channel modes");
719 if (cap
->block_length
& SBC_BLOCK_LENGTH_16
)
720 config
.block_length
= SBC_BLOCK_LENGTH_16
;
721 else if (cap
->block_length
& SBC_BLOCK_LENGTH_12
)
722 config
.block_length
= SBC_BLOCK_LENGTH_12
;
723 else if (cap
->block_length
& SBC_BLOCK_LENGTH_8
)
724 config
.block_length
= SBC_BLOCK_LENGTH_8
;
725 else if (cap
->block_length
& SBC_BLOCK_LENGTH_4
)
726 config
.block_length
= SBC_BLOCK_LENGTH_4
;
728 pa_log_error("No supported block lengths");
732 if (cap
->subbands
& SBC_SUBBANDS_8
)
733 config
.subbands
= SBC_SUBBANDS_8
;
734 else if (cap
->subbands
& SBC_SUBBANDS_4
)
735 config
.subbands
= SBC_SUBBANDS_4
;
737 pa_log_error("No supported subbands");
741 if (cap
->allocation_method
& SBC_ALLOCATION_LOUDNESS
)
742 config
.allocation_method
= SBC_ALLOCATION_LOUDNESS
;
743 else if (cap
->allocation_method
& SBC_ALLOCATION_SNR
)
744 config
.allocation_method
= SBC_ALLOCATION_SNR
;
746 config
.min_bitpool
= (uint8_t) PA_MAX(MIN_BITPOOL
, cap
->min_bitpool
);
747 config
.max_bitpool
= (uint8_t) PA_MIN(a2dp_default_bitpool(config
.frequency
, config
.channel_mode
), cap
->max_bitpool
);
749 if (config
.min_bitpool
> config
.max_bitpool
)
752 pa_assert_se(r
= dbus_message_new_method_return(m
));
753 pa_assert_se(dbus_message_append_args(r
, DBUS_TYPE_ARRAY
, DBUS_TYPE_BYTE
, &pconf
, size
, DBUS_TYPE_INVALID
));
758 pa_assert_se(r
= dbus_message_new_error(m
, "org.bluez.Error.InvalidArguments", "Unable to select configuration"));
762 static DBusMessage
*endpoint_clear_configuration(DBusConnection
*conn
, DBusMessage
*m
, void *userdata
) {
765 pa_assert_se(r
= dbus_message_new_error(m
, BLUEZ_MEDIA_ENDPOINT_INTERFACE
".Error.NotImplemented",
766 "Method not implemented"));
771 static DBusMessage
*endpoint_release(DBusConnection
*conn
, DBusMessage
*m
, void *userdata
) {
774 pa_assert_se(r
= dbus_message_new_error(m
, BLUEZ_MEDIA_ENDPOINT_INTERFACE
".Error.NotImplemented",
775 "Method not implemented"));
780 static DBusHandlerResult
endpoint_handler(DBusConnection
*c
, DBusMessage
*m
, void *userdata
) {
781 struct pa_bluetooth_discovery
*y
= userdata
;
782 DBusMessage
*r
= NULL
;
783 const char *path
, *interface
, *member
;
787 path
= dbus_message_get_path(m
);
788 interface
= dbus_message_get_interface(m
);
789 member
= dbus_message_get_member(m
);
791 pa_log_debug("dbus: path=%s, interface=%s, member=%s", path
, interface
, member
);
793 if (!pa_streq(path
, A2DP_SOURCE_ENDPOINT
) && !pa_streq(path
, A2DP_SINK_ENDPOINT
))
794 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
796 if (dbus_message_is_method_call(m
, "org.freedesktop.DBus.Introspectable", "Introspect")) {
797 const char *xml
= ENDPOINT_INTROSPECT_XML
;
799 pa_assert_se(r
= dbus_message_new_method_return(m
));
800 pa_assert_se(dbus_message_append_args(r
, DBUS_TYPE_STRING
, &xml
, DBUS_TYPE_INVALID
));
802 } else if (dbus_message_is_method_call(m
, BLUEZ_MEDIA_ENDPOINT_INTERFACE
, "SetConfiguration"))
803 r
= endpoint_set_configuration(c
, m
, userdata
);
804 else if (dbus_message_is_method_call(m
, BLUEZ_MEDIA_ENDPOINT_INTERFACE
, "SelectConfiguration"))
805 r
= endpoint_select_configuration(c
, m
, userdata
);
806 else if (dbus_message_is_method_call(m
, BLUEZ_MEDIA_ENDPOINT_INTERFACE
, "ClearConfiguration"))
807 r
= endpoint_clear_configuration(c
, m
, userdata
);
808 else if (dbus_message_is_method_call(m
, BLUEZ_MEDIA_ENDPOINT_INTERFACE
, "Release"))
809 r
= endpoint_release(c
, m
, userdata
);
811 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
814 pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y
->connection
), r
, NULL
));
815 dbus_message_unref(r
);
818 return DBUS_HANDLER_RESULT_HANDLED
;
821 static void endpoint_init(pa_bluetooth_discovery
*y
, pa_bluetooth_profile_t profile
) {
822 static const DBusObjectPathVTable vtable_endpoint
= {
823 .message_function
= endpoint_handler
,
829 case PA_BLUETOOTH_PROFILE_A2DP_SINK
:
830 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SOURCE_ENDPOINT
,
831 &vtable_endpoint
, y
));
833 case PA_BLUETOOTH_PROFILE_A2DP_SOURCE
:
834 pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SINK_ENDPOINT
,
835 &vtable_endpoint
, y
));
838 pa_assert_not_reached();
843 static void endpoint_done(pa_bluetooth_discovery
*y
, pa_bluetooth_profile_t profile
) {
847 case PA_BLUETOOTH_PROFILE_A2DP_SINK
:
848 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SOURCE_ENDPOINT
);
850 case PA_BLUETOOTH_PROFILE_A2DP_SOURCE
:
851 dbus_connection_unregister_object_path(pa_dbus_connection_get(y
->connection
), A2DP_SINK_ENDPOINT
);
854 pa_assert_not_reached();
859 pa_bluetooth_discovery
* pa_bluetooth_discovery_get(pa_core
*c
) {
860 pa_bluetooth_discovery
*y
;
862 DBusConnection
*conn
;
865 if ((y
= pa_shared_get(c
, "bluetooth-discovery")))
866 return pa_bluetooth_discovery_ref(y
);
868 y
= pa_xnew0(pa_bluetooth_discovery
, 1);
871 y
->adapters
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
872 y
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
873 y
->transports
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
875 for (i
= 0; i
< PA_BLUETOOTH_HOOK_MAX
; i
++)
876 pa_hook_init(&y
->hooks
[i
], y
);
878 pa_shared_set(c
, "bluetooth-discovery", y
);
880 dbus_error_init(&err
);
882 if (!(y
->connection
= pa_dbus_bus_get(y
->core
, DBUS_BUS_SYSTEM
, &err
))) {
883 pa_log_error("Failed to get D-Bus connection: %s", err
.message
);
887 conn
= pa_dbus_connection_get(y
->connection
);
889 /* dynamic detection of bluetooth audio devices */
890 if (!dbus_connection_add_filter(conn
, filter_cb
, y
, NULL
)) {
891 pa_log_error("Failed to add filter function");
894 y
->filter_added
= true;
896 if (pa_dbus_add_matches(conn
, &err
,
897 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"
898 ",arg0='" BLUEZ_SERVICE
"'",
900 pa_log_error("Failed to add D-Bus matches: %s", err
.message
);
903 y
->matches_added
= true;
905 endpoint_init(y
, PA_BLUETOOTH_PROFILE_A2DP_SINK
);
906 endpoint_init(y
, PA_BLUETOOTH_PROFILE_A2DP_SOURCE
);
911 pa_bluetooth_discovery_unref(y
);
912 dbus_error_free(&err
);
917 pa_bluetooth_discovery
* pa_bluetooth_discovery_ref(pa_bluetooth_discovery
*y
) {
919 pa_assert(PA_REFCNT_VALUE(y
) > 0);
926 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery
*y
) {
928 pa_assert(PA_REFCNT_VALUE(y
) > 0);
930 if (PA_REFCNT_DEC(y
) > 0)
934 device_remove_all(y
);
935 pa_hashmap_free(y
->devices
);
939 adapter_remove_all(y
);
940 pa_hashmap_free(y
->adapters
);
944 pa_assert(pa_hashmap_isempty(y
->transports
));
945 pa_hashmap_free(y
->transports
);
950 if (y
->matches_added
)
951 pa_dbus_remove_matches(pa_dbus_connection_get(y
->connection
),
952 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',"
953 "arg0='" BLUEZ_SERVICE
"'",
957 dbus_connection_remove_filter(pa_dbus_connection_get(y
->connection
), filter_cb
, y
);
959 endpoint_done(y
, PA_BLUETOOTH_PROFILE_A2DP_SINK
);
960 endpoint_done(y
, PA_BLUETOOTH_PROFILE_A2DP_SOURCE
);
962 pa_dbus_connection_unref(y
->connection
);
965 pa_shared_remove(y
->core
, "bluetooth-discovery");