diff options
-rw-r--r-- | audio/avrcp.c | 294 | ||||
-rw-r--r-- | audio/avrcp.h | 2 |
2 files changed, 172 insertions, 124 deletions
diff --git a/audio/avrcp.c b/audio/avrcp.c index 095dafe0b..53c4e135b 100644 --- a/audio/avrcp.c +++ b/audio/avrcp.c @@ -158,7 +158,7 @@ struct avrcp_server { uint32_t tg_record_id; uint32_t ct_record_id; GSList *players; - struct avrcp_player *addressed_player; + GSList *sessions; }; struct pending_pdu { @@ -169,8 +169,18 @@ struct pending_pdu { struct avrcp_player { struct avrcp_server *server; - struct avctp *session; + GSList *sessions; + + struct avrcp_player_cb *cb; + void *user_data; + GDestroyNotify destroy; +}; + +struct avrcp { + struct avrcp_server *server; + struct avctp *conn; struct audio_device *dev; + struct avrcp_player *player; unsigned int control_handler; unsigned int browsing_handler; @@ -178,10 +188,6 @@ struct avrcp_player { uint8_t transaction; uint8_t transaction_events[AVRCP_EVENT_LAST + 1]; struct pending_pdu *pending_pdu; - - struct avrcp_player_cb *cb; - void *user_data; - GDestroyNotify destroy; }; static GSList *servers = NULL; @@ -192,7 +198,7 @@ static uint32_t company_ids[] = { IEEEID_BTSIG, }; -static void register_volume_notification(struct avrcp_player *player); +static void register_volume_notification(struct avrcp *session); static sdp_record_t *avrcp_ct_record(void) { @@ -407,18 +413,15 @@ static void set_company_id(uint8_t cid[3], const uint32_t cid_in) cid[2] = cid_in; } -int avrcp_player_event(struct avrcp_player *player, uint8_t id, void *data) +void avrcp_player_event(struct avrcp_player *player, uint8_t id, void *data) { uint8_t buf[AVRCP_HEADER_LENGTH + 9]; struct avrcp_header *pdu = (void *) buf; uint16_t size; - int err; - - if (player->session == NULL) - return -ENOTCONN; + GSList *l; - if (!(player->registered_events & (1 << id))) - return 0; + if (player->sessions == NULL) + return; memset(buf, 0, sizeof(buf)); @@ -446,21 +449,30 @@ int avrcp_player_event(struct avrcp_player *player, uint8_t id, void *data) break; default: error("Unknown event %u", id); - return -EINVAL; + return; } pdu->params_len = htons(size); - err = avctp_send_vendordep(player->session, player->transaction_events[id], + for (l = player->sessions; l; l = l->next) { + struct avrcp *session = l->data; + int err; + + if (!(session->registered_events & (1 << id))) + continue; + + err = avctp_send_vendordep(session->conn, + session->transaction_events[id], AVC_CTYPE_CHANGED, AVC_SUBUNIT_PANEL, buf, size + AVRCP_HEADER_LENGTH); - if (err < 0) - return err; + if (err < 0) + continue; - /* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */ - player->registered_events ^= 1 << id; + /* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */ + session->registered_events ^= 1 << id; + } - return 0; + return; } static uint16_t player_write_media_attribute(struct avrcp_player *player, @@ -557,14 +569,14 @@ static struct pending_pdu *pending_pdu_new(uint8_t pdu_id, GList *attr_ids, return pending; } -static gboolean player_abort_pending_pdu(struct avrcp_player *player) +static gboolean session_abort_pending_pdu(struct avrcp *session) { - if (player->pending_pdu == NULL) + if (session->pending_pdu == NULL) return FALSE; - g_list_free(player->pending_pdu->attr_ids); - g_free(player->pending_pdu); - player->pending_pdu = NULL; + g_list_free(session->pending_pdu->attr_ids); + g_free(session->pending_pdu); + session->pending_pdu = NULL; return TRUE; } @@ -590,7 +602,7 @@ static int player_get_attribute(struct avrcp_player *player, uint8_t attr) return value; } -static uint8_t avrcp_handle_get_capabilities(struct avrcp_player *player, +static uint8_t avrcp_handle_get_capabilities(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { @@ -631,14 +643,15 @@ err: return AVC_CTYPE_REJECTED; } -static uint8_t avrcp_handle_list_player_attributes(struct avrcp_player *player, +static uint8_t avrcp_handle_list_player_attributes(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { + struct avrcp_player *player = session->player; uint16_t len = ntohs(pdu->params_len); unsigned int i; - if (len != 0) { + if (len != 0 || player == NULL) { pdu->params_len = htons(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; @@ -662,14 +675,15 @@ done: return AVC_CTYPE_STABLE; } -static uint8_t avrcp_handle_list_player_values(struct avrcp_player *player, +static uint8_t avrcp_handle_list_player_values(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { + struct avrcp_player *player = session->player; uint16_t len = ntohs(pdu->params_len); unsigned int i; - if (len != 1 || !player) + if (len != 1 || player == NULL) goto err; if (player_get_attribute(player, pdu->params[0]) < 0) @@ -691,10 +705,11 @@ err: return AVC_CTYPE_REJECTED; } -static uint8_t avrcp_handle_get_element_attributes(struct avrcp_player *player, +static uint8_t avrcp_handle_get_element_attributes(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { + struct avrcp_player *player = session->player; uint16_t len = ntohs(pdu->params_len); uint64_t identifier = bt_get_le64(&pdu->params[0]); uint16_t pos; @@ -702,7 +717,7 @@ static uint8_t avrcp_handle_get_element_attributes(struct avrcp_player *player, GList *attr_ids; uint16_t offset; - if (len < 9 || identifier != 0) + if (len < 9 || identifier != 0 || player == NULL) goto err; nattr = pdu->params[8]; @@ -740,14 +755,14 @@ static uint8_t avrcp_handle_get_element_attributes(struct avrcp_player *player, if (!len) goto err; - player_abort_pending_pdu(player); + session_abort_pending_pdu(session); pos = 1; offset = 0; attr_ids = player_fill_media_attribute(player, attr_ids, pdu->params, &pos, &offset); if (attr_ids != NULL) { - player->pending_pdu = pending_pdu_new(pdu->pdu_id, attr_ids, + session->pending_pdu = pending_pdu_new(pdu->pdu_id, attr_ids, offset); pdu->packet_type = AVRCP_PACKET_TYPE_START; } @@ -762,15 +777,17 @@ err: return AVC_CTYPE_REJECTED; } -static uint8_t avrcp_handle_get_current_player_value(struct avrcp_player *player, +static uint8_t avrcp_handle_get_current_player_value(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { + struct avrcp_player *player = session->player; uint16_t len = ntohs(pdu->params_len); uint8_t *settings; unsigned int i; - if (player == NULL || len <= 1 || pdu->params[0] != len - 1) + if (player == NULL || len <= 1 || pdu->params[0] != len - 1 || + player == NULL) goto err; /* @@ -820,15 +837,16 @@ err: return AVC_CTYPE_REJECTED; } -static uint8_t avrcp_handle_set_player_value(struct avrcp_player *player, +static uint8_t avrcp_handle_set_player_value(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { + struct avrcp_player *player = session->player; uint16_t len = ntohs(pdu->params_len); unsigned int i; uint8_t *param; - if (len < 3 || len > 2 * pdu->params[0] + 1U) + if (len < 3 || len > 2 * pdu->params[0] + 1U || player == NULL) goto err; /* @@ -858,7 +876,7 @@ err: return AVC_CTYPE_REJECTED; } -static uint8_t avrcp_handle_displayable_charset(struct avrcp_player *player, +static uint8_t avrcp_handle_displayable_charset(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { @@ -878,7 +896,7 @@ static uint8_t avrcp_handle_displayable_charset(struct avrcp_player *player, return AVC_CTYPE_STABLE; } -static uint8_t avrcp_handle_ct_battery_status(struct avrcp_player *player, +static uint8_t avrcp_handle_ct_battery_status(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { @@ -902,16 +920,17 @@ err: return AVC_CTYPE_REJECTED; } -static uint8_t avrcp_handle_get_play_status(struct avrcp_player *player, +static uint8_t avrcp_handle_get_play_status(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { + struct avrcp_player *player = session->player; uint16_t len = ntohs(pdu->params_len); uint32_t position; uint32_t duration; void *pduration; - if (len != 0) { + if (len != 0 || player == NULL) { pdu->params_len = htons(1); pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; return AVC_CTYPE_REJECTED; @@ -936,10 +955,11 @@ static uint8_t avrcp_handle_get_play_status(struct avrcp_player *player, return AVC_CTYPE_STABLE; } -static uint8_t avrcp_handle_register_notification(struct avrcp_player *player, +static uint8_t avrcp_handle_register_notification(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { + struct avrcp_player *player = session->player; uint16_t len = ntohs(pdu->params_len); uint64_t uid; @@ -948,7 +968,7 @@ static uint8_t avrcp_handle_register_notification(struct avrcp_player *player, * one is applicable only for EVENT_PLAYBACK_POS_CHANGED. See AVRCP * 1.3 spec, section 5.4.2. */ - if (len != 5) + if (len != 5 || player == NULL) goto err; switch (pdu->params[0]) { @@ -973,8 +993,8 @@ static uint8_t avrcp_handle_register_notification(struct avrcp_player *player, } /* Register event and save the transaction used */ - player->registered_events |= (1 << pdu->params[0]); - player->transaction_events[pdu->params[0]] = transaction; + session->registered_events |= (1 << pdu->params[0]); + session->transaction_events[pdu->params[0]] = transaction; pdu->params_len = htons(len); @@ -986,17 +1006,18 @@ err: return AVC_CTYPE_REJECTED; } -static uint8_t avrcp_handle_request_continuing(struct avrcp_player *player, +static uint8_t avrcp_handle_request_continuing(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { + struct avrcp_player *player = session->player; uint16_t len = ntohs(pdu->params_len); struct pending_pdu *pending; - if (len != 1 || player->pending_pdu == NULL) + if (len != 1 || session->pending_pdu == NULL) goto err; - pending = player->pending_pdu; + pending = session->pending_pdu; if (pending->pdu_id != pdu->params[0]) goto err; @@ -1010,8 +1031,8 @@ static uint8_t avrcp_handle_request_continuing(struct avrcp_player *player, pdu->pdu_id = pending->pdu_id; if (pending->attr_ids == NULL) { - g_free(player->pending_pdu); - player->pending_pdu = NULL; + g_free(session->pending_pdu); + session->pending_pdu = NULL; pdu->packet_type = AVRCP_PACKET_TYPE_END; } else { pdu->packet_type = AVRCP_PACKET_TYPE_CONTINUING; @@ -1026,22 +1047,22 @@ err: return AVC_CTYPE_REJECTED; } -static uint8_t avrcp_handle_abort_continuing(struct avrcp_player *player, +static uint8_t avrcp_handle_abort_continuing(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) { uint16_t len = ntohs(pdu->params_len); struct pending_pdu *pending; - if (len != 1 || player->pending_pdu == NULL) + if (len != 1 || session->pending_pdu == NULL) goto err; - pending = player->pending_pdu; + pending = session->pending_pdu; if (pending->pdu_id != pdu->params[0]) goto err; - player_abort_pending_pdu(player); + session_abort_pending_pdu(session); pdu->params_len = 0; return AVC_CTYPE_ACCEPTED; @@ -1055,9 +1076,8 @@ err: static struct control_pdu_handler { uint8_t pdu_id; uint8_t code; - uint8_t (*func) (struct avrcp_player *player, - struct avrcp_header *pdu, - uint8_t transaction); + uint8_t (*func) (struct avrcp *session, struct avrcp_header *pdu, + uint8_t transaction); } control_handlers[] = { { AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS, avrcp_handle_get_capabilities }, @@ -1091,12 +1111,12 @@ static struct control_pdu_handler { }; /* handle vendordep pdu inside an avctp packet */ -static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction, +static size_t handle_vendordep_pdu(struct avctp *conn, uint8_t transaction, uint8_t *code, uint8_t *subunit, uint8_t *operands, size_t operand_count, void *user_data) { - struct avrcp_player *player = user_data; + struct avrcp *session = user_data; struct control_pdu_handler *handler; struct avrcp_header *pdu = (void *) operands; uint32_t company_id = get_company_id(pdu->company_id); @@ -1132,13 +1152,13 @@ static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction, goto err_metadata; } - *code = handler->func(player, pdu, transaction); + *code = handler->func(session, pdu, transaction); if (*code != AVC_CTYPE_REJECTED && pdu->pdu_id != AVRCP_GET_ELEMENT_ATTRIBUTES && pdu->pdu_id != AVRCP_REQUEST_CONTINUING && pdu->pdu_id != AVRCP_ABORT_CONTINUING) - player_abort_pending_pdu(player); + session_abort_pending_pdu(session); return AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); @@ -1151,18 +1171,17 @@ err_metadata: static struct browsing_pdu_handler { uint8_t pdu_id; - void (*func) (struct avrcp_player *player, - struct avrcp_browsing_header *pdu, - uint8_t transaction); + void (*func) (struct avrcp *session, struct avrcp_browsing_header *pdu, + uint8_t transaction); } browsing_handlers[] = { { }, }; -static size_t handle_browsing_pdu(struct avctp *session, +static size_t handle_browsing_pdu(struct avctp *conn, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data) { - struct avrcp_player *player = user_data; + struct avrcp *session = user_data; struct browsing_pdu_handler *handler; struct avrcp_browsing_header *pdu = (void *) operands; uint8_t status; @@ -1181,8 +1200,8 @@ static size_t handle_browsing_pdu(struct avctp *session, goto err; } - player->transaction = transaction; - handler->func(player, pdu, transaction); + session->transaction = transaction; + handler->func(session, pdu, transaction); return AVRCP_BROWSING_HEADER_LENGTH + ntohs(pdu->param_len); err: @@ -1218,12 +1237,13 @@ static struct avrcp_server *find_server(GSList *list, const bdaddr_t *src) return NULL; } -static gboolean avrcp_handle_volume_changed(struct avctp *session, +static gboolean avrcp_handle_volume_changed(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { - struct avrcp_player *player = user_data; + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; struct avrcp_header *pdu = (void *) operands; uint8_t volume; @@ -1232,17 +1252,19 @@ static gboolean avrcp_handle_volume_changed(struct avctp *session, volume = pdu->params[1] & 0x7F; - player->cb->set_volume(volume, player->dev, player->user_data); + if (player) + player->cb->set_volume(volume, session->dev, + player->user_data); if (code == AVC_CTYPE_CHANGED) { - register_volume_notification(player); + register_volume_notification(session); return FALSE; } return TRUE; } -static void register_volume_notification(struct avrcp_player *player) +static void register_volume_notification(struct avrcp *session) { uint8_t buf[AVRCP_HEADER_LENGTH + AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH]; struct avrcp_header *pdu = (void *) buf; @@ -1258,16 +1280,28 @@ static void register_volume_notification(struct avrcp_player *player) length = AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); - avctp_send_vendordep_req(player->session, AVC_CTYPE_NOTIFY, + avctp_send_vendordep_req(session->conn, AVC_CTYPE_NOTIFY, AVC_SUBUNIT_PANEL, buf, length, - avrcp_handle_volume_changed, player); + avrcp_handle_volume_changed, session); +} + +static struct avrcp *find_session(GSList *list, struct audio_device *dev) +{ + for (; list; list = list->next) { + struct avrcp *session = list->data; + + if (session->dev == dev) + return session; + } + + return NULL; } static void state_changed(struct audio_device *dev, avctp_state_t old_state, avctp_state_t new_state, void *user_data) { struct avrcp_server *server; - struct avrcp_player *player; + struct avrcp *session; const sdp_record_t *rec; sdp_list_t *list; sdp_profile_desc_t *desc; @@ -1276,43 +1310,49 @@ static void state_changed(struct audio_device *dev, avctp_state_t old_state, if (!server) return; - player = server->addressed_player; - if (!player) - return; + session = find_session(server->sessions, dev); switch (new_state) { case AVCTP_STATE_DISCONNECTED: - player->session = NULL; - player->dev = NULL; - player->registered_events = 0; + if (session == NULL) + break; - if (player->control_handler) { - avctp_unregister_pdu_handler(player->control_handler); - player->control_handler = 0; - } + server->sessions = g_slist_remove(server->sessions, session); - if (player->browsing_handler) { + if (session->control_handler) + avctp_unregister_pdu_handler(session->control_handler); + + if (session->browsing_handler) avctp_unregister_browsing_pdu_handler( - player->browsing_handler); - player->browsing_handler = 0; - } + session->browsing_handler); + + g_free(session); break; case AVCTP_STATE_CONNECTING: - player->session = avctp_connect(&dev->src, &dev->dst); - player->dev = dev; + if (session != NULL) + break; + + session = g_new0(struct avrcp, 1); + session->server = server; + session->conn = avctp_connect(&dev->src, &dev->dst); + session->dev = dev; + session->player = g_slist_nth_data(server->players, 0); - if (!player->control_handler) - player->control_handler = avctp_register_pdu_handler( + session->control_handler = avctp_register_pdu_handler( AVC_OP_VENDORDEP, handle_vendordep_pdu, - player); - if (!player->browsing_handler) - player->browsing_handler = + session); + session->browsing_handler = avctp_register_browsing_pdu_handler( handle_browsing_pdu, - player); + session); + + server->sessions = g_slist_append(server->sessions, session); break; case AVCTP_STATE_CONNECTED: + if (session == NULL) + break; + rec = btd_device_get_record(dev->btd_dev, AVRCP_TARGET_UUID); if (rec == NULL) return; @@ -1326,13 +1366,13 @@ static void state_changed(struct audio_device *dev, avctp_state_t old_state, int feat; int ret; - register_volume_notification(player); + register_volume_notification(session); ret = sdp_get_int_attr(rec, SDP_ATTR_SUPPORTED_FEATURES, &feat); if (ret == 0 && (feat & AVRCP_FEATURE_BROWSING)) - avctp_connect_browsing(player->session); + avctp_connect_browsing(session->conn); } sdp_list_free(list, free); @@ -1433,11 +1473,6 @@ static void player_destroy(gpointer data) if (player->destroy) player->destroy(player->user_data); - player_abort_pending_pdu(player); - - if (player->control_handler) - avctp_unregister_pdu_handler(player->control_handler); - g_free(player); } @@ -1475,6 +1510,7 @@ struct avrcp_player *avrcp_register_player(const bdaddr_t *src, { struct avrcp_server *server; struct avrcp_player *player; + GSList *l; server = find_server(servers, src); if (!server) @@ -1486,35 +1522,50 @@ struct avrcp_player *avrcp_register_player(const bdaddr_t *src, player->user_data = user_data; player->destroy = destroy; - if (!server->players) - server->addressed_player = player; - if (!avctp_id) avctp_id = avctp_add_state_cb(state_changed, NULL); server->players = g_slist_append(server->players, player); + /* Assign player to session without current player */ + for (l = server->sessions; l; l = l->next) { + struct avrcp *session = l->data; + + if (session->player == NULL) { + session->player = player; + player->sessions = g_slist_append(player->sessions, + session); + } + } + return player; } void avrcp_unregister_player(struct avrcp_player *player) { struct avrcp_server *server = player->server; + GSList *l; server->players = g_slist_remove(server->players, player); - if (server->addressed_player == player) - server->addressed_player = g_slist_nth_data(server->players, 0); + /* Remove player from sessions using it */ + for (l = player->sessions; l; l = l->next) { + struct avrcp *session = l->data; + + if (session->player == player) + session->player = g_slist_nth_data(server->players, 0); + } player_destroy(player); } -static gboolean avrcp_handle_set_volume(struct avctp *session, +static gboolean avrcp_handle_set_volume(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { - struct avrcp_player *player = user_data; + struct avrcp *session = user_data; + struct avrcp_player *player = session->player; struct avrcp_header *pdu = (void *) operands; uint8_t volume; @@ -1523,7 +1574,7 @@ static gboolean avrcp_handle_set_volume(struct avctp *session, volume = pdu->params[0] & 0x7F; - player->cb->set_volume(volume, player->dev, player->user_data); + player->cb->set_volume(volume, session->dev, player->user_data); return FALSE; } @@ -1531,7 +1582,7 @@ static gboolean avrcp_handle_set_volume(struct avctp *session, int avrcp_set_volume(struct audio_device *dev, uint8_t volume) { struct avrcp_server *server; - struct avrcp_player *player; + struct avrcp *session; uint8_t buf[AVRCP_HEADER_LENGTH + 1]; struct avrcp_header *pdu = (void *) buf; @@ -1539,11 +1590,8 @@ int avrcp_set_volume(struct audio_device *dev, uint8_t volume) if (server == NULL) return -EINVAL; - player = server->addressed_player; - if (player == NULL) - return -ENOTSUP; - - if (player->session == NULL) + session = find_session(server->sessions, dev); + if (session == NULL) return -ENOTCONN; memset(buf, 0, sizeof(buf)); @@ -1556,7 +1604,7 @@ int avrcp_set_volume(struct audio_device *dev, uint8_t volume) DBG("volume=%u", volume); - return avctp_send_vendordep_req(player->session, AVC_CTYPE_CONTROL, + return avctp_send_vendordep_req(session->conn, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, buf, sizeof(buf), - avrcp_handle_set_volume, player); + avrcp_handle_set_volume, session); } diff --git a/audio/avrcp.h b/audio/avrcp.h index d94a050b9..42d902b2b 100644 --- a/audio/avrcp.h +++ b/audio/avrcp.h @@ -101,7 +101,7 @@ struct avrcp_player *avrcp_register_player(const bdaddr_t *src, GDestroyNotify destroy); void avrcp_unregister_player(struct avrcp_player *player); -int avrcp_player_event(struct avrcp_player *player, uint8_t id, void *data); +void avrcp_player_event(struct avrcp_player *player, uint8_t id, void *data); size_t avrcp_handle_vendor_reject(uint8_t *code, uint8_t *operands); |