diff options
-rw-r--r-- | audio/a2dp.c | 611 | ||||
-rw-r--r-- | audio/a2dp.h | 48 | ||||
-rw-r--r-- | audio/avdtp.c | 2174 | ||||
-rw-r--r-- | audio/avdtp.h | 177 | ||||
-rw-r--r-- | audio/device.c | 207 | ||||
-rw-r--r-- | audio/device.h | 7 | ||||
-rw-r--r-- | audio/headset.c | 99 | ||||
-rw-r--r-- | audio/headset.h | 24 | ||||
-rw-r--r-- | audio/ipc.h | 106 | ||||
-rw-r--r-- | audio/main.c | 48 | ||||
-rw-r--r-- | audio/manager.c | 63 | ||||
-rw-r--r-- | audio/manager.h | 17 | ||||
-rw-r--r-- | audio/pcm_bluetooth.c | 541 | ||||
-rw-r--r-- | audio/sink.c | 429 | ||||
-rw-r--r-- | audio/sink.h | 11 | ||||
-rw-r--r-- | audio/unix.c | 227 | ||||
-rw-r--r-- | audio/unix.h | 5 |
17 files changed, 4451 insertions, 343 deletions
diff --git a/audio/a2dp.c b/audio/a2dp.c index db0ed7cb..3afc133d 100644 --- a/audio/a2dp.c +++ b/audio/a2dp.c @@ -25,4 +25,615 @@ #include <config.h> #endif +#include <stdlib.h> + +#include <dbus/dbus.h> +#include <glib.h> + +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include "logging.h" +#include "manager.h" +#include "avdtp.h" #include "a2dp.h" + +static DBusConnection *connection = NULL; + +static uint32_t sink_record_id = 0; +static uint32_t source_record_id = 0; + +static struct avdtp_local_sep *sink_sep = NULL; +static struct avdtp_local_sep *source_sep = NULL; + +static gboolean setconf_ind(struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + uint8_t int_seid, GSList *caps, + uint8_t *err) +{ + if (sep == sink_sep) + debug("SBC Sink: Set_Configuration_Ind"); + else + debug("SBC Source: Set_Configuration_Ind"); + + return TRUE; +} + +static gboolean getcap_ind(struct avdtp_local_sep *sep, + GSList **caps, uint8_t *err) +{ + struct avdtp_service_capability *media_transport, *media_codec; + struct sbc_codec_cap sbc_cap; + + if (sep == sink_sep) + debug("SBC Sink: Get_Capability_Ind"); + else + debug("SBC Source: Get_Capability_Ind"); + + *caps = NULL; + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + *caps = g_slist_append(*caps, media_transport); + + memset(&sbc_cap, 0, sizeof(struct sbc_codec_cap)); + + sbc_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + sbc_cap.cap.media_codec_type = A2DP_CODEC_SBC; + + sbc_cap.frequency = ( A2DP_SAMPLING_FREQ_48000 | + A2DP_SAMPLING_FREQ_44100 | + A2DP_SAMPLING_FREQ_32000 | + A2DP_SAMPLING_FREQ_16000 ); + + sbc_cap.channel_mode = ( A2DP_CHANNEL_MODE_JOINT_STEREO | + A2DP_CHANNEL_MODE_STEREO | + A2DP_CHANNEL_MODE_DUAL_CHANNEL | + A2DP_CHANNEL_MODE_MONO ); + + sbc_cap.block_length = ( A2DP_BLOCK_LENGTH_16 | + A2DP_BLOCK_LENGTH_12 | + A2DP_BLOCK_LENGTH_8 | + A2DP_BLOCK_LENGTH_4 ); + + sbc_cap.subbands = ( A2DP_SUBBANDS_8 | A2DP_SUBBANDS_4 ); + + sbc_cap.allocation_method = ( A2DP_ALLOCATION_LOUDNESS | + A2DP_ALLOCATION_SNR ); + + sbc_cap.min_bitpool = 2; + sbc_cap.max_bitpool = 250; + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, + sizeof(sbc_cap)); + + *caps = g_slist_append(*caps, media_codec); + + return TRUE; +} + +static void setconf_cfm(struct avdtp_local_sep *sep, + struct avdtp_stream *stream) +{ + if (sep == sink_sep) + debug("SBC Sink: Set_Configuration_Cfm"); + else + debug("SBC Source: Set_Configuration_Cfm"); +} + +static gboolean getconf_ind(struct avdtp_local_sep *sep, uint8_t *err) +{ + if (sep == sink_sep) + debug("SBC Sink: Get_Configuration_Ind"); + else + debug("SBC Source: Get_Configuration_Ind"); + return TRUE; +} + +static void getconf_cfm(struct avdtp_local_sep *sep, + struct avdtp_stream *stream) +{ + if (sep == sink_sep) + debug("SBC Sink: Set_Configuration_Cfm"); + else + debug("SBC Source: Set_Configuration_Cfm"); +} + +static gboolean open_ind(struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err) +{ + if (sep == sink_sep) + debug("SBC Sink: Open_Ind"); + else + debug("SBC Source: Open_Ind"); + return TRUE; +} + +static void open_cfm(struct avdtp_local_sep *sep, struct avdtp_stream *stream) +{ + if (sep == sink_sep) + debug("SBC Sink: Open_Cfm"); + else + debug("SBC Source: Open_Cfm"); +} + +static gboolean start_ind(struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err) +{ + if (sep == sink_sep) + debug("SBC Sink: Start_Ind"); + else + debug("SBC Source: Start_Ind"); + return TRUE; +} + +static void start_cfm(struct avdtp_local_sep *sep, struct avdtp_stream *stream) +{ + if (sep == sink_sep) + debug("SBC Sink: Start_Cfm"); + else + debug("SBC Source: Start_Cfm"); +} + +static gboolean suspend_ind(struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err) +{ + if (sep == sink_sep) + debug("SBC Sink: Suspend_Ind"); + else + debug("SBC Source: Suspend_Ind"); + return TRUE; +} + +static void suspend_cfm(struct avdtp_local_sep *sep, + struct avdtp_stream *stream) +{ + if (sep == sink_sep) + debug("SBC Sink: Suspend_Cfm"); + else + debug("SBC Source: Suspend_Cfm"); +} + +static gboolean close_ind(struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err) +{ + if (sep == sink_sep) + debug("SBC Sink: Close_Ind"); + else + debug("SBC Source: Close_Ind"); + return TRUE; +} + +static void close_cfm(struct avdtp_local_sep *sep, struct avdtp_stream *stream) +{ + if (sep == sink_sep) + debug("SBC Sink: Close_Cfm"); + else + debug("SBC Source: Close_Cfm"); +} + +static gboolean abort_ind(struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err) +{ + if (sep == sink_sep) + debug("SBC Sink: Abort_Ind"); + else + debug("SBC Source: Abort_Ind"); + return TRUE; +} + +static void abort_cfm(struct avdtp_local_sep *sep, struct avdtp_stream *stream) +{ + if (sep == sink_sep) + debug("SBC Sink: Abort_Cfm"); + else + debug("SBC Source: Abort_Cfm"); +} + +static gboolean reconf_ind(struct avdtp_local_sep *sep, uint8_t *err) +{ + if (sep == sink_sep) + debug("SBC Sink: ReConfigure_Ind"); + else + debug("SBC Source: ReConfigure_Ind"); + return TRUE; +} + +static void reconf_cfm(struct avdtp_local_sep *sep) +{ + if (sep == sink_sep) + debug("SBC Sink: ReConfigure_Cfm"); + else + debug("SBC Source: ReConfigure_Cfm"); +} + +static struct avdtp_sep_cfm cfm = { + .set_configuration = setconf_cfm, + .get_configuration = getconf_cfm, + .open = open_cfm, + .start = start_cfm, + .suspend = suspend_cfm, + .close = close_cfm, + .abort = abort_cfm, + .reconfigure = reconf_cfm +}; + +static struct avdtp_sep_ind ind = { + .get_capability = getcap_ind, + .set_configuration = setconf_ind, + .get_configuration = getconf_ind, + .open = open_ind, + .start = start_ind, + .suspend = suspend_ind, + .close = close_ind, + .abort = abort_ind, + .reconfigure = reconf_ind +}; + +static int a2dp_source_record(sdp_buf_t *buf) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, l2cap, avdtp, a2src; + sdp_profile_desc_t profile[1]; + sdp_list_t *aproto, *proto[2]; + sdp_record_t record; + sdp_data_t *psm, *version, *features; + uint16_t lp = AVDTP_UUID, ver = 0x0100, feat = 0x000F; + int ret = 0; + + memset(&record, 0, sizeof(sdp_record_t)); + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(&record, root); + + sdp_uuid16_create(&a2src, AUDIO_SOURCE_SVCLASS_ID); + svclass_id = sdp_list_append(0, &a2src); + sdp_set_service_classes(&record, svclass_id); + + sdp_uuid16_create(&profile[0].uuid, ADVANCED_AUDIO_PROFILE_ID); + profile[0].version = 0x0100; + pfseq = sdp_list_append(0, &profile[0]); + sdp_set_profile_descs(&record, pfseq); + + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap); + psm = sdp_data_alloc(SDP_UINT16, &lp); + proto[0] = sdp_list_append(proto[0], psm); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&avdtp, AVDTP_UUID); + proto[1] = sdp_list_append(0, &avdtp); + version = sdp_data_alloc(SDP_UINT16, &ver); + proto[1] = sdp_list_append(proto[1], version); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(&record, aproto); + + features = sdp_data_alloc(SDP_UINT16, &feat); + sdp_attr_add(&record, SDP_ATTR_SUPPORTED_FEATURES, features); + + sdp_set_info_attr(&record, "Audio Source", 0, 0); + + if (sdp_gen_record_pdu(&record, buf) < 0) + ret = -1; + else + ret = 0; + + free(psm); + free(version); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + sdp_list_free(record.attrlist, (sdp_free_func_t) sdp_data_free); + sdp_list_free(record.pattern, free); + + return ret; +} + +static int a2dp_sink_record(sdp_buf_t *buf) +{ + return 0; +} + +int a2dp_init(DBusConnection *conn, gboolean enable_sink, gboolean enable_source) +{ + sdp_buf_t buf; + + if (!enable_sink && !enable_source) + return 0; + + connection = dbus_connection_ref(conn); + + avdtp_init(); + + if (enable_sink) { + source_sep = avdtp_register_sep(AVDTP_SEP_TYPE_SOURCE, + AVDTP_MEDIA_TYPE_AUDIO, + &ind, &cfm); + if (source_sep == NULL) + return -1; + + if (a2dp_source_record(&buf) < 0) { + error("Unable to allocate new service record"); + return -1; + } + + source_record_id = add_service_record(conn, &buf); + free(buf.data); + if (!source_record_id) { + error("Unable to register A2DP Source service record"); + return -1; + } + } + + if (enable_source) { + sink_sep = avdtp_register_sep(AVDTP_SEP_TYPE_SINK, + AVDTP_MEDIA_TYPE_AUDIO, + &ind, &cfm); + if (sink_sep == NULL) + return -1; + + if (a2dp_sink_record(&buf) < 0) { + error("Unable to allocate new service record"); + return -1; + } + + sink_record_id = add_service_record(conn, &buf); + free(buf.data); + if (!sink_record_id) { + error("Unable to register A2DP Sink service record"); + return -1; + } + } + + return 0; +} + +void a2dp_exit() +{ + if (sink_sep) + avdtp_unregister_sep(sink_sep); + + if (source_sep) + avdtp_unregister_sep(source_sep); + + if (source_record_id) { + remove_service_record(connection, source_record_id); + source_record_id = 0; + } + + if (sink_record_id) { + remove_service_record(connection, sink_record_id); + sink_record_id = 0; + } + + dbus_connection_unref(connection); +} + +static uint8_t default_bitpool(uint8_t freq, uint8_t mode) { + switch (freq) { + case A2DP_SAMPLING_FREQ_16000: + case A2DP_SAMPLING_FREQ_32000: + return 53; + case A2DP_SAMPLING_FREQ_44100: + switch (mode) { + case A2DP_CHANNEL_MODE_MONO: + case A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + case A2DP_CHANNEL_MODE_STEREO: + case A2DP_CHANNEL_MODE_JOINT_STEREO: + return 53; + default: + error("Invalid channel mode %u", mode); + return 53; + } + case A2DP_SAMPLING_FREQ_48000: + switch (mode) { + case A2DP_CHANNEL_MODE_MONO: + case A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + case A2DP_CHANNEL_MODE_STEREO: + case A2DP_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) +{ + uint 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 & A2DP_SAMPLING_FREQ_48000) + cap->frequency = A2DP_SAMPLING_FREQ_48000; + else if (supported->frequency & A2DP_SAMPLING_FREQ_44100) + cap->frequency = A2DP_SAMPLING_FREQ_44100; + else if (supported->frequency & A2DP_SAMPLING_FREQ_32000) + cap->frequency = A2DP_SAMPLING_FREQ_32000; + else if (supported->frequency & A2DP_SAMPLING_FREQ_16000) + cap->frequency = A2DP_SAMPLING_FREQ_16000; + else { + error("No supported frequencies"); + return FALSE; + } + + if (supported->channel_mode & A2DP_CHANNEL_MODE_JOINT_STEREO) + cap->channel_mode = A2DP_CHANNEL_MODE_JOINT_STEREO; + else if (supported->channel_mode & A2DP_CHANNEL_MODE_STEREO) + cap->channel_mode = A2DP_CHANNEL_MODE_STEREO; + else if (supported->channel_mode & A2DP_CHANNEL_MODE_DUAL_CHANNEL) + cap->channel_mode = A2DP_CHANNEL_MODE_DUAL_CHANNEL; + else if (supported->channel_mode & A2DP_CHANNEL_MODE_MONO) + cap->channel_mode = A2DP_CHANNEL_MODE_MONO; + else { + error("No supported channel modes"); + return FALSE; + } + + if (supported->block_length & A2DP_BLOCK_LENGTH_16) + cap->block_length = A2DP_BLOCK_LENGTH_16; + else if (supported->block_length & A2DP_BLOCK_LENGTH_12) + cap->block_length = A2DP_BLOCK_LENGTH_12; + else if (supported->block_length & A2DP_BLOCK_LENGTH_8) + cap->block_length = A2DP_BLOCK_LENGTH_8; + else if (supported->block_length & A2DP_BLOCK_LENGTH_4) + cap->block_length = A2DP_BLOCK_LENGTH_4; + else { + error("No supported block lengths"); + return FALSE; + } + + if (supported->subbands & A2DP_SUBBANDS_8) + cap->subbands = A2DP_SUBBANDS_8; + else if (supported->subbands & A2DP_SUBBANDS_4) + cap->subbands = A2DP_SUBBANDS_4; + else { + error("No supported subbands"); + return FALSE; + } + + if (supported->allocation_method & A2DP_ALLOCATION_LOUDNESS) + cap->allocation_method = A2DP_ALLOCATION_LOUDNESS; + else if (supported->allocation_method & A2DP_ALLOCATION_SNR) + cap->allocation_method = A2DP_ALLOCATION_SNR; + + min_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode), + 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; +} + +gboolean a2dp_select_capabilities(struct avdtp_remote_sep *rsep, GSList **caps) +{ + struct avdtp_service_capability *media_transport, *media_codec; + struct sbc_codec_cap sbc_cap, *acp_sbc; + + media_codec = avdtp_get_codec(rsep); + if (!media_codec) + return FALSE; + + acp_sbc = (void *) media_codec->data; + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + *caps = g_slist_append(*caps, media_transport); + + select_sbc_params(&sbc_cap, acp_sbc); + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, + sizeof(sbc_cap)); + + *caps = g_slist_append(*caps, media_codec); + + return TRUE; +} + +gboolean a2dp_get_config(struct avdtp_stream *stream, struct ipc_data_cfg **cfg) +{ + struct avdtp_service_capability *cap; + struct avdtp_media_codec_capability *codec_cap = NULL; + struct sbc_codec_cap *sbc_cap; + struct ipc_data_cfg *rsp; + struct ipc_codec_sbc *sbc; + GSList *caps; + + rsp = g_malloc0(sizeof(struct ipc_data_cfg) + + sizeof(struct ipc_codec_sbc)); + rsp->fd = -1; + sbc = (void *) rsp->data; + + if (!avdtp_stream_get_transport(stream, &rsp->fd, &rsp->pkt_len, + &caps)) { + g_free(rsp); + return FALSE; + } + + for (; caps; caps = g_slist_next(caps)) { + cap = caps->data; + if (cap->category == AVDTP_MEDIA_CODEC) { + codec_cap = (void *) cap->data; + break; + } + } + + if (codec_cap == NULL) { + g_free(rsp); + return FALSE; + } + + rsp->fd_opt = CFG_FD_OPT_WRITE; + + *cfg = rsp; + + if (codec_cap->media_codec_type != A2DP_CODEC_SBC) + return TRUE; + + sbc_cap = (struct sbc_codec_cap *) codec_cap; + rsp->channels = sbc_cap->channel_mode == + A2DP_CHANNEL_MODE_MONO ? 1 : 2; + rsp->channel_mode = sbc_cap->channel_mode; + rsp->sample_size = 2; + + switch (sbc_cap->frequency) { + case A2DP_SAMPLING_FREQ_16000: + rsp->rate = 16000; + break; + case A2DP_SAMPLING_FREQ_32000: + rsp->rate = 32000; + break; + case A2DP_SAMPLING_FREQ_44100: + rsp->rate = 44100; + break; + case A2DP_SAMPLING_FREQ_48000: + rsp->rate = 48000; + break; + } + + rsp->codec = CFG_CODEC_SBC; + sbc->allocation = sbc_cap->allocation_method == A2DP_ALLOCATION_SNR ? + 0x01 : 0x00; + sbc->subbands = sbc_cap->subbands == A2DP_SUBBANDS_4 ? 4 : 8; + + switch (sbc_cap->block_length) { + case A2DP_BLOCK_LENGTH_4: + sbc->blocks = 4; + break; + case A2DP_BLOCK_LENGTH_8: + sbc->blocks = 8; + break; + case A2DP_BLOCK_LENGTH_12: + sbc->blocks = 12; + break; + case A2DP_BLOCK_LENGTH_16: + sbc->blocks = 16; + break; + } + + sbc->bitpool = sbc_cap->max_bitpool; + + return TRUE; +} diff --git a/audio/a2dp.h b/audio/a2dp.h index 153a1731..7e53f7a9 100644 --- a/audio/a2dp.h +++ b/audio/a2dp.h @@ -21,3 +21,51 @@ * */ +#include <dbus.h> +#include <glib.h> +#include "avdtp.h" + +#define A2DP_CODEC_SBC 0x00 +#define A2DP_CODEC_MPEG12 0x01 +#define A2DP_CODEC_MPEG24 0x02 +#define A2DP_CODEC_ATRAC 0x03 + +#define A2DP_SAMPLING_FREQ_16000 (1 << 3) +#define A2DP_SAMPLING_FREQ_32000 (1 << 2) +#define A2DP_SAMPLING_FREQ_44100 (1 << 1) +#define A2DP_SAMPLING_FREQ_48000 1 + +#define A2DP_CHANNEL_MODE_MONO (1 << 3) +#define A2DP_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define A2DP_CHANNEL_MODE_STEREO (1 << 1) +#define A2DP_CHANNEL_MODE_JOINT_STEREO 1 + +#define A2DP_BLOCK_LENGTH_4 (1 << 3) +#define A2DP_BLOCK_LENGTH_8 (1 << 2) +#define A2DP_BLOCK_LENGTH_12 (1 << 1) +#define A2DP_BLOCK_LENGTH_16 1 + +#define A2DP_SUBBANDS_4 (1 << 1) +#define A2DP_SUBBANDS_8 1 + +#define A2DP_ALLOCATION_SNR (1 << 1) +#define A2DP_ALLOCATION_LOUDNESS 1 + +struct sbc_codec_cap { + struct avdtp_media_codec_capability cap; + uint8_t channel_mode:4; + uint8_t frequency:4; + uint8_t allocation_method:2; + uint8_t subbands:2; + uint8_t block_length:4; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)); + +int a2dp_init(DBusConnection *conn, gboolean enable_sink, + gboolean enable_source); +void a2dp_exit(void); + +gboolean a2dp_select_capabilities(struct avdtp_remote_sep *rsep, GSList **caps); + +gboolean a2dp_get_config(struct avdtp_stream *stream, struct ipc_data_cfg **cfg); diff --git a/audio/avdtp.c b/audio/avdtp.c index 2f4ab59e..8615a18a 100644 --- a/audio/avdtp.c +++ b/audio/avdtp.c @@ -24,3 +24,2177 @@ #ifdef HAVE_CONFIG_H #include <config.h> #endif + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> +#include <signal.h> +#include <netinet/in.h> + +#include <glib.h> + +#include "avdtp.h" +#include "logging.h" +#include "dbus.h" + +#include <bluetooth/l2cap.h> + +#define AVDTP_PSM 25 + +#define MAX_SEID 0x3E + +#define AVDTP_DISCOVER 0x01 +#define AVDTP_GET_CAPABILITIES 0x02 +#define AVDTP_SET_CONFIGURATION 0x03 +#define AVDTP_GET_CONFIGURATION 0x04 +#define AVDTP_RECONFIGURE 0x05 +#define AVDTP_OPEN 0x06 +#define AVDTP_START 0x07 +#define AVDTP_CLOSE 0x08 +#define AVDTP_SUSPEND 0x09 +#define AVDTP_ABORT 0x0A +#define AVDTP_SECURITY_CONTROL 0x0B + +#define AVDTP_PKT_TYPE_SINGLE 0x00 +#define AVDTP_PKT_TYPE_START 0x01 +#define AVDTP_PKT_TYPE_CONTINUE 0x02 +#define AVDTP_PKT_TYPE_END 0x03 + +#define AVDTP_MSG_TYPE_COMMAND 0x00 +#define AVDTP_MSG_TYPE_ACCEPT 0x02 +#define AVDTP_MSG_TYPE_REJECT 0x03 + +#define REQ_TIMEOUT 2000 +#define DISCONNECT_TIMEOUT 5000 + +typedef enum { + AVDTP_ERROR_ERRNO, + AVDTP_ERROR_ERROR_CODE +} avdtp_error_type_t; + +typedef enum { + AVDTP_SESSION_STATE_DISCONNECTED, + AVDTP_SESSION_STATE_CONNECTING, + AVDTP_SESSION_STATE_CONNECTED +} avdtp_session_state_t; + +struct avdtp_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; + uint8_t signal_id:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +typedef struct seid_info { + uint8_t rfa0:1; + uint8_t inuse:1; + uint8_t seid:6; + uint8_t rfa2:3; + uint8_t type:1; + uint8_t media_type:4; +} __attribute__ ((packed)) seid_info_t; + +/* packets */ + +struct discover_req { + struct avdtp_header header; +} __attribute__ ((packed)); + +struct discover_resp { + struct avdtp_header header; + struct seid_info seps[0]; +} __attribute__ ((packed)); + +struct getcap_resp { + struct avdtp_header header; + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct seid_req { + struct avdtp_header header; + uint8_t rfa0:2; + uint8_t acp_seid:6; +} __attribute__ ((packed)); + +struct seid_rej { + struct avdtp_header header; + uint8_t error; +} __attribute__ ((packed)); + +struct setconf_req { + struct avdtp_header header; + + uint8_t rfa0:2; + uint8_t acp_seid:6; + uint8_t rfa1:2; + uint8_t int_seid:6; + + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct setconf_resp { + struct avdtp_header header; +} __attribute__ ((packed)); + +struct conf_rej { + struct avdtp_header header; + uint8_t category; + uint8_t error; +} __attribute__ ((packed)); + +struct stream_rej { + struct avdtp_header header; + uint8_t rfa0; + uint8_t acp_seid:6; + uint8_t error_code; +} __attribute__ ((packed)); + +struct reconf_req { + struct avdtp_header header; + + uint8_t rfa0:2; + uint8_t acp_seid:6; + + uint8_t serv_cap; + uint8_t serv_cap_len; + + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct reconf_resp { + struct avdtp_header header; +} __attribute__ ((packed)); + +struct abort_resp { + struct avdtp_header header; +} __attribute__ ((packed)); + +struct stream_pause_resp { + struct avdtp_header header; + uint8_t rfa0:2; + uint8_t acp_seid:6; + uint8_t error; +} __attribute__ ((packed)); + +struct avdtp_general_rej { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; + uint8_t rfa0; +} __attribute__ ((packed)); + +struct pending_req { + struct avdtp_header *msg; + int msg_size; + struct avdtp_stream *stream; /* Set if the request targeted a stream */ + guint timeout; +}; + +struct avdtp_remote_sep { + uint8_t seid; + uint8_t type; + uint8_t media_type; + struct avdtp_service_capability *codec; + GSList *caps; /* of type struct avdtp_service_capability */ + struct avdtp_stream *stream; +}; + +struct avdtp_local_sep { + avdtp_state_t state; + struct avdtp_stream *stream; + struct seid_info info; + uint8_t codec; + GSList *caps; + struct avdtp_sep_ind *ind; + struct avdtp_sep_cfm *cfm; + void *data; +}; + +struct avdtp_stream { + int sock; + uint16_t mtu; + struct avdtp *session; + struct avdtp_local_sep *lsep; + struct avdtp_remote_sep *rsep; + GSList *caps; + avdtp_stream_state_cb cb; + void *user_data; + guint io; /* Transport GSource ID */ + guint close_timer; /* Waiting for other side to close transport */ + gboolean open_acp; /* If we are in ACT role for Open */ + gboolean close_int; /* If we are in INT role for Close */ +}; + +/* Structure describing an AVDTP connection between two devices */ +struct avdtp { + int ref; + + bdaddr_t src; + bdaddr_t dst; + + avdtp_session_state_t last_state; + avdtp_session_state_t state; + + guint io; + int sock; + + GSList *seps; /* Elements of type struct avdtp_remote_sep * */ + + GSList *streams; /* Elements of type struct avdtp_stream * */ + + GSList *req_queue; /* Elements of type struct pending_req * */ + GSList *prio_queue; /* Same as req_queue but is processed before it */ + + struct avdtp_stream *pending_open; + + uint16_t mtu; + char *buf; + + avdtp_discover_cb_t discov_cb; + void *user_data; + + struct pending_req *req; + + guint dc_timer; +}; + +struct avdtp_error { + avdtp_error_type_t type; + union { + uint8_t error_code; + int posix_errno; + } err; +}; + +static uint8_t free_seid = 1; +static GSList *local_seps = NULL; + +static GIOChannel *avdtp_server = NULL; + +static GSList *sessions = NULL; + +static int send_request(struct avdtp *session, gboolean priority, + struct avdtp_stream *stream, void *buffer, int size); +static gboolean avdtp_parse_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct avdtp_header *header, int size); +static gboolean avdtp_parse_rej(struct avdtp *session, + struct avdtp_stream *stream, + struct avdtp_header *header, int size); +static int process_queue(struct avdtp *session); + +static const char *avdtp_statestr(avdtp_state_t state) +{ + switch (state) { + case AVDTP_STATE_IDLE: + return "IDLE"; + case AVDTP_STATE_CONFIGURED: + return "CONFIGURED"; + case AVDTP_STATE_OPEN: + return "OPEN"; + case AVDTP_STATE_STREAMING: + return "STREAMING"; + case AVDTP_STATE_CLOSING: + return "CLOSING"; + case AVDTP_STATE_ABORTING: + return "ABORTING"; + default: + return "<unknown state>"; + } +} + +static gboolean avdtp_send(struct avdtp *session, void *data, int len) +{ + int ret; + + ret = send(session->sock, data, len, 0); + + if (ret < 0) + ret = -errno; + else if (ret != len) + ret = -EIO; + + if (ret < 0) { + error("avdtp_send: %s (%d)", strerror(-ret), -ret); + return FALSE; + } + + return TRUE; +} + +static void pending_req_free(struct pending_req *req) +{ + if (req->timeout) + g_source_remove(req->timeout); + g_free(req->msg); + g_free(req); +} + +#if 0 +static gboolean stream_close_timeout(gpointer user_data) +{ + struct avdtp_stream *stream = user_data; + + close(stream->sock); + + return FALSE; +} +#endif + +static gboolean disconnect_timeout(gpointer user_data) +{ + struct avdtp *session = user_data; + + assert(session->ref == 1); + + sessions = g_slist_remove(sessions, session); + + session->dc_timer = 0; + + avdtp_unref(session); + + return FALSE; +} + +static void remove_disconnect_timer(struct avdtp *session) +{ + g_source_remove(session->dc_timer); + session->dc_timer = 0; +} + +static void set_disconnect_timer(struct avdtp *session) +{ + if (session->dc_timer) + remove_disconnect_timer(session); + + session->dc_timer = g_timeout_add(DISCONNECT_TIMEOUT, + disconnect_timeout, session); +} + +static void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id) +{ + err->type = type; + switch (type) { + case AVDTP_ERROR_ERRNO: + err->err.posix_errno = id; + break; + case AVDTP_ERROR_ERROR_CODE: + err->err.error_code = id; + break; + } +} + +static void stream_free(struct avdtp_stream *stream) +{ + stream->lsep->info.inuse = 0; + stream->lsep->stream = NULL; + stream->rsep->stream = NULL; + if (stream->caps) { + g_slist_foreach(stream->caps, (GFunc) g_free, NULL); + g_slist_free(stream->caps); + } + g_free(stream); +} + +static void avdtp_sep_set_state(struct avdtp *session, + struct avdtp_local_sep *sep, + avdtp_state_t state) +{ + struct avdtp_stream *stream = sep->stream; + avdtp_state_t old_state; + + if (sep->state == state) + return; + + debug("stream state changed: %s -> %s", avdtp_statestr(sep->state), + avdtp_statestr(state)); + + old_state = sep->state; + sep->state = state; + + if (stream && stream->cb) + stream->cb(stream, old_state, state, NULL, + stream->user_data); + + if (state == AVDTP_STATE_IDLE) { + session->streams = g_slist_remove(session->streams, stream); + stream_free(stream); + } +} + +static void finalize_discovery(struct avdtp *session, int err) +{ + if (!session->discov_cb) + return; + + session->discov_cb(session, session->seps, err, + session->user_data); + + session->discov_cb = NULL; + session->user_data = NULL; +} + +static void release_stream(struct avdtp_stream *stream, struct avdtp *session) +{ + if (stream->sock >= 0) + close(stream->sock); + if (stream->io) + g_source_remove(stream->io); + avdtp_sep_set_state(session, stream->lsep, AVDTP_STATE_IDLE); +} + +static void connection_lost(struct avdtp *session) +{ + if (session->state == AVDTP_SESSION_STATE_CONNECTED) { + char address[18]; + + ba2str(&session->dst, address); + debug("Disconnected from %s", address); + } + + if (session->discov_cb) + finalize_discovery(session, -ECONNABORTED); + + if (session->sock >= 0) { + close(session->sock); + session->sock = -1; + } + + session->state = AVDTP_SESSION_STATE_DISCONNECTED; + + if (session->io) { + g_source_remove(session->io); + session->io = 0; + } + + g_slist_foreach(session->streams, (GFunc) release_stream, session); + session->streams = NULL; +} + +void avdtp_unref(struct avdtp *session) +{ + if (!session) + return; + + if (!g_slist_find(sessions, session)) { + error("avdtp_unref: trying to unref a unknown session"); + return; + } + + session->ref--; + + debug("avdtp_unref: ref=%d", session->ref); + + if (session->ref == 1) { + if (session->dc_timer) + remove_disconnect_timer(session); + if (session->sock >= 0) + set_disconnect_timer(session); + return; + } + + if (session->ref > 0) + return; + + if (session->dc_timer) + remove_disconnect_timer(session); + + connection_lost(session); + + sessions = g_slist_remove(sessions, session); + + if (session->req) + pending_req_free(session->req); + + g_slist_foreach(session->seps, (GFunc) g_free, NULL); + g_slist_free(session->seps); + + g_free(session->buf); + + g_free(session); +} + +struct avdtp *avdtp_ref(struct avdtp *session) +{ + session->ref++; + debug("avdtp_ref: ref=%d", session->ref); + if (session->dc_timer) + remove_disconnect_timer(session); + return session; +} + +static struct avdtp_local_sep *find_local_sep_by_seid(uint8_t seid) +{ + GSList *l; + + for (l = local_seps; l != NULL; l = g_slist_next(l)) { + struct avdtp_local_sep *sep = l->data; + + if (sep->info.seid == seid) + return sep; + } + + return NULL; +} + +static struct avdtp_local_sep *find_local_sep(uint8_t type, uint8_t media_type, + uint8_t codec) +{ + GSList *l; + + for (l = local_seps; l != NULL; l = g_slist_next(l)) { + struct avdtp_local_sep *sep = l->data; + + if (sep->info.inuse) + continue; + + if (sep->info.type == type && + sep->info.media_type == media_type && + sep->codec == codec) + return sep; + } + + return NULL; +} + +static void init_response(struct avdtp_header *rsp, struct avdtp_header *req, + gboolean accept) +{ + rsp->packet_type = AVDTP_PKT_TYPE_SINGLE; + rsp->message_type = accept ? AVDTP_MSG_TYPE_ACCEPT : + AVDTP_MSG_TYPE_REJECT; + rsp->transaction = req->transaction; + rsp->signal_id = req->signal_id; + rsp->rfa0 = 0; +} + +static gboolean avdtp_unknown_cmd(struct avdtp *session, + struct avdtp_header *req, int size) +{ + struct avdtp_general_rej rej; + + memset(&rej, 0, sizeof(rej)); + + rej.packet_type = AVDTP_PKT_TYPE_SINGLE; + rej.message_type = AVDTP_MSG_TYPE_REJECT; + rej.transaction = req->transaction; + + return avdtp_send(session, &rej, sizeof(rej)); +} + +static gboolean avdtp_discover_cmd(struct avdtp *session, + struct discover_req *req, int size) +{ + GSList *l; + struct discover_resp *rsp = (struct discover_resp *) session->buf; + struct seid_info *info; + int rsp_size; + + init_response(&rsp->header, &req->header, TRUE); + rsp_size = sizeof(struct discover_resp); + info = rsp->seps; + + for (l = local_seps; l != NULL; l = l->next) { + struct avdtp_local_sep *sep = l->data; + + if (rsp_size + sizeof(struct seid_info) > session->mtu) + break; + + memcpy(&info, &sep->info, sizeof(struct seid_info)); + rsp_size += sizeof(struct seid_info); + info++; + } + + return avdtp_send(session, session->buf, rsp_size); +} + +static gboolean avdtp_getcap_cmd(struct avdtp *session, + struct seid_req *req, int size) +{ + GSList *l, *caps; + struct avdtp_local_sep *sep = NULL; + struct seid_rej rej; + struct getcap_resp *rsp = (struct getcap_resp *) session->buf; + int rsp_size; + unsigned char *ptr; + uint8_t err; + + if (size < sizeof(struct seid_req)) { + error("Too short getcap request"); + return FALSE; + } + + sep = find_local_sep_by_seid(req->acp_seid); + if (!sep) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (!sep->ind->get_capability(sep, &caps, &err)) + goto failed; + + init_response(&rsp->header, &req->header, TRUE); + rsp_size = sizeof(struct getcap_resp); + ptr = rsp->caps; + + for (l = caps; l != NULL; l = g_slist_next(l)) { + struct avdtp_service_capability *cap = l->data; + + if (rsp_size + cap->length + 2 > session->mtu) + break; + + memcpy(ptr, cap, cap->length + 2); + rsp_size += cap->length + 2; + ptr += cap->length + 2; + + g_free(cap); + } + + g_slist_free(caps); + + return avdtp_send(session, session->buf, rsp_size); + +failed: + init_response(&rej.header, &req->header, FALSE); + rej.error = AVDTP_BAD_ACP_SEID; + return avdtp_send(session, &rej, sizeof(rej)); +} + +static gboolean avdtp_setconf_cmd(struct avdtp *session, + struct setconf_req *req, int size) +{ + struct conf_rej rej; + struct setconf_resp *rsp = (struct setconf_resp *) session->buf; + struct avdtp_local_sep *lsep; + gboolean ret; + + if (size < sizeof(struct setconf_req)) { + error("Too short getcap request"); + return FALSE; + } + + lsep = find_local_sep_by_seid(req->acp_seid); + if (!lsep || !lsep->stream) { + init_response(&rej.header, &req->header, FALSE); + rej.error = AVDTP_BAD_ACP_SEID; + return avdtp_send(session, &rej, sizeof(rej)); + } + + init_response(&rsp->header, &req->header, TRUE); + + ret = avdtp_send(session, rsp, sizeof(struct setconf_req)); + + if (ret) + avdtp_sep_set_state(session, lsep, AVDTP_STATE_CONFIGURED); + + return ret; +} + +static gboolean avdtp_getconf_cmd(struct avdtp *session, struct seid_req *req, + int size) +{ + return avdtp_unknown_cmd(session, (void *) req, size); +} + +static gboolean avdtp_reconf_cmd(struct avdtp *session, struct seid_req *req, + int size) +{ + return avdtp_unknown_cmd(session, (void *) req, size); +} + +static gboolean avdtp_open_cmd(struct avdtp *session, struct seid_req *req, + int size) +{ + return avdtp_unknown_cmd(session, (void *) req, size); +} + +static gboolean avdtp_start_cmd(struct avdtp *session, struct seid_req *req, + int size) +{ + return avdtp_unknown_cmd(session, (void *) req, size); +} + +static gboolean avdtp_close_cmd(struct avdtp *session, struct seid_req *req, + int size) +{ + return avdtp_unknown_cmd(session, (void *) req, size); +} + +static gboolean avdtp_suspend_cmd(struct avdtp *session, struct seid_req *req, + int size) +{ + return avdtp_unknown_cmd(session, (void *) req, size); +} + +static gboolean avdtp_abort_cmd(struct avdtp *session, struct seid_req *req, + int size) +{ + struct avdtp_local_sep *sep; + struct abort_resp *rsp = (struct abort_resp *) session->buf; + struct seid_rej rej; + uint8_t err; + gboolean ret; + + if (size < sizeof(struct seid_req)) { + error("Too short abort request"); + return FALSE; + } + + sep = find_local_sep_by_seid(req->acp_seid); + if (!sep || !sep->stream) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + if (sep->ind && sep->ind->abort) { + if (!sep->ind->abort(sep, sep->stream, &err)) + goto failed; + } + + init_response(&rsp->header, &req->header, TRUE); + ret = avdtp_send(session, rsp, sizeof(struct abort_resp)); + + if (ret) + avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING); + + return ret; + +failed: + init_response(&rej.header, &req->header, FALSE); + rej.error = err; + return avdtp_send(session, &rej, sizeof(rej)); +} + +static gboolean avdtp_secctl_cmd(struct avdtp *session, struct seid_req *req, + int size) +{ + return avdtp_unknown_cmd(session, (void *) req, size); +} + +static gboolean avdtp_parse_cmd(struct avdtp *session, + struct avdtp_header *header, int size) +{ + switch (header->signal_id) { + case AVDTP_DISCOVER: + debug("Received DISCOVER_CMD"); + return avdtp_discover_cmd(session, (void *) header, size); + case AVDTP_GET_CAPABILITIES: + debug("Received GET_CAPABILITIES_CMD"); + return avdtp_getcap_cmd(session, (void *) header, size); + case AVDTP_SET_CONFIGURATION: + debug("Received SET_CONFIGURATION_CMD"); + return avdtp_setconf_cmd(session, (void *) header, size); + case AVDTP_GET_CONFIGURATION: + debug("Received GET_CONFIGURATION_CMD"); + return avdtp_getconf_cmd(session, (void *) header, size); + case AVDTP_RECONFIGURE: + debug("Received RECONFIGURE_CMD"); + return avdtp_reconf_cmd(session, (void *) header, size); + case AVDTP_OPEN: + debug("Received OPEN_CMD"); + return avdtp_open_cmd(session, (void *) header, size); + case AVDTP_START: + debug("Received START_CMD"); + return avdtp_start_cmd(session, (void *) header, size); + case AVDTP_CLOSE: + debug("Received CLOSE_CMD"); + return avdtp_close_cmd(session, (void *) header, size); + case AVDTP_SUSPEND: + debug("Received SUSPEND_CMD"); + return avdtp_suspend_cmd(session, (void *) header, size); + case AVDTP_ABORT: + debug("Received ABORT_CMD"); + return avdtp_abort_cmd(session, (void *) header, size); + case AVDTP_SECURITY_CONTROL: + debug("Received SECURITY_CONTROL_CMD"); + return avdtp_secctl_cmd(session, (void *) header, size); + default: + debug("Received unknown request id %u", header->signal_id); + return avdtp_unknown_cmd(session, (void *) header, size); + } +} + +static gboolean transport_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct avdtp_stream *stream = data; + struct avdtp_local_sep *sep = stream->lsep; + + if (stream->close_int && sep->cfm && sep->cfm->close) + sep->cfm->close(sep, stream); + + avdtp_sep_set_state(stream->session, sep, AVDTP_STATE_IDLE); + + return FALSE; +} + +static void handle_transport_connect(struct avdtp *session, int sock, + uint16_t mtu) +{ + struct avdtp_stream *stream = session->pending_open; + struct avdtp_local_sep *sep = stream->lsep; + GIOChannel *channel; + + session->pending_open = NULL; + + stream->sock = sock; + stream->mtu = mtu; + + if (sep->cfm && sep->cfm->open) + sep->cfm->open(sep, stream); + + channel = g_io_channel_unix_new(stream->sock); + + stream->io = g_io_add_watch(channel, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) transport_cb, stream); + g_io_channel_unref(channel); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); +} + +static void init_request(struct avdtp_header *header, int request_id) +{ + static int transaction = 0; + + header->packet_type = AVDTP_PKT_TYPE_SINGLE; + header->message_type = AVDTP_MSG_TYPE_COMMAND; + header->transaction = transaction; + header->signal_id = request_id; + + /* clear rfa bits */ + header->rfa0 = 0; + + transaction = (transaction + 1) % 16; +} + +static gboolean session_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct avdtp *session = data; + struct avdtp_header *header; + gsize size; + + debug("session_cb"); + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) + goto failed; + + if (g_io_channel_read(chan, session->buf, session->mtu, + &size) != G_IO_ERROR_NONE) { + error("IO Channel read error"); + goto failed; + } + + if (size < sizeof(struct avdtp_header)) { + error("Received too small packet (%d bytes)", size); + goto failed; + } + + header = (struct avdtp_header *) session->buf; + + if (header->message_type == AVDTP_MSG_TYPE_COMMAND) { + if (!avdtp_parse_cmd(session, header, size)) { + error("Unable to handle command. Disconnecting"); + goto failed; + } + + if (session->ref == 1 && !session->streams) + set_disconnect_timer(session); + + return TRUE; + } + + if (session->req == NULL) { + error("No pending request, rejecting message"); + return TRUE; + } + + if (header->transaction != session->req->msg->transaction) { + error("Transaction label doesn't match"); + return TRUE; + } + + if (header->signal_id != session->req->msg->signal_id) { + error("Reponse signal doesn't match"); + return TRUE; + } + + g_source_remove(session->req->timeout); + session->req->timeout = 0; + + switch(header->message_type) { + case AVDTP_MSG_TYPE_ACCEPT: + if (!avdtp_parse_resp(session, session->req->stream, header, + size)) { + error("Unable to parse accept response"); + goto failed; + } + break; + case AVDTP_MSG_TYPE_REJECT: + if (!avdtp_parse_rej(session, session->req->stream, header, + size)) { + error("Unable to parse reject response"); + goto failed; + } + break; + default: + error("Unknown message type"); + break; + } + + pending_req_free(session->req); + session->req = NULL; + + process_queue(session); + + return TRUE; + +failed: + connection_lost(session); + avdtp_unref(session); + return FALSE; +} + +static gboolean l2cap_connect_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct avdtp *session = data; + struct l2cap_options l2o; + socklen_t len; + int ret, err, sk; + char address[18]; + + if (cond & G_IO_NVAL) + return FALSE; + + if (!g_slist_find(sessions, session)) { + debug("l2cap_connect_cb: session got removed"); + return FALSE; + } + + if (cond & (G_IO_ERR | G_IO_HUP)) { + err = EIO; + goto failed; + } + + sk = g_io_channel_unix_get_fd(chan); + len = sizeof(ret); + if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &ret, &len) < 0) { + err = errno; + error("getsockopt(SO_ERROR): %s (%d)", strerror(err), err); + goto failed; + } + + if (ret != 0) { + err = ret; + error("connect(): %s (%d)", strerror(err), err); + goto failed; + } + + ba2str(&session->dst, address); + debug("AVDTP: connected %s channel to %s", + session->pending_open ? "transport" : "signaling", + address); + + memset(&l2o, 0, sizeof(l2o)); + len = sizeof(l2o); + if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, + &len) < 0) { + err = errno; + error("getsockopt(L2CAP_OPTIONS): %s (%d)", strerror(err), + err); + goto failed; + } + + if (session->state == AVDTP_SESSION_STATE_CONNECTING) { + session->sock = sk; + session->mtu = l2o.imtu; + session->buf = g_malloc0(session->mtu); + session->state = AVDTP_SESSION_STATE_CONNECTED; + session->io = g_io_add_watch(chan, + G_IO_IN | G_IO_ERR | G_IO_HUP + | G_IO_NVAL, + (GIOFunc) session_cb, session); + } + else if (session->pending_open) + handle_transport_connect(session, sk, l2o.imtu); + + process_queue(session); + + return FALSE; + +failed: + if (session->pending_open) { + avdtp_sep_set_state(session, session->pending_open->lsep, + AVDTP_STATE_IDLE); + session->pending_open = NULL; + } else { + finalize_discovery(session, -err); + connection_lost(session); + avdtp_unref(session); + } + + return FALSE; +} + +static int l2cap_connect(struct avdtp *session) +{ + struct sockaddr_l2 l2a; + GIOChannel *io; + int sk; + + memset(&l2a, 0, sizeof(l2a)); + l2a.l2_family = AF_BLUETOOTH; + bacpy(&l2a.l2_bdaddr, &session->src); + + sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sk < 0) { + error("Cannot create L2CAP socket. %s(%d)", strerror(errno), + errno); + return -errno; + } + + if (bind(sk, (struct sockaddr *) &l2a, sizeof(l2a)) < 0) { + error("Bind failed. %s (%d)", strerror(errno), errno); + return -errno; + } + + memset(&l2a, 0, sizeof(l2a)); + l2a.l2_family = AF_BLUETOOTH; + bacpy(&l2a.l2_bdaddr, &session->dst); + l2a.l2_psm = htobs(AVDTP_PSM); + + if (set_nonblocking(sk) < 0) { + error("Set non blocking: %s (%d)", strerror(errno), errno); + return -errno; + } + + io = g_io_channel_unix_new(sk); + g_io_channel_set_close_on_unref(io, FALSE); + + if (connect(sk, (struct sockaddr *) &l2a, sizeof(l2a)) < 0) { + if (!(errno == EAGAIN || errno == EINPROGRESS)) { + error("Connect failed. %s(%d)", strerror(errno), + errno); + finalize_discovery(session, errno); + g_io_channel_close(io); + g_io_channel_unref(io); + return -errno; + } + g_io_add_watch(io, G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + (GIOFunc) l2cap_connect_cb, session); + + if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) + session->state = AVDTP_SESSION_STATE_CONNECTING; + } else + l2cap_connect_cb(io, G_IO_OUT, session); + + g_io_channel_unref(io); + + return 0; +} + +static void queue_request(struct avdtp *session, struct pending_req *req, + gboolean priority) +{ + if (priority) + session->prio_queue = g_slist_append(session->prio_queue, req); + else + session->req_queue = g_slist_append(session->req_queue, req); +} + +static struct avdtp_remote_sep *find_remote_sep(GSList *seps, uint8_t seid) +{ + GSList *l; + + for (l = seps; l != NULL; l = g_slist_next(l)) { + struct avdtp_remote_sep *sep = l->data; + + if (sep->seid == seid) + return sep; + } + + return NULL; +} + +static gboolean request_timeout(gpointer user_data) +{ + struct avdtp *session = user_data; + struct pending_req *req; + struct seid_req sreq; + struct avdtp_remote_sep *sep; + struct avdtp_stream *stream; + uint8_t seid; + + error("Request timed out"); + + req = session->req; + session->req = NULL; + + switch (req->msg->signal_id) { + case AVDTP_DISCOVER: + case AVDTP_GET_CAPABILITIES: + case AVDTP_SET_CONFIGURATION: + case AVDTP_ABORT: + goto failed; + } + + seid = ((struct seid_req *) (req->msg))->acp_seid; + + sep = find_remote_sep(session->seps, seid); + if (!sep) { + error("Unable to find matching remote SEID %u", seid); + goto failed; + } + + stream = sep->stream; + + memset(&sreq, 0, sizeof(sreq)); + init_request(&sreq.header, AVDTP_ABORT); + sreq.acp_seid = seid; + + if (!send_request(session, TRUE, stream, &sreq, sizeof(sreq))) { + error("Unable to send abort request"); + goto failed; + } + + goto done; + +failed: + connection_lost(session); + avdtp_unref(session); +done: + pending_req_free(req); + return FALSE; +} + +static int send_req(struct avdtp *session, gboolean priority, + struct pending_req *req) +{ + int err; + + if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) { + err = l2cap_connect(session); + if (err < 0) + goto failed; + } + + if (session->state < AVDTP_SESSION_STATE_CONNECTED || + session->req != NULL) { + queue_request(session, req, priority); + return 0; + } + + /* FIXME: Should we retry to send if the buffer + was not totally sent or in case of EINTR? */ + err = avdtp_send(session, req->msg, req->msg_size); + if (err < 0) + goto failed; + + session->req = req; + + req->timeout = g_timeout_add(REQ_TIMEOUT, request_timeout, + session); + return 0; + +failed: + g_free(req->msg); + g_free(req); + return err; +} + +static int send_request(struct avdtp *session, gboolean priority, + struct avdtp_stream *stream, void *buffer, int size) +{ + struct pending_req *req; + + req = g_new0(struct pending_req, 1); + req->msg = g_malloc(size); + memcpy(req->msg, buffer, size); + req->msg_size = size; + req->stream = stream; + + return send_req(session, priority, req); +} + +static gboolean avdtp_discover_resp(struct avdtp *session, + struct discover_resp *resp, int size) +{ + int sep_count, i, isize = sizeof(struct seid_info); + + sep_count = (size - sizeof(struct avdtp_header)) / isize; + + for (i = 0; i < sep_count; i++) { + struct avdtp_remote_sep *sep; + struct seid_req req; + int ret; + + /* Skip SEP's which are in use */ + if (resp->seps[i].inuse) + continue; + + sep = find_remote_sep(session->seps, resp->seps[i].seid); + if (!sep) { + sep = g_new0(struct avdtp_remote_sep, 1); + session->seps = g_slist_append(session->seps, sep); + } + else if (sep && sep->stream) + continue; + + sep->seid = resp->seps[i].seid; + sep->type = resp->seps[i].type; + sep->media_type = resp->seps[i].media_type; + + memset(&req, 0, sizeof(req)); + init_request(&req.header, AVDTP_GET_CAPABILITIES); + req.acp_seid = sep->seid; + + ret = send_request(session, TRUE, NULL, &req, sizeof(req)); + if (ret < 0) { + finalize_discovery(session, ret); + break; + } + } + + return TRUE; +} + +static gboolean avdtp_get_capabilities_resp(struct avdtp *session, + struct getcap_resp *resp, + int size) +{ + int processed; + struct avdtp_remote_sep *sep; + unsigned char *ptr; + uint8_t seid; + + /* Check for minimum required packet size includes: + * 1. getcap resp header + * 2. media transport capability (2 bytes) + * 3. media codec capability type + length (2 bytes) + * 4. the actual media codec elements + * */ + if (size < (sizeof(struct getcap_resp) + 4 + + sizeof(struct avdtp_media_codec_capability))) { + error("Too short getcap resp packet"); + return FALSE; + } + + seid = ((struct seid_req *) session->req->msg)->acp_seid; + + sep = find_remote_sep(session->seps, seid); + + if (sep->caps) { + g_slist_foreach(sep->caps, (GFunc) g_free, NULL); + g_slist_free(sep->caps); + sep->caps = NULL; + sep->codec = NULL; + } + + ptr = resp->caps; + processed = sizeof(struct getcap_resp); + + while (processed + 2 < size) { + struct avdtp_service_capability *cap; + uint8_t length, category; + + category = ptr[0]; + length = ptr[1]; + + if (processed + 2 + length > size) { + error("Invalid capability data in getcap resp"); + return FALSE; + } + + cap = g_malloc(sizeof(struct avdtp_service_capability) + + length); + memcpy(cap, ptr, 2 + length); + + processed += 2 + length; + ptr += 2 + length; + + sep->caps = g_slist_append(sep->caps, cap); + + if (category == AVDTP_MEDIA_CODEC && + length >= + sizeof(struct avdtp_media_codec_capability)) + sep->codec = cap; + + } + + return TRUE; +} + +static gboolean avdtp_set_configuration_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct avdtp_header *resp, + int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (sep->cfm && sep->cfm->set_configuration) + sep->cfm->set_configuration(sep, stream); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED); + + return TRUE; +} + +static gboolean avdtp_reconfigure_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct avdtp_header *resp, int size) +{ + return TRUE; +} + +static gboolean avdtp_open_resp(struct avdtp *session, struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (l2cap_connect(session) < 0) + avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE); + + session->pending_open = stream; + + return TRUE; +} + +static gboolean avdtp_start_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (sep->cfm && sep->cfm->start) + sep->cfm->start(sep, stream); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING); + + return TRUE; +} + +static gboolean avdtp_close_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING); + + close(stream->sock); + stream->sock = -1; + + return TRUE; +} + +static gboolean avdtp_suspend_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct stream_pause_resp *resp, + int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (sep->cfm && sep->cfm->suspend) + sep->cfm->suspend(sep, stream); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); + + return TRUE; +} + +static gboolean avdtp_abort_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (sep->cfm && sep->cfm->suspend) + sep->cfm->suspend(sep, stream); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE); + + return TRUE; +} + +static gboolean avdtp_parse_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct avdtp_header *header, int size) +{ + struct avdtp_header *next; + + if (session->prio_queue) + next = ((struct pending_req *) + (session->prio_queue->data))->msg; + else if (session->req_queue) + next = ((struct pending_req *) + (session->req_queue->data))->msg; + else + next = NULL; + + switch (header->signal_id) { + case AVDTP_DISCOVER: + debug("DISCOVER request succeeded"); + return avdtp_discover_resp(session, (void *) header, size); + case AVDTP_GET_CAPABILITIES: + debug("GET_CAPABILITIES request succeeded"); + if (!avdtp_get_capabilities_resp(session, (void *) header, + size)) + return FALSE; + if (!(next && next->signal_id == AVDTP_GET_CAPABILITIES)) + finalize_discovery(session, 0); + return TRUE; + case AVDTP_SET_CONFIGURATION: + debug("SET_CONFIGURATION request succeeded"); + return avdtp_set_configuration_resp(session, stream, + (void *) header, size); + case AVDTP_RECONFIGURE: + debug("RECONFIGURE request succeeded"); + return avdtp_reconfigure_resp(session, stream, (void *) header, + size); + case AVDTP_OPEN: + debug("OPEN request succeeded"); + return avdtp_open_resp(session, stream, (void *) header, size); + case AVDTP_SUSPEND: + debug("SUSPEND request succeeded"); + return avdtp_suspend_resp(session, stream, (void *) header, + size); + case AVDTP_START: + debug("START request succeeded"); + return avdtp_start_resp(session, stream, (void *) header, + size); + case AVDTP_CLOSE: + debug("CLOSE request succeeded"); + return avdtp_close_resp(session, stream, (void *) header, + size); + case AVDTP_ABORT: + debug("ABORT request succeeded"); + return avdtp_abort_resp(session, stream, (void *) header, + size); + } + + error("Unknown signal id in accept response: %u", header->signal_id); + + return TRUE; +} + +static gboolean seid_rej_to_err(struct seid_rej *rej, int size, + struct avdtp_error *err) +{ + if (size < sizeof(struct seid_rej)) { + error("Too small packet for seid_rej"); + return FALSE; + } + + avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error); + + return TRUE; +} + +static gboolean conf_rej_to_err(struct conf_rej *rej, int size, + struct avdtp_error *err, uint8_t *category) +{ + if (size < sizeof(struct conf_rej)) { + error("Too small packet for conf_rej"); + return FALSE; + } + + avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error); + + if (category) + *category = rej->category; + + return TRUE; +} + +static gboolean stream_rej_to_err(struct stream_rej *rej, int size, + struct avdtp_error *err, uint8_t *acp_seid) +{ + if (size < sizeof(struct conf_rej)) { + error("Too small packet for stream_rej"); + return FALSE; + } + + avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error_code); + + if (acp_seid) + *acp_seid = rej->acp_seid; + + return TRUE; +} + +static gboolean avdtp_parse_rej(struct avdtp *session, struct avdtp_stream *stream, + struct avdtp_header *header, int size) +{ + struct avdtp_error err; + uint8_t acp_seid, category; + + switch (header->signal_id) { + case AVDTP_DISCOVER: + if (!seid_rej_to_err((void *) header, size, &err)) + return FALSE; + error("DISCOVER request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + return TRUE; + case AVDTP_GET_CAPABILITIES: + if (!seid_rej_to_err((void *) header, size, &err)) + return FALSE; + error("GET_CAPABILITIES request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + return TRUE; + case AVDTP_OPEN: + if (!seid_rej_to_err((void *) header, size, &err)) + return FALSE; + error("OPEN request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + return TRUE; + case AVDTP_SET_CONFIGURATION: + if (!conf_rej_to_err((void *) header, size, &err, &category)) + return FALSE; + error("SET_CONFIGURATION request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + return TRUE; + case AVDTP_RECONFIGURE: + if (!conf_rej_to_err((void *) header, size, &err, &category)) + return FALSE; + error("RECONFIGURE request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + return TRUE; + case AVDTP_START: + if (!stream_rej_to_err((void *) header, size, &err, &acp_seid)) + return FALSE; + error("START request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + return TRUE; + case AVDTP_SUSPEND: + if (!stream_rej_to_err((void *) header, size, &err, &acp_seid)) + return FALSE; + error("SUSPEND request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + return TRUE; + case AVDTP_CLOSE: + if (!stream_rej_to_err((void *) header, size, &err, &acp_seid)) + return FALSE; + error("CLOSE request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + return TRUE; + case AVDTP_ABORT: + if (!stream_rej_to_err((void *) header, size, &err, &acp_seid)) + return FALSE; + error("ABORT request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + return TRUE; + default: + error("Unknown reject response signal id: %u", + header->signal_id); + return TRUE; + } +} + +static struct avdtp *find_session(bdaddr_t *src, bdaddr_t *dst) +{ + GSList *l; + + for (l = sessions; l != NULL; l = g_slist_next(l)) { + struct avdtp *s = l->data; + + if (bacmp(src, &s->src) || bacmp(dst, &s->dst)) + continue; + + return s; + } + + return NULL; +} + +struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst) +{ + struct avdtp *session; + + assert(src != NULL); + assert(dst != NULL); + + session = find_session(src, dst); + if (session) + return avdtp_ref(session); + + session = g_new0(struct avdtp, 1); + + session->sock = -1; + bacpy(&session->src, src); + bacpy(&session->dst, dst); + session->ref = 1; + session->state = AVDTP_SESSION_STATE_DISCONNECTED; + + sessions = g_slist_append(sessions, session); + + return avdtp_ref(session); +} + +gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock, + uint16_t *mtu, GSList **caps) +{ + if (stream->sock < 0) + return FALSE; + + if (sock) + *sock = stream->sock; + + if (mtu) + *mtu = stream->mtu; + + if (caps) + *caps = stream->caps; + + return TRUE; +} + +static int process_queue(struct avdtp *session) +{ + GSList **queue, *l; + struct pending_req *req; + + if (session->req) + return 0; + + if (session->prio_queue) + queue = &session->prio_queue; + else + queue = &session->req_queue; + + if (!*queue) + return 0; + + l = *queue; + req = l->data; + + *queue = g_slist_remove(*queue, req); + + return send_req(session, FALSE, req); +} + +struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep) +{ + return sep->codec; +} + +struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, + void *data, int length) +{ + struct avdtp_service_capability *cap; + + cap = g_malloc(sizeof(struct avdtp_service_capability) + length); + cap->category = category; + cap->length = length; + memcpy(cap->data, data, length); + + return cap; +} + +int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb, void *user_data) +{ + struct discover_req req; + int ret; + + if (session->discov_cb) + return -EBUSY; + + memset(&req, 0, sizeof(req)); + init_request(&req.header, AVDTP_DISCOVER); + + ret = send_request(session, FALSE, NULL, &req, sizeof(req)); + if (ret == 0) { + session->discov_cb = cb; + session->user_data = user_data; + } + + return ret; +} + +int avdtp_get_seps(struct avdtp *session, uint8_t acp_type, uint8_t media_type, + uint8_t codec, struct avdtp_local_sep **lsep, + struct avdtp_remote_sep **rsep) +{ + GSList *l; + uint8_t int_type; + + int_type = acp_type == AVDTP_SEP_TYPE_SINK ? + AVDTP_SEP_TYPE_SOURCE : AVDTP_SEP_TYPE_SINK; + + *lsep = find_local_sep(int_type, media_type, codec); + if (!*lsep) + return -EINVAL; + + for (l = session->seps; l != NULL; l = g_slist_next(l)) { + struct avdtp_remote_sep *sep = l->data; + struct avdtp_service_capability *cap; + struct avdtp_media_codec_capability *codec_data; + + if (sep->type != acp_type) + continue; + + if (sep->media_type != media_type) + continue; + + if (!sep->codec) + continue; + + cap = sep->codec; + codec_data = (void *) cap->data; + + if (codec_data->media_codec_type != codec) + continue; + + if (!sep->stream) { + *rsep = sep; + return 0; + } + } + + return -EINVAL; +} + +void avdtp_stream_set_cb(struct avdtp *session, struct avdtp_stream *stream, + avdtp_stream_state_cb cb, void *data) +{ + stream->cb = cb; + stream->user_data = data; +} + +int avdtp_get_configuration(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + + if (session->state < AVDTP_SESSION_STATE_CONNECTED) + return -EINVAL; + + memset(&req, 0, sizeof(req)); + init_request(&req.header, AVDTP_GET_CONFIGURATION); + req.acp_seid = stream->rsep->seid; + + return send_request(session, FALSE, stream, &req, sizeof(req)); +} + +int avdtp_set_configuration(struct avdtp *session, + struct avdtp_remote_sep *rsep, + struct avdtp_local_sep *lsep, + GSList *caps, + struct avdtp_stream **stream) +{ + struct setconf_req *req; + struct avdtp_stream *new_stream; + unsigned char *ptr; + int ret, caps_len; + struct avdtp_service_capability *cap; + GSList *l; + + if (session->state != AVDTP_SESSION_STATE_CONNECTED) + return -ENOTCONN; + + if (!(lsep && rsep)) + return -EINVAL; + + new_stream = g_new0(struct avdtp_stream, 1); + + new_stream->session = session; + new_stream->lsep = lsep; + new_stream->rsep = rsep; + new_stream->caps = caps; + + /* Calculate total size of request */ + for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) { + cap = l->data; + caps_len += cap->length + 2; + } + + req = g_malloc0(sizeof(struct setconf_req) + caps_len); + + init_request(&req->header, AVDTP_SET_CONFIGURATION); + req->acp_seid = lsep->info.seid; + req->int_seid = rsep->seid; + + /* Copy the capabilities into the request */ + for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) { + cap = l->data; + memcpy(ptr, cap, cap->length + 2); + ptr += cap->length + 2; + } + + ret = send_request(session, FALSE, new_stream, req, + sizeof(struct setconf_req) + caps_len); + if (ret < 0) + stream_free(new_stream); + else { + lsep->info.inuse = 1; + lsep->stream = new_stream; + rsep->stream = new_stream; + session->streams = g_slist_append(session->streams, new_stream); + if (stream) + *stream = new_stream; + } + + g_free(req); + + return ret; +} + +int avdtp_reconfigure(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state != AVDTP_STATE_OPEN) + return -EINVAL; + + memset(&req, 0, sizeof(req)); + init_request(&req.header, AVDTP_GET_CONFIGURATION); + req.acp_seid = stream->rsep->seid; + + return send_request(session, FALSE, NULL, &req, sizeof(req)); +} + +int avdtp_open(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state > AVDTP_STATE_CONFIGURED) + return -EINVAL; + + memset(&req, 0, sizeof(req)); + init_request(&req.header, AVDTP_OPEN); + req.acp_seid = stream->rsep->seid; + + return send_request(session, FALSE, stream, &req, sizeof(req)); +} + +int avdtp_start(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state != AVDTP_STATE_OPEN) + return -EINVAL; + + memset(&req, 0, sizeof(req)); + init_request(&req.header, AVDTP_START); + req.acp_seid = stream->rsep->seid; + + return send_request(session, FALSE, stream, &req, sizeof(req)); +} + +int avdtp_close(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + int ret; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state < AVDTP_STATE_OPEN) + return -EINVAL; + + memset(&req, 0, sizeof(req)); + init_request(&req.header, AVDTP_CLOSE); + req.acp_seid = stream->rsep->seid; + + ret = send_request(session, FALSE, stream, &req, sizeof(req)); + if (ret == 0) + stream->close_int = TRUE; + + return ret; +} + +int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + int ret; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state <= AVDTP_STATE_OPEN) + return -EINVAL; + + memset(&req, 0, sizeof(req)); + init_request(&req.header, AVDTP_SUSPEND); + req.acp_seid = stream->rsep->seid; + + ret = send_request(session, FALSE, stream, &req, sizeof(req)); + if (ret == 0) + avdtp_sep_set_state(session, stream->lsep, + AVDTP_STATE_OPEN); + + return ret; +} + +int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + int ret; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state <= AVDTP_STATE_OPEN) + return -EINVAL; + + memset(&req, 0, sizeof(req)); + init_request(&req.header, AVDTP_ABORT); + req.acp_seid = stream->rsep->seid; + + ret = send_request(session, FALSE, stream, &req, sizeof(req)); + if (ret == 0) + avdtp_sep_set_state(session, stream->lsep, + AVDTP_STATE_ABORTING); + + return 0; +} + +struct avdtp_local_sep *avdtp_register_sep(uint8_t type, uint8_t media_type, + struct avdtp_sep_ind *ind, + struct avdtp_sep_cfm *cfm) +{ + struct avdtp_local_sep *sep; + + if (free_seid > MAX_SEID) + return NULL; + + sep = g_new0(struct avdtp_local_sep, 1); + + sep->state = AVDTP_STATE_IDLE; + sep->info.seid = free_seid++; + sep->info.type = type; + sep->info.media_type = media_type; + sep->ind = ind; + sep->cfm = cfm; + + local_seps = g_slist_append(local_seps, sep); + + return sep; +} + +int avdtp_unregister_sep(struct avdtp_local_sep *sep) +{ + if (!sep) + return -EINVAL; + + if (sep->info.inuse) + return -EBUSY; + + local_seps = g_slist_remove(local_seps, sep); + + g_free(sep); + + return 0; +} + +static gboolean avdtp_server_cb(GIOChannel *chan, GIOCondition cond, void *data) +{ + int srv_sk, cli_sk; + socklen_t size; + struct sockaddr_l2 addr; + struct l2cap_options l2o; + bdaddr_t src, dst; + struct avdtp *session; + GIOChannel *cli_io; + char address[18]; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) { + error("Hangup or error on AVDTP server socket"); + g_io_channel_close(chan); + raise(SIGTERM); + return FALSE; + } + + srv_sk = g_io_channel_unix_get_fd(chan); + + size = sizeof(struct sockaddr_l2); + cli_sk = accept(srv_sk, (struct sockaddr *) &addr, &size); + if (cli_sk < 0) { + error("AVDTP accept: %s (%d)", strerror(errno), errno); + return TRUE; + } + + bacpy(&dst, &addr.l2_bdaddr); + + ba2str(&dst, address); + debug("AVDTP: incoming connect from %s", address); + + size = sizeof(struct sockaddr_l2); + if (getsockname(srv_sk, (struct sockaddr *) &addr, &size) < 0) { + error("getsockname: %s (%d)", strerror(errno), errno); + close(cli_sk); + return TRUE; + } + + bacpy(&src, &addr.l2_bdaddr); + + memset(&l2o, 0, sizeof(l2o)); + size = sizeof(l2o); + if (getsockopt(cli_sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &size) < 0) { + error("getsockopt(L2CAP_OPTIONS): %s (%d)", strerror(errno), + errno); + close(cli_sk); + return TRUE; + } + + session = avdtp_get(&src, &dst); + + if (session->pending_open && session->pending_open->open_acp) { + handle_transport_connect(session, cli_sk, l2o.imtu); + return TRUE; + } + + if (session->sock >= 0) { + error("Refusing unexpected connect from %s", address); + close(cli_sk); + return TRUE; + } + + if (session->ref == 1) + set_disconnect_timer(session); + + session->mtu = l2o.imtu; + session->buf = g_malloc0(session->mtu); + session->sock = cli_sk; + session->state = AVDTP_SESSION_STATE_CONNECTED; + + cli_io = g_io_channel_unix_new(session->sock); + session->io = g_io_add_watch(cli_io, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) session_cb, session); + g_io_channel_unref(cli_io); + + return TRUE; +} + +static GIOChannel *avdtp_server_socket(void) +{ + int sock, lm; + struct sockaddr_l2 addr; + GIOChannel *io; + + sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sock < 0) { + error("AVDTP server socket: %s (%d)", strerror(errno), errno); + return NULL; + } + + lm = L2CAP_LM_SECURE; + if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &lm, sizeof(lm)) < 0) { + error("AVDTP server setsockopt: %s (%d)", strerror(errno), errno); + close(sock); + return NULL; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, BDADDR_ANY); + addr.l2_psm = htobs(AVDTP_PSM); + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + error("AVDTP server bind: %s", strerror(errno), errno); + close(sock); + return NULL; + } + + if (listen(sock, 4) < 0) { + error("AVDTP server listen: %s", strerror(errno), errno); + close(sock); + return NULL; + } + + io = g_io_channel_unix_new(sock); + if (!io) { + error("Unable to allocate new io channel"); + close(sock); + return NULL; + } + + return io; +} + +const char *avdtp_strerror(struct avdtp_error *err) +{ + if (err->type == AVDTP_ERROR_ERRNO) + return strerror(err->err.posix_errno); + + switch(err->err.error_code) { + case AVDTP_BAD_HEADER_FORMAT: + return "Bad Header Format"; + case AVDTP_BAD_LENGTH: + return "Bad Packet Lenght"; + case AVDTP_BAD_ACP_SEID: + return "Bad Acceptor SEID"; + case AVDTP_SEP_IN_USE: + return "Stream End Point in Use"; + case AVDTP_SEP_NOT_IN_USE: + return "Stream End Point Not in Use"; + case AVDTP_BAD_SERV_CATEGORY: + return "Bad Service Category"; + case AVDTP_BAD_PAYLOAD_FORMAT: + return "Bad Payload format"; + case AVDTP_NOT_SUPPORTED_COMMAND: + return "Command Not Supported"; + case AVDTP_INVALID_CAPABILITIES: + return "Invalid Capabilities"; + case AVDTP_BAD_RECOVERY_TYPE: + return "Bad Recovery Type"; + case AVDTP_BAD_MEDIA_TRANSPORT_FORMAT: + return "Bad Media Transport Format"; + case AVDTP_BAD_RECOVERY_FORMAT: + return "Bad Recovery Format"; + case AVDTP_BAD_ROHC_FORMAT: + return "Bad Header Compression Format"; + case AVDTP_BAD_CP_FORMAT: + return "Bad Content Protetion Format"; + case AVDTP_BAD_MULTIPLEXING_FORMAT: + return "Bad Multiplexing Format"; + case AVDTP_UNSUPPORTED_CONFIGURATION: + return "Configuration not supported"; + case AVDTP_BAD_STATE: + return "Bad State"; + default: + return "Unknow error"; + } +} + +int avdtp_init(void) +{ + if (avdtp_server) + return 0; + + avdtp_server = avdtp_server_socket(); + if (!avdtp_server) + return -1; + + g_io_add_watch(avdtp_server, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + (GIOFunc) avdtp_server_cb, NULL); + + return 0; +} + +void avdtp_exit(void) +{ + if (!avdtp_server) + return; + + g_io_channel_close(avdtp_server); + g_io_channel_unref(avdtp_server); + avdtp_server = NULL; +} diff --git a/audio/avdtp.h b/audio/avdtp.h index 153a1731..b4af9eb3 100644 --- a/audio/avdtp.h +++ b/audio/avdtp.h @@ -20,4 +20,181 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ +#ifndef __AVDTP_H__ +#define __AVDTP_H__ +#include <stdint.h> +#include <bluetooth/bluetooth.h> + +struct avdtp; +struct avdtp_stream; +struct avdtp_local_sep; +struct avdtp_remote_sep; +struct avdtp_error; + +/* SEP capability categories */ +#define AVDTP_MEDIA_TRANSPORT 0x01 +#define AVDTP_REPORTING 0x02 +#define AVDTP_RECOVERY 0x03 +#define AVDTP_CONTENT_PROTECTION 0x04 +#define AVDTP_HEADER_COMPRESSION 0x05 +#define AVDTP_MULTIPLEXING 0x06 +#define AVDTP_MEDIA_CODEC 0x07 + +/* AVDTP error definitions */ +#define AVDTP_BAD_HEADER_FORMAT 0x01 +#define AVDTP_BAD_LENGTH 0x11 +#define AVDTP_BAD_ACP_SEID 0x12 +#define AVDTP_SEP_IN_USE 0x13 +#define AVDTP_SEP_NOT_IN_USE 0x14 +#define AVDTP_BAD_SERV_CATEGORY 0x17 +#define AVDTP_BAD_PAYLOAD_FORMAT 0x18 +#define AVDTP_NOT_SUPPORTED_COMMAND 0x19 +#define AVDTP_INVALID_CAPABILITIES 0x1A +#define AVDTP_BAD_RECOVERY_TYPE 0x22 +#define AVDTP_BAD_MEDIA_TRANSPORT_FORMAT 0x23 +#define AVDTP_BAD_RECOVERY_FORMAT 0x25 +#define AVDTP_BAD_ROHC_FORMAT 0x26 +#define AVDTP_BAD_CP_FORMAT 0x27 +#define AVDTP_BAD_MULTIPLEXING_FORMAT 0x28 +#define AVDTP_UNSUPPORTED_CONFIGURATION 0x29 +#define AVDTP_BAD_STATE 0x31 + +/* SEP types definitions */ +#define AVDTP_SEP_TYPE_SOURCE 0x00 +#define AVDTP_SEP_TYPE_SINK 0x01 + +/* Media types definitions */ +#define AVDTP_MEDIA_TYPE_AUDIO 0x00 +#define AVDTP_MEDIA_TYPE_VIDEO 0x01 +#define AVDTP_MEDIA_TYPE_MULTIMEDIA 0x02 + +typedef enum { + AVDTP_STATE_IDLE, + AVDTP_STATE_CONFIGURED, + AVDTP_STATE_OPEN, + AVDTP_STATE_STREAMING, + AVDTP_STATE_CLOSING, + AVDTP_STATE_ABORTING, +} avdtp_state_t; + +struct avdtp_service_capability { + uint8_t category; + uint8_t length; + uint8_t data[0]; +} __attribute__ ((packed)); + +struct avdtp_media_codec_capability { + uint8_t rfa0:4; + uint8_t media_type:4; + uint8_t media_codec_type; + uint8_t data[0]; +} __attribute__ ((packed)); + +typedef void (*avdtp_stream_state_cb) (struct avdtp_stream *stream, + avdtp_state_t old_state, + avdtp_state_t new_state, + struct avdtp_error *err, + void *user_data); + +/* Callbacks for when a reply is received to a command that we sent */ +struct avdtp_sep_cfm { + void (*set_configuration) (struct avdtp_local_sep *lsep, + struct avdtp_stream *stream); + void (*get_configuration) (struct avdtp_local_sep *lsep, + struct avdtp_stream *stream); + void (*open) (struct avdtp_local_sep *lsep, + struct avdtp_stream *stream); + void (*start) (struct avdtp_local_sep *lsep, + struct avdtp_stream *stream); + void (*suspend) (struct avdtp_local_sep *lsep, + struct avdtp_stream *stream); + void (*close) (struct avdtp_local_sep *lsep, + struct avdtp_stream *stream); + void (*abort) (struct avdtp_local_sep *lsep, + struct avdtp_stream *stream); + void (*reconfigure) (struct avdtp_local_sep *lsep); +}; + +/* Callbacks for indicating when we received a new command. The return value + * indicates whether the command should be rejected or accepted */ +struct avdtp_sep_ind { + gboolean (*get_capability) (struct avdtp_local_sep *sep, + GSList **caps, uint8_t *err); + gboolean (*set_configuration) (struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + uint8_t int_seid, GSList *caps, + uint8_t *err); + gboolean (*get_configuration) (struct avdtp_local_sep *lsep, + uint8_t *err); + gboolean (*open) (struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, uint8_t *err); + gboolean (*start) (struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + uint8_t *err); + gboolean (*suspend) (struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + uint8_t *err); + gboolean (*close) (struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + uint8_t *err); + gboolean (*abort) (struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err); + gboolean (*reconfigure) (struct avdtp_local_sep *lsep, uint8_t *err); +}; + +typedef void (*avdtp_discover_cb_t) (struct avdtp *session, GSList *seps, + int err, void *user_data); + +struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst); + +void avdtp_unref(struct avdtp *session); +struct avdtp *avdtp_ref(struct avdtp *session); + +struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, + void *data, int size); + +struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep); + +int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb, + void *user_data); + +void avdtp_stream_set_cb(struct avdtp *session, struct avdtp_stream *stream, + avdtp_stream_state_cb cb, void *data); + +gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock, + uint16_t *mtu, GSList **caps); + +int avdtp_set_configuration(struct avdtp *session, + struct avdtp_remote_sep *rsep, + struct avdtp_local_sep *lsep, + GSList *caps, + struct avdtp_stream **stream); + +int avdtp_get_configuration(struct avdtp *session, + struct avdtp_stream *stream); + +int avdtp_open(struct avdtp *session, struct avdtp_stream *stream); +int avdtp_reconfigure(struct avdtp *session, struct avdtp_stream *stream); +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); + +struct avdtp_local_sep *avdtp_register_sep(uint8_t type, uint8_t media_type, + struct avdtp_sep_ind *ind, + struct avdtp_sep_cfm *cfm); + +/* Find a matching pair of local and remote SEP ID's */ +int avdtp_get_seps(struct avdtp *session, uint8_t type, uint8_t media, + uint8_t codec, struct avdtp_local_sep **lsep, + struct avdtp_remote_sep **rsep); + +int avdtp_unregister_sep(struct avdtp_local_sep *sep); + +const char *avdtp_strerror(struct avdtp_error *err); + +int avdtp_init(void); +void avdtp_exit(void); + +#endif diff --git a/audio/device.c b/audio/device.c index 785d0115..af7cca80 100644 --- a/audio/device.c +++ b/audio/device.c @@ -100,21 +100,24 @@ static DBusMethodVTable device_methods[] = { { NULL, NULL, NULL, NULL } }; -static void device_free(struct device *device) +static void device_free(struct device *dev) { - if (device->headset) - headset_free(device); + if (dev->headset) + headset_free(dev); - if (device->conn) - dbus_connection_unref(device->conn); + if (dev->sink) + sink_free(dev); - if (device->adapter_path) - g_free(device->adapter_path); + if (dev->conn) + dbus_connection_unref(dev->conn); - if (device->path) - g_free(device->path); + if (dev->adapter_path) + g_free(dev->adapter_path); - g_free(device); + if (dev->path) + g_free(dev->path); + + g_free(dev); } static void device_unregister(DBusConnection *conn, void *data) @@ -129,7 +132,7 @@ static void device_unregister(DBusConnection *conn, void *data) struct device *device_register(DBusConnection *conn, const char *path, bdaddr_t *bda) { - struct device *device; + struct device *dev; bdaddr_t src; int dev_id; @@ -141,12 +144,12 @@ struct device *device_register(DBusConnection *conn, if ((dev_id < 0) || (hci_devba(dev_id, &src) < 0)) return NULL; - device = g_new0(struct device, 1); + dev = g_new0(struct device, 1); - if (!dbus_connection_create_object_path(conn, path, device, + if (!dbus_connection_create_object_path(conn, path, dev, device_unregister)) { error("D-Bus failed to register %s path", path); - device_free(device); + device_free(dev); return NULL; } @@ -158,58 +161,69 @@ struct device *device_register(DBusConnection *conn, return NULL; } - device->path = g_strdup(path); - bacpy(&device->dst, bda); - bacpy(&device->src, &src); - device->conn = dbus_connection_ref(conn); - device->adapter_path = g_malloc0(16); - snprintf(device->adapter_path, 16, "/org/bluez/hci%d", dev_id); + dev->path = g_strdup(path); + bacpy(&dev->dst, bda); + bacpy(&dev->src, &src); + dev->conn = dbus_connection_ref(conn); + dev->adapter_path = g_malloc0(16); + snprintf(dev->adapter_path, 16, "/org/bluez/hci%d", dev_id); - return device; + return dev; } -int device_store(struct device *device, gboolean is_default) +int device_store(struct device *dev, gboolean is_default) { char value[64]; char filename[PATH_MAX + 1]; char src_addr[18], dst_addr[18]; + int offset = 0; - if (!device->path) + if (!dev->path) return -EINVAL; - ba2str(&device->dst, dst_addr); - ba2str(&device->src, src_addr); + ba2str(&dev->dst, dst_addr); + ba2str(&dev->src, src_addr); create_name(filename, PATH_MAX, STORAGEDIR, src_addr, "audio"); create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (is_default) textfile_put(filename, "default", dst_addr); - if (device->headset) - snprintf(value, 64, "headset"); - else if (device->gateway) - snprintf(value, 64, "gateway"); - else if (device->sink) - snprintf(value, 64, "sink"); - else if (device->source) - snprintf(value, 64, "source"); - else if (device->control) - snprintf(value, 64, "control"); - else - snprintf(value, 64, "target"); + if (dev->headset) { + snprintf(value, 64, "headset "); + offset += strlen("headset "); + } + if (dev->gateway) { + snprintf(value + offset, 64 - offset, "gateway "); + offset += strlen("gateway "); + } + if (dev->sink) { + snprintf(value + offset, 64 - offset, "sink "); + offset += strlen("sink "); + } + if (dev->source) { + snprintf(value + offset, 64 - offset, "source "); + offset += strlen("source "); + } + if (dev->control) { + snprintf(value + offset, 64 - offset, "control "); + offset += strlen("control "); + } + if (dev->target) + snprintf(value + offset, 64 - offset, "target"); return textfile_put(filename, dst_addr, value); } -void device_finish_sdp_transaction(struct device *device) +void device_finish_sdp_transaction(struct device *dev) { char address[18], *addr_ptr = address; DBusMessage *msg, *reply; DBusError derr; - ba2str(&device->dst, address); + ba2str(&dev->dst, address); - msg = dbus_message_new_method_call("org.bluez", device->adapter_path, + msg = dbus_message_new_method_call("org.bluez", dev->adapter_path, "org.bluez.Adapter", "FinishRemoteServiceTransaction"); if (!msg) { @@ -221,7 +235,7 @@ void device_finish_sdp_transaction(struct device *device) DBUS_TYPE_INVALID); dbus_error_init(&derr); - reply = dbus_connection_send_with_reply_and_block(device->conn, + reply = dbus_connection_send_with_reply_and_block(dev->conn, msg, -1, &derr); dbus_message_unref(msg); @@ -236,3 +250,114 @@ void device_finish_sdp_transaction(struct device *device) dbus_message_unref(reply); } + +int device_get_config(struct device *dev, int sock, struct ipc_packet *req, + int pkt_len, struct ipc_data_cfg **rsp) +{ + if (dev->sink && sink_is_active(dev)) + return sink_get_config(dev, sock, req, pkt_len, rsp); + else if (dev->headset && headset_is_active(dev)) + return headset_get_config(dev, sock, req, pkt_len, rsp); + else if (dev->sink) + return sink_get_config(dev, sock, req, pkt_len, rsp); + else if (dev->headset) + return headset_get_config(dev, sock, req, pkt_len, rsp); + + return -EINVAL; +} + +static avdtp_state_t ipc_to_avdtp_state(uint8_t ipc_state) +{ + switch (ipc_state) { + case STATE_DISCONNECTED: + return AVDTP_STATE_IDLE; + case STATE_CONNECTING: + return AVDTP_STATE_CONFIGURED; + case STATE_CONNECTED: + return AVDTP_STATE_OPEN; + case STATE_STREAM_STARTING: + case STATE_STREAMING: + return AVDTP_STATE_STREAMING; + default: + error("Unknown ipc state"); + return AVDTP_STATE_IDLE; + } +} + +static headset_state_t ipc_to_hs_state(uint8_t ipc_state) +{ + switch (ipc_state) { + case STATE_DISCONNECTED: + return HEADSET_STATE_DISCONNECTED; + case STATE_CONNECTING: + return HEADSET_STATE_CONNECT_IN_PROGRESS; + case STATE_CONNECTED: + return HEADSET_STATE_CONNECTED; + case STATE_STREAM_STARTING: + return HEADSET_STATE_PLAY_IN_PROGRESS; + case STATE_STREAMING: + return HEADSET_STATE_PLAYING; + default: + error("Unknown ipc state"); + return HEADSET_STATE_DISCONNECTED; + } +} + +void device_set_state(struct device *dev, uint8_t state) +{ + if (dev->sink && sink_is_active(dev)) + sink_set_state(dev, ipc_to_avdtp_state(state)); + else if (dev->headset && headset_is_active(dev)) + headset_set_state(dev, ipc_to_hs_state(state)); +} + +static uint8_t avdtp_to_ipc_state(avdtp_state_t state) +{ + switch (state) { + case AVDTP_STATE_IDLE: + return STATE_DISCONNECTED; + case AVDTP_STATE_CONFIGURED: + return STATE_CONNECTING; + case AVDTP_STATE_OPEN: + return STATE_CONNECTED; + case AVDTP_STATE_STREAMING: + return STATE_STREAMING; + default: + error("Unknown avdt state"); + return AVDTP_STATE_IDLE; + } +} + +static uint8_t hs_to_ipc_state(headset_state_t state) +{ + switch (state) { + case HEADSET_STATE_DISCONNECTED: + return STATE_DISCONNECTED; + case HEADSET_STATE_CONNECT_IN_PROGRESS: + return STATE_CONNECTING; + case HEADSET_STATE_CONNECTED: + return STATE_CONNECTED; + case HEADSET_STATE_PLAY_IN_PROGRESS: + return STATE_STREAMING; + default: + error("Unknown headset state"); + return AVDTP_STATE_IDLE; + } +} + +uint8_t device_get_state(struct device *dev) +{ + avdtp_state_t sink_state; + headset_state_t hs_state; + + if (dev->sink && sink_is_active(dev)) { + sink_state = sink_get_state(dev); + return avdtp_to_ipc_state(sink_state); + } + else if (dev->headset && headset_is_active(dev)) { + hs_state = headset_get_state(dev); + return hs_to_ipc_state(hs_state); + } + + return STATE_DISCONNECTED; +} diff --git a/audio/device.h b/audio/device.h index 71e1053f..62c13e9e 100644 --- a/audio/device.h +++ b/audio/device.h @@ -75,3 +75,10 @@ struct device *device_register(DBusConnection *conn, int device_store(struct device *device, gboolean is_default); void device_finish_sdp_transaction(struct device *device); + +int device_get_config(struct device *dev, int sock, struct ipc_packet *req, + int pkt_len, struct ipc_data_cfg **rsp); + +void device_set_state(struct device *dev, uint8_t state); + +uint8_t device_get_state(struct device *dev); diff --git a/audio/headset.c b/audio/headset.c index 9dccc5ad..2bc8583b 100644 --- a/audio/headset.c +++ b/audio/headset.c @@ -53,6 +53,7 @@ #include "logging.h" #include "manager.h" #include "error.h" +#include "unix.h" #define RING_INTERVAL 3000 @@ -68,6 +69,7 @@ struct pending_connect { DBusMessage *msg; GIOChannel *io; struct ipc_packet *pkt; + int pkt_len; guint io_id; int sock; int err; @@ -102,7 +104,7 @@ static int rfcomm_connect(struct device *device, struct pending_connect *c); static void pending_connect_free(struct pending_connect *c) { if (c->pkt) - unix_send_cfg(c->sock, c->pkt); + g_free(c->pkt); if (c->io) { g_io_channel_close(c->io); g_io_channel_unref(c->io); @@ -339,8 +341,19 @@ static void pending_connect_ok(struct pending_connect *c, struct device *dev) if (reply) send_message_and_unref(dev->conn, reply); } - else if (c->pkt) - headset_get_config(dev, c->sock, c->pkt); + else if (c->pkt) { + struct ipc_data_cfg *rsp; + int ret; + + ret = headset_get_config(dev, c->sock, c->pkt, + c->pkt_len, &rsp); + if (ret == 0) { + unix_send_cfg(c->sock, rsp); + g_free(rsp); + } + else + unix_send_cfg(c->sock, NULL); + } pending_connect_free(c); } @@ -349,6 +362,8 @@ static void pending_connect_failed(struct pending_connect *c, struct device *dev { if (c->msg) err_connect_failed(dev->conn, c->msg, strerror(c->err)); + if (c->pkt) + unix_send_cfg(c->sock, NULL); pending_connect_free(c); } @@ -1358,7 +1373,8 @@ register_iface: void headset_free(void *device) { - struct headset *hs = ((struct device *) device)->headset; + struct device *dev = device; + struct headset *hs = dev->headset; if (hs->sco) { g_io_channel_close(hs->sco); @@ -1371,54 +1387,54 @@ void headset_free(void *device) } g_free(hs); - hs = NULL; + dev->headset = NULL; } -int headset_get_config(void *device, int sock, struct ipc_packet *pkt) +int headset_get_config(void *device, int sock, struct ipc_packet *pkt, + int pkt_len, struct ipc_data_cfg **cfg) { struct headset *hs = ((struct device *) device)->headset; - struct ipc_data_cfg *cfg = (struct ipc_data_cfg *) pkt->data; int err = EINVAL; struct pending_connect *c; + struct ipc_data_cfg *rsp; + + if (hs->rfcomm && hs->sco) + goto proceed; - if (hs->rfcomm == NULL) { - c = g_try_new0(struct pending_connect, 1); - if (c == NULL) - goto error; - c->sock = sock; - c->pkt = pkt; + c = g_new0(struct pending_connect, 1); + c->sock = sock; + c->pkt = g_malloc(pkt_len); + memcpy(c->pkt, pkt, pkt_len); + + if (hs->rfcomm == NULL) err = rfcomm_connect(device, c); - if (err < 0) - goto error; - return 0; - } - else if (hs->sco == NULL) { - c = g_try_new0(struct pending_connect, 1); - if (c == NULL) - goto error; - c->sock = sock; - c->pkt = pkt; + else if (hs->sco == NULL) err = sco_connect(device, c); - if (err < 0) - goto error; - return 0; - } + else + goto error; + + if (err < 0) + goto error; - cfg->fd = g_io_channel_unix_get_fd(hs->sco); - cfg->fd_opt = CFG_FD_OPT_READWRITE; - cfg->encoding = 0; - cfg->bitpool = 0; - cfg->channels = 1; - cfg->pkt_len = 48; - cfg->sample_size = 2; - cfg->rate = 8000; + return 1; + +proceed: + *cfg = g_new0(struct ipc_data_cfg, 1); + rsp = *cfg; + rsp->fd = g_io_channel_unix_get_fd(hs->sco); + rsp->fd_opt = CFG_FD_OPT_READWRITE; + rsp->codec = CFG_CODEC_NONE; + rsp->channels = 1; + rsp->channel_mode = CFG_CHANNEL_MODE_MONO; + rsp->pkt_len = 48; + rsp->sample_size = 2; + rsp->rate = 8000; return 0; error: if (c) pending_connect_free(c); - cfg->fd = -1; return -err; } @@ -1547,3 +1563,14 @@ int headset_get_channel(void *device) return hs->rfcomm_ch; } + +gboolean headset_is_active(void *device) +{ + struct device *dev = device; + struct headset *hs = dev->headset; + + if (hs->state != HEADSET_STATE_DISCONNECTED) + return TRUE; + + return FALSE; +} diff --git a/audio/headset.h b/audio/headset.h index d3fd86d9..530bdea8 100644 --- a/audio/headset.h +++ b/audio/headset.h @@ -20,12 +20,15 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ +#ifndef __AUDIO_HEADSET_H__ +#define __AUDIO_HEADSET_H__ + #include <bluetooth/sdp.h> #include <bluetooth/sdp_lib.h> #include <dbus/dbus.h> -#include "unix.h" +#include "ipc.h" #define AUDIO_HEADSET_INTERFACE "org.bluez.audio.Headset" @@ -40,11 +43,11 @@ typedef enum { } headset_event_t; typedef enum { - HEADSET_STATE_DISCONNECTED = STATE_DISCONNECTED, - HEADSET_STATE_CONNECT_IN_PROGRESS = STATE_CONNECTING, - HEADSET_STATE_CONNECTED = STATE_CONNECTED, - HEADSET_STATE_PLAY_IN_PROGRESS = STATE_STREAM_STARTING, - HEADSET_STATE_PLAYING = STATE_STREAMING, + HEADSET_STATE_DISCONNECTED, + HEADSET_STATE_CONNECT_IN_PROGRESS, + HEADSET_STATE_CONNECTED, + HEADSET_STATE_PLAY_IN_PROGRESS, + HEADSET_STATE_PLAYING } headset_state_t; typedef enum { @@ -55,13 +58,14 @@ typedef enum { struct headset; struct headset *headset_init(void *device, sdp_record_t *record, - uint16_t svc); + uint16_t svc); void headset_free(void *device); void headset_update(void *device, sdp_record_t *record, uint16_t svc); -int headset_get_config(void *device, int sock, struct ipc_packet *pkt); +int headset_get_config(void *device, int sock, struct ipc_packet *pkt, + int pkt_len, struct ipc_data_cfg **rsp); headset_type_t headset_get_type(void *device); void headset_set_type(void *device, headset_type_t type); @@ -73,3 +77,7 @@ headset_state_t headset_get_state(void *device); void headset_set_state(void *device, headset_state_t state); int headset_get_channel(void *device); + +gboolean headset_is_active(void *device); + +#endif diff --git a/audio/ipc.h b/audio/ipc.h index e56dca24..bd31abbc 100644 --- a/audio/ipc.h +++ b/audio/ipc.h @@ -20,11 +20,15 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ +#ifndef __AUDIO_IPC_H__ +#define __AUDIO_IPC_H__ #include <stdint.h> #define IPC_TYPE_CONNECT 0x0001 +#define IPC_MTU 32 + #define IPC_SOCKET_NAME "\0/org/bluez/audio" #ifndef UNIX_PATH_MAX @@ -32,22 +36,22 @@ #endif /* Supported roles */ -#define PKT_ROLE_NONE 0 -#define PKT_ROLE_AUTO 1 -#define PKT_ROLE_VOICE 2 -#define PKT_ROLE_HIFI 3 +#define PKT_ROLE_NONE 0 +#define PKT_ROLE_AUTO 1 +#define PKT_ROLE_VOICE 2 +#define PKT_ROLE_HIFI 3 /* Packet types */ -#define PKT_TYPE_CFG_REQ 0 -#define PKT_TYPE_CFG_RSP 1 -#define PKT_TYPE_STATE_REQ 2 -#define PKT_TYPE_STATE_RSP 3 -#define PKT_TYPE_CTL_REQ 4 -#define PKT_TYPE_CTL_RSP 5 -#define PKT_TYPE_CTL_NTFY 6 +#define PKT_TYPE_CFG_REQ 0 +#define PKT_TYPE_CFG_RSP 1 +#define PKT_TYPE_STATE_REQ 2 +#define PKT_TYPE_STATE_RSP 3 +#define PKT_TYPE_CTL_REQ 4 +#define PKT_TYPE_CTL_RSP 5 +#define PKT_TYPE_CTL_NTFY 6 /* Errors codes */ -#define PKT_ERROR_NONE 0 +#define PKT_ERROR_NONE 0 struct ipc_packet { uint8_t id; /* Device id */ @@ -59,52 +63,76 @@ struct ipc_packet { } __attribute__ ((packed)); /* File descriptor options */ -#define CFG_FD_OPT_READ 0 -#define CFG_FD_OPT_WRITE 1 -#define CFG_FD_OPT_READWRITE 2 +#define CFG_FD_OPT_READ 0 +#define CFG_FD_OPT_WRITE 1 +#define CFG_FD_OPT_READWRITE 2 + +/* Audio channel mode */ +#define CFG_CHANNEL_MODE_MONO (1 << 3) +#define CFG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define CFG_CHANNEL_MODE_STEREO (1 << 1) +#define CFG_CHANNEL_MODE_JOINT_STEREO 1 + +/* Codec options */ +#define CFG_CODEC_NONE 0 +#define CFG_CODEC_SBC 1 struct ipc_data_cfg { int fd; /* Stream file descriptor */ uint8_t fd_opt; /* Stream file descriptor options: read, write or readwrite*/ - uint8_t encoding; /* Stream encoding */ - uint8_t bitpool; /* Encoding bitpool */ uint8_t channels; /* Number of audio channel */ - uint8_t pkt_len; /* Stream packet length */ + uint8_t channel_mode; /* Audio channel mode*/ + uint16_t pkt_len; /* Stream packet length */ uint8_t sample_size; /* Sample size in bytes */ uint16_t rate; /* Stream sample rate */ + uint8_t codec; /* Stream codec */ + uint8_t data[0]; /* Codec payload */ +} __attribute__ ((packed)); + +/* SBC codec options */ +#define CODEC_SBC_ALLOCATION_SNR (1 << 1) +#define CODEC_SBC_ALLOCATION_LOUDNESS 1 + +struct ipc_codec_sbc { + uint8_t allocation; + uint8_t subbands; + uint8_t blocks; + uint8_t bitpool; } __attribute__ ((packed)); /* Device status */ -#define STATE_DISCONNECTED 0 -#define STATE_CONNECTING 1 -#define STATE_CONNECTED 2 -#define STATE_STREAM_STARTING 3 -#define STATE_STREAMING 4 +#define STATE_DISCONNECTED 0 +#define STATE_CONNECTING 1 +#define STATE_CONNECTED 2 +#define STATE_STREAM_STARTING 3 +#define STATE_STREAMING 4 struct ipc_data_state { uint8_t state; /* Stream state */ } __attribute__ ((packed)); -#define CTL_MODE_PLAYBACK 0 -#define CTL_MODE_CAPTURE 1 -#define CTL_MODE_GENERAL 2 +#define CTL_MODE_PLAYBACK 0 +#define CTL_MODE_CAPTURE 1 +#define CTL_MODE_GENERAL 2 /* Supported control operations */ -#define CTL_KEY_POWER 0x40 -#define CTL_KEY_VOL_UP 0x41 -#define CTL_KEY_VOL_DOWN 0x42 -#define CTL_KEY_MUTE 0x43 -#define CTL_KEY_PLAY 0x44 -#define CTL_KEY_STOP 0x45 -#define CTL_KEY_PAUSE 0x46 -#define CTL_KEY_RECORD 0x47 -#define CTL_KEY_REWIND 0x48 -#define CTL_KEY_FAST_FORWARD 0x49 -#define CTL_KEY_EJECT 0x4A -#define CTL_KEY_FORWARD 0x4B -#define CTL_KEY_BACKWARD 0x4C +#define CTL_KEY_POWER 0x40 +#define CTL_KEY_VOL_UP 0x41 +#define CTL_KEY_VOL_DOWN 0x42 +#define CTL_KEY_MUTE 0x43 +#define CTL_KEY_PLAY 0x44 +#define CTL_KEY_STOP 0x45 +#define CTL_KEY_PAUSE 0x46 +#define CTL_KEY_RECORD 0x47 +#define CTL_KEY_REWIND 0x48 +#define CTL_KEY_FAST_FORWARD 0x49 +#define CTL_KEY_EJECT 0x4A +#define CTL_KEY_FORWARD 0x4B +#define CTL_KEY_BACKWARD 0x4C struct ipc_data_ctl { uint8_t mode; /* Control Mode */ uint8_t key; /* Control Key */ } __attribute__ ((packed)); + +#endif diff --git a/audio/main.c b/audio/main.c index 4da85a29..72e54133 100644 --- a/audio/main.c +++ b/audio/main.c @@ -35,7 +35,7 @@ #include "dbus.h" #include "logging.h" - +#include "unix.h" #include "manager.h" static gboolean disable_hfp = TRUE; @@ -43,6 +43,15 @@ static gboolean sco_hci = FALSE; static GMainLoop *main_loop = NULL; +static struct enabled_interfaces enabled = { + .headset = TRUE, + .gateway = FALSE, + .sink = TRUE, + .source = FALSE, + .control = FALSE, + .target = FALSE, +}; + static void sig_term(int sig) { g_main_loop_quit(main_loop); @@ -53,7 +62,7 @@ static void read_config(const char *file) GKeyFile *keyfile; GError *err = NULL; gboolean no_hfp; - char *sco_routing; + char *str; keyfile = g_key_file_new(); @@ -64,21 +73,42 @@ static void read_config(const char *file) return; } - sco_routing = g_key_file_get_string(keyfile, "General", + str = g_key_file_get_string(keyfile, "General", "SCORouting", &err); if (err) { debug("%s: %s", file, err->message); g_error_free(err); err = NULL; } else { - if (strcmp(sco_routing, "PCM") == 0) + if (strcmp(str, "PCM") == 0) sco_hci = FALSE; - else if (strcmp(sco_routing, "HCI") == 0) + else if (strcmp(str, "HCI") == 0) sco_hci = TRUE; else - error("Invalid Headset Routing value: %s", - sco_routing); - g_free(sco_routing); + error("Invalid Headset Routing value: %s", str); + g_free(str); + } + + str = g_key_file_get_string(keyfile, "General", + "Disabled", &err); + if (err) { + debug("%s: %s", file, err->message); + g_error_free(err); + err = NULL; + } else { + if (strstr(str, "Headset")) + enabled.headset = FALSE; + if (strstr(str, "Gateway")) + enabled.gateway = FALSE; + if (strstr(str, "Sink")) + enabled.sink = FALSE; + if (strstr(str, "Source")) + enabled.source = FALSE; + if (strstr(str, "Control")) + enabled.control = FALSE; + if (strstr(str, "Target")) + enabled.target = FALSE; + g_free(str); } no_hfp = g_key_file_get_boolean(keyfile, "Headset", @@ -131,7 +161,7 @@ int main(int argc, char *argv[]) exit(1); } - if (audio_init(conn, disable_hfp, sco_hci) < 0) { + if (audio_init(conn, &enabled, disable_hfp, sco_hci) < 0) { error("Audio init failed!"); exit(1); } diff --git a/audio/manager.c b/audio/manager.c index 7e250785..58f19ed0 100644 --- a/audio/manager.c +++ b/audio/manager.c @@ -52,6 +52,8 @@ #include "textfile.h" #include "manager.h" #include "error.h" +#include "a2dp.h" +#include "avdtp.h" typedef enum { HEADSET = 1 << 0, @@ -93,6 +95,8 @@ static uint32_t hf_record_id = 0; static GIOChannel *hs_server = NULL; static GIOChannel *hf_server = NULL; +static const struct enabled_interfaces *enabled; + static void get_next_record(struct audio_sdp_data *data); static DBusHandlerResult get_handles(const char *uuid, struct audio_sdp_data *data); @@ -190,6 +194,8 @@ static gboolean server_is_enabled(uint16_t svc) case HANDSFREE_SVCLASS_ID: ret = (hf_server != NULL); break; + case AUDIO_SINK_SVCLASS_ID: + return enabled->sink; default: ret = FALSE; break; @@ -233,6 +239,8 @@ static void handle_record(sdp_record_t *record, struct device *device) break; case AUDIO_SINK_SVCLASS_ID: debug("Found Audio Sink"); + if (device->sink == NULL) + device->sink = sink_init(device); break; case AUDIO_SOURCE_SVCLASS_ID: debug("Found Audio Source"); @@ -713,7 +721,7 @@ static gboolean device_supports_interface(struct device *device, return device->source ? TRUE : FALSE; if (strcmp(iface, AUDIO_SINK_INTERFACE) == 0) - return device->sink ? TRUE : FALSE; + return device->sink ? TRUE : FALSE; if (strcmp(iface, AUDIO_CONTROL_INTERFACE) == 0) return device->control ? TRUE : FALSE; @@ -1135,9 +1143,10 @@ static void parse_stored_devices(char *key, char *value, void *data) if (!device) return; - if (strncmp(value, "headset", strlen("headset")) == 0) + if (strstr(value, "headset")) device->headset = headset_init(device, NULL, 0); - + if (strstr(value, "sink")) + device->sink = sink_init(device); add_device(device); } @@ -1353,7 +1362,7 @@ static int hfp_ag_record(sdp_buf_t *buf, uint8_t ch) return ret; } -static uint32_t add_record(uint8_t channel, sdp_buf_t *buf) +uint32_t add_service_record(DBusConnection *conn, sdp_buf_t *buf) { DBusMessage *msg, *reply; DBusError derr; @@ -1397,12 +1406,12 @@ static uint32_t add_record(uint8_t channel, sdp_buf_t *buf) dbus_message_unref(reply); - debug("add_record: got record id 0x%x", rec_id); + debug("add_service_record: got record id 0x%x", rec_id); return rec_id; } -static int remove_record(uint32_t rec_id) +int remove_service_record(DBusConnection *conn, uint32_t rec_id) { DBusMessage *msg, *reply; DBusError derr; @@ -1633,11 +1642,14 @@ static GIOChannel *server_socket(uint8_t *channel) return io; } -static int server_init(DBusConnection *conn, gboolean no_hfp) +static int headset_server_init(DBusConnection *conn, gboolean no_hfp) { uint8_t chan = DEFAULT_HS_AG_CHANNEL; sdp_buf_t buf; + if (!(enabled->headset || enabled->gateway)) + return 0; + hs_server = server_socket(&chan); if (!hs_server) return -1; @@ -1647,7 +1659,7 @@ static int server_init(DBusConnection *conn, gboolean no_hfp) return -1; } - hs_record_id = add_record(chan, &buf); + hs_record_id = add_service_record(conn, &buf); free(buf.data); if (!hs_record_id) { error("Unable to register HS AG service record"); @@ -1673,7 +1685,7 @@ static int server_init(DBusConnection *conn, gboolean no_hfp) return -1; } - hf_record_id = add_record(chan, &buf); + hf_record_id = add_service_record(conn, &buf); free(buf.data); if (!hf_record_id) { error("Unable to register HS AG service record"); @@ -1691,7 +1703,7 @@ static int server_init(DBusConnection *conn, gboolean no_hfp) static void server_exit(void) { if (hs_record_id) { - remove_record(hs_record_id); + remove_service_record(connection, hs_record_id); hs_record_id = 0; } @@ -1701,7 +1713,7 @@ static void server_exit(void) } if (hf_record_id) { - remove_record(hf_record_id); + remove_service_record(connection, hf_record_id); hf_record_id = 0; } @@ -1711,11 +1723,17 @@ static void server_exit(void) } } -int audio_init(DBusConnection *conn, gboolean no_hfp, gboolean sco_hci) +int audio_init(DBusConnection *conn, struct enabled_interfaces *enable, + gboolean no_hfp, gboolean sco_hci) { connection = dbus_connection_ref(conn); - if (server_init(conn, no_hfp) < 0) + enabled = enable; + + if (headset_server_init(conn, no_hfp) < 0) + goto failed; + + if (a2dp_init(conn, enable->sink, enable->source) < 0) goto failed; if (!dbus_connection_create_object_path(conn, AUDIO_MANAGER_PATH, @@ -1754,7 +1772,24 @@ void audio_exit(void) connection = NULL; } -struct device *manager_default_device() +struct device *manager_default_device(void) { return default_dev; } + +struct device *manager_get_connected_device(void) +{ + GSList *l; + + for (l = devices; l != NULL; l = g_slist_next(l)) { + struct device *device = l->data; + + if (device->sink && sink_is_active(device)) + return device; + + if (device->headset && headset_is_active(device)) + return device; + } + + return NULL; +} diff --git a/audio/manager.h b/audio/manager.h index 79fe9090..9fbc4940 100644 --- a/audio/manager.h +++ b/audio/manager.h @@ -29,10 +29,25 @@ #define AUDIO_MANAGER_PATH "/org/bluez/audio" #define AUDIO_MANAGER_INTERFACE "org.bluez.audio.Manager" -int audio_init(DBusConnection *conn, gboolean no_hfp, gboolean sco_hci); +struct enabled_interfaces { + gboolean headset; + gboolean gateway; + gboolean sink; + gboolean source; + gboolean control; + gboolean target; +}; + +int audio_init(DBusConnection *conn, struct enabled_interfaces *enabled, + gboolean no_hfp, gboolean sco_hci); void audio_exit(void); +uint32_t add_service_record(DBusConnection *conn, sdp_buf_t *buf); +int remove_service_record(DBusConnection *conn, uint32_t rec_id); + struct device *manager_device_connected(bdaddr_t *bda); struct device *manager_default_device(); + +struct device *manager_get_connected_device(void); diff --git a/audio/pcm_bluetooth.c b/audio/pcm_bluetooth.c index 92d0383c..3f428ecd 100644 --- a/audio/pcm_bluetooth.c +++ b/audio/pcm_bluetooth.c @@ -27,6 +27,9 @@ #include <sys/socket.h> #include <sys/un.h> +#include <sys/time.h> + +#include <netinet/in.h> #include <alsa/asoundlib.h> #include <alsa/pcm_external.h> @@ -35,6 +38,11 @@ #include <bluetooth/sco.h> #include "ipc.h" +#include "sbc.h" + +/*#define ENABLE_DEBUG */ + +#define BUFFER_SIZE 1024 #ifdef ENABLE_DEBUG #define DBG(fmt, arg...) printf("DEBUG: %s: " fmt "\n" , __FUNCTION__ , ## arg) @@ -50,15 +58,65 @@ #define SCO_RXBUFS 0x04 #endif +struct rtp_header { + uint8_t cc:4; + uint8_t x:1; + uint8_t p:1; + uint8_t v:2; + + uint8_t pt:7; + uint8_t m:1; + + uint16_t sequence_number; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc[0]; +} __attribute__ ((packed)); + +struct rtp_payload { + uint8_t frame_count:4; + uint8_t rfa0:1; + uint8_t is_last_fragment:1; + uint8_t is_first_fragment:1; + uint8_t is_fragmented:1; +} __attribute__ ((packed)); + +struct bluetooth_a2dp { + sbc_t sbc; /* Codec data */ + int samples; /* Number of encoded samples */ + time_t timestamp; /* Codec samples timestamp */ + uint8_t buffer[BUFFER_SIZE]; /* Codec transfer buffer */ + int count; /* Codec transfer buffer counter */ + + int nsamples; /* Cumulative number of codec samples */ + struct timeval ntimestamp; /* Cumulative timeval */ + uint16_t seq_num; /* */ + int frame_count; /* */ + + int bandwithcount; + struct timeval bandwithtimestamp; +}; + struct bluetooth_data { snd_pcm_ioplug_t io; snd_pcm_sframes_t hw_ptr; struct ipc_data_cfg cfg; /* Bluetooth device config */ int sock; /* Daemon unix socket */ - uint8_t *buffer; /* Transfer buffer */ - uint8_t count; /* Transfer buffer counter */ + uint8_t buffer[BUFFER_SIZE]; /* Encoded transfer buffer */ + int count; /* Transfer buffer counter */ + struct bluetooth_a2dp a2dp; /* a2dp data */ }; +void memcpy_changeendian(void *dst, const void *src, int size) +{ + int i; + const uint16_t *ptrsrc = src; + uint16_t *ptrdst = dst; + for (i = 0; i < size / 2; i++) { + *ptrdst++ = htons(*ptrsrc++); + } +} + static int bluetooth_start(snd_pcm_ioplug_t *io) { DBG("bluetooth_start %p", io); @@ -77,49 +135,21 @@ static snd_pcm_sframes_t bluetooth_pointer(snd_pcm_ioplug_t *io) { struct bluetooth_data *data = io->private_data; - DBG("bluetooth_pointer %p", io); - - DBG("hw_ptr=%lu", data->hw_ptr); +#if 0 + DBG("bluetooth_pointer %p, hw_ptr=%lu", io, data->hw_ptr); +#endif return data->hw_ptr; } static void bluetooth_exit(struct bluetooth_data *data) { - int ret, len = sizeof(struct ipc_packet) + sizeof(struct ipc_data_state); - struct ipc_packet *pkt; - struct ipc_data_state *state; - - DBG("Sending PKT_TYPE_STATUS_REQ..."); - - if ((pkt = malloc(len)) == NULL) - goto done; - - memset(pkt, 0, len); - pkt->type = PKT_TYPE_STATE_REQ; - pkt->role = PKT_ROLE_NONE; - pkt->error = PKT_ERROR_NONE; - - state = (struct ipc_data_state *) pkt->data; - state->state = STATE_DISCONNECTED; - - if ((ret = send(data->sock, pkt, len, 0)) < 0) - DBG("Error %s (%d)", strerror(errno), errno); - - free(pkt); -done: - if (data == NULL) - return; - if (data->sock >= 0) close(data->sock); if (data->cfg.fd >= 0) close(data->cfg.fd); - if (data->buffer) - free(data->buffer); - free(data); } @@ -127,7 +157,7 @@ static int bluetooth_close(snd_pcm_ioplug_t *io) { struct bluetooth_data *data = io->private_data; - DBG("bluetooth_close %p", io); + DBG("%p", io); bluetooth_exit(data); @@ -170,7 +200,7 @@ static int bluetooth_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params return 0; opt_name = (io->stream == SND_PCM_STREAM_PLAYBACK) ? - SO_SNDBUF : SO_RCVBUF; + SO_SNDBUF : SO_RCVBUF; if (setsockopt(cfg.fd, SOL_SCO, opt_name, &period_count, sizeof(period_count)) == 0) @@ -178,14 +208,15 @@ static int bluetooth_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params err = errno; SNDERR("%s (%d)", strerror(err), err); + bluetooth_close(io); return -err; } -static snd_pcm_sframes_t bluetooth_read(snd_pcm_ioplug_t *io, - const snd_pcm_channel_area_t *areas, - snd_pcm_uframes_t offset, - snd_pcm_uframes_t size) +static snd_pcm_sframes_t bluetooth_hsp_read(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size) { struct bluetooth_data *data = io->private_data; struct ipc_data_cfg cfg = data->cfg; @@ -193,8 +224,9 @@ static snd_pcm_sframes_t bluetooth_read(snd_pcm_ioplug_t *io, unsigned char *buff; int nrecv, frame_size = 0; - DBG("areas->step=%u, areas->first=%u, offset=%lu, size=%lu, io->nonblock=%u", - areas->step, areas->first, offset, size, io->nonblock); + DBG("areas->step=%u, areas->first=%u, offset=%lu, size=%lu," + "io->nonblock=%u", areas->step, areas->first, offset, size, + io->nonblock); if (data->count > 0) goto proceed; @@ -216,10 +248,12 @@ static snd_pcm_sframes_t bluetooth_read(snd_pcm_ioplug_t *io, } /* Increment hardware transmition pointer */ - data->hw_ptr = (data->hw_ptr + cfg.pkt_len / cfg.sample_size) % io->buffer_size; + data->hw_ptr = (data->hw_ptr + cfg.pkt_len / cfg.sample_size) % + io->buffer_size; proceed: - buff = (unsigned char *) areas->addr + (areas->first + areas->step * offset) / 8; + buff = (unsigned char *) areas->addr + + (areas->first + areas->step * offset) / 8; if ((data->count + size * frame_size) <= cfg.pkt_len) frames_to_write = size; @@ -238,10 +272,10 @@ done: return ret; } -static snd_pcm_sframes_t bluetooth_write(snd_pcm_ioplug_t *io, - const snd_pcm_channel_area_t *areas, - snd_pcm_uframes_t offset, - snd_pcm_uframes_t size) +static snd_pcm_sframes_t bluetooth_hsp_write(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size) { struct bluetooth_data *data = io->private_data; struct ipc_data_cfg cfg = data->cfg; @@ -263,10 +297,11 @@ static snd_pcm_sframes_t bluetooth_write(snd_pcm_ioplug_t *io, DBG("count = %d, frames_to_read = %lu", data->count, frames_to_read); /* Ready for more data */ - buff = (uint8_t *) areas->addr + (areas->first + areas->step * offset) / 8; + buff = (uint8_t *) areas->addr + + (areas->first + areas->step * offset) / 8; memcpy(data->buffer + data->count, buff, frame_size * frames_to_read); - /* Remember we have some frame in the pipe now */ + /* Remember we have some frames in the pipe now */ data->count += frames_to_read * frame_size; if (data->count != cfg.pkt_len) { ret = frames_to_read; @@ -290,28 +325,245 @@ static snd_pcm_sframes_t bluetooth_write(snd_pcm_ioplug_t *io, ret = -EIO; done: - DBG("returning %d", (int)ret); + DBG("returning %lu", ret); + return ret; +} + +static snd_pcm_sframes_t bluetooth_a2dp_read(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size) +{ + snd_pcm_uframes_t ret = 0; + return ret; +} + +static int avdtp_write(struct bluetooth_a2dp *a2dp, struct ipc_data_cfg *cfg, + unsigned int nonblock) +{ + int count = 0; + int written; + struct rtp_header *header; + struct rtp_payload *payload; +#ifdef ENABLE_DEBUG + static struct timeval send_date = { 0, 0 }; + static struct timeval prev_date = { 0, 0 }; + struct timeval send_delay = { 0, 0 }; + struct timeval sendz_delay = { 0, 0 }; +#endif + + header = (void *) a2dp->buffer; + payload = (void *) (a2dp->buffer + sizeof(*header)); + + memset(a2dp->buffer, 0, sizeof(*header) + sizeof(*payload)); + + payload->frame_count = a2dp->frame_count; + header->v = 2; + header->pt = 1; + header->sequence_number = htons(a2dp->seq_num); + header->timestamp = htonl(a2dp->nsamples); + header->ssrc = htonl(1); + + while (count++ < 50) { +#ifdef ENABLE_DEBUG + gettimeofday(&send_date, NULL); +#endif + written = send(cfg->fd, a2dp->buffer, a2dp->count, + nonblock ? MSG_DONTWAIT : 0); + +#ifdef ENABLE_DEBUG + if ((written >= 0 || errno == EAGAIN) && prev_date.tv_sec != 0) { + long delay, real, theo, delta; + + delay = (long) (send_delay.tv_sec * 1000 + + send_delay.tv_usec / 1000), + real = (long) (sendz_delay.tv_sec * 1000 + + sendz_delay.tv_usec / 1000); + theo = (long) (((float) a2dp->nsamples) / + cfg->rate * 1000.0); + delta = (long) (sendz_delay.tv_sec * 1000 + + sendz_delay.tv_usec / 1000) - + (long) (((float) a2dp->nsamples) / + cfg->rate * 1000.0); + + timersub(&send_date, &prev_date, &send_delay); + timersub(&send_date, &a2dp->ntimestamp, &sendz_delay); + + DBG("send %d (cumul=%d) samples (delay=%ld ms," + " real=%ld ms, theo=%ld ms," + " delta=%ld ms).", a2dp->samples, + a2dp->nsamples, delay, real, theo, + delta); + } +#endif + if (written >= 0) + break; + + if (errno != EAGAIN) + break; + + DBG("send (retry)."); + usleep(150000); + } + +#ifdef ENABLE_DEBUG + prev_date = send_date; +#endif + + /* Send our data */ + if (written != a2dp->count) + DBG("Wrote %d not %d bytes", written, a2dp->count); +#if 0 + else { + /* Measure bandwith usage */ + struct timeval now = { 0, 0 }; + struct timeval interval = { 0, 0 }; + + if(a2dp->bandwithtimestamp.tv_sec == 0) + gettimeofday(&a2dp->bandwithtimestamp, NULL); + + /* See if we must wait again */ + gettimeofday(&now, NULL); + timersub(&now, &a2dp->bandwithtimestamp, &interval); + if(interval.tv_sec > 0) + DBG("Bandwith: %d (%d kbps)", a2dp->bandwithcount, + a2dp->bandwithcount/128); + a2dp->bandwithtimestamp = now; + a2dp->bandwithcount = 0; + } + + a2dp->bandwithcount += written; + +#endif + /* Reset buffer of data to send */ + a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); + a2dp->frame_count = 0; + a2dp->samples = 0; + a2dp->seq_num++; + + return written; +} + +static snd_pcm_sframes_t bluetooth_a2dp_write(snd_pcm_ioplug_t *io, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, + snd_pcm_uframes_t size) +{ + struct bluetooth_data *data = io->private_data; + struct bluetooth_a2dp *a2dp = &data->a2dp; + snd_pcm_sframes_t ret = 0; + snd_pcm_uframes_t frames_to_read; + int frame_size, encoded; + uint8_t *buff; + static int codesize = 0; + + DBG("areas->step=%u, areas->first=%u, offset=%lu, size=%lu," + "io->nonblock=%u", areas->step, areas->first, + offset, size, io->nonblock); + + if (codesize == 0) { + /* How much data can be encoded by sbc at a time? */ + codesize = a2dp->sbc.subbands * a2dp->sbc.blocks * + a2dp->sbc.channels * 2; + /* Reserv header space in outgoing buffer */ + a2dp->count = sizeof(struct rtp_header) + + sizeof(struct rtp_payload); + gettimeofday(&a2dp->ntimestamp, NULL); + } + + frame_size = areas->step / 8; + if ((data->count + size * frame_size) <= codesize) + frames_to_read = size; + else + frames_to_read = (codesize - data->count) / frame_size; + + DBG("count = %d, frames_to_read = %lu", data->count, frames_to_read); + DBG("a2dp.count = %d cfg.pkt_len = %d", a2dp->count, + data->cfg.pkt_len); + + /* FIXME: If state is not streaming then return */ + + /* Ready for more data */ + buff = (uint8_t *) areas->addr + + (areas->first + areas->step * offset) / 8; + memcpy_changeendian(data->buffer + data->count, buff, + frame_size * frames_to_read); + + /* Remember we have some frames in the pipe now */ + data->count += frames_to_read * frame_size; + if (data->count != codesize) { + ret = frames_to_read; + goto done; + } + + /* Enough data to encode (sbc wants 1k blocks) */ + encoded = sbc_encode(&(a2dp->sbc), data->buffer, codesize); + if (encoded <= 0) { + DBG("Encoding error %d", encoded); + goto done; + } + + data->count -= encoded; + + DBG("encoded = %d a2dp.sbc.len= %d", encoded, a2dp->sbc.len); + + if (a2dp->count + a2dp->sbc.len >= data->cfg.pkt_len) + avdtp_write(a2dp, &data->cfg, io->nonblock); + + memcpy(a2dp->buffer + a2dp->count, a2dp->sbc.data, a2dp->sbc.len); + a2dp->count += a2dp->sbc.len; + a2dp->frame_count++; + a2dp->samples += encoded / frame_size; + a2dp->nsamples += encoded / frame_size; + /* Increment hardware transmition pointer */ + data->hw_ptr = (data->hw_ptr + codesize / frame_size) + % io->buffer_size; + + ret = frames_to_read; + +done: + DBG("returning %lu", ret); return ret; } -static snd_pcm_ioplug_callback_t bluetooth_playback_callback = { +static snd_pcm_ioplug_callback_t bluetooth_hsp_playback = { + .start = bluetooth_start, + .stop = bluetooth_stop, + .pointer = bluetooth_pointer, + .close = bluetooth_close, + .hw_params = bluetooth_hw_params, + .prepare = bluetooth_prepare, + .transfer = bluetooth_hsp_write, +}; + +static snd_pcm_ioplug_callback_t bluetooth_hsp_capture = { .start = bluetooth_start, .stop = bluetooth_stop, .pointer = bluetooth_pointer, .close = bluetooth_close, .hw_params = bluetooth_hw_params, .prepare = bluetooth_prepare, - .transfer = bluetooth_write, + .transfer = bluetooth_hsp_read, }; -static snd_pcm_ioplug_callback_t bluetooth_capture_callback = { +static snd_pcm_ioplug_callback_t bluetooth_a2dp_playback = { .start = bluetooth_start, .stop = bluetooth_stop, .pointer = bluetooth_pointer, .close = bluetooth_close, .hw_params = bluetooth_hw_params, .prepare = bluetooth_prepare, - .transfer = bluetooth_read, + .transfer = bluetooth_a2dp_write, +}; + +static snd_pcm_ioplug_callback_t bluetooth_a2dp_capture = { + .start = bluetooth_start, + .stop = bluetooth_stop, + .pointer = bluetooth_pointer, + .close = bluetooth_close, + .hw_params = bluetooth_hw_params, + .prepare = bluetooth_prepare, + .transfer = bluetooth_a2dp_read, }; #define ARRAY_NELEMS(a) (sizeof((a)) / sizeof((a)[0])) @@ -390,7 +642,6 @@ static int bluetooth_recvmsg_fd(struct bluetooth_data *data) }; ret = recvmsg(data->sock, &msgh, 0); - if (ret < 0) { err = errno; SNDERR("Unable to receive fd: %s (%d)", strerror(err), err); @@ -415,102 +666,143 @@ static int bluetooth_recvmsg_fd(struct bluetooth_data *data) return -EINVAL; } -static int bluetooth_cfg(struct bluetooth_data *data) +static int bluetooth_a2dp_init(struct bluetooth_data *data, + struct ipc_codec_sbc *sbc) { - int ret, len = sizeof(struct ipc_packet) + sizeof(struct ipc_data_cfg); - struct ipc_packet *pkt; + struct bluetooth_a2dp *a2dp = &data->a2dp; + struct ipc_data_cfg *cfg = &data->cfg; - DBG("Sending PKT_TYPE_CFG_REQ..."); + if (cfg == NULL) { + SNDERR("Error getting codec parameters"); + return -1; + } - if ((pkt = malloc(len)) == 0) - return -ENOMEM; + if (cfg->codec != CFG_CODEC_SBC) + return -1; + + /* FIXME: init using flags? */ + sbc_init(&a2dp->sbc, 0); + a2dp->sbc.rate = cfg->rate; + a2dp->sbc.channels = cfg->channels; + if (cfg->channel_mode == CFG_CHANNEL_MODE_MONO || + cfg->channel_mode == CFG_CHANNEL_MODE_JOINT_STEREO) + a2dp->sbc.joint = 1; + a2dp->sbc.allocation = sbc->allocation; + a2dp->sbc.subbands = sbc->subbands; + a2dp->sbc.blocks = sbc->blocks; + a2dp->sbc.bitpool = sbc->bitpool; - memset(pkt, 0, len); + return 0; +} + +static int bluetooth_cfg(struct bluetooth_data *data, snd_config_t *conf) +{ + int ret, total; + char buf[IPC_MTU]; + struct ipc_packet *pkt = (void *) buf; + struct ipc_data_cfg *cfg = (void *) pkt->data; + struct ipc_codec_sbc *sbc = (void *) cfg->data; + + DBG("Sending PKT_TYPE_CFG_REQ..."); + + memset(buf, 0, sizeof(buf)); pkt->type = PKT_TYPE_CFG_REQ; pkt->role = PKT_ROLE_NONE; pkt->error = PKT_ERROR_NONE; - if ((ret = send(data->sock, pkt, len, 0)) < 0) { - ret = -errno; - goto done; - } else if (ret == 0) { - ret = -EIO; - goto done; - } + ret = send(data->sock, pkt, sizeof(struct ipc_packet), 0); + if (ret < 0) + return -errno; + else if (ret == 0) + return -EIO; - DBG("OK - %d bytes sent", ret); + DBG("OK - %d bytes sent. Waiting for response...", ret); - DBG("Waiting for response..."); + memset(buf, 0, sizeof(buf)); - memset(pkt, 0, len); - if ((ret = recv(data->sock, pkt, len, 0)) < 0) { - ret = -errno; - goto done; - } else if (ret == 0) { - ret = -EIO; - goto done; - } + ret = recv(data->sock, buf, sizeof(*pkt) + sizeof(*cfg), 0); + if (ret < 0) + return -errno; + else if (ret == 0) + return -EIO; - DBG("OK - %d bytes received", ret); + total = ret; if (pkt->type != PKT_TYPE_CFG_RSP) { SNDERR("Unexpected packet type received: type = %d", pkt->type); - ret = -EINVAL; - goto done; + return -EINVAL; } if (pkt->error != PKT_ERROR_NONE) { SNDERR("Error while configuring device: error = %d", pkt->error); - ret = pkt->error; - goto done; + return pkt->error; } - if (pkt->length != sizeof(struct ipc_data_cfg)) { + if (cfg->codec != CFG_CODEC_SBC) + goto done; + + ret = recv(data->sock, sbc, sizeof(*sbc), 0); + if (ret < 0) + return -errno; + else if (ret == 0) + return -EIO; + + total += ret; + +done: + DBG("OK - %d bytes received", total); + + if (pkt->length != (total - sizeof(struct ipc_packet))) { SNDERR("Error while configuring device: packet size doesn't " "match"); - ret = -EINVAL; - goto done; + return -EINVAL; } - memcpy(&data->cfg, pkt->data, sizeof(struct ipc_data_cfg)); + memcpy(&data->cfg, cfg, sizeof(*cfg)); DBG("Device configuration:"); - DBG("fd=%d, fd_opt=%u, channels=%u, pkt_len=%u, sample_size=%u," - "rate=%u", data->cfg.fd, data->cfg.fd_opt, - data->cfg.channels, data->cfg.pkt_len, - data->cfg.sample_size, data->cfg.rate); + DBG("\n\tfd=%d\n\tfd_opt=%u\n\tchannels=%u\n\tpkt_len=%u\n" + "\tsample_size=%u\n\trate=%u", data->cfg.fd, + data->cfg.fd_opt, data->cfg.channels, data->cfg.pkt_len, + data->cfg.sample_size, data->cfg.rate); + + if (data->cfg.codec == CFG_CODEC_SBC) { + struct bluetooth_a2dp *a2dp = &data->a2dp; + ret = bluetooth_a2dp_init(data, sbc); + if (ret < 0) + return ret; + printf("\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\t" + "bitpool=%u\n", a2dp->sbc.allocation, + a2dp->sbc.subbands, a2dp->sbc.blocks, + a2dp->sbc.bitpool); + } if (data->cfg.fd == -1) { SNDERR("Error while configuring device: could not acquire " "audio socket"); - ret = -EINVAL; - goto done; + return -EINVAL; } - if ((ret = bluetooth_recvmsg_fd(data)) < 0) - goto done; - - if ((data->buffer = malloc(data->cfg.pkt_len)) == 0) - return -ENOMEM; + ret = bluetooth_recvmsg_fd(data); + if (ret < 0) + return ret; /* It is possible there is some outstanding data in the pipe - we have to empty it */ - while(recv(data->cfg.fd, data->buffer, data->cfg.pkt_len, - MSG_DONTWAIT) > 0); + while (recv(data->cfg.fd, data->buffer, data->cfg.pkt_len, + MSG_DONTWAIT) > 0); memset(data->buffer, 0, data->cfg.pkt_len); -done: - free(pkt); - return ret; + return 0; } -static int bluetooth_init(struct bluetooth_data *data) +static int bluetooth_init(struct bluetooth_data *data, snd_config_t *conf) { - int sk, err, id; + int sk, err; struct sockaddr_un addr = { AF_UNIX, IPC_SOCKET_NAME }; @@ -522,28 +814,24 @@ static int bluetooth_init(struct bluetooth_data *data) data->sock = -1; - id = abs(getpid() * rand()); - - if ((sk = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) { - err = -errno; - SNDERR("Can't open socket"); - return -errno; + sk = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sk < 0) { + err = errno; + SNDERR("Cannot open socket: %s (%d)", strerror(err), err); + return -err; } DBG("Connecting to address: %s", addr.sun_path + 1); if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { - err = -errno; - SNDERR("Can't connect socket"); + err = errno; + SNDERR("Connection fail", strerror(err), err); close(sk); - return err; + return -err; } data->sock = sk; - if ((err = bluetooth_cfg(data)) < 0) - return err; - - return 0; + return bluetooth_cfg(data, conf); } SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth) @@ -555,27 +843,32 @@ SND_PCM_PLUGIN_DEFINE_FUNC(bluetooth) stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"); data = malloc(sizeof(struct bluetooth_data)); - memset(data, 0, sizeof(struct bluetooth_data)); if (!data) { err = -ENOMEM; goto error; } - err = bluetooth_init(data); + err = bluetooth_init(data, conf); if (err < 0) goto error; data->io.version = SND_PCM_IOPLUG_VERSION; data->io.name = "Bluetooth Audio Device"; data->io.mmap_rw = 0; /* No direct mmap communication */ - - data->io.callback = stream == SND_PCM_STREAM_PLAYBACK ? - &bluetooth_playback_callback : &bluetooth_capture_callback; data->io.poll_fd = data->cfg.fd; data->io.poll_events = stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN; data->io.private_data = data; + if (data->cfg.codec == CFG_CODEC_SBC) + data->io.callback = stream == SND_PCM_STREAM_PLAYBACK ? + &bluetooth_a2dp_playback : + &bluetooth_a2dp_capture; + else + data->io.callback = stream == SND_PCM_STREAM_PLAYBACK ? + &bluetooth_hsp_playback : + &bluetooth_hsp_capture; + err = snd_pcm_ioplug_create(&data->io, name, stream, mode); if (err < 0) goto error; diff --git a/audio/sink.c b/audio/sink.c index 436dfbce..f7e32647 100644 --- a/audio/sink.c +++ b/audio/sink.c @@ -25,4 +25,433 @@ #include <config.h> #endif +#include <errno.h> + +#include <glib.h> +#include <dbus/dbus.h> + +#include "dbus.h" +#include "dbus-helper.h" +#include "logging.h" + #include "avdtp.h" +#include "device.h" +#include "a2dp.h" +#include "error.h" +#include "unix.h" + +struct pending_connect { + DBusMessage *msg; + struct ipc_packet *pkt; + int pkt_len; + int sock; +}; + +struct sink { + struct avdtp *session; + struct avdtp_stream *stream; + uint8_t state; + struct pending_connect *c; + DBusConnection *conn; +}; + +static void pending_connect_free(struct pending_connect *c) +{ + if (c->pkt) + g_free(c->pkt); + if (c->msg) + dbus_message_unref(c->msg); + g_free(c); +} + +void stream_state_changed(struct avdtp_stream *stream, avdtp_state_t old_state, + avdtp_state_t new_state, + struct avdtp_error *err, void *user_data) +{ + struct device *dev = user_data; + struct sink *sink = dev->sink; + struct pending_connect *c = NULL; + DBusMessage *reply; + int cmd_err; + + if (err) + goto failed; + + switch (new_state) { + case AVDTP_STATE_IDLE: + dbus_connection_emit_signal(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, + "Disconnected", + DBUS_TYPE_INVALID); + if (sink->session) { + avdtp_unref(sink->session); + sink->session = NULL; + } + c = sink->c; + break; + case AVDTP_STATE_CONFIGURED: + cmd_err = avdtp_open(sink->session, stream); + if (cmd_err < 0) { + error("Error on avdtp_open %s (%d)", strerror(-cmd_err), + cmd_err); + goto failed; + } + break; + case AVDTP_STATE_OPEN: + if (old_state == AVDTP_STATE_CONFIGURED) + dbus_connection_emit_signal(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, + "Connected", + DBUS_TYPE_INVALID); + if (sink->c && sink->c->pkt) { + cmd_err = avdtp_start(sink->session, stream); + if (cmd_err < 0) { + error("Error on avdtp_start %s (%d)", + strerror(-cmd_err), cmd_err); + goto failed; + } + } + else + c = sink->c; + break; + case AVDTP_STATE_STREAMING: + c = sink->c; + break; + case AVDTP_STATE_CLOSING: + break; + case AVDTP_STATE_ABORTING: + break; + } + + sink->state = new_state; + + if (c) { + if (c->msg) { + reply = dbus_message_new_method_return(c->msg); + send_message_and_unref(dev->conn, reply); + } + if (c->pkt) { + struct ipc_data_cfg *rsp; + int ret; + + ret = sink_get_config(dev, c->sock, c->pkt, + c->pkt_len, &rsp); + if (ret == 0) { + unix_send_cfg(c->sock, rsp); + g_free(rsp); + } + else + unix_send_cfg(c->sock, NULL); + } + + pending_connect_free(c); + sink->c = NULL; + } + + return; + +failed: + if (sink->c) { + if (sink->c->msg && err) + err_failed(dev->conn, sink->c->msg, + avdtp_strerror(err)); + + pending_connect_free(sink->c); + sink->c = NULL; + } + + if (new_state == AVDTP_STATE_IDLE) { + avdtp_unref(sink->session); + sink->session = NULL; + } +} + + + +static void discovery_complete(struct avdtp *session, GSList *seps, int err, + void *user_data) +{ + struct device *dev = user_data; + struct sink *sink = dev->sink; + struct avdtp_local_sep *lsep; + struct avdtp_remote_sep *rsep; + GSList *caps = NULL; + const char *err_str = NULL; + + if (err < 0) { + error("Discovery failed"); + err_str = strerror(-err); + goto failed; + } + + debug("Discovery complete"); + + if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SINK, AVDTP_MEDIA_TYPE_AUDIO, + A2DP_CODEC_SBC, &lsep, &rsep) < 0) { + err_str = "No matching ACP and INT SEPs found"; + goto failed; + } + + if (!a2dp_select_capabilities(rsep, &caps)) { + err_str = "Unable to select remote SEP capabilities"; + goto failed; + } + + err = avdtp_set_configuration(session, rsep, lsep, caps, + &sink->stream); + if (err < 0) { + error("avdtp_set_configuration: %s", strerror(-err)); + err_str = "Unable to set configuration"; + goto failed; + } + + avdtp_stream_set_cb(session, sink->stream, stream_state_changed, dev); + + return; + +failed: + error("%s", err_str); + if (sink->c) { + if (sink->c->msg) + err_failed(dev->conn, sink->c->msg, err_str); + pending_connect_free(sink->c); + sink->c = NULL; + } + if (sink->session) { + avdtp_unref(sink->session); + sink->session = NULL; + } +} + +static DBusHandlerResult sink_connect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct device *dev = data; + struct sink *sink = dev->sink; + struct pending_connect *c; + int err; + + if (!sink->session) + sink->session = avdtp_get(&dev->src, &dev->dst); + + if (sink->c) + return err_connect_failed(conn, msg, "Connect in progress"); + + if (sink->state >= AVDTP_STATE_OPEN) + return err_already_connected(conn, msg); + + c = g_new0(struct pending_connect, 1); + c->msg = dbus_message_ref(msg); + sink->c = c; + + err = avdtp_discover(sink->session, discovery_complete, data); + if (err < 0) { + dbus_message_unref(c->msg); + pending_connect_free(c); + sink->c = NULL; + return err_connect_failed(conn, msg, strerror(err)); + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult sink_disconnect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct device *device = data; + struct sink *sink = device->sink; + struct pending_connect *c; + int err; + + if (!sink->session) + return err_not_connected(conn, msg); + + if (sink->c) + return err_failed(conn, msg, strerror(EBUSY)); + + if (sink->state < AVDTP_STATE_OPEN) { + avdtp_unref(sink->session); + sink->session = NULL; + } else { + err = avdtp_close(sink->session, sink->stream); + if (err < 0) + return err_failed(conn, msg, strerror(-err)); + } + + c = g_new0(struct pending_connect, 1); + c->msg = dbus_message_ref(msg); + sink->c = c; + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult sink_is_connected(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct device *device = data; + struct sink *sink = device->sink; + DBusMessage *reply; + dbus_bool_t connected; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + connected = (sink->state != AVDTP_STATE_IDLE); + + dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, + DBUS_TYPE_INVALID); + + send_message_and_unref(conn, reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusMethodVTable sink_methods[] = { + { "Connect", sink_connect, "", "" }, + { "Disconnect", sink_disconnect, "", "" }, + { "IsConnected", sink_is_connected, "", "b" }, + { NULL, NULL, NULL, NULL } +}; + +static DBusSignalVTable sink_signals[] = { + { "Connected", "" }, + { "Disconnected", "" }, + { NULL, NULL } +}; + +struct sink *sink_init(void *device) +{ + struct device *dev = device; + + if (!dbus_connection_register_interface(dev->conn, dev->path, + AUDIO_SINK_INTERFACE, + sink_methods, + sink_signals, NULL)) + return NULL; + + return g_new0(struct sink, 1); +} + +void sink_free(void *device) +{ + struct device *dev = device; + struct sink *sink = dev->sink; + + if (sink->session) + avdtp_unref(sink->session); + + if (sink->c) + pending_connect_free(sink->c); + + g_free(sink); + dev->sink = NULL; +} + +int sink_get_config(void *device, int sock, struct ipc_packet *req, + int pkt_len, struct ipc_data_cfg **rsp) +{ + struct device *dev = device; + struct sink *sink = dev->sink; + int err = EINVAL; + struct pending_connect *c = NULL; + + if (sink->state == AVDTP_STATE_STREAMING) + goto proceed; + + if (!sink->session) + sink->session = avdtp_get(&dev->src, &dev->dst); + + c = g_new0(struct pending_connect, 1); + c->sock = sock; + c->pkt = g_malloc(pkt_len); + memcpy(c->pkt, req, pkt_len); + sink->c = c; + + if (sink->state == AVDTP_STATE_IDLE) + err = avdtp_discover(sink->session, discovery_complete, device); + else if (sink->state < AVDTP_STATE_STREAMING) + err = avdtp_start(sink->session, sink->stream); + else + goto error; + + if (err < 0) + goto error; + + return 1; + +proceed: + if (!a2dp_get_config(sink->stream, rsp)) + goto error; + + return 0; + +error: + if (c) + pending_connect_free(c); + return -err; +} + +gboolean sink_is_active(void *device) +{ + struct device *dev = device; + struct sink *sink = dev->sink; + + if (sink->session) + return TRUE; + + return FALSE; +} + +void sink_set_state(void *device, avdtp_state_t state) +{ + struct device *dev = device; + struct sink *sink = dev->sink; + int err = 0; + + if (sink->state == state) + return; + + if (!sink->session || !sink->stream) + goto failed; + + switch (sink->state) { + case AVDTP_STATE_OPEN: + if (state == AVDTP_STATE_STREAMING) { + err = avdtp_start(sink->session, sink->stream); + if (err == 0) + return; + } + else if (state == AVDTP_STATE_IDLE) { + err = avdtp_close(sink->session, sink->stream); + if (err == 0) + return; + } + break; + case AVDTP_STATE_STREAMING: + if (state == AVDTP_STATE_OPEN) { + err = avdtp_suspend(sink->session, sink->stream); + if (err == 0) + return; + } + else if (state == AVDTP_STATE_IDLE) { + err = avdtp_close(sink->session, sink->stream); + if (err == 0) + return; + } + break; + default: + goto failed; + } +failed: + error("%s: Error changing states", dev->path); +} + +avdtp_state_t sink_get_state(void *device) +{ + struct device *dev = device; + struct sink *sink = dev->sink; + + return sink->state; +} diff --git a/audio/sink.h b/audio/sink.h index 4cc3a0e4..9d65e278 100644 --- a/audio/sink.h +++ b/audio/sink.h @@ -21,7 +21,18 @@ * */ +#include "ipc.h" +#include "avdtp.h" + #define AUDIO_SINK_INTERFACE "org.bluez.audio.Sink" struct sink; +struct sink *sink_init(void *device); +void sink_new_stream(void *device, void *lsep); +void sink_free(void *device); +int sink_get_config(void *device, int sock, struct ipc_packet *req, + int pkt_len, struct ipc_data_cfg **rsp); +gboolean sink_is_active(void *device); +void sink_set_state(void *device, avdtp_state_t state); +avdtp_state_t sink_get_state(void *device); diff --git a/audio/unix.c b/audio/unix.c index fbda7ed9..0880f4ff 100644 --- a/audio/unix.c +++ b/audio/unix.c @@ -37,11 +37,28 @@ #include "logging.h" #include "dbus.h" - #include "manager.h" +#include "ipc.h" +#include "unix.h" + +struct unix_client { + struct device *dev; + int sock; +}; + +static GSList *clients = NULL; static int unix_sock = -1; +static int unix_send_state(int sock, struct ipc_packet *pkt); + +static void client_free(struct unix_client *client) +{ + if (client->sock >= 0) + close(client->sock); + g_free(client); +} + /* Pass file descriptor through local domain sockets (AF_LOCAL, formerly AF_UNIX) and the sendmsg() system call with the cmsg_type field of a "struct cmsghdr" set to SCM_RIGHTS and the data being an integer value equal to the handle of the @@ -75,52 +92,120 @@ static int unix_sendmsg_fd(int sock, int fd, struct ipc_packet *pkt) return sendmsg(sock, &msgh, MSG_NOSIGNAL); } -static void cfg_event(int clisk, struct ipc_packet *pkt) +static void cfg_event(struct unix_client *client, struct ipc_packet *pkt, + int len) { - struct ipc_data_cfg *cfg = (struct ipc_data_cfg *) pkt->data; - struct device *device; + struct ipc_data_cfg *rsp; + struct device *dev; + int ret; - memset(cfg, 0, sizeof(struct ipc_data_cfg)); + dev = manager_get_connected_device(); + if (dev) + goto proceed; - if ((device = manager_default_device())) { - if (device->headset) - headset_get_config(device, clisk, pkt); - } - else - cfg->fd = -1; + dev = manager_default_device(); + if (!dev) + goto failed; - if (cfg->fd != 0) - unix_send_cfg(clisk, pkt); +proceed: + ret = device_get_config(dev, client->sock, pkt, len, &rsp); + if (ret < 0) + goto failed; + + client->dev = dev; + + /* Connecting in progress */ + if (ret == 1) + return; + + unix_send_cfg(client->sock, rsp); + g_free(rsp); + + return; + +failed: + unix_send_cfg(client->sock, NULL); } -static void ctl_event(int clisk, struct ipc_packet *pkt) +static void ctl_event(struct unix_client *client, struct ipc_packet *pkt, + int len) { } -static void state_event(int clisk, struct ipc_packet *pkt) +static void state_event(struct unix_client *client, struct ipc_packet *pkt, + int len) { struct ipc_data_state *state = (struct ipc_data_state *) pkt->data; - struct device *device; + struct device *dev = client->dev; - if (!(device = manager_default_device())) - return; + if (len > sizeof(struct ipc_packet)) + device_set_state(dev, state->state); + else + state->state = device_get_state(dev); - if (device->headset) - headset_set_state(device, state->state); + unix_send_state(client->sock, pkt); +} + +static gboolean client_cb(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + char buf[IPC_MTU]; + struct ipc_packet *pkt = (void *) buf; + struct unix_client *client = data; + int len, len_check; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) { + debug("Unix client disconnected"); + device_set_state(client->dev, STATE_CONNECTED); + goto failed; + } + + memset(buf, 0, sizeof(buf)); + + len = recv(client->sock, buf, sizeof(buf), 0); + if (len < 0) { + error("recv: %s (%d)", strerror(errno), errno); + goto failed; + } + + len_check = pkt->length + sizeof(struct ipc_packet); + if (len != len_check) { + error("Packet lenght doesn't match"); + goto failed; + } + + switch (pkt->type) { + case PKT_TYPE_CFG_REQ: + info("Package PKT_TYPE_CFG_REQ:%u", pkt->role); + cfg_event(client, pkt, len); + break; + case PKT_TYPE_STATE_REQ: + info("Package PKT_TYPE_STATE_REQ"); + state_event(client, pkt, len); + break; + case PKT_TYPE_CTL_REQ: + info("Package PKT_TYPE_CTL_REQ"); + ctl_event(client, pkt, len); + break; + } - unix_send_status(clisk, pkt); + return TRUE; - g_free(pkt); +failed: + clients = g_slist_remove(clients, client); + client_free(client); + return FALSE; } -static gboolean unix_event(GIOChannel *chan, GIOCondition cond, gpointer data) +static gboolean server_cb(GIOChannel *chan, GIOCondition cond, gpointer data) { struct sockaddr_un addr; socklen_t addrlen; - struct ipc_packet *pkt; - int sk, clisk, len; - - debug("chan %p cond %td data %p", chan, cond, data); + int sk, cli_sk; + struct unix_client *client; + GIOChannel *io; if (cond & G_IO_NVAL) return FALSE; @@ -135,32 +220,22 @@ static gboolean unix_event(GIOChannel *chan, GIOCondition cond, gpointer data) memset(&addr, 0, sizeof(addr)); addrlen = sizeof(addr); - clisk = accept(sk, (struct sockaddr *) &addr, &addrlen); - if (clisk < 0) { + cli_sk = accept(sk, (struct sockaddr *) &addr, &addrlen); + if (cli_sk < 0) { error("accept: %s (%d)", strerror(errno), errno); return TRUE; } - len = sizeof(struct ipc_packet) + sizeof(struct ipc_data_cfg); - pkt = g_malloc0(len); - len = recv(clisk, pkt, len, 0); + debug("Accepted new client connection on unix socket"); - debug("path %s len %d", addr.sun_path + 1, len); + client = g_new(struct unix_client, 1); + client->sock = cli_sk; + clients = g_slist_append(clients, client); - switch (pkt->type) { - case PKT_TYPE_CFG_REQ: - info("Package PKT_TYPE_CFG_REQ:%u", pkt->role); - cfg_event(clisk, pkt); - break; - case PKT_TYPE_STATE_REQ: - info("Package PKT_TYPE_STATE_REQ"); - state_event(clisk, pkt); - break; - case PKT_TYPE_CTL_REQ: - info("Package PKT_TYPE_CTL_REQ"); - ctl_event(clisk, pkt); - break; - } + io = g_io_channel_unix_new(cli_sk); + g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + client_cb, client); + g_io_channel_unref(io); return TRUE; } @@ -194,10 +269,8 @@ int unix_init(void) listen(sk, 1); io = g_io_channel_unix_new(sk); - g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, - unix_event, NULL); - + server_cb, NULL); g_io_channel_unref(io); info("Unix socket created: %d", sk); @@ -207,59 +280,77 @@ int unix_init(void) void unix_exit(void) { + g_slist_foreach(clients, (GFunc) client_free, NULL); + g_slist_free(clients); close(unix_sock); unix_sock = -1; } -int unix_send_cfg(int sock, struct ipc_packet *pkt) +int unix_send_cfg(int sock, struct ipc_data_cfg *cfg) { - struct ipc_data_cfg *cfg = (struct ipc_data_cfg *) pkt->data; - int len; + char buf[IPC_MTU]; + struct ipc_packet *pkt = (void *) buf; + int len, codec_len; - info("fd=%d, fd_opt=%u, channels=%u, pkt_len=%u, sample_size=%u," - "rate=%u", cfg->fd, cfg->fd_opt, cfg->channels, - cfg->pkt_len, cfg->sample_size, cfg->rate); + memset(buf, 0, sizeof(buf)); pkt->type = PKT_TYPE_CFG_RSP; - pkt->length = sizeof(struct ipc_data_cfg); + + if (!cfg) { + pkt->error = EINVAL; + len = send(sock, pkt, sizeof(struct ipc_packet), 0); + if (len < 0) + error("send: %s (%d)", strerror(errno), errno); + return len; + } + + debug("fd=%d, fd_opt=%u, channels=%u, pkt_len=%u," + "sample_size=%u, rate=%u", cfg->fd, cfg->fd_opt, + cfg->channels, cfg->pkt_len, cfg->sample_size, cfg->rate); + + if (cfg->codec == CFG_CODEC_SBC) + codec_len = sizeof(struct ipc_codec_sbc); + else + codec_len = 0; + pkt->error = PKT_ERROR_NONE; + pkt->length = sizeof(struct ipc_data_cfg) + codec_len; + memcpy(pkt->data, cfg, pkt->length); - len = sizeof(struct ipc_packet) + sizeof(struct ipc_data_cfg); + len = sizeof(struct ipc_packet) + pkt->length; len = send(sock, pkt, len, 0); if (len < 0) - info("Error %s(%d)", strerror(errno), errno); + error("Error %s(%d)", strerror(errno), errno); - info("%d bytes sent", len); + debug("%d bytes sent", len); if (cfg->fd != -1) { len = unix_sendmsg_fd(sock, cfg->fd, pkt); if (len < 0) - info("Error %s(%d)", strerror(errno), errno); - info("%d bytes sent", len); + error("Error %s(%d)", strerror(errno), errno); + debug("%d bytes sent", len); } - g_free(pkt); return 0; } -int unix_send_status(int sock, struct ipc_packet *pkt) +static int unix_send_state(int sock, struct ipc_packet *pkt) { struct ipc_data_state *state = (struct ipc_data_state *) pkt->data; int len; info("status=%u", state->state); - pkt->type = PKT_TYPE_CFG_RSP; + pkt->type = PKT_TYPE_STATE_RSP; pkt->length = sizeof(struct ipc_data_state); pkt->error = PKT_ERROR_NONE; len = sizeof(struct ipc_packet) + sizeof(struct ipc_data_state); len = send(sock, pkt, len, 0); if (len < 0) - info("Error %s(%d)", strerror(errno), errno); + error("Error %s(%d)", strerror(errno), errno); - info("%d bytes sent", len); + debug("%d bytes sent", len); - g_free(pkt); return 0; } diff --git a/audio/unix.h b/audio/unix.h index 32cf4af9..c771b965 100644 --- a/audio/unix.h +++ b/audio/unix.h @@ -24,7 +24,6 @@ #include "ipc.h" int unix_init(void); - void unix_exit(void); -int unix_send_cfg(int sock, struct ipc_packet *pkt); -int unix_send_status(int sock, struct ipc_packet *pkt); + +int unix_send_cfg(int sock, struct ipc_data_cfg *cfg); |