diff options
author | Johan Hedberg <johan.hedberg@nokia.com> | 2009-10-06 15:41:53 +0200 |
---|---|---|
committer | Johan Hedberg <johan.hedberg@nokia.com> | 2009-10-06 19:18:53 +0200 |
commit | 5f8a274881a823ea51df7a62ca17948b1f4ebe63 (patch) | |
tree | ed2a71b1df7675fe2b14f68560941216f74ab27d | |
parent | 9c39d108d8c1057ad29e83a4db0f4a396772987f (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.c | 124 | ||||
-rw-r--r-- | audio/avdtp.c | 289 | ||||
-rw-r--r-- | audio/avdtp.h | 16 | ||||
-rw-r--r-- | audio/ipc.h | 11 | ||||
-rw-r--r-- | audio/sink.c | 6 | ||||
-rw-r--r-- | audio/unix.c | 77 | ||||
-rw-r--r-- | audio/unix.h | 2 |
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); |