Hi, On Tue, Jan 5, 2010 at 1:39 AM, Marcel Holtmann <marcel@xxxxxxxxxxxx> wrote: > Hi Luiz, > >> The idea is to complete replace the existing audio IPC with DBus. >> Johan and I discussed this a few times in the past and today we >> finally archive something, so here are some design choices so far: >> >> 1. Codec capabilities and configuration are blobs (array of bytes or >> 'ay'), so there is no attempt to format codec structures into dbus >> structures, this make it easier for both end points as well as >> bluetoothd and also enables proprietary codecs. (suggested by Marcel >> in the last BlueZ meeting) >> >> 2. The spec is not a2dp specific. So it should be possible to register >> end points for HFP and VDP. > > if you really wanna achieve that, then I would prefer not to use > StreamEndPoint as interface. > > So first of all it should be StreamEndpoint. While SEP takes the P into > account, the word "endpoint" in itself is proper enough. > > And if this should support HFP, then the term Stream is kinda tricky. We > could just allow that, but I prefer not to mix streaming with headset > functionality. So what about just using the "Endpoint" as a generic > agent type interface. Or "MediaEndpoint" if you need to tie it to the > media interface? Here are the final documentation and a very basic implementation, note that to compile it needs dbus 1.3 (@Marcel: What about making it a hard dependency?), other remarks: - Media interface is disabled by default, to enable it one has to edit audio.conf e.g. Enable=Media - simple-endpoint can be used to emulate an endpoint e.g ./simple-endpoint hci0 sbcsource (other predefined are: sbcsink mp3source mp3sink) - Due to the timeout of avdtp requests (4 sec.) Ive been using 3 sec for endpoint method calls, that why simple-endpoint always respond a predefined configuration when SelectConfiguration although SetConfiguration still requires an answer. - To not have an even bigger change which would reflect on unix socket support I had to make media_endpoint_set_configuration to block when not given a callback, this is on purpose because we are replying to avdtp commands in a sync manner. - There are still a lot missing such as: making proper use of caller of {Audio,Headset,AudioSource,AudioSink}.Connect when selecting an endpoint, hfp support, simple-endpoint for hfp roles, proper transport locking, file descriptor passing. -- Luiz Augusto von Dentz Engenheiro de Computação
From 4759a880a30691a7ae9081154e2e06ac7eaf94be Mon Sep 17 00:00:00 2001 From: Luiz Augusto Von Dentz <luiz.dentz-von@xxxxxxxxx> Date: Wed, 13 Jan 2010 16:56:23 +0200 Subject: [PATCH 1/4] Add media API documentation Media API is a replacement for the internal audio IPC which is no longer necessary as DBus 1.3 and newer are capable of transfering file descriptors. --- doc/media-api.txt | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 123 insertions(+), 0 deletions(-) create mode 100644 doc/media-api.txt diff --git a/doc/media-api.txt b/doc/media-api.txt new file mode 100644 index 0000000..b7c641c --- /dev/null +++ b/doc/media-api.txt @@ -0,0 +1,123 @@ +BlueZ D-Bus Media API description +********************************* + +Media hierarchy +=============== + +Service org.bluez +Interface org.bluez.Media +Object path [variable prefix]/{hci0,hci1,...} + +Methods void RegisterEndpoint(object endpoint, dict properties) + + Register a local end point to sender, the sender can + register as many end points as it likes. + + Note: If the sender disconnects the end points are + automatically unregistered. + + possible properties: + + string UUID: + + UUID of the profile which the endpoint + is for. + + byte Codec: + + Assigned mumber of codec that the + endpoint implements. The values should + match the profile specification which + is indicated by the UUID. + + array{byte} Capabilities: + + Capabilities blob, it is used as it is + so the size and byte order must match. + + void UnregisterEndpoint(object endpoint) + + Unregister sender end point. + +MediaEndpoint hierarchy +======================= + +Service unique name +Interface org.bluez.MediaEndpoint +Object path freely definable + +Methods void SetConfiguration(object transport, array{byte} configuration) + + Set configuration for the transport. + + array{byte} SelectConfiguration(array{byte} capabilities) + + Select preferable configuration from the supported + capabilities. + + Returns a configuration which can be used to setup + a transport. + + Note: There is no need to cache the selected + configuration since on success the configuration is + send back as parameter of SetConfiguration. + + void ClearConfiguration() + + Clear any configuration set. + +MediaTransport hierarchy +======================== + +Service org.bluez +Interface org.bluez.MediaTransport +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/fdX + +Methods dict GetProperties() + + Returns all properties for the interface. See the + properties section for available properties. + + fd Acquire(string accesstype) + + Acquire transport file descriptor. + + possible accesstype: + + "r" : Read only access + + "w" : Write only access + + "rw": Read and write access + + void Release(string accesstype) + + Releases file descriptor. + + void SetProperty(string name, variant value) + + Changes the value of the specified property. Only + properties that are listed a read-write are changeable. + On success this will emit a PropertyChanged signal. + +Signals void PropertyChanged(string name, variant value) + + This signal indicates a changed value of the given + property. + +Properties uint16 Delay [readwrite] + + Optional. Transport delay in 1/10 of milisecond, this + property is only writeable when the transport was + acquired by the sender and the sender is a sink. + + boolean NREC [readwrite] + + Optional. It tells if echo cancelling and noise + reduction functions are active in the transport, this + property is only writeable when the transport was + acquired by the sender. + + object Device [readonly] + + Device object which the transport is connected to. -- 1.6.3.3
From 3577ee604187c758180bcea6ecd404fc8f4c357e Mon Sep 17 00:00:00 2001 From: Luiz Augusto Von Dentz <luiz.dentz-von@xxxxxxxxx> Date: Wed, 20 Jan 2010 14:53:00 +0200 Subject: [PATCH 2/4] Add simple-endpoint test script --- test/simple-endpoint | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 106 insertions(+), 0 deletions(-) create mode 100755 test/simple-endpoint diff --git a/test/simple-endpoint b/test/simple-endpoint new file mode 100755 index 0000000..5fee8bf --- /dev/null +++ b/test/simple-endpoint @@ -0,0 +1,106 @@ +#!/usr/bin/python + +import sys +import dbus +import dbus.service +import dbus.mainloop.glib +import gobject + +A2DP_SOURCE_UUID = "0000110A-0000-1000-8000-00805F9B34FB" +A2DP_SINK_UUID = "0000110B-0000-1000-8000-00805F9B34FB" + +SBC_CODEC = dbus.Byte(0x00) +#Channel Modes: Mono DualChannel Stereo JointStereo +#Frequencies: 16Khz 32Khz 44.1Khz 48Khz +#Subbands: 4 8 +#Blocks: 4 8 12 16 +#Bitpool Range: 2-64 +SBC_CAPABILITIES = dbus.Array([dbus.Byte(0xff), dbus.Byte(0xff), dbus.Byte(2), dbus.Byte(64)]) +# JointStereo 44.1Khz Subbands: Blocks: 16 Bitpool Range: 64-64 +SBC_CONFIGURATION = dbus.Array([dbus.Byte(0x12), dbus.Byte(0x15), dbus.Byte(64), dbus.Byte(64)]) + +MP3_CODEC = dbus.Byte(0x01) +#Channel Modes: Mono DualChannel Stereo JointStereo +#Frequencies: 16Khz 22.05Khz 24Khz 32Khz 44.1Khz 48Khz +#CRC: No +#Layer: 1 2 3 +#Bit Rate: Free format +#VBR: Yes +#Payload Format: RFC-2250 +MP3_CAPABILITIES = dbus.Array([dbus.Byte(0xef), dbus.Byte(0x3f), dbus.Byte(0xff), dbus.Byte(0xff)]) +# JointStereo 44.1Khz Layer: 3 Bit Rate: VBR Format: RFC-2250 +MP3_CONFIGURATION = dbus.Array([dbus.Byte(0x22), dbus.Byte(0x02), dbus.Byte(0x80), dbus.Byte(0x00)]) + +class Rejected(dbus.DBusException): + _dbus_error_name = "org.bluez.Error.Rejected" + +class Endpoint(dbus.service.Object): + configuration = SBC_CONFIGURATION + + def default_configuration(self, configuration): + self.configuration = configuration + + @dbus.service.method("org.bluez.MediaEndpoint", + in_signature="", out_signature="") + def ClearConfiguration(self): + print "ClearConfiguration" + + @dbus.service.method("org.bluez.MediaEndpoint", + in_signature="oay", out_signature="") + def SetConfiguration(self, transport, config): + print "SetConfiguration (%s, %s)" % (transport, config) + accept = raw_input("Accept configuration (yes/no): ") + if (accept == "yes"): + return + raise Rejected("Invalid configuration") + + @dbus.service.method("org.bluez.MediaEndpoint", + in_signature="ay", out_signature="ay") + def SelectConfiguration(self, caps): + print "SelectConfiguration (%s)" % (caps) + return self.configuration + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + manager = dbus.Interface(bus.get_object("org.bluez", "/"), + "org.bluez.Manager") + + if len(sys.argv) > 1: + path = manager.FindAdapter(sys.argv[1]) + else: + path = manager.DefaultAdapter() + + media = dbus.Interface(bus.get_object("org.bluez", path), + "org.bluez.Media") + + path = "/test/endpoint" + endpoint = Endpoint(bus, path) + mainloop = gobject.MainLoop() + + properties = dbus.Dictionary({ "UUID" : A2DP_SOURCE_UUID, + "Codec" : SBC_CODEC, + "Capabilities" : SBC_CAPABILITIES }) + + if len(sys.argv) > 2: + if sys.argv[2] == "sbcsink": + properties = dbus.Dictionary({ "UUID" : A2DP_SINK_UUID, + "Codec" : SBC_CODEC, + "Capabilities" : SBC_CAPABILITIES }) + if sys.argv[2] == "mp3source": + properties = dbus.Dictionary({ "UUID" : A2DP_SOURCE_UUID, + "Codec" : MP3_CODEC, + "Capabilities" : MP3_CAPABILITIES }) + endpoint.default_configuration(MP3_CONFIGURATION) + if sys.argv[2] == "mp3sink": + properties = dbus.Dictionary({ "UUID" : A2DP_SOURCE_UUID, + "Codec" : MP3_CODEC, + "Capabilities" : MP3_CAPABILITIES }) + endpoint.default_configuration(MP3_CONFIGURATION) + + print properties + + media.RegisterEndpoint(path, properties) + + mainloop.run() -- 1.6.3.3
From e372552bd220b4edac601c278e75d5c2f54fe5dc Mon Sep 17 00:00:00 2001 From: Luiz Augusto Von Dentz <luiz.dentz-von@xxxxxxxxx> Date: Thu, 21 Jan 2010 18:23:45 +0200 Subject: [PATCH 3/4] Add rule to enabling talking to org.bluez.MediaEndpoint --- src/bluetooth.conf | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/src/bluetooth.conf b/src/bluetooth.conf index 315009c..6bc6bba 100644 --- a/src/bluetooth.conf +++ b/src/bluetooth.conf @@ -11,6 +11,7 @@ <allow own="org.bluez"/> <allow send_destination="org.bluez"/> <allow send_interface="org.bluez.Agent"/> + <allow send_interface="org.bluez.MediaEndpoint"/> </policy> <policy at_console="true"> -- 1.6.3.3
From 3f2adc271f9da153518b301d3da6e47d178818e0 Mon Sep 17 00:00:00 2001 From: Luiz Augusto Von Dentz <luiz.dentz-von@xxxxxxxxx> Date: Thu, 28 Jan 2010 15:11:13 +0200 Subject: [PATCH 4/4] Add initial implementation of org.bluez.Media spec --- Makefile.am | 1 + audio/a2dp.c | 592 +++++++++++++++++++++++++++++++++++++----- audio/a2dp.h | 14 + audio/avdtp.c | 3 + audio/manager.c | 50 ++++ audio/manager.h | 1 + audio/media.c | 778 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ audio/media.h | 45 ++++ audio/sink.c | 178 ++----------- audio/source.c | 174 ++----------- audio/unix.c | 1 + 11 files changed, 1463 insertions(+), 374 deletions(-) create mode 100644 audio/media.c create mode 100644 audio/media.h diff --git a/Makefile.am b/Makefile.am index f8111a9..c55834a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -121,6 +121,7 @@ builtin_sources += audio/main.c \ audio/avdtp.h audio/avdtp.c \ audio/ipc.h audio/ipc.c \ audio/unix.h audio/unix.c \ + audio/media.h audio/media.c \ audio/telephony.h builtin_nodist += audio/telephony.c diff --git a/audio/a2dp.c b/audio/a2dp.c index 575291a..05d5971 100644 --- a/audio/a2dp.c +++ b/audio/a2dp.c @@ -43,6 +43,7 @@ #include "sink.h" #include "source.h" #include "unix.h" +#include "media.h" #include "a2dp.h" #include "sdpd.h" @@ -60,6 +61,8 @@ #endif struct a2dp_sep { + struct a2dp_server *server; + struct media_endpoint *endpoint; uint8_t type; uint8_t codec; struct avdtp_local_sep *sep; @@ -73,6 +76,7 @@ struct a2dp_sep { }; struct a2dp_setup_cb { + a2dp_select_cb_t select_cb; a2dp_config_cb_t config_cb; a2dp_stream_cb_t resume_cb; a2dp_stream_cb_t suspend_cb; @@ -87,6 +91,7 @@ struct a2dp_setup { struct avdtp_stream *stream; struct avdtp_error *err; GSList *client_caps; + gboolean delay_reporting; gboolean reconfigure; gboolean canceled; gboolean start; @@ -228,6 +233,25 @@ static gboolean finalize_suspend_errno(struct a2dp_setup *s, int err) return finalize_suspend(s); } +static gboolean finalize_select(struct a2dp_setup *s, GSList *caps) +{ + GSList *l; + + setup_ref(s); + for (l = s->cb; l != NULL; l = l->next) { + struct a2dp_setup_cb *cb = l->data; + + if (cb->select_cb) { + cb->select_cb(s->session, s->sep, caps, cb->user_data); + cb->select_cb = NULL; + setup_unref(s); + } + } + + setup_unref(s); + return FALSE; +} + static struct a2dp_setup *find_setup_by_session(struct avdtp *session) { GSList *l; @@ -277,6 +301,9 @@ static void stream_state_changed(struct avdtp_stream *stream, sep->session = NULL; } + if (sep->endpoint) + media_endpoint_clear_configuration(sep->endpoint); + sep->stream = NULL; } @@ -506,6 +533,117 @@ static gboolean mpeg_getcap_ind(struct avdtp *session, return TRUE; } +static gboolean endpoint_setconf_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + GSList *caps, uint8_t *err, + uint8_t *category, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct audio_device *dev; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + debug("Sink %p: Set_Configuration_Ind", sep); + else + debug("Source %p: Set_Configuration_Ind", sep); + + dev = a2dp_get_dev(session); + if (!dev) { + *err = AVDTP_UNSUPPORTED_CONFIGURATION; + *category = 0x00; + return FALSE; + } + + for (; caps != NULL; caps = g_slist_next(caps)) { + struct avdtp_service_capability *cap = caps->data; + struct avdtp_media_codec_capability *codec; + gboolean ret; + + if (cap->category == AVDTP_DELAY_REPORTING && + !a2dp_sep->delay_reporting) { + *err = AVDTP_UNSUPPORTED_CONFIGURATION; + *category = AVDTP_DELAY_REPORTING; + return FALSE; + } + + if (cap->category != AVDTP_MEDIA_CODEC) + continue; + + codec = (struct avdtp_media_codec_capability *) cap->data; + + if (codec->media_codec_type != a2dp_sep->codec) { + *err = AVDTP_UNSUPPORTED_CONFIGURATION; + *category = AVDTP_MEDIA_CODEC; + return FALSE; + } + + ret = media_endpoint_set_configuration(a2dp_sep->endpoint, dev, + codec->data, cap->length - sizeof(*codec), + NULL, NULL); + if (ret) + break; + + *err = AVDTP_UNSUPPORTED_CONFIGURATION; + *category = AVDTP_MEDIA_CODEC; + return FALSE; + } + + avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep); + a2dp_sep->stream = stream; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE) + sink_new_stream(dev, session, stream); + + return TRUE; +} + +static gboolean endpoint_getcap_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + gboolean get_all, + GSList **caps, uint8_t *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct avdtp_service_capability *media_transport, *media_codec; + struct avdtp_media_codec_capability *codec_caps; + uint8_t *capabilities; + size_t length; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + debug("Sink %p: Get_Capability_Ind", sep); + else + debug("Source %p: Get_Capability_Ind", sep); + + *caps = NULL; + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + *caps = g_slist_append(*caps, media_transport); + + length = media_endpoint_get_capabilities(a2dp_sep->endpoint, + &capabilities); + + codec_caps = g_malloc(sizeof(*codec_caps) + length); + codec_caps->media_type = AVDTP_MEDIA_TYPE_AUDIO; + codec_caps->media_codec_type = a2dp_sep->codec; + memcpy(codec_caps->data, capabilities, length); + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, codec_caps, + sizeof(*codec_caps) + length); + + *caps = g_slist_append(*caps, media_codec); + g_free(codec_caps); + + if (get_all) { + struct avdtp_service_capability *delay_reporting; + delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING, + NULL, 0); + *caps = g_slist_append(*caps, delay_reporting); + } + + return TRUE; +} + static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) @@ -544,6 +682,22 @@ static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, else source_new_stream(dev, session, setup->stream); + /* Notify Endpoint */ + if (a2dp_sep->endpoint) { + struct avdtp_service_capability *service = avdtp_stream_get_codec(stream); + struct avdtp_media_codec_capability *codec; + + codec = (struct avdtp_media_codec_capability *) service->data; + + if (media_endpoint_set_configuration(a2dp_sep->endpoint, dev, + codec->data, service->length - + sizeof(*codec), NULL, NULL) == + FALSE) { + setup->stream = NULL; + finalize_config_errno(setup, -EPERM); + } + } + ret = avdtp_open(session, stream); if (ret < 0) { error("Error on avdtp_open %s (%d)", strerror(-ret), -ret); @@ -1005,6 +1159,19 @@ static struct avdtp_sep_ind mpeg_ind = { .delayreport = delayreport_ind, }; +static struct avdtp_sep_ind endpoint_ind = { + .get_capability = endpoint_getcap_ind, + .set_configuration = endpoint_setconf_ind, + .get_configuration = getconf_ind, + .open = open_ind, + .start = start_ind, + .suspend = suspend_ind, + .close = close_ind, + .abort = abort_ind, + .reconfigure = reconf_ind, + .delayreport = delayreport_ind, +}; + static sdp_record_t *a2dp_record(uint8_t type, uint16_t avdtp_ver) { sdp_list_t *svclass_id, *pfseq, *apseq, *root; @@ -1072,64 +1239,6 @@ static sdp_record_t *a2dp_record(uint8_t type, uint16_t avdtp_ver) return record; } -static struct a2dp_sep *a2dp_add_sep(struct a2dp_server *server, uint8_t type, - uint8_t codec, gboolean delay_reporting) -{ - struct a2dp_sep *sep; - GSList **l; - uint32_t *record_id; - sdp_record_t *record; - struct avdtp_sep_ind *ind; - - sep = g_new0(struct a2dp_sep, 1); - - ind = (codec == A2DP_CODEC_MPEG12) ? &mpeg_ind : &sbc_ind; - sep->sep = avdtp_register_sep(&server->src, type, - AVDTP_MEDIA_TYPE_AUDIO, codec, - delay_reporting, ind, &cfm, sep); - if (sep->sep == NULL) { - g_free(sep); - return NULL; - } - - sep->codec = codec; - sep->type = type; - sep->delay_reporting = delay_reporting; - - if (type == AVDTP_SEP_TYPE_SOURCE) { - l = &server->sources; - record_id = &server->source_record_id; - } else { - l = &server->sinks; - record_id = &server->sink_record_id; - } - - if (*record_id != 0) - goto add; - - record = a2dp_record(type, server->version); - if (!record) { - error("Unable to allocate new service record"); - avdtp_unregister_sep(sep->sep); - g_free(sep); - return NULL; - } - - if (add_record_to_server(&server->src, record) < 0) { - error("Unable to register A2DP service record");\ - sdp_record_free(record); - avdtp_unregister_sep(sep->sep); - g_free(sep); - return NULL; - } - *record_id = record->handle; - -add: - *l = g_slist_append(*l, sep); - - return sep; -} - static struct a2dp_server *find_server(GSList *list, const bdaddr_t *src) { GSList *l; @@ -1250,22 +1359,23 @@ proceed: if (source) { for (i = 0; i < sbc_srcs; i++) - a2dp_add_sep(server, AVDTP_SEP_TYPE_SOURCE, - A2DP_CODEC_SBC, delay_reporting); + a2dp_add_sep(src, AVDTP_SEP_TYPE_SOURCE, + A2DP_CODEC_SBC, delay_reporting, NULL); for (i = 0; i < mpeg12_srcs; i++) - a2dp_add_sep(server, AVDTP_SEP_TYPE_SOURCE, - A2DP_CODEC_MPEG12, delay_reporting); + a2dp_add_sep(src, AVDTP_SEP_TYPE_SOURCE, + A2DP_CODEC_MPEG12, delay_reporting, NULL); } if (sink) { for (i = 0; i < sbc_sinks; i++) - a2dp_add_sep(server, AVDTP_SEP_TYPE_SINK, - A2DP_CODEC_SBC, delay_reporting); + a2dp_add_sep(src, AVDTP_SEP_TYPE_SINK, + A2DP_CODEC_SBC, delay_reporting, NULL); for (i = 0; i < mpeg12_sinks; i++) - a2dp_add_sep(server, AVDTP_SEP_TYPE_SINK, - A2DP_CODEC_MPEG12, delay_reporting); + a2dp_add_sep(src, AVDTP_SEP_TYPE_SINK, + A2DP_CODEC_MPEG12, delay_reporting, + NULL); } return 0; @@ -1309,6 +1419,91 @@ void a2dp_unregister(const bdaddr_t *src) connection = NULL; } +struct a2dp_sep *a2dp_add_sep(const bdaddr_t *src, uint8_t type, + uint8_t codec, gboolean delay_reporting, + struct media_endpoint *endpoint) +{ + struct a2dp_server *server; + struct a2dp_sep *sep; + GSList **l; + uint32_t *record_id; + sdp_record_t *record; + struct avdtp_sep_ind *ind; + + server = find_server(servers, src); + if (server == NULL) + return NULL; + + sep = g_new0(struct a2dp_sep, 1); + + if (endpoint) { + ind = &endpoint_ind; + goto proceed; + } + + ind = (codec == A2DP_CODEC_MPEG12) ? &mpeg_ind : &sbc_ind; + +proceed: + sep->sep = avdtp_register_sep(&server->src, type, + AVDTP_MEDIA_TYPE_AUDIO, codec, + delay_reporting, ind, &cfm, sep); + if (sep->sep == NULL) { + g_free(sep); + return NULL; + } + + sep->server = server; + sep->endpoint = endpoint; + sep->codec = codec; + sep->type = type; + sep->delay_reporting = delay_reporting; + + if (type == AVDTP_SEP_TYPE_SOURCE) { + l = &server->sources; + record_id = &server->source_record_id; + } else { + l = &server->sinks; + record_id = &server->sink_record_id; + } + + if (*record_id != 0) + goto add; + + record = a2dp_record(type, server->version); + if (!record) { + error("Unable to allocate new service record"); + avdtp_unregister_sep(sep->sep); + g_free(sep); + return NULL; + } + + if (add_record_to_server(&server->src, record) < 0) { + error("Unable to register A2DP service record");\ + sdp_record_free(record); + avdtp_unregister_sep(sep->sep); + g_free(sep); + return NULL; + } + *record_id = record->handle; + +add: + *l = g_slist_append(*l, sep); + + return sep; +} + +void a2dp_remove_sep(struct a2dp_sep *sep) +{ + struct a2dp_server *server = sep->server; + + if (sep->type == AVDTP_SEP_TYPE_SOURCE) + server->sources = g_slist_remove(server->sources, sep); + else + server->sinks = g_slist_remove(server->sinks, sep); + + a2dp_unregister_sep(sep); +} + struct a2dp_sep *a2dp_get(struct avdtp *session, struct avdtp_remote_sep *rsep) { @@ -1347,6 +1542,269 @@ struct a2dp_sep *a2dp_get(struct avdtp *session, return NULL; } +static uint8_t default_bitpool(uint8_t freq, uint8_t mode) +{ + switch (freq) { + case SBC_SAMPLING_FREQ_16000: + case SBC_SAMPLING_FREQ_32000: + return 53; + case SBC_SAMPLING_FREQ_44100: + switch (mode) { + case SBC_CHANNEL_MODE_MONO: + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + case SBC_CHANNEL_MODE_STEREO: + case SBC_CHANNEL_MODE_JOINT_STEREO: + return 53; + default: + error("Invalid channel mode %u", mode); + return 53; + } + case SBC_SAMPLING_FREQ_48000: + switch (mode) { + case SBC_CHANNEL_MODE_MONO: + case SBC_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + case SBC_CHANNEL_MODE_STEREO: + case SBC_CHANNEL_MODE_JOINT_STEREO: + return 51; + default: + error("Invalid channel mode %u", mode); + return 51; + } + default: + error("Invalid sampling freq %u", freq); + return 53; + } +} + +static gboolean select_sbc_params(struct sbc_codec_cap *cap, + struct sbc_codec_cap *supported) +{ + unsigned int max_bitpool, min_bitpool; + + memset(cap, 0, sizeof(struct sbc_codec_cap)); + + cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + cap->cap.media_codec_type = A2DP_CODEC_SBC; + + if (supported->frequency & SBC_SAMPLING_FREQ_44100) + cap->frequency = SBC_SAMPLING_FREQ_44100; + else if (supported->frequency & SBC_SAMPLING_FREQ_48000) + cap->frequency = SBC_SAMPLING_FREQ_48000; + else if (supported->frequency & SBC_SAMPLING_FREQ_32000) + cap->frequency = SBC_SAMPLING_FREQ_32000; + else if (supported->frequency & SBC_SAMPLING_FREQ_16000) + cap->frequency = SBC_SAMPLING_FREQ_16000; + else { + error("No supported frequencies"); + return FALSE; + } + + if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) + cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; + else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO) + cap->channel_mode = SBC_CHANNEL_MODE_STEREO; + else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) + cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL; + else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO) + cap->channel_mode = SBC_CHANNEL_MODE_MONO; + else { + error("No supported channel modes"); + return FALSE; + } + + if (supported->block_length & SBC_BLOCK_LENGTH_16) + cap->block_length = SBC_BLOCK_LENGTH_16; + else if (supported->block_length & SBC_BLOCK_LENGTH_12) + cap->block_length = SBC_BLOCK_LENGTH_12; + else if (supported->block_length & SBC_BLOCK_LENGTH_8) + cap->block_length = SBC_BLOCK_LENGTH_8; + else if (supported->block_length & SBC_BLOCK_LENGTH_4) + cap->block_length = SBC_BLOCK_LENGTH_4; + else { + error("No supported block lengths"); + return FALSE; + } + + if (supported->subbands & SBC_SUBBANDS_8) + cap->subbands = SBC_SUBBANDS_8; + else if (supported->subbands & SBC_SUBBANDS_4) + cap->subbands = SBC_SUBBANDS_4; + else { + error("No supported subbands"); + return FALSE; + } + + if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS) + cap->allocation_method = SBC_ALLOCATION_LOUDNESS; + else if (supported->allocation_method & SBC_ALLOCATION_SNR) + cap->allocation_method = SBC_ALLOCATION_SNR; + + min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool); + max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode), + supported->max_bitpool); + + cap->min_bitpool = min_bitpool; + cap->max_bitpool = max_bitpool; + + return TRUE; +} + +static gboolean select_capabilities(struct avdtp *session, + struct avdtp_remote_sep *rsep, + GSList **caps) +{ + struct avdtp_service_capability *media_transport, *media_codec; + struct sbc_codec_cap sbc_cap; + + media_codec = avdtp_get_codec(rsep); + if (!media_codec) + return FALSE; + + select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data); + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + *caps = g_slist_append(*caps, media_transport); + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, + sizeof(sbc_cap)); + + *caps = g_slist_append(*caps, media_codec); + + if (avdtp_get_delay_reporting(rsep)) { + struct avdtp_service_capability *delay_reporting; + delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING, + NULL, 0); + *caps = g_slist_append(*caps, delay_reporting); + } + + return TRUE; +} + +static void select_cb(struct media_endpoint *endpoint, void *ret, size_t size, + void *user_data) +{ + struct a2dp_setup *setup = user_data; + struct avdtp_service_capability *media_transport, *media_codec; + struct avdtp_media_codec_capability *cap; + GSList *caps = NULL; + + if (size == 0) { + debug("Endpoint replied an invalid configuration"); + goto done; + } + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + caps = g_slist_append(caps, media_transport); + + cap = g_malloc0(sizeof(*cap) + size); + cap->media_type = AVDTP_MEDIA_TYPE_AUDIO; + cap->media_codec_type = setup->sep->codec; + memcpy(cap->data, ret, size); + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, cap, + sizeof(*cap) + size); + + caps = g_slist_append(caps, media_codec); + + if (setup->delay_reporting) { + struct avdtp_service_capability *delay_reporting; + delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING, + NULL, 0); + caps = g_slist_append(caps, delay_reporting); + } + +done: + finalize_select(setup, caps); +} + +static gboolean auto_select(gpointer data) +{ + struct a2dp_setup *setup = data; + + finalize_select(setup, setup->client_caps); + + return FALSE; +} + +unsigned int a2dp_select_capabilities(struct avdtp *session, + uint8_t type, const char *sender, + a2dp_select_cb_t cb, + void *user_data) +{ + struct a2dp_setup *setup; + struct a2dp_setup_cb *cb_data; + struct a2dp_sep *sep; + struct avdtp_local_sep *lsep; + struct avdtp_remote_sep *rsep; + struct avdtp_service_capability *service; + struct avdtp_media_codec_capability *codec; + + if (avdtp_get_seps(session, type, AVDTP_MEDIA_TYPE_AUDIO, + A2DP_CODEC_SBC, &lsep, &rsep) < 0) { + error("No matching ACP and INT SEPs found"); + return 0; + } + + sep = a2dp_get(session, rsep); + if (!sep) { + error("Unable to get a local source SEP"); + return 0; + } + + cb_data = g_new0(struct a2dp_setup_cb, 1); + cb_data->select_cb = cb; + cb_data->user_data = user_data; + cb_data->id = ++cb_id; + + setup = find_setup_by_session(session); + if (!setup) { + setup = g_new0(struct a2dp_setup, 1); + setup->session = avdtp_ref(session); + setup->dev = a2dp_get_dev(session); + setups = g_slist_append(setups, setup); + } + + setup_ref(setup); + setup->cb = g_slist_append(setup->cb, cb_data); + setup->sep = sep; +// setup->delay_reporting = (sep->delay_reporting && +// avdtp_get_delay_reporting(rsep)); + + /* FIXME: Remove auto select when it is not longer possible to register + endpoint in the configuration file */ + if (sep->endpoint == NULL) { + if (!select_capabilities(session, rsep, &setup->client_caps)) { + error("Unable to auto select remote SEP capabilities"); + goto fail; + } + + g_idle_add(auto_select, setup); + + return cb_data->id; + } + + service = avdtp_get_codec(rsep); + codec = (struct avdtp_media_codec_capability *) service->data; + + if (media_endpoint_select_configuration(sep->endpoint, codec->data, + service->length - sizeof(*codec), + select_cb, setup) == + TRUE) + return cb_data->id; + +fail: + setup_unref(setup); + cb_id--; + return 0; + +} + unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep, a2dp_config_cb_t cb, GSList *caps, void *user_data) @@ -1620,7 +2078,7 @@ gboolean a2dp_cancel(struct audio_device *dev, unsigned int id) gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session) { - if (sep->locked) + if (sep->locked || sep->endpoint) return FALSE; debug("SEP %p locked", sep->sep); diff --git a/audio/a2dp.h b/audio/a2dp.h index fa81776..16bac53 100644 --- a/audio/a2dp.h +++ b/audio/a2dp.h @@ -121,6 +121,10 @@ struct mpeg_codec_cap { struct a2dp_sep; + +typedef void (*a2dp_select_cb_t) (struct avdtp *session, + struct a2dp_sep *sep, GSList *caps, + void *user_data); typedef void (*a2dp_config_cb_t) (struct avdtp *session, struct a2dp_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, @@ -132,7 +136,17 @@ typedef void (*a2dp_stream_cb_t) (struct avdtp *session, int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config); void a2dp_unregister(const bdaddr_t *src); +struct a2dp_sep *a2dp_add_sep(const bdaddr_t *src, uint8_t type, + uint8_t codec, gboolean delay_reporting, + struct media_endpoint *endpoint); +void a2dp_remove_sep(struct a2dp_sep *sep); + struct a2dp_sep *a2dp_get(struct avdtp *session, struct avdtp_remote_sep *sep); + +unsigned int a2dp_select_capabilities(struct avdtp *session, + uint8_t type, const char *sender, + a2dp_select_cb_t cb, + void *user_data); unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep, a2dp_config_cb_t cb, GSList *caps, void *user_data); diff --git a/audio/avdtp.c b/audio/avdtp.c index e5622e4..a844cab 100644 --- a/audio/avdtp.c +++ b/audio/avdtp.c @@ -3544,6 +3544,9 @@ int avdtp_unregister_sep(struct avdtp_local_sep *sep) if (sep->stream) release_stream(sep->stream, sep->stream->session); + debug("SEP %p unregistered: type:%d codec:%d seid:%d", sep, + sep->info.type, sep->codec, sep->info.seid); + g_free(sep); return 0; diff --git a/audio/manager.c b/audio/manager.c index 413c1f3..2b16b6c 100644 --- a/audio/manager.c +++ b/audio/manager.c @@ -61,6 +61,7 @@ #include "device.h" #include "error.h" #include "avdtp.h" +#include "media.h" #include "a2dp.h" #include "headset.h" #include "gateway.h" @@ -113,6 +114,7 @@ static struct enabled_interfaces enabled = { .sink = TRUE, .source = FALSE, .control = TRUE, + .media = FALSE }; static struct audio_adapter *find_adapter(GSList *list, @@ -1022,6 +1024,38 @@ static void avrcp_server_remove(struct btd_adapter *adapter) audio_adapter_unref(adp); } +static int media_server_probe(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + bdaddr_t src; + + DBG("path %s", path); + + adp = audio_adapter_get(adapter); + if (!adp) + return -EINVAL; + + adapter_get_address(adapter, &src); + + return media_register(connection, path, &src); +} + +static void media_server_remove(struct btd_adapter *adapter) +{ + struct audio_adapter *adp; + const gchar *path = adapter_get_path(adapter); + + DBG("path %s", path); + + adp = find_adapter(adapters, adapter); + if (!adp) + return; + + media_unregister(path); + audio_adapter_unref(adp); +} + static struct btd_device_driver audio_driver = { .name = "audio", .uuids = BTD_UUIDS(HSP_HS_UUID, HFP_HS_UUID, HSP_AG_UUID, HFP_AG_UUID, @@ -1055,6 +1089,12 @@ static struct btd_adapter_driver avrcp_server_driver = { .remove = avrcp_server_remove, }; +static struct btd_adapter_driver media_server_driver = { + .name = "media", + .probe = media_server_probe, + .remove = media_server_remove, +}; + int audio_manager_init(DBusConnection *conn, GKeyFile *conf, gboolean *enable_sco) { @@ -1083,6 +1123,8 @@ int audio_manager_init(DBusConnection *conn, GKeyFile *conf, enabled.source = TRUE; else if (g_str_equal(list[i], "Control")) enabled.control = TRUE; + else if (g_str_equal(list[i], "Media")) + enabled.media = TRUE; } g_strfreev(list); @@ -1099,6 +1141,8 @@ int audio_manager_init(DBusConnection *conn, GKeyFile *conf, enabled.source = FALSE; else if (g_str_equal(list[i], "Control")) enabled.control = FALSE; + else if (g_str_equal(list[i], "Media")) + enabled.media = FALSE; } g_strfreev(list); @@ -1140,6 +1184,9 @@ proceed: if (enabled.control) btd_register_adapter_driver(&avrcp_server_driver); + if (enabled.media) + btd_register_adapter_driver(&media_server_driver); + btd_register_device_driver(&audio_driver); *enable_sco = (enabled.gateway || enabled.headset); @@ -1175,6 +1222,9 @@ void audio_manager_exit(void) if (enabled.control) btd_unregister_adapter_driver(&avrcp_server_driver); + if (enabled.media) + btd_unregister_adapter_driver(&media_server_driver); + btd_unregister_device_driver(&audio_driver); } diff --git a/audio/manager.h b/audio/manager.h index 8e1abf4..bee2a9b 100644 --- a/audio/manager.h +++ b/audio/manager.h @@ -29,6 +29,7 @@ struct enabled_interfaces { gboolean sink; gboolean source; gboolean control; + gboolean media; }; int audio_manager_init(DBusConnection *conn, GKeyFile *config, diff --git a/audio/media.c b/audio/media.c new file mode 100644 index 0000000..f24d354 --- /dev/null +++ b/audio/media.c @@ -0,0 +1,778 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann <marcel@xxxxxxxxxxxx> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> + +#include <glib.h> +#include <gdbus.h> + +#include "../src/adapter.h" +#include "../src/dbus-common.h" + +#include "logging.h" +#include "error.h" +#include "device.h" +#include "avdtp.h" +#include "media.h" +#include "a2dp.h" + +#define MEDIA_INTERFACE "org.bluez.Media" +#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint" +#define MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport" + +#define REQUEST_TIMEOUT (3 * 1000) /* 3 seconds */ + +struct media_adapter { + bdaddr_t src; /* Adapter address */ + char *path; /* Adapter path */ + DBusConnection *conn; /* Adapter connection */ + GSList *endpoints; /* Endpoints list */ +}; + +struct endpoint_request { + DBusMessage *msg; + DBusPendingCall *call; + media_endpoint_cb_t cb; + void *user_data; +}; + +struct media_endpoint { + struct a2dp_sep *sep; + char *sender; /* Endpoint DBus bus id */ + char *path; /* Endpoint object path */ + char *uuid; /* Endpoint property UUID */ + uint8_t codec; /* Endpoint codec */ + uint8_t *capabilities; /* Endpoint property capabilities */ + size_t size; /* Endpoint capabilities size */ + guint watch; + struct endpoint_request *request; + struct media_transport *transport; + struct media_adapter *adapter; +}; + +struct media_transport { + char *path; /* Transport object path */ + struct audio_device *device; /* Transport device */ + int fd; /* Transport file descriptor */ + uint16_t delay; /* Transport delay (a2dp only) */ + gboolean nrec; /* Transport nrec (hfp only) */ + gboolean read_lock; + gboolean write_lock; +}; + +static GSList *adapters = NULL; + +static void media_endpoint_remove(struct media_endpoint *endpoint) +{ + struct media_adapter *adapter = endpoint->adapter; + + info("Endpoint unregistered: sender=%s path=%s", endpoint->sender, + endpoint->path); + + adapter->endpoints = g_slist_remove(adapter->endpoints, endpoint); + + if (endpoint->sep) + a2dp_remove_sep(endpoint->sep); + + if (endpoint->transport) { + g_free(endpoint->transport->path); + g_free(endpoint->transport); + } + + g_dbus_remove_watch(adapter->conn, endpoint->watch); + g_free(endpoint->capabilities); + g_free(endpoint->sender); + g_free(endpoint->path); + g_free(endpoint->uuid); + g_free(endpoint); +} + +static void media_endpoint_exit(DBusConnection *connection, void *user_data) +{ + struct media_endpoint *endpoint = user_data; + + endpoint->watch = 0; + media_endpoint_remove(endpoint); +} + +static struct media_endpoint *media_endpoint_create(struct media_adapter *adapter, + const char *sender, + const char *path, + const char *uuid, + gboolean delay_reporting, + uint8_t codec, + uint8_t *capabilities, + size_t size) +{ + struct media_endpoint *endpoint; + + endpoint = g_new0(struct media_endpoint, 1); + endpoint->sender = g_strdup(sender); + endpoint->path = g_strdup(path); + endpoint->uuid = g_strdup(uuid); + endpoint->codec = codec; + endpoint->capabilities = g_new(uint8_t, size); + memcpy(endpoint->capabilities, capabilities, size); + endpoint->size = size; + endpoint->adapter = adapter; + + if (g_strcmp0(uuid, A2DP_SOURCE_UUID) == 0) + endpoint->sep = a2dp_add_sep(&adapter->src, AVDTP_SEP_TYPE_SOURCE, codec, + delay_reporting, endpoint); + else if (g_strcmp0(uuid, A2DP_SINK_UUID) == 0) + endpoint->sep = a2dp_add_sep(&adapter->src, AVDTP_SEP_TYPE_SINK, codec, + delay_reporting, endpoint); + + if (endpoint->sep == NULL) { + g_free(endpoint); + return NULL; + } + + endpoint->watch = g_dbus_add_disconnect_watch(adapter->conn, sender, + media_endpoint_exit, endpoint, + NULL); + + adapter->endpoints = g_slist_append(adapter->endpoints, endpoint); + info("Endpoint registered: sender=%s path=%s", sender, path); + + return endpoint; +} + +static struct media_endpoint *media_adapter_find_endpoint( + struct media_adapter *adapter, + const char *sender, + const char *path) +{ + GSList *l; + + for (l = adapter->endpoints; l; l = l->next) { + struct media_endpoint *endpoint = l->data; + + if (g_strcmp0(endpoint->sender, sender) != 0) + continue; + + if (g_strcmp0(endpoint->path, path) == 0) + return endpoint; + } + + return NULL; +} + +static int parse_byte_array(DBusMessageIter *iter, uint8_t *value, size_t *length) +{ + DBusMessageIter array; + size_t i; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return -EINVAL; + + dbus_message_iter_recurse(iter, &array); + + for (i = 0; dbus_message_iter_get_arg_type(&array) != + DBUS_TYPE_INVALID; i++) { + + if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_BYTE) + return -EINVAL; + + if (i >= *length) + return -ENOMEM; + + dbus_message_iter_get_basic(&array, &value[i]); + + dbus_message_iter_next(&array); + } + + *length = i; + + return 0; +} + +static int parse_properties(DBusMessageIter *props, const char **uuid, + gboolean *delay_reporting, uint8_t *codec, + uint8_t *capabilities, size_t *size) +{ + while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + if (strcasecmp(key, "UUID") == 0) { + if (var != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(&value, uuid); + } else if (strcasecmp(key, "Codec") == 0) { + if (var != DBUS_TYPE_BYTE) + return -EINVAL; + dbus_message_iter_get_basic(&value, codec); + } else if (strcasecmp(key, "DelayReporting") == 0) { + if (var != DBUS_TYPE_BOOLEAN) + return -EINVAL; + dbus_message_iter_get_basic(&value, delay_reporting); + } else if (strcasecmp(key, "Capabilities") == 0) { + int ret = parse_byte_array(&value, capabilities, size); + + if (ret < 0) + return ret; + } + + dbus_message_iter_next(props); + } + + return 0; +} + +static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_adapter *adapter = data; + DBusMessageIter args, props; + const char *sender, *path, *uuid = NULL; + gboolean delay_reporting; + uint8_t codec; + uint8_t capabilities[128]; + size_t size = sizeof(capabilities); + + sender = dbus_message_get_sender(msg); + + dbus_message_iter_init(msg, &args); + + dbus_message_iter_get_basic(&args, &path); + dbus_message_iter_next(&args); + + if (media_adapter_find_endpoint(adapter, sender, path) != NULL) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Endpoint already registered"); + + dbus_message_iter_recurse(&args, &props); + if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".Failed", "Invalid argument"); + + if (parse_properties(&props, &uuid, &delay_reporting, &codec, + capabilities, &size) || uuid == NULL) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Invalid argument"); + + media_endpoint_create(adapter, sender, path, uuid, delay_reporting, + codec, capabilities, size); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *unregister_endpoint(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_adapter *adapter = data; + struct media_endpoint *endpoint; + const char *sender, *path; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return NULL; + + sender = dbus_message_get_sender(msg); + + endpoint = media_adapter_find_endpoint(adapter, sender, path); + if (endpoint == NULL) + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Endpoint not registered"); + + media_endpoint_remove(endpoint); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static GDBusMethodTable media_methods[] = { + { "RegisterEndpoint", "oa{sv}", "", register_endpoint }, + { "UnregisterEndpoint", "o", "", unregister_endpoint }, + { }, +}; + +static void path_free(void *data) +{ + struct media_adapter *adapter = data; + + dbus_connection_unref(adapter->conn); + + g_free(adapter->path); + g_free(adapter); +} + +int media_register(DBusConnection *conn, const char *path, const bdaddr_t *src) +{ + struct media_adapter *adapter; + + adapter = g_new0(struct media_adapter, 1); + adapter->conn = dbus_connection_ref(conn); + bacpy(&adapter->src, src); + adapter->path = g_strdup(path); + + if (!g_dbus_register_interface(conn, path, MEDIA_INTERFACE, + media_methods, NULL, NULL, + adapter, path_free)) { + error("D-Bus failed to register %s path", path); + path_free(adapter); + return -1; + } + + return 0; +} + +void media_unregister(const char *path) +{ + GSList *l; + + for (l = adapters; l; l = l->next) { + struct media_adapter *adapter = l->data; + + if (g_strcmp0(path, adapter->path) == 0) { + g_dbus_unregister_interface(adapter->conn, path, + MEDIA_INTERFACE); + return; + } + } +} + +static DBusMessage *get_properties(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_transport *transport = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + /* Device */ + dict_append_entry(&dict, "Device", DBUS_TYPE_OBJECT_PATH, + &transport->device->path); + + /* Delay (a2dp only) */ + if (transport->delay > 0) + dict_append_entry(&dict, "Delay", DBUS_TYPE_UINT16, + &transport->delay); + + /* NREC (hfp only) */ + if (transport->nrec) + dict_append_entry(&dict, "NREC", DBUS_TYPE_BOOLEAN, + &transport->nrec); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static DBusMessage *acquire(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_transport *transport = data; + const char *accesstype; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &accesstype, + DBUS_TYPE_INVALID)) + return NULL; + + if (g_strstr_len(accesstype, -1, "r") != NULL) { + if (transport->read_lock == TRUE) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".Failed", + "Permission denied"); + transport->read_lock = TRUE; + } + + if (g_strstr_len(accesstype, -1, "w") != NULL) { + if (transport->write_lock == TRUE) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".Failed", + "Permission denied"); + transport->write_lock = TRUE; + } + + return g_dbus_create_reply(msg, DBUS_TYPE_UNIX_FD, transport->fd, + DBUS_TYPE_INVALID); +} + +static DBusMessage *release(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_transport *transport = data; + const char *accesstype; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &accesstype, + DBUS_TYPE_INVALID)) + return NULL; + + if (g_strstr_len(accesstype, -1, "r") != NULL) { + if (transport->read_lock == FALSE) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".Failed", + "Permission denied"); + transport->read_lock = FALSE; + } + + if (g_strstr_len(accesstype, -1, "w") != NULL) { + if (transport->write_lock == FALSE) + return g_dbus_create_error(msg, ERROR_INTERFACE + ".Failed", + "Permission denied"); + transport->write_lock = FALSE; + } + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *set_property(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + return NULL; +} + +static GDBusMethodTable transport_methods[] = { + { "GetProperties", "", "a{sv}", get_properties }, + { "Acquire", "s", "u", acquire }, + { "Release", "s", "", release }, + { "SetProperty", "sv", "", set_property }, + { }, +}; + +static GDBusSignalTable transport_signals[] = { + { "PropertyChanged", "sv" }, + { } +}; + +static void media_transport_free(void *data) +{ + struct media_transport *transport = data; + + g_free(transport); +} + +static void media_transport_remove(DBusConnection *conn, + struct media_transport *transport) +{ + g_dbus_unregister_interface(conn, transport->path, + MEDIA_TRANSPORT_INTERFACE); +} + +static struct media_transport *media_transport_create(DBusConnection *conn, + struct audio_device *device) +{ + struct media_transport *transport; + + static int fd = 0; + + transport = g_new0(struct media_transport, 1); + transport->device = device; + transport->path = g_strdup_printf("%s/fd%d", device->path, fd++); + transport->fd = -1; + + if (g_dbus_register_interface(conn, transport->path, + MEDIA_TRANSPORT_INTERFACE, + transport_methods, transport_signals, NULL, + transport, media_transport_free) == FALSE) { + error("Could not register transport %s", transport->path); + g_free(transport); + return NULL; + } + + return transport; +} + +static void append_byte_array(DBusMessageIter *iter, uint8_t *val, size_t size) +{ + DBusMessageIter value; + char sig[2] = { DBUS_TYPE_BYTE, '\0' }; + size_t i; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, sig, &value); + + for (i = 0; i < size; i++) + dbus_message_iter_append_basic(&value, DBUS_TYPE_BYTE, &val[i]); + + dbus_message_iter_close_container(iter, &value); +} + +size_t media_endpoint_get_capabilities(struct media_endpoint *endpoint, + uint8_t **capabilities) +{ + *capabilities = endpoint->capabilities; + return endpoint->size; +} + +static void endpoint_request_free(struct endpoint_request *request) +{ + if (request->call) + dbus_pending_call_cancel(request->call); + + dbus_message_unref(request->msg); + g_free(request); +} + +static void endpoint_reply(DBusPendingCall *call, void *user_data) +{ + struct media_endpoint *endpoint = user_data; + struct endpoint_request *request = endpoint->request; + DBusMessage *reply; + DBusError err; + gboolean value; + void *ret = NULL; + size_t i = 0; + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("Endpoint replied with an error: %s", + err.name); + + if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) { + media_endpoint_clear_configuration(endpoint); + dbus_message_unref(reply); + dbus_error_free(&err); + return; + } + + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (dbus_message_is_method_call(request->msg, MEDIA_ENDPOINT_INTERFACE, + "SelectConfiguration")) { + DBusMessageIter args; + uint8_t configuration[128]; + int perr; + + dbus_message_iter_init(reply, &args); + + i = sizeof(configuration); + perr = parse_byte_array(&args, configuration, &i); + if (perr < 0) { + error("Error parsing configuration: %s", strerror(-perr)); + i = 0; + goto done; + } + + ret = configuration; + goto done; + } else if (!dbus_message_get_args(reply, &err, DBUS_TYPE_INVALID)) { + error("Wrong reply signature: %s", err.message); + dbus_error_free(&err); + goto done; + } + + i = 1; + value = TRUE; + ret = &value; + +done: + dbus_message_unref(reply); + + request->cb(endpoint, ret, i, request->user_data); + + endpoint_request_free(request); + endpoint->request = NULL; +} + +static gboolean media_endpoint_async_call(DBusConnection *conn, + DBusMessage *msg, + struct media_endpoint *endpoint, + media_endpoint_cb_t cb, + void *user_data) +{ + struct endpoint_request *request; + + if (endpoint->request) + return FALSE; + + request = g_new0(struct endpoint_request, 1); + + /* Timeout should be less than avdtp request timeout (4 seconds) */ + if (dbus_connection_send_with_reply(conn, msg, &request->call, + REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + g_free(request); + return FALSE; + } + + dbus_pending_call_set_notify(request->call, endpoint_reply, endpoint, NULL); + + request->msg = msg; + request->cb = cb; + request->user_data = user_data; + endpoint->request = request; + + debug("Calling %s: name = %s path = %s", dbus_message_get_member(msg), + dbus_message_get_destination(msg), + dbus_message_get_path(msg)); + + return TRUE; +} + +gboolean media_endpoint_set_configuration(struct media_endpoint *endpoint, + struct audio_device *device, + uint8_t *configuration, size_t size, + media_endpoint_cb_t cb, + void *user_data) +{ + DBusConnection *conn; + DBusMessage *msg, *reply; + DBusMessageIter iter; + DBusError err; + + if (endpoint->transport != NULL || endpoint->request != NULL) + return FALSE; + + conn = endpoint->adapter->conn; + + endpoint->transport = media_transport_create(conn, device); + if (endpoint->transport == NULL) + return FALSE; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "SetConfiguration"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return FALSE; + } + + dbus_message_iter_init_append(msg, &iter); + + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, + &endpoint->transport->path); + + append_byte_array(&iter, configuration, size); + + if (cb != NULL) + return media_endpoint_async_call(conn, msg, endpoint, cb, user_data); + + dbus_error_init(&err); + + /* FIXME: remove once we can reply setconf asyncronously */ + reply = dbus_connection_send_with_reply_and_block(conn, msg, + REQUEST_TIMEOUT, &err); + + dbus_message_unref(msg); + + if (reply) { + dbus_message_unref(reply); + return TRUE; + } + + if (dbus_error_is_set(&err)) { + error("Endpoint replied with an error: %s", err.name); + + if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) + media_endpoint_clear_configuration(endpoint); + + dbus_error_free(&err); + } + + return FALSE; +} + +gboolean media_endpoint_select_configuration(struct media_endpoint *endpoint, + uint8_t *capabilities, + size_t length, + media_endpoint_cb_t cb, + void *user_data) +{ + DBusConnection *conn; + DBusMessage *msg; + DBusMessageIter iter; + + if (endpoint->request != NULL) + return FALSE; + + conn = endpoint->adapter->conn; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "SelectConfiguration"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return FALSE; + } + + dbus_message_iter_init_append(msg, &iter); + + append_byte_array(&iter, capabilities, length); + + return media_endpoint_async_call(conn, msg, endpoint, cb, user_data); +} + +void media_endpoint_clear_configuration(struct media_endpoint *endpoint) +{ + DBusConnection *conn; + DBusMessage *msg; + + if (endpoint->transport == NULL) + return; + + if (endpoint->request) { + endpoint_request_free(endpoint->request); + endpoint->request = NULL; + } + + conn = endpoint->adapter->conn; + + media_transport_remove(conn, endpoint->transport); + endpoint->transport = NULL; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "ClearConfiguration"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return; + } + + g_dbus_send_message(conn, msg); +} diff --git a/audio/media.h b/audio/media.h new file mode 100644 index 0000000..b54a7de --- /dev/null +++ b/audio/media.h @@ -0,0 +1,45 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann <marcel@xxxxxxxxxxxx> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct media_endpoint; + +typedef void (*media_endpoint_cb_t) (struct media_endpoint *endpoint, + void *ret, size_t size, void *user_data); + +int media_register(DBusConnection *conn, const char *path, const bdaddr_t *src); +void media_unregister(const char *path); + +size_t media_endpoint_get_capabilities(struct media_endpoint *endpoint, + uint8_t **capabilities); +gboolean media_endpoint_set_configuration(struct media_endpoint *endpoint, + struct audio_device *device, + uint8_t *configuration, size_t size, + media_endpoint_cb_t cb, + void *user_data); +gboolean media_endpoint_select_configuration(struct media_endpoint *endpoint, + uint8_t *capabilities, + size_t length, + media_endpoint_cb_t cb, + void *user_data); +void media_endpoint_clear_configuration(struct media_endpoint *endpoint); diff --git a/audio/sink.c b/audio/sink.c index 3a8eb23..dc3994e 100644 --- a/audio/sink.c +++ b/audio/sink.c @@ -40,6 +40,7 @@ #include "device.h" #include "avdtp.h" +#include "media.h" #include "a2dp.h" #include "error.h" #include "sink.h" @@ -343,146 +344,29 @@ static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep, } } -static uint8_t default_bitpool(uint8_t freq, uint8_t mode) +static void select_complete(struct avdtp *session, struct a2dp_sep *sep, + GSList *caps, void *user_data) { - switch (freq) { - case SBC_SAMPLING_FREQ_16000: - case SBC_SAMPLING_FREQ_32000: - return 53; - case SBC_SAMPLING_FREQ_44100: - switch (mode) { - case SBC_CHANNEL_MODE_MONO: - case SBC_CHANNEL_MODE_DUAL_CHANNEL: - return 31; - case SBC_CHANNEL_MODE_STEREO: - case SBC_CHANNEL_MODE_JOINT_STEREO: - return 53; - default: - error("Invalid channel mode %u", mode); - return 53; - } - case SBC_SAMPLING_FREQ_48000: - switch (mode) { - case SBC_CHANNEL_MODE_MONO: - case SBC_CHANNEL_MODE_DUAL_CHANNEL: - return 29; - case SBC_CHANNEL_MODE_STEREO: - case SBC_CHANNEL_MODE_JOINT_STEREO: - return 51; - default: - error("Invalid channel mode %u", mode); - return 51; - } - default: - error("Invalid sampling freq %u", freq); - return 53; - } -} - -static gboolean select_sbc_params(struct sbc_codec_cap *cap, - struct sbc_codec_cap *supported) -{ - unsigned int max_bitpool, min_bitpool; - - memset(cap, 0, sizeof(struct sbc_codec_cap)); - - cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; - cap->cap.media_codec_type = A2DP_CODEC_SBC; - - if (supported->frequency & SBC_SAMPLING_FREQ_44100) - cap->frequency = SBC_SAMPLING_FREQ_44100; - else if (supported->frequency & SBC_SAMPLING_FREQ_48000) - cap->frequency = SBC_SAMPLING_FREQ_48000; - else if (supported->frequency & SBC_SAMPLING_FREQ_32000) - cap->frequency = SBC_SAMPLING_FREQ_32000; - else if (supported->frequency & SBC_SAMPLING_FREQ_16000) - cap->frequency = SBC_SAMPLING_FREQ_16000; - else { - error("No supported frequencies"); - return FALSE; - } - - if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) - cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; - else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO) - cap->channel_mode = SBC_CHANNEL_MODE_STEREO; - else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) - cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL; - else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO) - cap->channel_mode = SBC_CHANNEL_MODE_MONO; - else { - error("No supported channel modes"); - return FALSE; - } - - if (supported->block_length & SBC_BLOCK_LENGTH_16) - cap->block_length = SBC_BLOCK_LENGTH_16; - else if (supported->block_length & SBC_BLOCK_LENGTH_12) - cap->block_length = SBC_BLOCK_LENGTH_12; - else if (supported->block_length & SBC_BLOCK_LENGTH_8) - cap->block_length = SBC_BLOCK_LENGTH_8; - else if (supported->block_length & SBC_BLOCK_LENGTH_4) - cap->block_length = SBC_BLOCK_LENGTH_4; - else { - error("No supported block lengths"); - return FALSE; - } - - if (supported->subbands & SBC_SUBBANDS_8) - cap->subbands = SBC_SUBBANDS_8; - else if (supported->subbands & SBC_SUBBANDS_4) - cap->subbands = SBC_SUBBANDS_4; - else { - error("No supported subbands"); - return FALSE; - } - - if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS) - cap->allocation_method = SBC_ALLOCATION_LOUDNESS; - else if (supported->allocation_method & SBC_ALLOCATION_SNR) - cap->allocation_method = SBC_ALLOCATION_SNR; - - min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool); - max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode), - supported->max_bitpool); - - cap->min_bitpool = min_bitpool; - cap->max_bitpool = max_bitpool; - - return TRUE; -} - -static gboolean select_capabilities(struct avdtp *session, - struct avdtp_remote_sep *rsep, - GSList **caps) -{ - struct avdtp_service_capability *media_transport, *media_codec; - struct sbc_codec_cap sbc_cap; - - media_codec = avdtp_get_codec(rsep); - if (!media_codec) - return FALSE; - - select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data); - - media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, - NULL, 0); - - *caps = g_slist_append(*caps, media_transport); + struct sink *sink = user_data; + struct pending_request *pending; + int id; - media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, - sizeof(sbc_cap)); + pending = sink->connect; - *caps = g_slist_append(*caps, media_codec); + id = a2dp_config(session, sep, stream_setup_complete, caps, sink); + if (id == 0) + goto failed; - if (avdtp_get_delay_reporting(rsep)) { - struct avdtp_service_capability *delay_reporting; - delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING, - NULL, 0); - *caps = g_slist_append(*caps, delay_reporting); - } + pending->id = id; + return; - return TRUE; +failed: + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + pending_request_free(sink->dev, pending); + sink->connect = NULL; + avdtp_unref(sink->session); + sink->session = NULL; } static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err, @@ -490,10 +374,6 @@ static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp { struct sink *sink = user_data; struct pending_request *pending; - struct avdtp_local_sep *lsep; - struct avdtp_remote_sep *rsep; - struct a2dp_sep *sep; - GSList *caps = NULL; int id; pending = sink->connect; @@ -515,24 +395,8 @@ static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp debug("Discovery complete"); - if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SINK, AVDTP_MEDIA_TYPE_AUDIO, - A2DP_CODEC_SBC, &lsep, &rsep) < 0) { - error("No matching ACP and INT SEPs found"); - goto failed; - } - - if (!select_capabilities(session, rsep, &caps)) { - error("Unable to select remote SEP capabilities"); - goto failed; - } - - sep = a2dp_get(session, rsep); - if (!sep) { - error("Unable to get a local source SEP"); - goto failed; - } - - id = a2dp_config(sink->session, sep, stream_setup_complete, caps, sink); + id = a2dp_select_capabilities(sink->session, AVDTP_SEP_TYPE_SINK, NULL, + select_complete, sink); if (id == 0) goto failed; diff --git a/audio/source.c b/audio/source.c index 1530c34..d9f62f4 100644 --- a/audio/source.c +++ b/audio/source.c @@ -41,6 +41,7 @@ #include "device.h" #include "avdtp.h" +#include "media.h" #include "a2dp.h" #include "error.h" #include "source.h" @@ -310,140 +311,34 @@ static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep, } } -static uint8_t default_bitpool(uint8_t freq, uint8_t mode) +static void select_complete(struct avdtp *session, struct a2dp_sep *sep, + GSList *caps, void *user_data) { - switch (freq) { - case SBC_SAMPLING_FREQ_16000: - case SBC_SAMPLING_FREQ_32000: - return 53; - case SBC_SAMPLING_FREQ_44100: - switch (mode) { - case SBC_CHANNEL_MODE_MONO: - case SBC_CHANNEL_MODE_DUAL_CHANNEL: - return 31; - case SBC_CHANNEL_MODE_STEREO: - case SBC_CHANNEL_MODE_JOINT_STEREO: - return 53; - default: - error("Invalid channel mode %u", mode); - return 53; - } - case SBC_SAMPLING_FREQ_48000: - switch (mode) { - case SBC_CHANNEL_MODE_MONO: - case SBC_CHANNEL_MODE_DUAL_CHANNEL: - return 29; - case SBC_CHANNEL_MODE_STEREO: - case SBC_CHANNEL_MODE_JOINT_STEREO: - return 51; - default: - error("Invalid channel mode %u", mode); - return 51; - } - default: - error("Invalid sampling freq %u", freq); - return 53; - } -} - -static gboolean select_sbc_params(struct sbc_codec_cap *cap, - struct sbc_codec_cap *supported) -{ - unsigned int max_bitpool, min_bitpool; - - memset(cap, 0, sizeof(struct sbc_codec_cap)); - - cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; - cap->cap.media_codec_type = A2DP_CODEC_SBC; - - if (supported->frequency & SBC_SAMPLING_FREQ_44100) - cap->frequency = SBC_SAMPLING_FREQ_44100; - else if (supported->frequency & SBC_SAMPLING_FREQ_48000) - cap->frequency = SBC_SAMPLING_FREQ_48000; - else if (supported->frequency & SBC_SAMPLING_FREQ_32000) - cap->frequency = SBC_SAMPLING_FREQ_32000; - else if (supported->frequency & SBC_SAMPLING_FREQ_16000) - cap->frequency = SBC_SAMPLING_FREQ_16000; - else { - error("No supported frequencies"); - return FALSE; - } - - if (supported->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) - cap->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; - else if (supported->channel_mode & SBC_CHANNEL_MODE_STEREO) - cap->channel_mode = SBC_CHANNEL_MODE_STEREO; - else if (supported->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) - cap->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL; - else if (supported->channel_mode & SBC_CHANNEL_MODE_MONO) - cap->channel_mode = SBC_CHANNEL_MODE_MONO; - else { - error("No supported channel modes"); - return FALSE; - } - - if (supported->block_length & SBC_BLOCK_LENGTH_16) - cap->block_length = SBC_BLOCK_LENGTH_16; - else if (supported->block_length & SBC_BLOCK_LENGTH_12) - cap->block_length = SBC_BLOCK_LENGTH_12; - else if (supported->block_length & SBC_BLOCK_LENGTH_8) - cap->block_length = SBC_BLOCK_LENGTH_8; - else if (supported->block_length & SBC_BLOCK_LENGTH_4) - cap->block_length = SBC_BLOCK_LENGTH_4; - else { - error("No supported block lengths"); - return FALSE; - } - - if (supported->subbands & SBC_SUBBANDS_8) - cap->subbands = SBC_SUBBANDS_8; - else if (supported->subbands & SBC_SUBBANDS_4) - cap->subbands = SBC_SUBBANDS_4; - else { - error("No supported subbands"); - return FALSE; - } - - if (supported->allocation_method & SBC_ALLOCATION_LOUDNESS) - cap->allocation_method = SBC_ALLOCATION_LOUDNESS; - else if (supported->allocation_method & SBC_ALLOCATION_SNR) - cap->allocation_method = SBC_ALLOCATION_SNR; - - min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool); - max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode), - supported->max_bitpool); - - cap->min_bitpool = min_bitpool; - cap->max_bitpool = max_bitpool; - - return TRUE; -} - -static gboolean select_capabilities(struct avdtp *session, - struct avdtp_remote_sep *rsep, - GSList **caps) -{ - struct avdtp_service_capability *media_transport, *media_codec; - struct sbc_codec_cap sbc_cap; - - media_codec = avdtp_get_codec(rsep); - if (!media_codec) - return FALSE; - - select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data); + struct source *source = user_data; + struct pending_request *pending; + int id; - media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, - NULL, 0); + pending = source->connect; - *caps = g_slist_append(*caps, media_transport); + pending->id = 0; - media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, - sizeof(sbc_cap)); + if (caps == NULL) + goto failed; - *caps = g_slist_append(*caps, media_codec); + id = a2dp_config(session, sep, stream_setup_complete, caps, source); + if (id == 0) + goto failed; + pending->id = id; + return; - return TRUE; +failed: + if (pending->msg) + error_failed(pending->conn, pending->msg, "Stream setup failed"); + pending_request_free(source->dev, pending); + source->connect = NULL; + avdtp_unref(source->session); + source->session = NULL; } static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err, @@ -451,10 +346,6 @@ static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp { struct source *source = user_data; struct pending_request *pending; - struct avdtp_local_sep *lsep; - struct avdtp_remote_sep *rsep; - struct a2dp_sep *sep; - GSList *caps = NULL; int id; pending = source->connect; @@ -476,25 +367,8 @@ static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp debug("Discovery complete"); - if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SOURCE, AVDTP_MEDIA_TYPE_AUDIO, - A2DP_CODEC_SBC, &lsep, &rsep) < 0) { - error("No matching ACP and INT SEPs found"); - goto failed; - } - - if (!select_capabilities(session, rsep, &caps)) { - error("Unable to select remote SEP capabilities"); - goto failed; - } - - sep = a2dp_get(session, rsep); - if (!sep) { - error("Unable to get a local sink SEP"); - goto failed; - } - - id = a2dp_config(source->session, sep, stream_setup_complete, caps, - source); + id = a2dp_select_capabilities(source->session, AVDTP_SEP_TYPE_SOURCE, NULL, + select_complete, source); if (id == 0) goto failed; diff --git a/audio/unix.c b/audio/unix.c index 5cf4f94..4113a56 100644 --- a/audio/unix.c +++ b/audio/unix.c @@ -44,6 +44,7 @@ #include "device.h" #include "manager.h" #include "avdtp.h" +#include "media.h" #include "a2dp.h" #include "headset.h" #include "sink.h" -- 1.6.3.3