* [RFC] Media API
@ 2010-01-04 14:14 Luiz Augusto von Dentz
2010-01-04 23:39 ` Marcel Holtmann
0 siblings, 1 reply; 4+ messages in thread
From: Luiz Augusto von Dentz @ 2010-01-04 14:14 UTC (permalink / raw)
To: linux-bluetooth
[-- Attachment #1: Type: text/plain, Size: 1571 bytes --]
Hi,
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.
3. There is no representation of remote end points, so end points in
this spec always refer to local end points. This is to minimize object
creation, which adds more round trips not necessary with the current
design as capabilities is now fully available to bluetoothd.
4. Stream object is the only new object which is created by
bluetoothd, this is necessary so end point process can pull the file
descriptor from bluetoothd. Note that Acquire/Release should works
differently depending on the profile, for A2DP it actually
resume/start the stream on Acquire and suspend/stop the stream on
Release while for HFP it should open sco socket on Acquire and closes
it on Release.
5. Stream.Acquire will return an error if the
StreamEndPoint.SetConfiguration has not returned yet, this is to avoid
any chance of a process block waiting the Acquire reply while
SetConfigure is in place.
--
Luiz Augusto von Dentz
Engenheiro de Computação
[-- Attachment #2: 0002-Add-media-API.patch --]
[-- Type: text/x-patch, Size: 2710 bytes --]
From 600dcbc158d1ed2f9540c9b835d9c08789c2324c Mon Sep 17 00:00:00 2001
From: Luiz Augusto Von Dentz <luiz.dentz-von@nokia.com>
Date: Mon, 4 Jan 2010 14:51:28 +0200
Subject: [PATCH 2/2] Add media API
Media API is a replacement for the internal audio IPC which is no longer
necessary as DBUS 1.4 and newer are capable of tranfering file descriptors.
---
doc/audio-api.txt | 16 +++++++++++++++
doc/media-api.txt | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 71 insertions(+), 0 deletions(-)
create mode 100644 doc/media-api.txt
diff --git a/doc/audio-api.txt b/doc/audio-api.txt
index 1f09cd5..59efdc8 100644
--- a/doc/audio-api.txt
+++ b/doc/audio-api.txt
@@ -456,3 +456,19 @@ properties boolean Connected [readonly]
uint16 MicrophoneGain [readonly]
The speaker gain when available.
+
+Stream hierarchy
+================
+
+Service org.bluez
+Interface org.bluez.Stream
+Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/streamX
+
+Methods int Acquire(object endpoint)
+
+ Acquire stream file descriptor using end point type
+ property as access type.
+
+ void Release(object endpoint)
+
+ Releases file descriptor
diff --git a/doc/media-api.txt b/doc/media-api.txt
new file mode 100644
index 0000000..9818cda
--- /dev/null
+++ b/doc/media-api.txt
@@ -0,0 +1,55 @@
+BlueZ D-Bus Media API description
+*********************************
+
+Media hierarchy
+===============
+
+Service org.bluez
+Interface org.bluez.Media
+Object path [variable prefix]/{hci0,hci1,...}
+
+Methods void RegisterStreamEndPoint(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.
+
+ void UnregisterStreamEndPoint(object endpoint)
+
+ Unregister sender end point
+
+StreamEndPoint hierarchy
+========================
+
+Service unique name
+Interface org.bluez.StreamEndPoint
+Object path freely definable
+
+Methods dict GetProperties()
+
+ Returns all properties for the interface. See the
+ properties section for available properties.
+
+ void SetConfiguration(object stream, array{bytes} configuration)
+
+ Set configuration for the stream
+
+ void ClearConfiguration()
+
+ Clear any configuration set.
+
+Properties string Media [readonly]
+
+ Possible values: "audio" or "video"
+
+ string Type [readonly]
+
+ Possible values: "source", "sink" or "?"
+
+ array{bytes} Capabilities [readonly]
+
+ Capabilities blob as in avdtp spec. This is copied as
+ it is to avdtp GET_CAPABILITIES command, so the size
+ and byte order must match.
--
1.6.3.3
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [RFC] Media API
2010-01-04 14:14 [RFC] Media API Luiz Augusto von Dentz
@ 2010-01-04 23:39 ` Marcel Holtmann
2010-01-28 13:54 ` Luiz Augusto von Dentz
0 siblings, 1 reply; 4+ messages in thread
From: Marcel Holtmann @ 2010-01-04 23:39 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: linux-bluetooth
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?
Regards
Marcel
^ permalink raw reply [flat|nested] 4+ messages in thread
* 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
* Re: [RFC] Media API
2010-01-28 13:54 ` Luiz Augusto von Dentz
@ 2010-01-28 14:15 ` Marcel Holtmann
0 siblings, 0 replies; 4+ messages in thread
From: Marcel Holtmann @ 2010-01-28 14:15 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: linux-bluetooth
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:
lets do it the same way we do within oFono for HFP.
#ifndef DBUS_TYPE_UNIX_FD
#define DBUS_TYPE_UNIX_FD -1
#endif
if (media.enabled == TRUE) {
if (DBUS_TYPE_UNIX_FD < 0)
error("missing fd support");
else
register_driver...
}
Regards
Marcel
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2010-01-28 14:15 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-01-04 14:14 [RFC] Media API Luiz Augusto von Dentz
2010-01-04 23:39 ` Marcel Holtmann
2010-01-28 13:54 ` Luiz Augusto von Dentz
2010-01-28 14:15 ` Marcel Holtmann
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).