* Re: [RFC] Media API
2010-01-04 23:39 ` Marcel Holtmann
@ 2010-01-28 13:54 ` Luiz Augusto von Dentz
2010-01-28 14:15 ` Marcel Holtmann
0 siblings, 1 reply; 4+ messages in thread
From: Luiz Augusto von Dentz @ 2010-01-28 13:54 UTC (permalink / raw)
To: Marcel Holtmann; +Cc: linux-bluetooth
[-- Attachment #1: Type: text/plain, Size: 2513 bytes --]
Hi,
On Tue, Jan 5, 2010 at 1:39 AM, Marcel Holtmann <marcel@holtmann.org> 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
[-- Attachment #2: 0001-Add-media-API-documentation.patch --]
[-- Type: text/x-patch, Size: 3821 bytes --]
From 4759a880a30691a7ae9081154e2e06ac7eaf94be Mon Sep 17 00:00:00 2001
From: Luiz Augusto Von Dentz <luiz.dentz-von@nokia.com>
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
[-- Attachment #3: 0002-Add-simple-endpoint-test-script.patch --]
[-- Type: text/x-patch, Size: 4041 bytes --]
From 3577ee604187c758180bcea6ecd404fc8f4c357e Mon Sep 17 00:00:00 2001
From: Luiz Augusto Von Dentz <luiz.dentz-von@nokia.com>
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
[-- Attachment #4: 0003-Add-rule-to-enabling-talking-to-org.bluez.MediaEndpo.patch --]
[-- Type: text/x-patch, Size: 711 bytes --]
From e372552bd220b4edac601c278e75d5c2f54fe5dc Mon Sep 17 00:00:00 2001
From: Luiz Augusto Von Dentz <luiz.dentz-von@nokia.com>
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
[-- Attachment #5: 0004-Add-initial-implementation-of-org.bluez.Media-spec.patch --]
[-- Type: text/x-patch, Size: 59990 bytes --]
From 3f2adc271f9da153518b301d3da6e47d178818e0 Mon Sep 17 00:00:00 2001
From: Luiz Augusto Von Dentz <luiz.dentz-von@nokia.com>
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@holtmann.org>
+ *
+ *
+ * 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@holtmann.org>
+ *
+ *
+ * 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
^ permalink raw reply related [flat|nested] 4+ messages in thread