Przeglądaj źródła

Merge branch 'feature/btdm_avrcp_volume' into 'master'

components/bt: Add AVRCP feature about volume

See merge request idf/esp-idf!5209
Jiang Jiang Jian 6 lat temu
rodzic
commit
70474ae844

+ 46 - 12
components/bt/host/bluedroid/api/esp_avrc_api.c

@@ -29,7 +29,7 @@ esp_err_t esp_avrc_ct_register_callback(esp_avrc_ct_cb_t callback)
     }
 
     if (callback == NULL) {
-        return ESP_FAIL;
+        return ESP_ERR_INVALID_ARG;
     }
 
     btc_profile_cb_set(BTC_PID_AVRC_CT, callback);
@@ -76,8 +76,8 @@ esp_err_t esp_avrc_ct_send_set_player_value_cmd(uint8_t tl, uint8_t attr_id, uin
         return ESP_ERR_INVALID_STATE;
     }
 
-    if (tl >= 16 || attr_id > ESP_AVRC_PS_MAX_ATTR - 1) {
-        return ESP_FAIL;
+    if (tl > ESP_AVRC_TRANS_LABEL_MAX || attr_id > ESP_AVRC_PS_MAX_ATTR - 1) {
+        return ESP_ERR_INVALID_ARG;
     }
 
     btc_msg_t msg;
@@ -103,7 +103,7 @@ esp_err_t esp_avrc_ct_send_get_rn_capabilities_cmd(uint8_t tl)
         return ESP_ERR_INVALID_STATE;
     }
 
-    if (tl >= 16) {
+    if (tl > ESP_AVRC_TRANS_LABEL_MAX) {
         return ESP_ERR_INVALID_ARG;
     }
 
@@ -128,8 +128,8 @@ esp_err_t esp_avrc_ct_send_register_notification_cmd(uint8_t tl, uint8_t event_i
         return ESP_ERR_INVALID_STATE;
     }
 
-    if (tl >= 16 || event_id > ESP_AVRC_RN_MAX_EVT - 1) {
-        return ESP_FAIL;
+    if (tl > ESP_AVRC_TRANS_LABEL_MAX || event_id > ESP_AVRC_RN_MAX_EVT - 1) {
+        return ESP_ERR_INVALID_ARG;
     }
 
     if (!btc_avrc_ct_rn_evt_supported(event_id)) {
@@ -153,14 +153,48 @@ esp_err_t esp_avrc_ct_send_register_notification_cmd(uint8_t tl, uint8_t event_i
     return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL;
 }
 
+esp_err_t esp_avrc_ct_send_set_absolute_volume_cmd(uint8_t tl, uint8_t volume)
+{
+    if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) {
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    if (tl > ESP_AVRC_TRANS_LABEL_MAX) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (volume > BTC_AVRC_MAX_VOLUME) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (!btc_avrc_ct_rn_evt_supported(ESP_AVRC_RN_VOLUME_CHANGE)) {
+        return ESP_ERR_NOT_SUPPORTED;
+    }
+
+    btc_msg_t msg;
+    msg.sig = BTC_SIG_API_CALL;
+    msg.pid = BTC_PID_AVRC_CT;
+    msg.act = BTC_AVRC_CTRL_API_SND_SET_ABSOLUTE_VOLUME_EVT;
+
+    btc_avrc_args_t arg;
+    memset(&arg, 0, sizeof(btc_avrc_args_t));
+
+    arg.set_abs_vol_cmd.tl = tl;
+    arg.set_abs_vol_cmd.volume = volume;
+
+    /* Switch to BTC context */
+    bt_status_t stat = btc_transfer_context(&msg, &arg, sizeof(btc_avrc_args_t), NULL);
+    return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL;
+}
+
 esp_err_t esp_avrc_ct_send_metadata_cmd(uint8_t tl, uint8_t attr_mask)
 {
     if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) {
         return ESP_ERR_INVALID_STATE;
     }
 
-    if (tl >= 16) {
-        return ESP_FAIL;
+    if (tl > ESP_AVRC_TRANS_LABEL_MAX) {
+        return ESP_ERR_INVALID_ARG;
     }
 
     btc_msg_t msg;
@@ -185,8 +219,8 @@ esp_err_t esp_avrc_ct_send_passthrough_cmd(uint8_t tl, uint8_t key_code, uint8_t
         return ESP_ERR_INVALID_STATE;
     }
 
-    if (tl >= 16 || key_state > ESP_AVRC_PT_CMD_STATE_RELEASED) {
-        return ESP_FAIL;
+    if (tl > ESP_AVRC_TRANS_LABEL_MAX || key_state > ESP_AVRC_PT_CMD_STATE_RELEASED) {
+        return ESP_ERR_INVALID_ARG;
     }
 
     btc_msg_t msg;
@@ -217,7 +251,7 @@ esp_err_t esp_avrc_tg_register_callback(esp_avrc_tg_cb_t callback)
     }
 
     if (callback == NULL) {
-        return ESP_FAIL;
+        return ESP_ERR_INVALID_ARG;
     }
 
     btc_profile_cb_set(BTC_PID_AVRC_TG, callback);
@@ -445,7 +479,7 @@ esp_err_t esp_avrc_tg_send_rn_rsp(esp_avrc_rn_event_ids_t event_id, esp_avrc_rn_
     /* Switch to BTC context */
     bt_status_t stat = btc_transfer_context(&msg, &arg, sizeof(btc_avrc_tg_args_t), NULL);
     return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL;
-    
+
 }
 
 #endif /* #if BTC_AV_INCLUDED */

+ 36 - 12
components/bt/host/bluedroid/api/include/api/esp_avrc_api.h

@@ -24,6 +24,8 @@
 extern "C" {
 #endif
 
+#define ESP_AVRC_TRANS_LABEL_MAX        15       /*!< max transaction label */
+
 /// AVRC feature bit mask
 typedef enum {
     ESP_AVRC_FEAT_RCTG = 0x0001,                 /*!< remote control target */
@@ -140,6 +142,7 @@ typedef enum {
     ESP_AVRC_CT_CHANGE_NOTIFY_EVT = 4,           /*!< notification event */
     ESP_AVRC_CT_REMOTE_FEATURES_EVT = 5,         /*!< feature of remote device indication event */
     ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT = 6,     /*!< supported notification events capability of peer device */
+    ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT = 7, /*!< set absolute volume response event */
 } esp_avrc_ct_cb_event_t;
 
 /// AVRC Target callback events
@@ -327,6 +330,13 @@ typedef union {
         uint8_t cap_count;                       /*!< number of items provided in event or company_id according to cap_id used */
         esp_avrc_rn_evt_cap_mask_t evt_set;      /*!< supported event_ids represented in bit-mask */
     } get_rn_caps_rsp;                           /*!< get supported event capabilities response from AVRCP target */
+
+    /**
+     * @brief ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT
+     */
+    struct avrc_ct_set_volume_rsp_param {
+        uint8_t volume;                          /*!< the volume which has actually been set, range is 0 to 0x7f, means 0% to 100% */
+    } set_volume_rsp;                            /*!< set absolute volume response event */
 } esp_avrc_ct_cb_param_t;
 
 /// AVRC target callback parameters
@@ -460,7 +470,8 @@ esp_err_t esp_avrc_ct_send_get_rn_capabilities_cmd(uint8_t tl);
  *
  * @param[in]       tl : transaction label, 0 to 15, consecutive commands should use different values.
  * @param[in]       event_id : id of events, e.g. ESP_AVRC_RN_PLAY_STATUS_CHANGE, ESP_AVRC_RN_TRACK_CHANGE, etc.
- * @param[in]       event_parameter : special parameters, eg. playback interval for ESP_AVRC_RN_PLAY_POS_CHANGED
+ * @param[in]       event_parameter : playback interval for ESP_AVRC_RN_PLAY_POS_CHANGED;
+ *                                     For other events , value of this parameter is ignored.
  * @return
  *                  - ESP_OK: success
  *                  - ESP_ERR_INVALID_STATE: if bluetooth stack is not yet enabled
@@ -469,6 +480,19 @@ esp_err_t esp_avrc_ct_send_get_rn_capabilities_cmd(uint8_t tl);
  */
 esp_err_t esp_avrc_ct_send_register_notification_cmd(uint8_t tl, uint8_t event_id, uint32_t event_parameter);
 
+/**
+ * @brief           Send set absolute volume command to AVRCP target, This function should be called after
+ *                  ESP_AVRC_CT_CONNECTION_STATE_EVT is received and AVRCP connection is established
+ *
+ * @param[in]       tl : transaction label, 0 to 15, consecutive commands should use different values.
+ * @param[in]       volume : volume, 0 to 0x7f, means 0% to 100%
+ * @return
+ *                  - ESP_OK: success
+ *                  - ESP_ERR_INVALID_STATE: if bluetooth stack is not yet enabled
+ *                  - ESP_ERR_NOT_SUPPORTED: if the event_id is not supported in current implementation
+ *                  - ESP_FAIL: others
+ */
+esp_err_t esp_avrc_ct_send_set_absolute_volume_cmd(uint8_t tl, uint8_t volume);
 
 /**
  * @brief           Send metadata command to AVRCP target, This function should be called after
@@ -562,22 +586,22 @@ esp_err_t esp_avrc_tg_get_psth_cmd_filter(esp_avrc_psth_filter_t filter, esp_avr
 
 /**
  *
- * @brief           Set the filter of remote passthrough commands on AVRC target. Filter is given by 
- *                  filter type and bit mask for the passthrough commands. This function should be called 
+ * @brief           Set the filter of remote passthrough commands on AVRC target. Filter is given by
+ *                  filter type and bit mask for the passthrough commands. This function should be called
  *                  after esp_avrc_tg_init().
  *                  If filter type is ESP_AVRC_PSTH_FILTER_SUPPORT_CMD, the passthrough commands which
- *                  are set "1" as given in cmd_set will generate ESP_AVRC_CT_PASSTHROUGH_RSP_EVT callback 
+ *                  are set "1" as given in cmd_set will generate ESP_AVRC_CT_PASSTHROUGH_RSP_EVT callback
  *                  event and are auto-accepted in the protocol stack, other commands are replied with response
- *                  type "NOT IMPLEMENTED" (8). The set of supported commands should be a subset of allowed 
- *                  command set. The allowed command set can be retrieved using esp_avrc_tg_get_psth_cmd_filter() 
+ *                  type "NOT IMPLEMENTED" (8). The set of supported commands should be a subset of allowed
+ *                  command set. The allowed command set can be retrieved using esp_avrc_tg_get_psth_cmd_filter()
  *                  with filter type "ESP_AVRC_PSTH_FILTER_ALLOWED_CMD".
- *                  
+ *
  *                  Filter type "ESP_AVRC_PSTH_FILTER_ALLOWED_CMD" does not apply to this function
  * @return
  *                  - ESP_OK: success
  *                  - ESP_ERR_INVALID_STATE: if bluetooth stack is not enabled
  *                  - ESP_ERR_INVALID_ARG: if filter type is invalid or cmd_set is NULL
- *                  - ESP_ERR_NOT_SUPPORTED:: if filter type is ESP_AVRC_PSTH_FILTER_ALLOWED_CMD, or cmd_set 
+ *                  - ESP_ERR_NOT_SUPPORTED:: if filter type is ESP_AVRC_PSTH_FILTER_ALLOWED_CMD, or cmd_set
  *                    includes unallowed commands
  *
  */
@@ -601,13 +625,13 @@ bool esp_avrc_psth_bit_mask_operation(esp_avrc_bit_mask_op_t op, esp_avrc_psth_b
 /**
  *
  * @brief           Get the requested event notification capabilies on local AVRC target. The capability is returned
- *                  in a bit mask representation in evt_set. This function should be called after 
+ *                  in a bit mask representation in evt_set. This function should be called after
  *                  esp_avrc_tg_init().
  *                  For capability type "ESP_AVRC_RN_CAP_ALLOWED_EVT, the retrieved event set is constant and
- *                  it covers all of the notifcation events that can possibly be supported with current 
+ *                  it covers all of the notifcation events that can possibly be supported with current
  *                  implementation.
  *                  For capability type ESP_AVRC_RN_CAP_SUPPORTED_EVT, the event set covers the notification
- *                  events selected to be supported under current configuration, The configuration can be 
+ *                  events selected to be supported under current configuration, The configuration can be
  *                  changed using esp_avrc_tg_set_rn_evt_cap()
  *
  * @return
@@ -652,7 +676,7 @@ bool esp_avrc_rn_evt_bit_mask_operation(esp_avrc_bit_mask_op_t op, esp_avrc_rn_e
  * @brief           Send RegisterNotification Response to remote AVRCP controller. Local event notification
  *                  capability can be set using esp_avrc_tg_set_rn_evt_cap(),
  *                  in a bit mask representation in evt_set. This function should be called after
- *                  esp_avrc_tg_init() 
+ *                  esp_avrc_tg_init()
  * @param[in]       event_id: notification event ID that remote AVRCP CT registers
  * @param[in]       rsp: notification response code
  * @param[in]       param: parameters included in the specific notification

+ 12 - 6
components/bt/host/bluedroid/bta/av/bta_av_cfg.c

@@ -40,8 +40,10 @@ const UINT32  bta_av_meta_caps_co_ids[] = {
     AVRC_CO_BROADCOM
 };
 
-/* AVRCP cupported categories */
-#define BTA_AV_RC_SUPF_CT       (AVRC_SUPF_CT_CAT1)
+/* AVRCP supported categories */
+#define BTA_AV_RC_SNK_SUPF_CT       (AVRC_SUPF_CT_CAT1)
+#define BTA_AV_RC_SRC_SUPF_CT       (AVRC_SUPF_CT_CAT2)
+
 
 /* Added to modify
 **  1. flush timeout
@@ -62,9 +64,11 @@ const UINT16  bta_av_audio_flush_to[] = {
 /* Note: Android doesnt support AVRC_SUPF_TG_GROUP_NAVI  */
 /* Note: if AVRC_SUPF_TG_GROUP_NAVI is set, bta_av_cfg.avrc_group should be TRUE */
 #if AVRC_METADATA_INCLUDED == TRUE
-#define BTA_AV_RC_SUPF_TG       (AVRC_SUPF_TG_CAT2) /* TODO: | AVRC_SUPF_TG_APP_SETTINGS) */
+#define BTA_AV_RC_SNK_SUPF_TG       (AVRC_SUPF_TG_CAT2) /* TODO: | AVRC_SUPF_TG_APP_SETTINGS) */
+#define BTA_AV_RC_SRC_SUPF_TG       (AVRC_SUPF_TG_CAT1) /* TODO: | AVRC_SUPF_TG_APP_SETTINGS) */
 #else
-#define BTA_AV_RC_SUPF_TG       (AVRC_SUPF_TG_CAT2)
+#define BTA_AV_RC_SNK_SUPF_TG       (AVRC_SUPF_TG_CAT2)
+#define BTA_AV_RC_SRC_SUPF_TG       (AVRC_SUPF_TG_CAT1)
 #endif
 
 /* the MTU for the AVRCP browsing channel */
@@ -80,8 +84,10 @@ const tBTA_AV_CFG bta_av_cfg = {
     48,                     /* AVRCP MTU at L2CAP for control channel */
 #endif
     BTA_AV_MAX_RC_BR_MTU,   /* AVRCP MTU at L2CAP for browsing channel */
-    BTA_AV_RC_SUPF_CT,      /* AVRCP controller categories */
-    BTA_AV_RC_SUPF_TG,      /* AVRCP target categories */
+    BTA_AV_RC_SNK_SUPF_CT,  /* AVRCP controller categories as SNK */
+    BTA_AV_RC_SNK_SUPF_TG,  /* AVRCP target categories as SNK */
+    BTA_AV_RC_SRC_SUPF_CT,  /* AVRCP controller categories as SRC */
+    BTA_AV_RC_SRC_SUPF_TG,  /* AVRCP target categories as SRC */
     672,                    /* AVDTP signaling channel MTU at L2CAP */
     BTA_AV_MAX_A2DP_MTU,    /* AVDTP audio transport channel MTU at L2CAP */
     bta_av_audio_flush_to,  /* AVDTP audio transport channel flush timeout */

+ 14 - 5
components/bt/host/bluedroid/bta/av/bta_av_main.c

@@ -576,9 +576,13 @@ static void bta_av_api_register(tBTA_AV_DATA *p_data)
                 bta_ar_reg_avct(p_bta_av_cfg->avrc_mtu, p_bta_av_cfg->avrc_br_mtu,
                                 (UINT8)(bta_av_cb.sec_mask & (~BTA_SEC_AUTHORIZE)), BTA_ID_AV);
 #endif
-
-                bta_ar_reg_avrc(UUID_SERVCLASS_AV_REM_CTRL_TARGET, "AV Remote Control Target\n", NULL,
-                                p_bta_av_cfg->avrc_tg_cat, BTA_ID_AV);
+                if (p_data->api_reg.tsep == AVDT_TSEP_SRC) {
+                    bta_ar_reg_avrc(UUID_SERVCLASS_AV_REM_CTRL_TARGET, "AV Remote Control Target\n", NULL,
+                                p_bta_av_cfg->avrc_src_tg_cat, BTA_ID_AV);
+                } else {
+                    bta_ar_reg_avrc(UUID_SERVCLASS_AV_REM_CTRL_TARGET, "AV Remote Control Target\n", NULL,
+                                p_bta_av_cfg->avrc_snk_tg_cat, BTA_ID_AV);
+                }
 #endif
             }
 
@@ -711,8 +715,13 @@ static void bta_av_api_register(tBTA_AV_DATA *p_data)
                     }
 #if( defined BTA_AR_INCLUDED ) && (BTA_AR_INCLUDED == TRUE)
                     /* create an SDP record as AVRC CT. */
-                    bta_ar_reg_avrc(UUID_SERVCLASS_AV_REMOTE_CONTROL, NULL, NULL,
-                                    p_bta_av_cfg->avrc_ct_cat, BTA_ID_AV);
+                    if (p_data->api_reg.tsep == AVDT_TSEP_SRC) {
+                        bta_ar_reg_avrc(UUID_SERVCLASS_AV_REMOTE_CONTROL, "AV Remote Control Controller\n", NULL,
+                                    p_bta_av_cfg->avrc_src_ct_cat, BTA_ID_AV);
+                    } else {
+                        bta_ar_reg_avrc(UUID_SERVCLASS_AV_REMOTE_CONTROL, "AV Remote Control Controller\n", NULL,
+                                    p_bta_av_cfg->avrc_snk_ct_cat, BTA_ID_AV);
+                    }
 #endif
                 }
             }

+ 4 - 2
components/bt/host/bluedroid/bta/include/bta/bta_av_api.h

@@ -529,8 +529,10 @@ typedef struct {
     UINT32  company_id;         /* AVRCP Company ID */
     UINT16  avrc_mtu;           /* AVRCP MTU at L2CAP for control channel */
     UINT16  avrc_br_mtu;        /* AVRCP MTU at L2CAP for browsing channel */
-    UINT16  avrc_ct_cat;        /* AVRCP controller categories */
-    UINT16  avrc_tg_cat;        /* AVRCP target categories */
+    UINT16  avrc_snk_ct_cat;    /* AVRCP controller categories as SNK */
+    UINT16  avrc_snk_tg_cat;    /* AVRCP target categories SNK */
+    UINT16  avrc_src_ct_cat;    /* AVRCP controller categories as SRC */
+    UINT16  avrc_src_tg_cat;    /* AVRCP target categories as SRC */
     UINT16  sig_mtu;            /* AVDTP signaling channel MTU at L2CAP */
     UINT16  audio_mtu;          /* AVDTP audio transport channel MTU at L2CAP */
     const UINT16 *p_audio_flush_to;/* AVDTP audio transport channel flush timeout */

+ 77 - 25
components/bt/host/bluedroid/btc/profile/std/avrc/btc_avrc.c

@@ -71,7 +71,7 @@ const static uint16_t cs_psth_allowed_cmd[8] = {
     0x0078, /* bit mask: 0=CHAN_UP, 1=CHAN_DOWN, 2=PREV_CHAN, 3=SOUND_SEL,
                          4=INPUT_SEL, 5=DISP_INFO, 6=HELP, 7=PAGE_UP,
                          8=PAGE_DOWN */
-    0x1b3F, /* bit mask: 0=POWER, 1=VOL_UP, 2=VOL_DOWN, 3=MUTE,
+    0x1b7F, /* bit mask: 0=POWER, 1=VOL_UP, 2=VOL_DOWN, 3=MUTE,
                          4=PLAY, 5=STOP, 6=PAUSE, 7=RECORD,
                          8=REWIND, 9=FAST_FOR, 10=EJECT, 11=FORWARD,
                          12=BACKWARD */
@@ -635,6 +635,16 @@ static void handle_rc_get_caps_rsp (tAVRC_GET_CAPS_RSP *rsp)
     }
 }
 
+static void handle_rc_set_absolute_volume_rsp(tAVRC_SET_VOLUME_RSP *rsp)
+{
+    esp_avrc_ct_cb_param_t param;
+    memset(&param, 0, sizeof(esp_avrc_ct_cb_param_t));
+
+    param.set_volume_rsp.volume = rsp->volume;
+
+    btc_avrc_ct_cb_to_app(ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT, &param);
+}
+
 /***************************************************************************
  *  Function       handle_rc_metamsg_cmd
  *
@@ -822,6 +832,11 @@ static void handle_rc_metamsg_rsp (tBTA_AV_META_MSG *p_meta_msg)
             handle_rc_get_caps_rsp(&avrc_response.get_caps);
         }
         break;
+    case AVRC_PDU_SET_ABSOLUTE_VOLUME:
+        if (vendor_msg->hdr.ctype == AVRC_RSP_ACCEPT) {
+            handle_rc_set_absolute_volume_rsp(&avrc_response.volume);
+        }
+        break;
     default:
         BTC_TRACE_WARNING("%s: unhandled meta rsp: pdu 0x%x", __FUNCTION__, avrc_response.rsp.pdu);
     }
@@ -1055,15 +1070,15 @@ static bt_status_t btc_avrc_ct_send_set_player_value_cmd(uint8_t tl, uint8_t att
     avrc_cmd.set_app_val.p_vals = &values;
     avrc_cmd.set_app_val.pdu = AVRC_PDU_SET_PLAYER_APP_VALUE;
 
-    status = AVRC_BldCommand(&avrc_cmd, &p_msg);
-    if (status == AVRC_STS_NO_ERROR) {
-        if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) {
+    if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) {
+        status = AVRC_BldCommand(&avrc_cmd, &p_msg);
+        if (status == AVRC_STS_NO_ERROR) {
             BTA_AvMetaCmd(btc_rc_cb.rc_handle, tl, BTA_AV_CMD_CTRL, p_msg);
             status = BT_STATUS_SUCCESS;
-        } else {
-            status = BT_STATUS_FAIL;
-            BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__);
         }
+    } else {
+        status = BT_STATUS_FAIL;
+        BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__);
     }
 
 #else
@@ -1088,15 +1103,15 @@ static bt_status_t btc_avrc_ct_send_get_rn_caps_cmd(uint8_t tl)
     avrc_cmd.get_caps.pdu = AVRC_PDU_GET_CAPABILITIES;
     avrc_cmd.get_caps.capability_id = AVRC_CAP_EVENTS_SUPPORTED;
 
-    status = AVRC_BldCommand(&avrc_cmd, &p_msg);
-    if (status == AVRC_STS_NO_ERROR) {
-        if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) {
+    if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) {
+        status = AVRC_BldCommand(&avrc_cmd, &p_msg);
+        if (status == AVRC_STS_NO_ERROR) {
             BTA_AvMetaCmd(btc_rc_cb.rc_handle, tl, AVRC_CMD_STATUS, p_msg);
             status = BT_STATUS_SUCCESS;
-        } else {
-            status = BT_STATUS_FAIL;
-            BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__);
         }
+    } else {
+        status = BT_STATUS_FAIL;
+        BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__);
     }
 
 #else
@@ -1122,15 +1137,48 @@ static bt_status_t btc_avrc_ct_send_register_notification_cmd(uint8_t tl, uint8_
     avrc_cmd.reg_notif.param = event_parameter;
     avrc_cmd.reg_notif.pdu = AVRC_PDU_REGISTER_NOTIFICATION;
 
-    status = AVRC_BldCommand(&avrc_cmd, &p_msg);
-    if (status == AVRC_STS_NO_ERROR) {
-        if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) {
+    if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) {
+        status = AVRC_BldCommand(&avrc_cmd, &p_msg);
+        if (status == AVRC_STS_NO_ERROR) {
             BTA_AvMetaCmd(btc_rc_cb.rc_handle, tl, AVRC_CMD_NOTIF, p_msg);
             status = BT_STATUS_SUCCESS;
-        } else {
-            status = BT_STATUS_FAIL;
-            BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__);
         }
+    } else {
+        status = BT_STATUS_FAIL;
+        BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__);
+    }
+
+#else
+    BTC_TRACE_DEBUG("%s: feature not enabled", __FUNCTION__);
+#endif
+
+    return status;
+}
+
+static bt_status_t btc_avrc_ct_send_set_absolute_volume_cmd(uint8_t tl, uint8_t volume)
+{
+    tAVRC_STS status = BT_STATUS_UNSUPPORTED;
+
+#if (AVRC_METADATA_INCLUDED == TRUE)
+    CHECK_ESP_RC_CONNECTED;
+
+    tAVRC_COMMAND avrc_cmd = {0};
+    BT_HDR *p_msg = NULL;
+
+    avrc_cmd.volume.opcode = AVRC_OP_VENDOR;
+    avrc_cmd.volume.status = AVRC_STS_NO_ERROR;
+    avrc_cmd.volume.volume = volume;
+    avrc_cmd.volume.pdu = AVRC_PDU_SET_ABSOLUTE_VOLUME;
+
+    if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) {
+        status = AVRC_BldCommand(&avrc_cmd, &p_msg);
+        if (status == AVRC_STS_NO_ERROR) {
+            BTA_AvMetaCmd(btc_rc_cb.rc_handle, tl, AVRC_CMD_CTRL, p_msg);
+            status = BT_STATUS_SUCCESS;
+        }
+    } else {
+        status = BT_STATUS_FAIL;
+        BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__);
     }
 
 #else
@@ -1164,15 +1212,15 @@ static bt_status_t btc_avrc_ct_send_metadata_cmd (uint8_t tl, uint8_t attr_mask)
 
     avrc_cmd.get_elem_attrs.num_attr = index;
 
-    status = AVRC_BldCommand(&avrc_cmd, &p_msg);
-    if (status == AVRC_STS_NO_ERROR) {
-        if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) {
+    if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) {
+        status = AVRC_BldCommand(&avrc_cmd, &p_msg);
+        if (status == AVRC_STS_NO_ERROR) {
             BTA_AvMetaCmd(btc_rc_cb.rc_handle, tl, AVRC_CMD_STATUS, p_msg);
             status = BT_STATUS_SUCCESS;
-        } else {
-            status = BT_STATUS_FAIL;
-            BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__);
         }
+    } else {
+        status = BT_STATUS_FAIL;
+        BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__);
     }
 
 #else
@@ -1338,6 +1386,10 @@ void btc_avrc_ct_call_handler(btc_msg_t *msg)
         btc_avrc_ct_send_set_player_value_cmd(arg->ps_cmd.tl, arg->ps_cmd.attr_id, arg->ps_cmd.value_id);
         break;
     }
+    case BTC_AVRC_CTRL_API_SND_SET_ABSOLUTE_VOLUME_EVT: {
+        btc_avrc_ct_send_set_absolute_volume_cmd(arg->set_abs_vol_cmd.tl, arg->set_abs_vol_cmd.volume);
+        break;
+    }
     default:
         BTC_TRACE_WARNING("%s : unhandled event: %d\n", __FUNCTION__, msg->act);
     }

+ 11 - 1
components/bt/host/bluedroid/btc/profile/std/include/btc_avrc.h

@@ -38,7 +38,8 @@ typedef enum {
     BTC_AVRC_STATUS_API_SND_PLAY_STATUS_EVT,
     BTC_AVRC_STATUS_API_SND_GET_RN_CAPS_EVT,
     BTC_AVRC_NOTIFY_API_SND_REG_NOTIFY_EVT,
-    BTC_AVRC_CTRL_API_SND_SET_PLAYER_SETTING_EVT
+    BTC_AVRC_CTRL_API_SND_SET_PLAYER_SETTING_EVT,
+    BTC_AVRC_CTRL_API_SND_SET_ABSOLUTE_VOLUME_EVT
 } btc_avrc_act_t;
 
 typedef struct {
@@ -68,6 +69,14 @@ typedef struct {
     uint8_t tl;
 } get_caps_cmd_t;
 
+#define BTC_AVRC_MIN_VOLUME 0x00
+#define BTC_AVRC_MAX_VOLUME 0x7f
+
+typedef struct {
+    uint8_t tl;
+    uint8_t volume;
+} set_abs_vol_cmd_t;
+
 /* btc_avrc_args_t */
 typedef union {
     pt_cmd_t pt_cmd;
@@ -75,6 +84,7 @@ typedef union {
     rn_cmd_t rn_cmd;
     ps_cmd_t ps_cmd;
     get_caps_cmd_t get_caps_cmd;
+    set_abs_vol_cmd_t set_abs_vol_cmd;
 } btc_avrc_args_t;
 
 /* btc_avrc_tg_act_t */

+ 115 - 1
examples/bluetooth/a2dp_source/main/main.c

@@ -27,6 +27,11 @@
 #include "esp_avrc_api.h"
 
 #define BT_AV_TAG               "BT_AV"
+#define BT_RC_CT_TAG            "RCCT"
+
+// AVRCP used transaction label
+#define APP_RC_CT_TL_GET_CAPS            (0)
+#define APP_RC_CT_TL_RN_VOLUME_CHANGE    (1)
 
 /* event for handler "bt_av_hdl_stack_up */
 enum {
@@ -63,11 +68,17 @@ static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param);
 /// callback function for A2DP source audio data stream
 static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len);
 
+/// callback function for AVRCP controller
+static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param);
+
 static void a2d_app_heart_beat(void *arg);
 
 /// A2DP application state machine
 static void bt_app_av_sm_hdlr(uint16_t event, void *param);
 
+/// avrc CT event handler
+static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param);
+
 /* A2DP application state machine handler for each state */
 static void bt_app_av_state_unconnected(uint16_t event, void *param);
 static void bt_app_av_state_connecting(uint16_t event, void *param);
@@ -81,7 +92,7 @@ static int s_media_state = APP_AV_MEDIA_STATE_IDLE;
 static int s_intv_cnt = 0;
 static int s_connecting_intv = 0;
 static uint32_t s_pkt_cnt = 0;
-
+static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap;
 static TimerHandle_t s_tmr;
 
 static char *bda2str(esp_bd_addr_t bda, char *str, size_t size)
@@ -322,6 +333,14 @@ static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
         /* register GAP callback function */
         esp_bt_gap_register_callback(bt_app_gap_cb);
 
+        /* initialize AVRCP controller */
+        esp_avrc_ct_init();
+        esp_avrc_ct_register_callback(bt_app_rc_ct_cb);
+
+        esp_avrc_rn_evt_cap_mask_t evt_set = {0};
+        esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &evt_set, ESP_AVRC_RN_VOLUME_CHANGE);
+        assert(esp_avrc_tg_set_rn_evt_cap(&evt_set) == ESP_OK);
+
         /* initialize A2DP source */
         esp_a2d_register_callback(&bt_app_a2d_cb);
         esp_a2d_source_register_data_callback(bt_app_a2d_data_cb);
@@ -578,3 +597,98 @@ static void bt_app_av_state_disconnecting(uint16_t event, void *param)
         break;
     }
 }
+
+static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param)
+{
+    switch (event) {
+    case ESP_AVRC_CT_METADATA_RSP_EVT:
+    case ESP_AVRC_CT_CONNECTION_STATE_EVT:
+    case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT:
+    case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
+    case ESP_AVRC_CT_REMOTE_FEATURES_EVT:
+    case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT:
+    case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: {
+        bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL);
+        break;
+    }
+    default:
+        ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event);
+        break;
+    }
+}
+
+static void bt_av_volume_changed(void)
+{
+    if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap,
+                                           ESP_AVRC_RN_VOLUME_CHANGE)) {
+        esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, ESP_AVRC_RN_VOLUME_CHANGE, 0);
+    }
+}
+
+void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter)
+{
+    switch (event_id) {
+    case ESP_AVRC_RN_VOLUME_CHANGE:
+        ESP_LOGI(BT_RC_CT_TAG, "Volume changed: %d", event_parameter->volume);
+        ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume: volume %d", event_parameter->volume + 5);
+        esp_avrc_ct_send_set_absolute_volume_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, event_parameter->volume + 5);
+        bt_av_volume_changed();
+        break;
+    }
+}
+
+static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
+{
+    ESP_LOGD(BT_RC_CT_TAG, "%s evt %d", __func__, event);
+    esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(p_param);
+    switch (event) {
+    case ESP_AVRC_CT_CONNECTION_STATE_EVT: {
+        uint8_t *bda = rc->conn_stat.remote_bda;
+        ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]",
+                 rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
+
+        if (rc->conn_stat.connected) {
+            // get remote supported event_ids of peer AVRCP Target
+            esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS);
+        } else {
+            // clear peer notification capability record
+            s_avrc_peer_rn_cap.bits = 0;
+        }
+        break;
+    }
+    case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: {
+        ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state);
+        break;
+    }
+    case ESP_AVRC_CT_METADATA_RSP_EVT: {
+        ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text);
+        free(rc->meta_rsp.attr_text);
+        break;
+    }
+    case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: {
+        ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id);
+        bt_av_notify_evt_handler(rc->change_ntf.event_id, &rc->change_ntf.event_parameter);
+        break;
+    }
+    case ESP_AVRC_CT_REMOTE_FEATURES_EVT: {
+        ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %x, TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag);
+        break;
+    }
+    case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: {
+        ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count,
+                 rc->get_rn_caps_rsp.evt_set.bits);
+        s_avrc_peer_rn_cap.bits = rc->get_rn_caps_rsp.evt_set.bits;
+
+        bt_av_volume_changed();
+        break;
+    }
+    case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: {
+        ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume rsp: volume %d", rc->set_volume_rsp.volume);
+        break;
+    }
+
+    default:
+        ESP_LOGE(BT_RC_CT_TAG, "%s unhandled evt %d", __func__, event);
+        break;
+    }
+}