summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohan Hedberg <johan.hedberg@nokia.com>2009-10-06 15:41:53 +0200
committerJohan Hedberg <johan.hedberg@nokia.com>2009-10-06 19:18:53 +0200
commit5f8a274881a823ea51df7a62ca17948b1f4ebe63 (patch)
treeed2a71b1df7675fe2b14f68560941216f74ab27d
parent9c39d108d8c1057ad29e83a4db0f4a396772987f (diff)
Add support for AVDTP 1.3 Delay Reporting
This patch adds support for the new Delay Report command in AVDTP 1.3.
-rw-r--r--audio/a2dp.c124
-rw-r--r--audio/avdtp.c289
-rw-r--r--audio/avdtp.h16
-rw-r--r--audio/ipc.h11
-rw-r--r--audio/sink.c6
-rw-r--r--audio/unix.c77
-rw-r--r--audio/unix.h2
7 files changed, 466 insertions, 59 deletions
diff --git a/audio/a2dp.c b/audio/a2dp.c
index 25dd5db76..9b8816453 100644
--- a/audio/a2dp.c
+++ b/audio/a2dp.c
@@ -42,6 +42,7 @@
#include "avdtp.h"
#include "sink.h"
#include "source.h"
+#include "unix.h"
#include "a2dp.h"
#include "sdpd.h"
@@ -65,6 +66,7 @@ struct a2dp_sep {
struct avdtp *session;
struct avdtp_stream *stream;
guint suspend_timer;
+ gboolean delay_reporting;
gboolean locked;
gboolean suspending;
gboolean starting;
@@ -100,6 +102,7 @@ struct a2dp_server {
GSList *sources;
uint32_t source_record_id;
uint32_t sink_record_id;
+ uint16_t version;
};
static GSList *servers = NULL;
@@ -286,9 +289,6 @@ static gboolean sbc_setconf_ind(struct avdtp *session,
{
struct a2dp_sep *a2dp_sep = user_data;
struct audio_device *dev;
- struct avdtp_service_capability *cap;
- struct avdtp_media_codec_capability *codec_cap;
- struct sbc_codec_cap *sbc_cap;
if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
debug("Sink %p: Set_Configuration_Ind", sep);
@@ -302,9 +302,19 @@ static gboolean sbc_setconf_ind(struct avdtp *session,
return FALSE;
}
- /* Check bipool range */
- for (codec_cap = NULL; caps; caps = g_slist_next(caps)) {
- cap = caps->data;
+ /* Check valid settings */
+ for (; caps != NULL; caps = g_slist_next(caps)) {
+ struct avdtp_service_capability *cap = caps->data;
+ struct avdtp_media_codec_capability *codec_cap;
+ struct sbc_codec_cap *sbc_cap;
+
+ 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;
@@ -324,8 +334,6 @@ static gboolean sbc_setconf_ind(struct avdtp *session,
*category = AVDTP_MEDIA_CODEC;
return FALSE;
}
-
- break;
}
avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep);
@@ -338,7 +346,8 @@ static gboolean sbc_setconf_ind(struct avdtp *session,
}
static gboolean sbc_getcap_ind(struct avdtp *session, struct avdtp_local_sep *sep,
- GSList **caps, uint8_t *err, void *user_data)
+ 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;
@@ -389,6 +398,13 @@ static gboolean sbc_getcap_ind(struct avdtp *session, struct avdtp_local_sep *se
*caps = g_slist_append(*caps, media_codec);
+ 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;
}
@@ -413,6 +429,17 @@ static gboolean mpeg_setconf_ind(struct avdtp *session,
return FALSE;
}
+ for (; caps != NULL; caps = g_slist_next(caps)) {
+ struct avdtp_service_capability *cap = caps->data;
+
+ if (cap->category == AVDTP_DELAY_REPORTING &&
+ !a2dp_sep->delay_reporting) {
+ *err = AVDTP_UNSUPPORTED_CONFIGURATION;
+ *category = AVDTP_DELAY_REPORTING;
+ return FALSE;
+ }
+ }
+
avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep);
a2dp_sep->stream = stream;
@@ -424,6 +451,7 @@ static gboolean mpeg_setconf_ind(struct avdtp *session,
static gboolean mpeg_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;
@@ -468,6 +496,13 @@ static gboolean mpeg_getcap_ind(struct avdtp *session,
*caps = g_slist_append(*caps, media_codec);
+ 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;
}
@@ -871,6 +906,24 @@ static gboolean reconf_ind(struct avdtp *session, struct avdtp_local_sep *sep,
return TRUE;
}
+static gboolean delayreport_ind(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ uint8_t rseid, uint16_t delay,
+ uint8_t *err, void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+ struct audio_device *dev = a2dp_get_dev(session);
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: DelayReport_Ind", sep);
+ else
+ debug("Source %p: DelayReport_Ind", sep);
+
+ unix_delay_report(dev, rseid, delay);
+
+ return TRUE;
+}
+
static void reconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_stream *stream, struct avdtp_error *err,
void *user_data)
@@ -902,6 +955,18 @@ static void reconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
finalize_config(setup);
}
+static void delay_report_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err, void *user_data)
+{
+ struct a2dp_sep *a2dp_sep = user_data;
+
+ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+ debug("Sink %p: DelayReport_Cfm", sep);
+ else
+ debug("Source %p: DelayReport_Cfm", sep);
+}
+
static struct avdtp_sep_cfm cfm = {
.set_configuration = setconf_cfm,
.get_configuration = getconf_cfm,
@@ -910,7 +975,8 @@ static struct avdtp_sep_cfm cfm = {
.suspend = suspend_cfm,
.close = close_cfm,
.abort = abort_cfm,
- .reconfigure = reconf_cfm
+ .reconfigure = reconf_cfm,
+ .delay_report = delay_report_cfm,
};
static struct avdtp_sep_ind sbc_ind = {
@@ -922,7 +988,8 @@ static struct avdtp_sep_ind sbc_ind = {
.suspend = suspend_ind,
.close = close_ind,
.abort = abort_ind,
- .reconfigure = reconf_ind
+ .reconfigure = reconf_ind,
+ .delayreport = delayreport_ind,
};
static struct avdtp_sep_ind mpeg_ind = {
@@ -934,10 +1001,11 @@ static struct avdtp_sep_ind mpeg_ind = {
.suspend = suspend_ind,
.close = close_ind,
.abort = abort_ind,
- .reconfigure = reconf_ind
+ .reconfigure = reconf_ind,
+ .delayreport = delayreport_ind,
};
-static sdp_record_t *a2dp_record(uint8_t type)
+static sdp_record_t *a2dp_record(uint8_t type, uint16_t avdtp_ver)
{
sdp_list_t *svclass_id, *pfseq, *apseq, *root;
uuid_t root_uuid, l2cap_uuid, avdtp_uuid, a2dp_uuid;
@@ -946,7 +1014,7 @@ static sdp_record_t *a2dp_record(uint8_t type)
sdp_record_t *record;
sdp_data_t *psm, *version, *features;
uint16_t lp = AVDTP_UUID;
- uint16_t avdtp_ver = 0x0102, a2dp_ver = 0x0102, feat = 0x000f;
+ uint16_t a2dp_ver = 0x0102, feat = 0x000f;
record = sdp_record_alloc();
if (!record)
@@ -1005,7 +1073,7 @@ static sdp_record_t *a2dp_record(uint8_t type)
}
static struct a2dp_sep *a2dp_add_sep(struct a2dp_server *server, uint8_t type,
- uint8_t codec)
+ uint8_t codec, gboolean delay_reporting)
{
struct a2dp_sep *sep;
GSList **l;
@@ -1017,8 +1085,8 @@ static struct a2dp_sep *a2dp_add_sep(struct a2dp_server *server, uint8_t type,
ind = (codec == A2DP_CODEC_MPEG12) ? &mpeg_ind : &sbc_ind;
sep->sep = avdtp_register_sep(&server->src, type,
- AVDTP_MEDIA_TYPE_AUDIO, codec, ind,
- &cfm, sep);
+ AVDTP_MEDIA_TYPE_AUDIO, codec,
+ delay_reporting, ind, &cfm, sep);
if (sep->sep == NULL) {
g_free(sep);
return NULL;
@@ -1026,6 +1094,7 @@ static struct a2dp_sep *a2dp_add_sep(struct a2dp_server *server, uint8_t type,
sep->codec = codec;
sep->type = type;
+ sep->delay_reporting = delay_reporting;
if (type == AVDTP_SEP_TYPE_SOURCE) {
l = &server->sources;
@@ -1038,7 +1107,7 @@ static struct a2dp_sep *a2dp_add_sep(struct a2dp_server *server, uint8_t type,
if (*record_id != 0)
goto add;
- record = a2dp_record(type);
+ record = a2dp_record(type, server->version);
if (!record) {
error("Unable to allocate new service record");
avdtp_unregister_sep(sep->sep);
@@ -1079,7 +1148,7 @@ int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
{
int sbc_srcs = 1, sbc_sinks = 1;
int mpeg12_srcs = 0, mpeg12_sinks = 0;
- gboolean source = TRUE, sink = FALSE;
+ gboolean source = TRUE, sink = FALSE, delay_reporting;
char *str;
GError *err = NULL;
int i;
@@ -1162,7 +1231,7 @@ proceed:
if (!server)
return -ENOMEM;
- av_err = avdtp_init(src, config);
+ av_err = avdtp_init(src, config, &server->version);
if (av_err < 0) {
g_free(server);
return av_err;
@@ -1172,24 +1241,31 @@ proceed:
servers = g_slist_append(servers, server);
}
+ delay_reporting = g_key_file_get_boolean(config, "A2DP",
+ "DelayReporting", NULL);
+ if (delay_reporting)
+ server->version = 0x0103;
+ else
+ server->version = 0x0102;
+
if (source) {
for (i = 0; i < sbc_srcs; i++)
a2dp_add_sep(server, AVDTP_SEP_TYPE_SOURCE,
- A2DP_CODEC_SBC);
+ A2DP_CODEC_SBC, delay_reporting);
for (i = 0; i < mpeg12_srcs; i++)
a2dp_add_sep(server, AVDTP_SEP_TYPE_SOURCE,
- A2DP_CODEC_MPEG12);
+ A2DP_CODEC_MPEG12, delay_reporting);
}
if (sink) {
for (i = 0; i < sbc_sinks; i++)
a2dp_add_sep(server, AVDTP_SEP_TYPE_SINK,
- A2DP_CODEC_SBC);
+ A2DP_CODEC_SBC, delay_reporting);
for (i = 0; i < mpeg12_sinks; i++)
a2dp_add_sep(server, AVDTP_SEP_TYPE_SINK,
- A2DP_CODEC_MPEG12);
+ A2DP_CODEC_MPEG12, delay_reporting);
}
return 0;
diff --git a/audio/avdtp.c b/audio/avdtp.c
index ba9b47a08..ced40ed74 100644
--- a/audio/avdtp.c
+++ b/audio/avdtp.c
@@ -26,6 +26,7 @@
#include <config.h>
#endif
+#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <errno.h>
@@ -36,12 +37,14 @@
#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
#include <glib.h>
#include <dbus/dbus.h>
#include "logging.h"
+#include "../src/manager.h"
#include "../src/adapter.h"
#include "../src/device.h"
@@ -71,6 +74,8 @@
#define AVDTP_SUSPEND 0x09
#define AVDTP_ABORT 0x0A
#define AVDTP_SECURITY_CONTROL 0x0B
+#define AVDTP_GET_ALL_CAPABILITIES 0x0C
+#define AVDTP_DELAY_REPORT 0x0D
#define AVDTP_PKT_TYPE_SINGLE 0x00
#define AVDTP_PKT_TYPE_START 0x01
@@ -241,6 +246,12 @@ struct reconf_req {
uint8_t caps[0];
} __attribute__ ((packed));
+struct delay_req {
+ uint8_t rfa0:2;
+ uint8_t acp_seid:6;
+ uint16_t delay;
+} __attribute__ ((packed));
+
#elif __BYTE_ORDER == __BIG_ENDIAN
struct seid_req {
@@ -273,6 +284,12 @@ struct reconf_req {
uint8_t caps[0];
} __attribute__ ((packed));
+struct delay_req {
+ uint8_t acp_seid:6;
+ uint8_t rfa0:2;
+ uint16_t delay;
+} __attribute__ ((packed));
+
#else
#error "Unknown byte order"
#endif
@@ -301,12 +318,14 @@ struct avdtp_remote_sep {
uint8_t type;
uint8_t media_type;
struct avdtp_service_capability *codec;
+ gboolean delay_reporting;
GSList *caps; /* of type struct avdtp_service_capability */
struct avdtp_stream *stream;
};
struct avdtp_server {
bdaddr_t src;
+ uint16_t version;
GIOChannel *io;
GSList *seps;
GSList *sessions;
@@ -317,6 +336,7 @@ struct avdtp_local_sep {
struct avdtp_stream *stream;
struct seid_info info;
uint8_t codec;
+ gboolean delay_reporting;
GSList *caps;
struct avdtp_sep_ind *ind;
struct avdtp_sep_cfm *cfm;
@@ -353,6 +373,8 @@ struct avdtp_stream {
gboolean close_int; /* If we are in INT role for Close */
gboolean abort_int; /* If we are in INT role for Abort */
guint idle_timer;
+ gboolean delay_reporting;
+ uint16_t delay; /* AVDTP 1.3 Delay Reporting feature */
};
/* Structure describing an AVDTP connection between two devices */
@@ -361,6 +383,8 @@ struct avdtp {
int ref;
int free_lock;
+ uint16_t version;
+
struct avdtp_server *server;
bdaddr_t dst;
@@ -908,6 +932,10 @@ static void avdtp_sep_set_state(struct avdtp *session,
}
switch (state) {
+ case AVDTP_STATE_CONFIGURED:
+ if (sep->info.type == AVDTP_SEP_TYPE_SINK)
+ avdtp_delay_report(session, stream, stream->delay);
+ break;
case AVDTP_STATE_OPEN:
if (old_state > AVDTP_STATE_OPEN && session->auto_dc)
stream->idle_timer = g_timeout_add_seconds(STREAM_TIMEOUT,
@@ -1121,11 +1149,15 @@ static struct avdtp_local_sep *find_local_sep(struct avdtp_server *server,
}
static GSList *caps_to_list(uint8_t *data, int size,
- struct avdtp_service_capability **codec)
+ struct avdtp_service_capability **codec,
+ gboolean *delay_reporting)
{
GSList *caps;
int processed;
+ if (delay_reporting)
+ *delay_reporting = FALSE;
+
for (processed = 0, caps = NULL; processed + 2 <= size;) {
struct avdtp_service_capability *cap;
uint8_t length, category;
@@ -1151,6 +1183,8 @@ static GSList *caps_to_list(uint8_t *data, int size,
length >=
sizeof(struct avdtp_media_codec_capability))
*codec = cap;
+ else if (category == AVDTP_DELAY_REPORTING && delay_reporting)
+ *delay_reporting = TRUE;
}
return caps;
@@ -1197,12 +1231,16 @@ static gboolean avdtp_discover_cmd(struct avdtp *session, uint8_t transaction,
}
static gboolean avdtp_getcap_cmd(struct avdtp *session, uint8_t transaction,
- struct seid_req *req, unsigned int size)
+ struct seid_req *req, unsigned int size,
+ gboolean get_all)
{
GSList *l, *caps;
struct avdtp_local_sep *sep = NULL;
unsigned int rsp_size;
uint8_t err, buf[1024], *ptr = buf;
+ uint8_t cmd;
+
+ cmd = get_all ? AVDTP_GET_ALL_CAPABILITIES : AVDTP_GET_CAPABILITIES;
if (size < sizeof(struct seid_req)) {
err = AVDTP_BAD_LENGTH;
@@ -1215,8 +1253,11 @@ static gboolean avdtp_getcap_cmd(struct avdtp *session, uint8_t transaction,
goto failed;
}
- if (!sep->ind->get_capability(session, sep, &caps, &err,
- sep->user_data))
+ if (get_all && session->server->version < 0x0103)
+ return avdtp_unknown_cmd(session, transaction, cmd);
+
+ if (!sep->ind->get_capability(session, sep, get_all, &caps,
+ &err, sep->user_data))
goto failed;
for (l = caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) {
@@ -1234,12 +1275,12 @@ static gboolean avdtp_getcap_cmd(struct avdtp *session, uint8_t transaction,
g_slist_free(caps);
- return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
- AVDTP_GET_CAPABILITIES, buf, rsp_size);
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, cmd,
+ buf, rsp_size);
failed:
- return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
- AVDTP_GET_CAPABILITIES, &err, sizeof(err));
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, cmd,
+ &err, sizeof(err));
}
static gboolean avdtp_setconf_cmd(struct avdtp *session, uint8_t transaction,
@@ -1299,7 +1340,8 @@ static gboolean avdtp_setconf_cmd(struct avdtp *session, uint8_t transaction,
stream->rseid = req->int_seid;
stream->caps = caps_to_list(req->caps,
size - sizeof(struct setconf_req),
- &stream->codec);
+ &stream->codec,
+ &stream->delay_reporting);
/* Verify that the Media Transport capability's length = 0. Reject otherwise */
for (l = stream->caps; l != NULL; l = g_slist_next(l)) {
@@ -1311,6 +1353,9 @@ static gboolean avdtp_setconf_cmd(struct avdtp *session, uint8_t transaction,
}
}
+ if (stream->delay_reporting && session->version < 0x0103)
+ session->version = 0x0103;
+
if (sep->ind && sep->ind->set_configuration) {
if (!sep->ind->set_configuration(session, sep, stream,
stream->caps, &err,
@@ -1644,6 +1689,51 @@ static gboolean avdtp_secctl_cmd(struct avdtp *session, uint8_t transaction,
return avdtp_unknown_cmd(session, transaction, AVDTP_SECURITY_CONTROL);
}
+static gboolean avdtp_delayreport_cmd(struct avdtp *session,
+ uint8_t transaction,
+ struct delay_req *req,
+ unsigned int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ uint8_t err;
+
+ if (size < sizeof(struct delay_req)) {
+ error("Too short delay report request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(session->server, req->acp_seid);
+ if (!sep || !sep->stream) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ stream = sep->stream;
+
+ if (sep->state != AVDTP_STATE_CONFIGURED &&
+ sep->state != AVDTP_STATE_STREAMING) {
+ err = AVDTP_BAD_STATE;
+ goto failed;
+ }
+
+ stream->delay = ntohs(req->delay);
+
+ if (sep->ind && sep->ind->delayreport) {
+ if (!sep->ind->delayreport(session, sep, stream->rseid,
+ stream->delay, &err,
+ sep->user_data))
+ goto failed;
+ }
+
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_DELAY_REPORT, NULL, 0);
+
+failed:
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_DELAY_REPORT, &err, sizeof(err));
+}
+
static gboolean avdtp_parse_cmd(struct avdtp *session, uint8_t transaction,
uint8_t signal_id, void *buf, int size)
{
@@ -1653,7 +1743,11 @@ static gboolean avdtp_parse_cmd(struct avdtp *session, uint8_t transaction,
return avdtp_discover_cmd(session, transaction, buf, size);
case AVDTP_GET_CAPABILITIES:
debug("Received GET_CAPABILITIES_CMD");
- return avdtp_getcap_cmd(session, transaction, buf, size);
+ return avdtp_getcap_cmd(session, transaction, buf, size,
+ FALSE);
+ case AVDTP_GET_ALL_CAPABILITIES:
+ debug("Received GET_ALL_CAPABILITIES_CMD");
+ return avdtp_getcap_cmd(session, transaction, buf, size, TRUE);
case AVDTP_SET_CONFIGURATION:
debug("Received SET_CONFIGURATION_CMD");
return avdtp_setconf_cmd(session, transaction, buf, size);
@@ -1681,6 +1775,9 @@ static gboolean avdtp_parse_cmd(struct avdtp *session, uint8_t transaction,
case AVDTP_SECURITY_CONTROL:
debug("Received SECURITY_CONTROL_CMD");
return avdtp_secctl_cmd(session, transaction, buf, size);
+ case AVDTP_DELAY_REPORT:
+ debug("Received DELAY_REPORT_CMD");
+ return avdtp_delayreport_cmd(session, transaction, buf, size);
default:
debug("Received unknown request id %u", signal_id);
return avdtp_unknown_cmd(session, transaction, signal_id);
@@ -1943,6 +2040,46 @@ static struct avdtp *find_session(GSList *list, const bdaddr_t *dst)
return NULL;
}
+static uint16_t get_version(struct avdtp *session)
+{
+ struct btd_adapter *adapter;
+ struct btd_device *device;
+ const sdp_record_t *rec;
+ sdp_list_t *protos;
+ sdp_data_t *proto_desc;
+ char addr[18];
+ uint16_t ver = 0x0100;
+
+ adapter = manager_find_adapter(&session->server->src);
+ if (!adapter)
+ goto done;
+
+ ba2str(&session->dst, addr);
+ device = adapter_find_device(adapter, addr);
+ if (!device)
+ goto done;
+
+ rec = btd_device_get_record(device, A2DP_SINK_UUID);
+ if (!rec)
+ rec = btd_device_get_record(device, A2DP_SOURCE_UUID);
+
+ if (!rec)
+ goto done;
+
+ if (sdp_get_access_protos(rec, &protos) < 0)
+ goto done;
+
+ proto_desc = sdp_get_proto_desc(protos, AVDTP_UUID);
+ if (proto_desc->dtd == SDP_UINT16)
+ ver = proto_desc->val.uint16;
+
+ sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL);
+ sdp_list_free(protos, NULL);
+
+done:
+ return ver;
+}
+
static struct avdtp *avdtp_get_internal(const bdaddr_t *src, const bdaddr_t *dst)
{
struct avdtp_server *server;
@@ -1973,6 +2110,8 @@ static struct avdtp *avdtp_get_internal(const bdaddr_t *src, const bdaddr_t *dst
session->state = AVDTP_SESSION_STATE_DISCONNECTED;
session->auto_dc = TRUE;
+ session->version = get_version(session);
+
server->sessions = g_slist_append(server->sessions, session);
return session;
@@ -2379,6 +2518,12 @@ static gboolean avdtp_discover_resp(struct avdtp *session,
struct discover_resp *resp, int size)
{
int sep_count, i;
+ uint8_t getcap_cmd;
+
+ if (session->version >= 0x0103 && session->server->version >= 0x0103)
+ getcap_cmd = AVDTP_GET_ALL_CAPABILITIES;
+ else
+ getcap_cmd = AVDTP_GET_CAPABILITIES;
sep_count = size / sizeof(struct seid_info);
@@ -2410,9 +2555,8 @@ static gboolean avdtp_discover_resp(struct avdtp *session,
memset(&req, 0, sizeof(req));
req.acp_seid = sep->seid;
- ret = send_request(session, TRUE, NULL,
- AVDTP_GET_CAPABILITIES,
- &req, sizeof(req));
+ ret = send_request(session, TRUE, NULL, getcap_cmd,
+ &req, sizeof(req));
if (ret < 0) {
finalize_discovery(session, -ret);
break;
@@ -2453,10 +2597,11 @@ static gboolean avdtp_get_capabilities_resp(struct avdtp *session,
g_slist_free(sep->caps);
sep->caps = NULL;
sep->codec = NULL;
+ sep->delay_reporting = FALSE;
}
sep->caps = caps_to_list(resp->caps, size - sizeof(struct getcap_resp),
- &sep->codec);
+ &sep->codec, &sep->delay_reporting);
return TRUE;
}
@@ -2559,12 +2704,25 @@ static gboolean avdtp_abort_resp(struct avdtp *session,
return TRUE;
}
+static gboolean avdtp_delay_report_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ void *data, int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ if (sep->cfm && sep->cfm->delay_report)
+ sep->cfm->delay_report(session, sep, stream, NULL, sep->user_data);
+
+ return TRUE;
+}
+
static gboolean avdtp_parse_resp(struct avdtp *session,
struct avdtp_stream *stream,
uint8_t transaction, uint8_t signal_id,
void *buf, int size)
{
struct pending_req *next;
+ const char *get_all = "";
if (session->prio_queue)
next = session->prio_queue->data;
@@ -2577,11 +2735,14 @@ static gboolean avdtp_parse_resp(struct avdtp *session,
case AVDTP_DISCOVER:
debug("DISCOVER request succeeded");
return avdtp_discover_resp(session, buf, size);
+ case AVDTP_GET_ALL_CAPABILITIES:
+ get_all = "ALL_";
case AVDTP_GET_CAPABILITIES:
- debug("GET_CAPABILITIES request succeeded");
+ debug("GET_%sCAPABILITIES request succeeded", get_all);
if (!avdtp_get_capabilities_resp(session, buf, size))
return FALSE;
- if (!(next && next->signal_id == AVDTP_GET_CAPABILITIES))
+ if (!(next && (next->signal_id == AVDTP_GET_CAPABILITIES ||
+ next->signal_id == AVDTP_GET_ALL_CAPABILITIES)))
finalize_discovery(session, 0);
return TRUE;
}
@@ -2616,6 +2777,9 @@ static gboolean avdtp_parse_resp(struct avdtp *session,
case AVDTP_ABORT:
debug("ABORT request succeeded");
return avdtp_abort_resp(session, stream, buf, size);
+ case AVDTP_DELAY_REPORT:
+ debug("DELAY_REPORT request succeeded");
+ return avdtp_delay_report_resp(session, stream, buf, size);
}
error("Unknown signal id in accept response: %u", signal_id);
@@ -2685,6 +2849,7 @@ static gboolean avdtp_parse_rej(struct avdtp *session,
avdtp_strerror(&err), err.err.error_code);
return TRUE;
case AVDTP_GET_CAPABILITIES:
+ case AVDTP_GET_ALL_CAPABILITIES:
if (!seid_rej_to_err(buf, size, &err))
return FALSE;
error("GET_CAPABILITIES request rejected: %s (%d)",
@@ -2755,6 +2920,15 @@ static gboolean avdtp_parse_rej(struct avdtp *session,
sep->cfm->abort(session, sep, stream, &err,
sep->user_data);
return TRUE;
+ case AVDTP_DELAY_REPORT:
+ if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+ return FALSE;
+ error("DELAY_REPORT request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->delay_report)
+ sep->cfm->delay_report(session, sep, stream, &err,
+ sep->user_data);
+ return TRUE;
default:
error("Unknown reject response signal id: %u", signal_id);
return TRUE;
@@ -2906,6 +3080,11 @@ struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep)
return sep->codec;
}
+gboolean avdtp_get_delay_reporting(struct avdtp_remote_sep *sep)
+{
+ return sep->delay_reporting;
+}
+
struct avdtp_stream *avdtp_get_stream(struct avdtp_remote_sep *sep)
{
return sep->stream;
@@ -2916,7 +3095,7 @@ struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category,
{
struct avdtp_service_capability *cap;
- if (category < AVDTP_MEDIA_TRANSPORT || category > AVDTP_MEDIA_CODEC)
+ if (category < AVDTP_MEDIA_TRANSPORT || category > AVDTP_DELAY_REPORTING)
return NULL;
cap = g_malloc(sizeof(struct avdtp_service_capability) + length);
@@ -3100,6 +3279,9 @@ int avdtp_set_configuration(struct avdtp *session,
new_stream->lsep = lsep;
new_stream->rseid = rsep->seid;
+ if (rsep->delay_reporting && lsep->delay_reporting)
+ new_stream->delay_reporting = TRUE;
+
g_slist_foreach(caps, copy_capabilities, &new_stream->caps);
/* Calculate total size of request */
@@ -3283,9 +3465,36 @@ int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream)
return ret;
}
+int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream,
+ uint16_t delay)
+{
+ struct delay_req req;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->lsep->state != AVDTP_STATE_CONFIGURED &&
+ stream->lsep->state != AVDTP_STATE_STREAMING)
+ return -EINVAL;
+
+ if (!stream->delay_reporting || session->version < 0x0103 ||
+ session->server->version < 0x0103)
+ return -EINVAL;
+
+ stream->delay = delay;
+
+ memset(&req, 0, sizeof(req));
+ req.acp_seid = stream->rseid;
+ req.delay = htons(delay);
+
+ return send_request(session, TRUE, stream, AVDTP_DELAY_REPORT,
+ &req, sizeof(req));
+}
+
struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type,
uint8_t media_type,
uint8_t codec_type,
+ gboolean delay_reporting,
struct avdtp_sep_ind *ind,
struct avdtp_sep_cfm *cfm,
void *user_data)
@@ -3311,6 +3520,7 @@ struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type,
sep->cfm = cfm;
sep->user_data = user_data;
sep->server = server;
+ sep->delay_reporting = TRUE;
debug("SEP %p registered: type:%d codec:%d seid:%d", sep,
sep->info.type, sep->codec, sep->info.seid);
@@ -3415,33 +3625,44 @@ void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst)
bacpy(dst, &session->dst);
}
-int avdtp_init(const bdaddr_t *src, GKeyFile *config)
+int avdtp_init(const bdaddr_t *src, GKeyFile *config, uint16_t *version)
{
GError *err = NULL;
gboolean tmp, master = TRUE;
struct avdtp_server *server;
+ uint16_t ver = 0x0102;
- if (config) {
- tmp = g_key_file_get_boolean(config, "General",
- "Master", &err);
- if (err) {
- debug("audio.conf: %s", err->message);
- g_clear_error(&err);
- } else
- master = tmp;
-
- tmp = g_key_file_get_boolean(config, "General", "AutoConnect",
- &err);
- if (err)
- g_clear_error(&err);
- else
- auto_connect = tmp;
- }
+ if (!config)
+ goto proceed;
+ tmp = g_key_file_get_boolean(config, "General",
+ "Master", &err);
+ if (err) {
+ debug("audio.conf: %s", err->message);
+ g_clear_error(&err);
+ } else
+ master = tmp;
+
+ tmp = g_key_file_get_boolean(config, "General", "AutoConnect",
+ &err);
+ if (err)
+ g_clear_error(&err);
+ else
+ auto_connect = tmp;
+
+ if (g_key_file_get_boolean(config, "A2DP", "DelayReporting", NULL))
+ ver = 0x0103;
+
+proceed:
server = g_new0(struct avdtp_server, 1);
if (!server)
return -ENOMEM;
+ server->version = ver;
+
+ if (version)
+ *version = server->version;
+
server->io = avdtp_server_socket(src, master);
if (!server->io) {
g_free(server);
diff --git a/audio/avdtp.h b/audio/avdtp.h
index 81b8f58b5..a155e7c7c 100644
--- a/audio/avdtp.h
+++ b/audio/avdtp.h
@@ -53,6 +53,7 @@ struct avdtp_error {
#define AVDTP_HEADER_COMPRESSION 0x05
#define AVDTP_MULTIPLEXING 0x06
#define AVDTP_MEDIA_CODEC 0x07
+#define AVDTP_DELAY_REPORTING 0x08
/* AVDTP error definitions */
#define AVDTP_BAD_HEADER_FORMAT 0x01
@@ -162,6 +163,9 @@ struct avdtp_sep_cfm {
struct avdtp_local_sep *lsep,
struct avdtp_stream *stream,
struct avdtp_error *err, void *user_data);
+ void (*delay_report) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err, void *user_data);
};
/* Callbacks for indicating when we received a new command. The return value
@@ -169,6 +173,7 @@ struct avdtp_sep_cfm {
struct avdtp_sep_ind {
gboolean (*get_capability) (struct avdtp *session,
struct avdtp_local_sep *sep,
+ gboolean get_all,
GSList **caps, uint8_t *err,
void *user_data);
gboolean (*set_configuration) (struct avdtp *session,
@@ -198,6 +203,10 @@ struct avdtp_sep_ind {
gboolean (*reconfigure) (struct avdtp *session,
struct avdtp_local_sep *lsep,
uint8_t *err, void *user_data);
+ gboolean (*delayreport) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ uint8_t rseid, uint16_t delay,
+ uint8_t *err, void *user_data);
};
typedef void (*avdtp_discover_cb_t) (struct avdtp *session, GSList *seps,
@@ -222,6 +231,8 @@ uint8_t avdtp_get_type(struct avdtp_remote_sep *sep);
struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep);
+gboolean avdtp_get_delay_reporting(struct avdtp_remote_sep *sep);
+
struct avdtp_stream *avdtp_get_stream(struct avdtp_remote_sep *sep);
int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb,
@@ -266,10 +277,13 @@ int avdtp_start(struct avdtp *session, struct avdtp_stream *stream);
int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream);
int avdtp_close(struct avdtp *session, struct avdtp_stream *stream);
int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream,
+ uint16_t delay);
struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type,
uint8_t media_type,
uint8_t codec_type,
+ gboolean delay_reporting,
struct avdtp_sep_ind *ind,
struct avdtp_sep_cfm *cfm,
void *user_data);
@@ -294,5 +308,5 @@ void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst);
void avdtp_set_auto_disconnect(struct avdtp *session, gboolean auto_dc);
gboolean avdtp_stream_setup_active(struct avdtp *session);
-int avdtp_init(const bdaddr_t *src, GKeyFile *config);
+int avdtp_init(const bdaddr_t *src, GKeyFile *config, uint16_t *version);
void avdtp_exit(const bdaddr_t *src);
diff --git a/audio/ipc.h b/audio/ipc.h
index 2e170f504..bd0dd38b1 100644
--- a/audio/ipc.h
+++ b/audio/ipc.h
@@ -101,6 +101,7 @@ typedef struct {
#define BT_STOP_STREAM 5
#define BT_CLOSE 6
#define BT_CONTROL 7
+#define BT_DELAY_REPORT 8
#define BT_CAPABILITIES_TRANSPORT_A2DP 0
#define BT_CAPABILITIES_TRANSPORT_SCO 1
@@ -324,6 +325,16 @@ struct bt_control_ind {
uint8_t key; /* Control Key */
} __attribute__ ((packed));
+struct bt_delay_report_req {
+ bt_audio_msg_header_t h;
+ uint16_t delay;
+} __attribute__ ((packed));
+
+struct bt_delay_report_ind {
+ bt_audio_msg_header_t h;
+ uint16_t delay;
+} __attribute__ ((packed));
+
/* Function declaration */
/* Opens a connection to the audio service: return a socket descriptor */
diff --git a/audio/sink.c b/audio/sink.c
index 00b2ea747..fdeff1e7b 100644
--- a/audio/sink.c
+++ b/audio/sink.c
@@ -475,6 +475,12 @@ static gboolean select_capabilities(struct avdtp *session,
*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;
}
diff --git a/audio/unix.c b/audio/unix.c
index 00435ee94..e61b0bb82 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -1621,6 +1621,56 @@ static void handle_control_req(struct unix_client *client,
unix_ipc_sendmsg(client, &rsp->h);
}
+static void handle_delay_report_req(struct unix_client *client,
+ struct bt_delay_report_req *req)
+{
+ char buf[BT_SUGGESTED_BUFFER_SIZE];
+ struct bt_set_configuration_rsp *rsp = (void *) buf;
+ struct a2dp_data *a2dp;
+ int err;
+
+ if (!client->dev) {
+ err = -ENODEV;
+ goto failed;
+ }
+
+ switch (client->type) {
+ case TYPE_HEADSET:
+ case TYPE_GATEWAY:
+ err = -EINVAL;
+ goto failed;
+ case TYPE_SOURCE:
+ case TYPE_SINK:
+ a2dp = &client->d.a2dp;
+ if (a2dp->session && a2dp->stream) {
+ err = avdtp_delay_report(a2dp->session, a2dp->stream,
+ req->delay);
+ if (err < 0)
+ goto failed;
+ } else {
+ err = -EINVAL;
+ goto failed;
+ }
+ break;
+ default:
+ error("No known services for device");
+ err = -EINVAL;
+ goto failed;
+ }
+
+ memset(buf, 0, sizeof(buf));
+ rsp->h.type = BT_RESPONSE;
+ rsp->h.name = BT_DELAY_REPORT;
+ rsp->h.length = sizeof(*rsp);
+
+ unix_ipc_sendmsg(client, &rsp->h);
+
+ return;
+
+failed:
+ unix_ipc_error(client, BT_DELAY_REPORT, -err);
+}
+
static gboolean client_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
{
char buf[BT_SUGGESTED_BUFFER_SIZE];
@@ -1685,6 +1735,10 @@ static gboolean client_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
handle_control_req(client,
(struct bt_control_req *) msghdr);
break;
+ case BT_DELAY_REPORT:
+ handle_delay_report_req(client,
+ (struct bt_delay_report_req *) msghdr);
+ break;
default:
error("Audio API: received unexpected message name %d",
msghdr->name);
@@ -1761,6 +1815,29 @@ void unix_device_removed(struct audio_device *dev)
}
}
+void unix_delay_report(struct audio_device *dev, uint8_t seid, uint16_t delay)
+{
+ GSList *l;
+ struct bt_delay_report_ind ind;
+
+ debug("unix_delay_report(%p): %u.%ums", dev, delay / 10, delay % 10);
+
+ memset(&ind, 0, sizeof(ind));
+ ind.h.type = BT_INDICATION;
+ ind.h.name = BT_DELAY_REPORT;
+ ind.h.length = sizeof(ind);
+ ind.delay = delay;
+
+ for (l = clients; l != NULL; l = g_slist_next(l)) {
+ struct unix_client *client = l->data;
+
+ if (client->dev != dev || client->seid != seid)
+ continue;
+
+ unix_ipc_sendmsg(client, (void *) &ind);
+ }
+}
+
int unix_init(void)
{
GIOChannel *io;
diff --git a/audio/unix.h b/audio/unix.h
index 12cf3efe7..aebba4de0 100644
--- a/audio/unix.h
+++ b/audio/unix.h
@@ -24,5 +24,7 @@
void unix_device_removed(struct audio_device *dev);
+void unix_delay_report(struct audio_device *dev, uint8_t seid, uint16_t delay);
+
int unix_init(void);
void unix_exit(void);