diff --git a/profiles/audio/avrcp.c b/profiles/audio/avrcp.c index cfa4a5181..e77e894d6 100644 --- a/profiles/audio/avrcp.c +++ b/profiles/audio/avrcp.c @@ -196,6 +196,7 @@ struct avrcp { gboolean target; uint16_t version; int features; + GSList *players; void (*init_control) (struct avrcp *session); void (*init_browsing) (struct avrcp *session); @@ -2058,264 +2059,6 @@ static void avrcp_player_parse_features(struct avrcp_player *player, } } -static void avrcp_parse_media_player_item(struct avrcp *session, - uint8_t *operands, uint16_t len) -{ - struct avrcp_player *player = session->player; - struct media_player *mp = player->user_data; - uint16_t id, namelen; - uint32_t subtype; - const char *curval, *strval; - char name[255]; - - if (len < 28) - return; - - id = bt_get_be16(&operands[0]); - - if (player->id != id) - return; - - media_player_set_type(mp, type_to_string(operands[2])); - - subtype = bt_get_be32(&operands[3]); - - media_player_set_subtype(mp, subtype_to_string(subtype)); - - curval = media_player_get_status(mp); - strval = status_to_string(operands[7]); - - if (g_strcmp0(curval, strval) != 0) { - media_player_set_status(mp, strval); - avrcp_get_play_status(session); - } - - avrcp_player_parse_features(player, &operands[8]); - - namelen = bt_get_be16(&operands[26]); - if (namelen > 0 && namelen + 28 == len) { - namelen = MIN(namelen, sizeof(name) - 1); - memcpy(name, &operands[28], namelen); - name[namelen] = '\0'; - media_player_set_name(mp, name); - } - - avrcp_set_browsed_player(session, player); -} - -static gboolean avrcp_get_media_player_list_rsp(struct avctp *conn, - uint8_t *operands, - size_t operand_count, - void *user_data) -{ - struct avrcp_browsing_header *pdu = (void *) operands; - struct avrcp *session = user_data; - uint16_t count; - size_t i; - - if (pdu->params[0] != AVRCP_STATUS_SUCCESS || operand_count < 5) - return FALSE; - - count = bt_get_be16(&operands[6]); - - for (i = 8; count && i < operand_count; count--) { - uint8_t type; - uint16_t len; - - type = operands[i++]; - len = bt_get_be16(&operands[i]); - i += 2; - - if (type != 0x01) { - i += len; - continue; - } - - if (i + len > operand_count) { - error("Invalid player item length"); - return FALSE; - } - - avrcp_parse_media_player_item(session, &operands[i], len); - - i += len; - } - - return FALSE; -} - -static void avrcp_get_media_player_list(struct avrcp *session) -{ - uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 10]; - struct avrcp_browsing_header *pdu = (void *) buf; - - memset(buf, 0, sizeof(buf)); - - pdu->pdu_id = AVRCP_GET_FOLDER_ITEMS; - pdu->param_len = htons(10); - - avctp_send_browsing_req(session->conn, buf, sizeof(buf), - avrcp_get_media_player_list_rsp, session); -} - -static void avrcp_volume_changed(struct avrcp *session, - struct avrcp_header *pdu) -{ - struct avrcp_player *player = session->player; - uint8_t volume; - - if (player == NULL) - return; - - volume = pdu->params[1] & 0x7F; - - player->cb->set_volume(volume, session->dev, player->user_data); -} - -static void avrcp_status_changed(struct avrcp *session, - struct avrcp_header *pdu) -{ - struct avrcp_player *player = session->player; - struct media_player *mp = player->user_data; - uint8_t value; - const char *curval, *strval; - - value = pdu->params[1]; - - curval = media_player_get_status(mp); - strval = status_to_string(value); - - if (g_strcmp0(curval, strval) == 0) - return; - - media_player_set_status(mp, strval); - avrcp_get_play_status(session); -} - -static void avrcp_track_changed(struct avrcp *session, - struct avrcp_header *pdu) -{ - uint64_t uid; - - if (session->browsing_id) { - uid = bt_get_be64(&pdu->params[1]); - avrcp_get_item_attributes(session, uid); - } else - avrcp_get_element_attributes(session); -} - -static void avrcp_setting_changed(struct avrcp *session, - struct avrcp_header *pdu) -{ - struct avrcp_player *player = session->player; - struct media_player *mp = player->user_data; - uint8_t count = pdu->params[1]; - int i; - - for (i = 2; count > 0; count--, i += 2) { - const char *key; - const char *value; - - key = attr_to_str(pdu->params[i]); - if (key == NULL) - continue; - - value = attrval_to_str(pdu->params[i], pdu->params[i + 1]); - if (value == NULL) - continue; - - media_player_set_setting(mp, key, value); - } -} - -static void avrcp_addressed_player_changed(struct avrcp *session, - struct avrcp_header *pdu) -{ - struct avrcp_player *player = session->player; - uint16_t id = bt_get_be16(&pdu->params[1]); - - if (player->id == id) - return; - - player->id = id; - player->uid_counter = bt_get_be16(&pdu->params[3]); - avrcp_get_media_player_list(session); -} - -static void avrcp_uids_changed(struct avrcp *session, struct avrcp_header *pdu) -{ - struct avrcp_player *player = session->player; - - player->uid_counter = bt_get_be16(&pdu->params[1]); -} - -static gboolean avrcp_handle_event(struct avctp *conn, - uint8_t code, uint8_t subunit, - uint8_t *operands, size_t operand_count, - void *user_data) -{ - struct avrcp *session = user_data; - struct avrcp_header *pdu = (void *) operands; - uint8_t event; - - if (code != AVC_CTYPE_INTERIM && code != AVC_CTYPE_CHANGED) - return FALSE; - - event = pdu->params[0]; - - if (code == AVC_CTYPE_CHANGED) { - session->registered_events ^= (1 << event); - avrcp_register_notification(session, event); - return FALSE; - } - - switch (event) { - case AVRCP_EVENT_VOLUME_CHANGED: - avrcp_volume_changed(session, pdu); - break; - case AVRCP_EVENT_STATUS_CHANGED: - avrcp_status_changed(session, pdu); - break; - case AVRCP_EVENT_TRACK_CHANGED: - avrcp_track_changed(session, pdu); - break; - case AVRCP_EVENT_SETTINGS_CHANGED: - avrcp_setting_changed(session, pdu); - break; - case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: - avrcp_addressed_player_changed(session, pdu); - break; - case AVRCP_EVENT_UIDS_CHANGED: - avrcp_uids_changed(session, pdu); - break; - } - - session->registered_events |= (1 << event); - - return TRUE; -} - -static void avrcp_register_notification(struct avrcp *session, uint8_t event) -{ - uint8_t buf[AVRCP_HEADER_LENGTH + AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH]; - struct avrcp_header *pdu = (void *) buf; - uint8_t length; - - memset(buf, 0, sizeof(buf)); - - set_company_id(pdu->company_id, IEEEID_BTSIG); - pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION; - pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; - pdu->params[0] = event; - pdu->params_len = htons(AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH); - - length = AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); - - avctp_send_vendordep_req(session->conn, AVC_CTYPE_NOTIFY, - AVC_SUBUNIT_PANEL, buf, length, - avrcp_handle_event, session); -} - static void avrcp_set_player_value(struct avrcp *session, uint8_t attr, uint8_t val) { @@ -2444,6 +2187,355 @@ static const struct media_player_callback ct_cbs = { .rewind = ct_rewind, }; +static struct avrcp_player *create_ct_player(struct avrcp *session, + uint16_t id) +{ + struct avrcp_player *player; + struct media_player *mp; + const char *path; + + player = g_new0(struct avrcp_player, 1); + player->sessions = g_slist_prepend(player->sessions, session); + + path = device_get_path(session->dev->btd_dev); + + mp = media_player_controller_create(path, id); + if (mp == NULL) + return NULL; + + media_player_set_callbacks(mp, &ct_cbs, player); + player->user_data = mp; + player->destroy = (GDestroyNotify) media_player_destroy; + + if (session->player == NULL) + session->player = player; + + session->players = g_slist_prepend(session->players, player); + + return player; +} + +static struct avrcp_player *find_ct_player(struct avrcp *session, uint16_t id) +{ + GSList *l; + + for (l = session->players; l; l = l->next) { + struct avrcp_player *player = l->data; + + if (player->id == 0) { + player->id = id; + return player; + } + + if (player->id == id) + return player; + } + + return NULL; +} + +static struct avrcp_player * +avrcp_parse_media_player_item(struct avrcp *session, uint8_t *operands, + uint16_t len) +{ + struct avrcp_player *player; + struct media_player *mp; + uint16_t id, namelen; + uint32_t subtype; + const char *curval, *strval; + char name[255]; + + if (len < 28) + return NULL; + + id = bt_get_be16(&operands[0]); + + player = find_ct_player(session, id); + if (player == NULL) { + player = create_ct_player(session, id); + if (player == NULL) + return NULL; + } + + mp = player->user_data; + + media_player_set_type(mp, type_to_string(operands[2])); + + subtype = bt_get_be32(&operands[3]); + + media_player_set_subtype(mp, subtype_to_string(subtype)); + + curval = media_player_get_status(mp); + strval = status_to_string(operands[7]); + + if (g_strcmp0(curval, strval) != 0) { + media_player_set_status(mp, strval); + avrcp_get_play_status(session); + } + + avrcp_player_parse_features(player, &operands[8]); + + namelen = bt_get_be16(&operands[26]); + if (namelen > 0 && namelen + 28 == len) { + namelen = MIN(namelen, sizeof(name) - 1); + memcpy(name, &operands[28], namelen); + name[namelen] = '\0'; + media_player_set_name(mp, name); + } + + if (session->player == player) + avrcp_set_browsed_player(session, player); + + return player; +} + +static void player_destroy(gpointer data) +{ + struct avrcp_player *player = data; + + if (player->destroy) + player->destroy(player->user_data); + + g_slist_free(player->sessions); + g_free(player->features); + g_free(player); +} + +static gboolean avrcp_get_media_player_list_rsp(struct avctp *conn, + uint8_t *operands, + size_t operand_count, + void *user_data) +{ + struct avrcp_browsing_header *pdu = (void *) operands; + struct avrcp *session = user_data; + uint16_t count; + size_t i; + GSList *removed; + + if (pdu->params[0] != AVRCP_STATUS_SUCCESS || operand_count < 5) + return FALSE; + + removed = g_slist_copy(session->players); + count = bt_get_be16(&operands[6]); + + for (i = 8; count && i < operand_count; count--) { + struct avrcp_player *player; + uint8_t type; + uint16_t len; + + type = operands[i++]; + len = bt_get_be16(&operands[i]); + i += 2; + + if (type != 0x01) { + i += len; + continue; + } + + if (i + len > operand_count) { + error("Invalid player item length"); + return FALSE; + } + + player = avrcp_parse_media_player_item(session, &operands[i], + len); + if (player) + removed = g_slist_remove(removed, player); + + i += len; + } + + g_slist_free_full(removed, player_destroy); + + return FALSE; +} + +static void avrcp_get_media_player_list(struct avrcp *session) +{ + uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 10]; + struct avrcp_browsing_header *pdu = (void *) buf; + + memset(buf, 0, sizeof(buf)); + + pdu->pdu_id = AVRCP_GET_FOLDER_ITEMS; + pdu->param_len = htons(10); + + avctp_send_browsing_req(session->conn, buf, sizeof(buf), + avrcp_get_media_player_list_rsp, session); +} + +static void avrcp_volume_changed(struct avrcp *session, + struct avrcp_header *pdu) +{ + struct avrcp_player *player = session->player; + uint8_t volume; + + if (player == NULL) + return; + + volume = pdu->params[1] & 0x7F; + + player->cb->set_volume(volume, session->dev, player->user_data); +} + +static void avrcp_status_changed(struct avrcp *session, + struct avrcp_header *pdu) +{ + struct avrcp_player *player = session->player; + struct media_player *mp = player->user_data; + uint8_t value; + const char *curval, *strval; + + value = pdu->params[1]; + + curval = media_player_get_status(mp); + strval = status_to_string(value); + + if (g_strcmp0(curval, strval) == 0) + return; + + media_player_set_status(mp, strval); + avrcp_get_play_status(session); +} + +static void avrcp_track_changed(struct avrcp *session, + struct avrcp_header *pdu) +{ + uint64_t uid; + + if (session->browsing_id) { + uid = bt_get_be64(&pdu->params[1]); + avrcp_get_item_attributes(session, uid); + } else + avrcp_get_element_attributes(session); +} + +static void avrcp_setting_changed(struct avrcp *session, + struct avrcp_header *pdu) +{ + struct avrcp_player *player = session->player; + struct media_player *mp = player->user_data; + uint8_t count = pdu->params[1]; + int i; + + for (i = 2; count > 0; count--, i += 2) { + const char *key; + const char *value; + + key = attr_to_str(pdu->params[i]); + if (key == NULL) + continue; + + value = attrval_to_str(pdu->params[i], pdu->params[i + 1]); + if (value == NULL) + continue; + + media_player_set_setting(mp, key, value); + } +} + +static void avrcp_available_players_changed(struct avrcp *session, + struct avrcp_header *pdu) +{ + avrcp_get_media_player_list(session); +} + +static void avrcp_addressed_player_changed(struct avrcp *session, + struct avrcp_header *pdu) +{ + struct avrcp_player *player = session->player; + uint16_t id = bt_get_be16(&pdu->params[1]); + + if (player->id == id) + return; + + player = find_ct_player(session, id); + if (player == NULL) + return; + + player->uid_counter = bt_get_be16(&pdu->params[3]); + session->player = player; + + avrcp_get_media_player_list(session); +} + +static void avrcp_uids_changed(struct avrcp *session, struct avrcp_header *pdu) +{ + struct avrcp_player *player = session->player; + + player->uid_counter = bt_get_be16(&pdu->params[1]); +} + +static gboolean avrcp_handle_event(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_header *pdu = (void *) operands; + uint8_t event; + + if (code != AVC_CTYPE_INTERIM && code != AVC_CTYPE_CHANGED) + return FALSE; + + event = pdu->params[0]; + + if (code == AVC_CTYPE_CHANGED) { + session->registered_events ^= (1 << event); + avrcp_register_notification(session, event); + return FALSE; + } + + switch (event) { + case AVRCP_EVENT_VOLUME_CHANGED: + avrcp_volume_changed(session, pdu); + break; + case AVRCP_EVENT_STATUS_CHANGED: + avrcp_status_changed(session, pdu); + break; + case AVRCP_EVENT_TRACK_CHANGED: + avrcp_track_changed(session, pdu); + break; + case AVRCP_EVENT_SETTINGS_CHANGED: + avrcp_setting_changed(session, pdu); + break; + case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: + avrcp_available_players_changed(session, pdu); + break; + case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: + avrcp_addressed_player_changed(session, pdu); + break; + case AVRCP_EVENT_UIDS_CHANGED: + avrcp_uids_changed(session, pdu); + break; + } + + session->registered_events |= (1 << event); + + return TRUE; +} + +static void avrcp_register_notification(struct avrcp *session, uint8_t event) +{ + uint8_t buf[AVRCP_HEADER_LENGTH + AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH]; + struct avrcp_header *pdu = (void *) buf; + uint8_t length; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION; + pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; + pdu->params[0] = event; + pdu->params_len = htons(AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH); + + length = AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); + + avctp_send_vendordep_req(session->conn, AVC_CTYPE_NOTIFY, + AVC_SUBUNIT_PANEL, buf, length, + avrcp_handle_event, session); +} + static gboolean avrcp_get_capabilities_resp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, @@ -2470,6 +2562,7 @@ static gboolean avrcp_get_capabilities_resp(struct avctp *conn, case AVRCP_EVENT_SETTINGS_CHANGED: case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: case AVRCP_EVENT_UIDS_CHANGED: + case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: avrcp_register_notification(session, event); break; } @@ -2581,8 +2674,6 @@ static void session_ct_init_browsing(struct avrcp *session) static void session_ct_init_control(struct avrcp *session) { struct avrcp_player *player; - struct media_player *mp; - const char *path; DBG("%p version 0x%04x", session, session->version); @@ -2594,20 +2685,10 @@ static void session_ct_init_control(struct avrcp *session) if (session->version >= 0x0104) session->supported_events = (1 << AVRCP_EVENT_VOLUME_CHANGED); - player = g_new0(struct avrcp_player, 1); - player->sessions = g_slist_prepend(player->sessions, session); - session->player = player; - - path = device_get_path(session->dev->btd_dev); - - mp = media_player_controller_create(path); - if (mp == NULL) + player = create_ct_player(session, 0); + if (player == NULL) return; - media_player_set_callbacks(mp, &ct_cbs, player); - player->user_data = mp; - player->destroy = (GDestroyNotify) media_player_destroy; - if (session->version < 0x0103) return; @@ -2641,26 +2722,11 @@ static void session_tg_destroy(struct avrcp *session) session_destroy(session); } -static void player_destroy(gpointer data) -{ - struct avrcp_player *player = data; - - if (player->destroy) - player->destroy(player->user_data); - - g_slist_free(player->sessions); - g_free(player->features); - g_free(player); -} - static void session_ct_destroy(struct avrcp *session) { - struct avrcp_player *player = session->player; - DBG("%p", session); - if (player != NULL) - player_destroy(player); + g_slist_free_full(session->players, player_destroy); session_destroy(session); } diff --git a/profiles/audio/avrcp.h b/profiles/audio/avrcp.h index 775904388..7b6fe9280 100644 --- a/profiles/audio/avrcp.h +++ b/profiles/audio/avrcp.h @@ -74,6 +74,7 @@ #define AVRCP_EVENT_TRACK_REACHED_END 0x03 #define AVRCP_EVENT_TRACK_REACHED_START 0x04 #define AVRCP_EVENT_SETTINGS_CHANGED 0x08 +#define AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED 0x0a #define AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED 0x0b #define AVRCP_EVENT_UIDS_CHANGED 0x0c #define AVRCP_EVENT_VOLUME_CHANGED 0x0d diff --git a/profiles/audio/player.c b/profiles/audio/player.c index c09106d62..ece33cc5a 100644 --- a/profiles/audio/player.c +++ b/profiles/audio/player.c @@ -765,13 +765,14 @@ void media_player_destroy(struct media_player *mp) g_free(mp); } -struct media_player *media_player_controller_create(const char *path) +struct media_player *media_player_controller_create(const char *path, + uint16_t id) { struct media_player *mp; mp = g_new0(struct media_player, 1); mp->device = g_strdup(path); - mp->path = g_strdup_printf("%s/player1", path); + mp->path = g_strdup_printf("%s/player%u", path, id); mp->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); mp->track = g_hash_table_new_full(g_str_hash, g_str_equal, diff --git a/profiles/audio/player.h b/profiles/audio/player.h index 8d6aa368f..852042eeb 100644 --- a/profiles/audio/player.h +++ b/profiles/audio/player.h @@ -55,7 +55,8 @@ struct media_player_callback { int (*rewind) (struct media_player *mp, void *user_data); }; -struct media_player *media_player_controller_create(const char *path); +struct media_player *media_player_controller_create(const char *path, + uint16_t id); void media_player_destroy(struct media_player *mp); void media_player_set_duration(struct media_player *mp, uint32_t duration); void media_player_set_position(struct media_player *mp, uint32_t position);