diff options
author | Johan Hedberg <johan.hedberg@nokia.com> | 2007-08-11 11:05:24 +0000 |
---|---|---|
committer | Johan Hedberg <johan.hedberg@nokia.com> | 2007-08-11 11:05:24 +0000 |
commit | 6763ebb3c231740c66a235f94d56e8d8cc213d90 (patch) | |
tree | 527ad7a778289b70ac64b2d4e49512eae6d634e2 /audio/a2dp.c | |
parent | 46e860574f3d6d70d961e38270522764191cea20 (diff) |
Integrate A2DP work from Johan's and Luiz's GIT trees
Diffstat (limited to 'audio/a2dp.c')
-rw-r--r-- | audio/a2dp.c | 611 |
1 files changed, 611 insertions, 0 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; +} |