当前位置: 首页 > news >正文

【Bluedroid】A2DP Sink播放流程源码分析(三)

AVCTP消息处理

avrc_msg_cback

/packages/modules/Bluetooth/system/stack/avrc/avrc_api.cc
/******************************************************************************** Function         avrc_msg_cback** Description      This is the callback function used by AVCTP to report*                  received AV control messages.** Returns          Nothing.******************************************************************************/
static void avrc_msg_cback(uint8_t handle, uint8_t label, uint8_t cr,BT_HDR* p_pkt) {uint8_t opcode;tAVRC_MSG msg;uint8_t* p_data;uint8_t* p_begin;bool drop = false;bool do_free = true;BT_HDR* p_rsp = NULL;uint8_t* p_rsp_data;int xx;bool reject = false;const char* p_drop_msg = "dropped";tAVRC_MSG_VENDOR* p_msg = &msg.vendor;if (cr == AVCT_CMD && (p_pkt->layer_specific & AVCT_DATA_CTRL &&p_pkt->len > AVRC_PACKET_LEN)) {log::warn("Command length {} too long: must be at most {}", p_pkt->len,AVRC_PACKET_LEN);osi_free(p_pkt);return;}// 1. 消息类型校验与初步处理if (cr == AVCT_REJ) { // 拒绝消息/* The peer thinks that this PID is no longer open - remove this handle *//*  */osi_free(p_pkt);AVCT_RemoveConn(handle); // 移除无效连接return;} else if (cr == AVCT_RSP) {  // 响应消息/* Received response. Stop command timeout timer */log::verbose("AVRC: stopping timer (handle=0x{:02x})", handle);alarm_cancel(avrc_cb.ccb_int[handle].tle); // 停止命令超时定时器}// 2. 消息头解析与操作码提取p_data = (uint8_t*)(p_pkt + 1) + p_pkt->offset;memset(&msg, 0, sizeof(tAVRC_MSG));if (p_pkt->layer_specific == AVCT_DATA_BROWSE) {opcode = AVRC_OP_BROWSE; // 浏览消息特殊处理msg.browse.hdr.ctype = cr;msg.browse.p_browse_data = p_data;msg.browse.browse_len = p_pkt->len;msg.browse.p_browse_pkt = p_pkt;} else {if (p_pkt->len < AVRC_AVC_HDR_SIZE) {log::warn("message length {} too short: must be at least {}", p_pkt->len,AVRC_AVC_HDR_SIZE);osi_free(p_pkt);return;  // 消息过短,直接丢弃}msg.hdr.ctype = p_data[0] & AVRC_CTYPE_MASK; // 提取消息类型(命令/响应)log::verbose("handle:{}, ctype:{}, offset:{}, len: {}", handle,msg.hdr.ctype, p_pkt->offset, p_pkt->len);msg.hdr.subunit_type =(p_data[1] & AVRC_SUBTYPE_MASK) >> AVRC_SUBTYPE_SHIFT;  // 子单元类型msg.hdr.subunit_id = p_data[1] & AVRC_SUBID_MASK; // 子单元 IDopcode = p_data[2]; // 操作码(如 UNIT_INFO, SUB_INFO, VENDOR 等)}// 3. 操作码分类处理if (((avrc_cb.ccb[handle].control & AVRC_CT_TARGET) && (cr == AVCT_CMD)) ||((avrc_cb.ccb[handle].control & AVRC_CT_CONTROL) && (cr == AVCT_RSP))) {switch (opcode) {case AVRC_OP_UNIT_INFO: // 单元信息(AVRC_OP_UNIT_INFO)if (cr == AVCT_CMD) {// 生成响应包:返回设备支持的单元类型(如面板子单元)/* send the response to the peer */p_rsp = avrc_copy_packet(p_pkt, AVRC_OP_UNIT_INFO_RSP_LEN);p_rsp_data = avrc_get_data_ptr(p_rsp);*p_rsp_data = AVRC_RSP_IMPL_STBL;/* check & set the offset. set response code, set subunit_type &subunit_id,set AVRC_OP_UNIT_INFO *//* 3 bytes: ctype, subunit*, opcode */p_rsp_data += AVRC_AVC_HDR_SIZE;*p_rsp_data++ = 7;/* Panel subunit & id=0 */*p_rsp_data++ = (AVRC_SUB_PANEL << AVRC_SUBTYPE_SHIFT);AVRC_CO_ID_TO_BE_STREAM(p_rsp_data, avrc_cb.ccb[handle].company_id);p_rsp->len =(uint16_t)(p_rsp_data - (uint8_t*)(p_rsp + 1) - p_rsp->offset);cr = AVCT_RSP;p_drop_msg = "auto respond";} else { / 解析响应:提取对端子单元类型和公司 ID/* parse response */if (p_pkt->len < AVRC_OP_UNIT_INFO_RSP_LEN) {log::warn("message length {} too short: must be at least {}",p_pkt->len, AVRC_OP_UNIT_INFO_RSP_LEN);drop = true;p_drop_msg = "UNIT_INFO_RSP too short";break;}p_data += 4; /* 3 bytes: ctype, subunit*, opcode + octet 3 (is 7)*/msg.unit.unit_type =(*p_data & AVRC_SUBTYPE_MASK) >> AVRC_SUBTYPE_SHIFT;msg.unit.unit = *p_data & AVRC_SUBID_MASK;p_data++;AVRC_BE_STREAM_TO_CO_ID(msg.unit.company_id, p_data);}break;case AVRC_OP_SUB_INFO:if (cr == AVCT_CMD) {/* send the response to the peer */p_rsp = avrc_copy_packet(p_pkt, AVRC_OP_SUB_UNIT_INFO_RSP_LEN);p_rsp_data = avrc_get_data_ptr(p_rsp);*p_rsp_data = AVRC_RSP_IMPL_STBL;/* check & set the offset. set response code, set (subunit_type &subunit_id),set AVRC_OP_SUB_INFO, set (page & extention code) */p_rsp_data += 4;/* Panel subunit & id=0 */*p_rsp_data++ = (AVRC_SUB_PANEL << AVRC_SUBTYPE_SHIFT);memset(p_rsp_data, AVRC_CMD_OPRND_PAD, AVRC_SUBRSP_OPRND_BYTES);p_rsp_data += AVRC_SUBRSP_OPRND_BYTES;p_rsp->len =(uint16_t)(p_rsp_data - (uint8_t*)(p_rsp + 1) - p_rsp->offset);cr = AVCT_RSP;p_drop_msg = "auto responded";} else {/* parse response */if (p_pkt->len < AVRC_OP_SUB_UNIT_INFO_RSP_LEN) {log::warn("message length {} too short: must be at least {}",p_pkt->len, AVRC_OP_SUB_UNIT_INFO_RSP_LEN);drop = true;p_drop_msg = "SUB_UNIT_INFO_RSP too short";break;}p_data += AVRC_AVC_HDR_SIZE; /* 3 bytes: ctype, subunit*, opcode */msg.sub.page =(*p_data++ >> AVRC_SUB_PAGE_SHIFT) & AVRC_SUB_PAGE_MASK;xx = 0;while (*p_data != AVRC_CMD_OPRND_PAD && xx < AVRC_SUB_TYPE_LEN) {msg.sub.subunit_type[xx] = *p_data++ >> AVRC_SUBTYPE_SHIFT;if (msg.sub.subunit_type[xx] == AVRC_SUB_PANEL)msg.sub.panel = true;xx++;}}break;case AVRC_OP_VENDOR: { // 供应商特定消息p_data = (uint8_t*)(p_pkt + 1) + p_pkt->offset; p_begin = p_data;if (p_pkt->len <AVRC_VENDOR_HDR_SIZE) /* 6 = ctype, subunit*, opcode & CO_ID */{if (cr == AVCT_CMD)reject = true;elsedrop = true;break;}p_data += AVRC_AVC_HDR_SIZE; /* skip the first 3 bytes: ctype, subunit*,opcode */AVRC_BE_STREAM_TO_CO_ID(p_msg->company_id, p_data);p_msg->p_vendor_data = p_data;p_msg->vendor_len = p_pkt->len - (p_data - p_begin);uint8_t drop_code = 0;if (p_msg->company_id == AVRC_CO_METADATA) {/* Validate length for metadata message */if (p_pkt->len < (AVRC_VENDOR_HDR_SIZE + AVRC_MIN_META_HDR_SIZE)) {if (cr == AVCT_CMD)reject = true;elsedrop = true;break;}/* Check+handle fragmented messages */drop_code = avrc_proc_far_msg(handle, label, cr, &p_pkt, p_msg);if (drop_code > 0) drop = true;}if (drop_code > 0) {if (drop_code != 4) do_free = false;switch (drop_code) {case 1:p_drop_msg = "sent_frag";break;case 2:p_drop_msg = "req_cont";break;case 3:p_drop_msg = "sent_frag3";break;case 4:p_drop_msg = "sent_frag_free";break;default:p_drop_msg = "sent_fragd";}}/* If vendor response received, and did not ask for continuation *//* then check queue for addition commands to send */if ((cr == AVCT_RSP) && (drop_code != 2)) {avrc_send_next_vendor_cmd(handle);}} break;case AVRC_OP_PASS_THRU:if (p_pkt->len < 5) /* 3 bytes: ctype, subunit*, opcode & op_id & len */{if (cr == AVCT_CMD)reject = true;elsedrop = true;break;}p_data += AVRC_AVC_HDR_SIZE; /* skip the first 3 bytes: ctype, subunit*,opcode */msg.pass.op_id = (AVRC_PASS_OP_ID_MASK & *p_data);if (AVRC_PASS_STATE_MASK & *p_data)msg.pass.state = true;elsemsg.pass.state = false;p_data++;msg.pass.pass_len = *p_data++;if (msg.pass.pass_len != p_pkt->len - 5)msg.pass.pass_len = p_pkt->len - 5;if (msg.pass.pass_len)msg.pass.p_pass_data = p_data;elsemsg.pass.p_pass_data = NULL;break;case AVRC_OP_BROWSE:/* If browse response received, then check queue for addition commands* to send */if (cr == AVCT_RSP) {avrc_send_next_vendor_cmd(handle);}break;default:if ((avrc_cb.ccb[handle].control & AVRC_CT_TARGET) &&(cr == AVCT_CMD)) {/* reject unsupported opcode */reject = true;}drop = true;break;}} else /* drop the event */{if (opcode != AVRC_OP_BROWSE) drop = true;}// 4. 拒绝与错误处理if (reject) {// 生成拒绝响应:不支持的操作码/* reject unsupported opcode */p_rsp = avrc_copy_packet(p_pkt, AVRC_OP_REJ_MSG_LEN);p_rsp_data = avrc_get_data_ptr(p_rsp);*p_rsp_data = AVRC_RSP_REJ; // 响应码为拒绝p_drop_msg = "rejected";cr = AVCT_RSP;drop = true;}if (p_rsp) {/* set to send response right away */AVCT_MsgReq(handle, label, cr, p_rsp);drop = true;}// 5. 回调分发与内存管理if (!drop) {msg.hdr.opcode = opcode;avrc_cb.ccb[handle].msg_cback.Run(handle, label, opcode, &msg); // 分发给上层回调} else {log::warn("{} msg handle:{}, control:{}, cr:{}, opcode:x{:x}", p_drop_msg,handle, avrc_cb.ccb[handle].control, cr, opcode);}if (opcode == AVRC_OP_BROWSE && msg.browse.p_browse_pkt == NULL) {do_free = false;}if (do_free) osi_free(p_pkt);
}

avrc_msg_cback 是 AVCTP(Audio/Video Control Transport Protocol)的回调函数,用于处理蓝牙 AVRC(Audio/Video Remote Control)接收到的控制消息。其核心功能包括 消息解析响应生成分段消息处理 和 回调分发,是 AVRC 协议栈处理远程控制命令的核心入口。

bta_av_rc_msg_cback

packages/modules/Bluetooth/system/bta/av/bta_av_act.cc
/********************************************************************************* Function         bta_av_rc_msg_cback** Description      AVRCP message callback.** Returns          void*******************************************************************************/
static void bta_av_rc_msg_cback(uint8_t handle, uint8_t label, uint8_t opcode,tAVRC_MSG* p_msg) {uint8_t* p_data_src = NULL;uint16_t data_len = 0;log::verbose("handle: {} opcode=0x{:x}", handle, opcode);// 1. 数据载荷提取(供应商 / 透传消息)/* Copy avrc packet into BTA message buffer (for sending to BTA state machine)*//* Get size of payload data  (for vendor and passthrough messages only; for* browsing* messages, use zero-copy) */if (opcode == AVRC_OP_VENDOR && p_msg->vendor.p_vendor_data != NULL) {p_data_src = p_msg->vendor.p_vendor_data;data_len = (uint16_t)p_msg->vendor.vendor_len;} else if (opcode == AVRC_OP_PASS_THRU && p_msg->pass.p_pass_data != NULL) {p_data_src = p_msg->pass.p_pass_data;data_len = (uint16_t)p_msg->pass.pass_len;}// 2. BTA 消息缓冲区创建/* Create a copy of the message */tBTA_AV_RC_MSG* p_buf =(tBTA_AV_RC_MSG*)osi_malloc(sizeof(tBTA_AV_RC_MSG) + data_len); // 分配包含 tBTA_AV_RC_MSG 结构体和数据载荷的连续内存,确保数据完整性p_buf->hdr.event = BTA_AV_AVRC_MSG_EVT;p_buf->handle = handle;p_buf->label = label;p_buf->opcode = opcode;memcpy(&p_buf->msg, p_msg, sizeof(tAVRC_MSG));// 3. 数据载荷复制与指针更新/* Copy the data payload, and set the pointer to it */if (p_data_src != NULL) {uint8_t* p_data_dst = (uint8_t*)(p_buf + 1); // 数据载荷紧接在结构体之后memcpy(p_data_dst, p_data_src, data_len);/* Update bta message buffer to point to payload data *//* (Note AVRC_OP_BROWSING uses zero-copy: p_buf->msg.browse.p_browse_data* already points to original avrc buffer) */// 更新 BTA 消息中的数据指针,指向新分配的缓冲区if (opcode == AVRC_OP_VENDOR)p_buf->msg.vendor.p_vendor_data = p_data_dst;else if (opcode == AVRC_OP_PASS_THRU)p_buf->msg.pass.p_pass_data = p_data_dst;}// 4. 浏览消息特殊处理if (opcode == AVRC_OP_BROWSE) {/* set p_pkt to NULL, so avrc would not free the buffer */p_msg->browse.p_browse_pkt = NULL;  // 防止 AVRCP 层释放原始缓冲区(BTA 层已处理)}// 5. 消息发送与清理bta_sys_sendmsg(p_buf);
}

bta_av_rc_msg_cback 是 AVRCP(音频 / 视频远程控制协议)的消息回调函数,负责将接收到的 AVRCP 消息转换为 BTA(Bluetooth Application Layer,蓝牙应用层)可处理的消息格式,并通过 bta_sys_sendmsg 发送给 BTA 状态机。其核心逻辑包括 消息解析数据载荷复制 和 消息格式转换,是连接 AVRCP 层与 BTA 层的桥梁。

bta_sys_sendmsg(BTA_AV_AVRC_MSG_EVT)

bta_av_rc_msg

packages/modules/Bluetooth/system/bta/av/bta_av_act.cc
/********************************************************************************* Function         bta_av_rc_msg** Description      Process an AVRCP message from the peer.** Returns          void*******************************************************************************/
void bta_av_rc_msg(tBTA_AV_CB* p_cb, tBTA_AV_DATA* p_data) {tBTA_AV_EVT evt = 0;tBTA_AV av;BT_HDR* p_pkt = NULL;tAVRC_MSG_VENDOR* p_vendor = &p_data->rc_msg.msg.vendor;bool is_inquiry = ((p_data->rc_msg.msg.hdr.ctype == AVRC_CMD_SPEC_INQ) ||p_data->rc_msg.msg.hdr.ctype == AVRC_CMD_GEN_INQ);uint8_t ctype = 0;tAVRC_RESPONSE rc_rsp;rc_rsp.rsp.status = BTA_AV_STS_NO_RSP;// 1. 输入检查if (NULL == p_data) {log::error("Message from peer with no data");return;}log::verbose("opcode={:x}, ctype={:x}", p_data->rc_msg.opcode,p_data->rc_msg.msg.hdr.ctype);// 2. 透传消息处理(AVRC_OP_PASS_THRU)if (p_data->rc_msg.opcode == AVRC_OP_PASS_THRU) { // 处理透传命令/* if this is a pass thru command */if ((p_data->rc_msg.msg.hdr.ctype == AVRC_CMD_CTRL) ||(p_data->rc_msg.msg.hdr.ctype == AVRC_CMD_SPEC_INQ) ||(p_data->rc_msg.msg.hdr.ctype == AVRC_CMD_GEN_INQ)) {/* check if operation is supported */char avrcp_ct_support[PROPERTY_VALUE_MAX]; // 检查操作是否支持osi_property_get("bluetooth.pts.avrcp_ct.support", avrcp_ct_support,"false");if (p_data->rc_msg.msg.pass.op_id == AVRC_ID_VENDOR) { // 处理供应商特定操作p_data->rc_msg.msg.hdr.ctype = AVRC_RSP_NOT_IMPL;if (p_cb->features & BTA_AV_FEAT_METADATA)p_data->rc_msg.msg.hdr.ctype = bta_av_group_navi_supported(p_data->rc_msg.msg.pass.pass_len,p_data->rc_msg.msg.pass.p_pass_data, is_inquiry);} else if (((p_data->rc_msg.msg.pass.op_id == AVRC_ID_VOL_UP) ||(p_data->rc_msg.msg.pass.op_id == AVRC_ID_VOL_DOWN)) &&!strcmp(avrcp_ct_support, "true")) {p_data->rc_msg.msg.hdr.ctype = AVRC_RSP_ACCEPT;} else {p_data->rc_msg.msg.hdr.ctype =bta_av_op_supported(p_data->rc_msg.msg.pass.op_id, is_inquiry);}log::verbose("ctype {}", p_data->rc_msg.msg.hdr.ctype);// 发送响应/* send response */if (p_data->rc_msg.msg.hdr.ctype != AVRC_RSP_INTERIM)AVRC_PassRsp(p_data->rc_msg.handle, p_data->rc_msg.label,&p_data->rc_msg.msg.pass);// 设置回调参数/* set up for callback if supported */if (p_data->rc_msg.msg.hdr.ctype == AVRC_RSP_ACCEPT ||p_data->rc_msg.msg.hdr.ctype == AVRC_RSP_INTERIM) {evt = BTA_AV_REMOTE_CMD_EVT;av.remote_cmd.rc_id = p_data->rc_msg.msg.pass.op_id;av.remote_cmd.key_state = p_data->rc_msg.msg.pass.state;av.remote_cmd.p_data = p_data->rc_msg.msg.pass.p_pass_data;av.remote_cmd.len = p_data->rc_msg.msg.pass.pass_len;memcpy(&av.remote_cmd.hdr, &p_data->rc_msg.msg.hdr, sizeof(tAVRC_HDR));av.remote_cmd.label = p_data->rc_msg.label;}}/* else if this is a pass thru response *//* id response type is not impl, we have to release label */else if (p_data->rc_msg.msg.hdr.ctype >= AVRC_RSP_NOT_IMPL) {  // 处理透传响应// 设置回调参数/* set up for callback */evt = BTA_AV_REMOTE_RSP_EVT;av.remote_rsp.rc_id = p_data->rc_msg.msg.pass.op_id;av.remote_rsp.key_state = p_data->rc_msg.msg.pass.state;av.remote_rsp.rsp_code = p_data->rc_msg.msg.hdr.ctype;av.remote_rsp.label = p_data->rc_msg.label;av.remote_rsp.len = p_data->rc_msg.msg.pass.pass_len;av.remote_rsp.p_data = NULL;/* If this response is for vendor unique command  */// // 处理供应商唯一命令的响应if ((p_data->rc_msg.msg.pass.op_id == AVRC_ID_VENDOR) &&(p_data->rc_msg.msg.pass.pass_len > 0)) {av.remote_rsp.p_data =(uint8_t*)osi_malloc(p_data->rc_msg.msg.pass.pass_len);log::verbose("Vendor Unique data len = {}",p_data->rc_msg.msg.pass.pass_len);memcpy(av.remote_rsp.p_data, p_data->rc_msg.msg.pass.p_pass_data,p_data->rc_msg.msg.pass.pass_len);}}/* must be a bad ctype -> reject*/else {p_data->rc_msg.msg.hdr.ctype = AVRC_RSP_REJ;AVRC_PassRsp(p_data->rc_msg.handle, p_data->rc_msg.label,&p_data->rc_msg.msg.pass);}}// 3. 供应商特定消息处理(AVRC_OP_VENDOR)/* else if this is a vendor specific command or response */else if (p_data->rc_msg.opcode == AVRC_OP_VENDOR) {/* set up for callback */av.vendor_cmd.code = p_data->rc_msg.msg.hdr.ctype;av.vendor_cmd.company_id = p_vendor->company_id;av.vendor_cmd.label = p_data->rc_msg.label;av.vendor_cmd.p_data = p_vendor->p_vendor_data;av.vendor_cmd.len = p_vendor->vendor_len;// 处理供应商特定命令/* if configured to support vendor specific and it's a command */if ((p_cb->features & BTA_AV_FEAT_VENDOR) &&p_data->rc_msg.msg.hdr.ctype <= AVRC_CMD_GEN_INQ) {if ((p_cb->features & BTA_AV_FEAT_METADATA) &&(p_vendor->company_id == AVRC_CO_METADATA)) {av.meta_msg.p_msg = &p_data->rc_msg.msg;rc_rsp.rsp.status = BTA_AV_STS_NO_RSP;evt = bta_av_proc_meta_cmd(&rc_rsp, &p_data->rc_msg, &ctype);} else {evt = BTA_AV_VENDOR_CMD_EVT;}} // 处理供应商特定响应else if ((p_cb->features & BTA_AV_FEAT_VENDOR) &&p_data->rc_msg.msg.hdr.ctype >= AVRC_RSP_NOT_IMPL) {/* else if configured to support vendor specific and it's a response */if ((p_cb->features & BTA_AV_FEAT_METADATA) &&(p_vendor->company_id == AVRC_CO_METADATA)) {av.meta_msg.p_msg = &p_data->rc_msg.msg;evt = BTA_AV_META_MSG_EVT;} else {evt = BTA_AV_VENDOR_RSP_EVT;}}// 不支持供应商特定命令的处理else if (!(p_cb->features & BTA_AV_FEAT_VENDOR) &&p_data->rc_msg.msg.hdr.ctype <= AVRC_CMD_GEN_INQ) {/* else if not configured to support vendor specific and it's a command */if (p_data->rc_msg.msg.vendor.p_vendor_data[0] == AVRC_PDU_INVALID) {/* reject it */p_data->rc_msg.msg.hdr.ctype = AVRC_RSP_REJ;p_data->rc_msg.msg.vendor.p_vendor_data[4] = AVRC_STS_BAD_CMD;} else {p_data->rc_msg.msg.hdr.ctype = AVRC_RSP_NOT_IMPL;}AVRC_VendorRsp(p_data->rc_msg.handle, p_data->rc_msg.label,&p_data->rc_msg.msg.vendor);}} // 4. 浏览消息处理else if (p_data->rc_msg.opcode == AVRC_OP_BROWSE) { /* set up for callback */av.meta_msg.rc_handle = p_data->rc_msg.handle;av.meta_msg.company_id = p_vendor->company_id;av.meta_msg.code = p_data->rc_msg.msg.hdr.ctype;av.meta_msg.label = p_data->rc_msg.label;av.meta_msg.p_msg = &p_data->rc_msg.msg;av.meta_msg.p_data = p_data->rc_msg.msg.browse.p_browse_data;av.meta_msg.len = p_data->rc_msg.msg.browse.browse_len;evt = BTA_AV_META_MSG_EVT;}// 5. 响应生成与发送if (evt == 0 && rc_rsp.rsp.status != BTA_AV_STS_NO_RSP) {if (!p_pkt) {rc_rsp.rsp.opcode = p_data->rc_msg.opcode;AVRC_BldResponse(0, &rc_rsp, &p_pkt);}if (p_pkt)AVRC_MsgReq(p_data->rc_msg.handle, p_data->rc_msg.label, ctype, p_pkt,false);}// 6. 回调函数调用与资源释放/* call callback */if (evt != 0) {av.remote_cmd.rc_handle = p_data->rc_msg.handle;(*p_cb->p_cback)(evt, &av);/* If browsing message, then free the browse message buffer */if (p_data->rc_msg.opcode == AVRC_OP_BROWSE &&p_data->rc_msg.msg.browse.p_browse_pkt != NULL) {bta_av_rc_free_browse_msg(p_cb, p_data);}}
}

处理来自对等设备的 AVRCP(Audio/Video Remote Control Profile)消息。根据消息的操作码(opcode)、消息类型(ctype)以及蓝牙模块的配置特性,对消息进行分类处理,然后设置相应的事件和回调参数,最后调用回调函数将处理结果传递给上层。同时,对于一些需要响应的消息,会生成并发送相应的响应。

bta_av_event_callback

packages/modules/Bluetooth/system/btif/src/btif_av.cc
static void bta_av_event_callback(tBTA_AV_EVT event, tBTA_AV* p_data) {if (btif_av_both_enable()) {BtifAvEvent btif_av_event(event, p_data, sizeof(tBTA_AV));do_in_main_thread(FROM_HERE,base::BindOnce(&btif_av_handle_bta_av_event,AVDT_TSEP_INVALID /* peer_sep */, btif_av_event));return;}if (btif_av_is_sink_enabled()) {return bta_av_sink_callback(event, p_data);}return bta_av_source_callback(event, p_data);
}

btif_av_handle_event

/*** Process BTA AV or BTA AVRCP events. The processing is done on the JNI* thread.** @param peer_sep the corresponding peer's SEP: AVDT_TSEP_SRC if the peer* is A2DP Source, or AVDT_TSEP_SNK if the peer is A2DP Sink.* @param btif_av_event the corresponding event*/
static void btif_av_handle_bta_av_event(uint8_t peer_sep,const BtifAvEvent& btif_av_event) {RawAddress peer_address = RawAddress::kEmpty;tBTA_AV_HNDL bta_handle = kBtaHandleUnknown;tBTA_AV_EVT event = btif_av_event.Event();tBTA_AV* p_data = (tBTA_AV*)btif_av_event.Data();std::string msg;log::debug("jni_thread: Handle BTA AV or AVRCP event {}: peer_sep={} event={}",peer_stream_endpoint_text(peer_sep), peer_sep, btif_av_event.ToString());switch (event) {...}if (!msg.empty()) {BTM_LogHistory(kBtmLogHistoryTag, peer_address, msg,btif_av_event.ToString());}btif_av_handle_event(peer_sep, peer_address, bta_handle, btif_av_event);
}

处理蓝牙相关的 BTA AV(蓝牙音频 / 视频相关)或 BTA AVRCP(Audio/Video Remote Control Profile,音频 / 视频远程控制协议)事件。它运行在 JNI(Java Native Interface,Java 本地接口)线程上,首先会对传入的事件相关参数进行提取和初始化,接着在 switch 语句中针对具体事件进行相应处理,最后调用另一个函数 btif_av_handle_event 继续后续的事件处理流程,以此来保障蓝牙音频 / 视频相关事件能够在 JNI 线程环境下有序且正确地被处理,确保整个蓝牙功能的正常运行。

btif_av_handle_event

btif_av_handle_event前文有分析,这里不再展开。

BtifAvStateMachine::StateStarted::ProcessEvent

packages/modules/Bluetooth/system/btif/src/btif_av.cc
bool BtifAvStateMachine::StateStarted::ProcessEvent(uint32_t event,void* p_data) {tBTA_AV* p_av = (tBTA_AV*)p_data;log::verbose("Peer {} : event={} flags={} active_peer={}",ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()),BtifAvEvent::EventName(event), peer_.FlagsToString(),logbool(peer_.IsActivePeer()));switch (event) {case BTIF_AV_ACL_DISCONNECTED:break;  // Ignorecase BTIF_AV_START_STREAM_REQ_EVT:log::info("Peer {} : event={} flags={}",ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()),BtifAvEvent::EventName(event), peer_.FlagsToString());// We were started remotely, just ACK back the local requestif (peer_.IsSink()) btif_a2dp_on_started(peer_.PeerAddress(), nullptr);break;// FIXME -- use suspend = true always to work around issue with BTA AVcase BTIF_AV_STOP_STREAM_REQ_EVT:case BTIF_AV_SUSPEND_STREAM_REQ_EVT:log::info("Peer {} : event={} flags={}",ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()),BtifAvEvent::EventName(event), peer_.FlagsToString());// There is a pending LocalSuspend already, ignore.if (peer_.CheckFlags(BtifAvPeer::kFlagLocalSuspendPending)) {break;}// Set pending flag to ensure the BTIF task is not trying to restart// the stream while suspend is in progress.peer_.SetFlags(BtifAvPeer::kFlagLocalSuspendPending);// If we were remotely suspended but suspend locally, local suspend// always overrides.peer_.ClearFlags(BtifAvPeer::kFlagRemoteSuspend);if (peer_.IsSink() &&(peer_.IsActivePeer() || !btif_av_stream_started_ready())) {// Immediately stop transmission of frames while suspend is pendingif (event == BTIF_AV_STOP_STREAM_REQ_EVT) {btif_a2dp_on_stopped(nullptr);} else {// ensure tx frames are immediately suspendedbtif_a2dp_source_set_tx_flush(true);}} else if (peer_.IsSource()) {btif_a2dp_on_stopped(nullptr);}BTA_AvStop(peer_.BtaHandle(), true);break;case BTIF_AV_DISCONNECT_REQ_EVT:log::info("Peer {} : event={} flags={}",ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()),BtifAvEvent::EventName(event), peer_.FlagsToString());// Request AVDTP to closeBTA_AvClose(peer_.BtaHandle());if (peer_.IsSource()) {BTA_AvCloseRc(peer_.BtaHandle());}// Inform the application that we are disconnectingbtif_report_connection_state(peer_.PeerAddress(), BTAV_CONNECTION_STATE_DISCONNECTING,bt_status_t::BT_STATUS_SUCCESS, BTA_AV_SUCCESS);// Wait in closing state until fully closedpeer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateClosing);break;case BTA_AV_SUSPEND_EVT: {log::info("Peer {} : event={} status={} initiator={} flags={}",ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()),BtifAvEvent::EventName(event), p_av->suspend.status,p_av->suspend.initiator, peer_.FlagsToString());// A2DP suspended, stop A2DP encoder / decoder until resumedif (peer_.IsActivePeer() || !btif_av_stream_started_ready()) {btif_a2dp_on_suspended(&p_av->suspend);}// If not successful, remain in current stateif (p_av->suspend.status != BTA_AV_SUCCESS) {peer_.ClearFlags(BtifAvPeer::kFlagLocalSuspendPending);if (peer_.IsSink() && peer_.IsActivePeer()) {// Suspend failed, reset back tx flush statebtif_a2dp_source_set_tx_flush(false);}return false;}btav_audio_state_t state = BTAV_AUDIO_STATE_REMOTE_SUSPEND;if (p_av->suspend.initiator != true) {// Remote suspend, notify HAL and await audioflinger to// suspend/stop stream.//// Set remote suspend flag to block media task from restarting// stream only if we did not already initiate a local suspend.if (!peer_.CheckFlags(BtifAvPeer::kFlagLocalSuspendPending))peer_.SetFlags(BtifAvPeer::kFlagRemoteSuspend);} else {state = BTAV_AUDIO_STATE_STOPPED;}btif_report_audio_state(peer_.PeerAddress(), state);// Suspend completed, clear local pending flags while entering Openedpeer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateOpened);} break;case BTA_AV_STOP_EVT:log::info("Peer {} : event={} flags={}",ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()),BtifAvEvent::EventName(event), peer_.FlagsToString());peer_.SetFlags(BtifAvPeer::kFlagPendingStop);peer_.ClearFlags(BtifAvPeer::kFlagLocalSuspendPending);// Don't change the encoder and audio provider state by a non-active// peer since they are shared between peersif (peer_.IsActivePeer() || !btif_av_stream_started_ready()) {btif_a2dp_on_stopped(&p_av->suspend);}btif_report_audio_state(peer_.PeerAddress(), BTAV_AUDIO_STATE_STOPPED);// If stop was successful, change state to Openif (p_av->suspend.status == BTA_AV_SUCCESS)peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateOpened);break;case BTA_AV_CLOSE_EVT:log::info("Peer {} : event={} flags={}",ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()),BtifAvEvent::EventName(event), peer_.FlagsToString());// Inform the application that we are disconnectingbtif_report_connection_state(peer_.PeerAddress(), BTAV_CONNECTION_STATE_DISCONNECTING,bt_status_t::BT_STATUS_SUCCESS, BTA_AV_SUCCESS);peer_.SetFlags(BtifAvPeer::kFlagPendingStop);// AVDTP link is closedif (peer_.IsActivePeer()) {btif_a2dp_on_stopped(nullptr);}// Inform the application that we are disconnectedbtif_report_connection_state(peer_.PeerAddress(), BTAV_CONNECTION_STATE_DISCONNECTED,bt_status_t::BT_STATUS_SUCCESS, BTA_AV_SUCCESS);peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateIdle);break;case BTIF_AV_OFFLOAD_START_REQ_EVT:if (peer_.CheckFlags(BtifAvPeer::kFlagLocalSuspendPending |BtifAvPeer::kFlagRemoteSuspend |BtifAvPeer::kFlagPendingStop)) {log::warn("Peer {} : event={} flags={}: stream is Suspending",ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()),BtifAvEvent::EventName(event), peer_.FlagsToString());btif_a2dp_on_offload_started(peer_.PeerAddress(), BTA_AV_FAIL);break;}BTA_AvOffloadStart(peer_.BtaHandle());break;case BTA_AV_OFFLOAD_START_RSP_EVT:btif_a2dp_on_offload_started(peer_.PeerAddress(), p_av->status);break;case BTIF_AV_SET_LATENCY_REQ_EVT: {const btif_av_set_latency_req_t* p_set_latency_req =static_cast<const btif_av_set_latency_req_t*>(p_data);log::info("Peer {} : event={} flags={} is_low_latency={}",ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()),BtifAvEvent::EventName(event), peer_.FlagsToString(),p_set_latency_req->is_low_latency ? "true" : "false");BTA_AvSetLatency(peer_.BtaHandle(), p_set_latency_req->is_low_latency);} break;CHECK_RC_EVENT(event, (tBTA_AV*)p_data);default:log::warn("Peer {} : Unhandled event={}",ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()),BtifAvEvent::EventName(event));return false;}return true;
}

ProcessEvent 函数是 BtifAvStateMachine 中 StateStarted 状态下用于处理各种蓝牙音频 / 视频(AV)相关事件的函数。它接收一个事件标识符 event 和对应的事件数据指针 p_data,根据不同的事件类型,执行相应的逻辑操作,例如处理连接断开、流启动 / 停止请求、暂停 / 恢复操作、设备关闭以及一些特定的功能请求(如卸载启动、设置延迟等)相关逻辑,同时通过日志记录详细的事件信息及处理过程,以此来维护蓝牙 AV 功能在相应状态下的正确行为和状态转换,确保整个蓝牙音频 / 视频业务流程的顺畅运行。 

btif_rc_handle

packages/modules/Bluetooth/system/btif/src/btif_rc.cc
/******************************************************************************* Function       btif_rc_handler**** Description    RC event handler*****************************************************************************/
void btif_rc_handler(tBTA_AV_EVT event, tBTA_AV* p_data) {log::verbose("event: {}", dump_rc_event(event));btif_rc_device_cb_t* p_dev = NULL;switch (event) {...// 1. 事件类型判断与回调检查case BTA_AV_META_MSG_EVT: {if (bt_rc_callbacks != NULL) { log::verbose("BTA_AV_META_MSG_EVT code: {} label: {}",p_data->meta_msg.code, p_data->meta_msg.label);log::verbose("company_id: 0x{:x} len: {} handle: {}",p_data->meta_msg.company_id, p_data->meta_msg.len,p_data->meta_msg.rc_handle);/* handle the metamsg command */handle_rc_metamsg_cmd(&(p_data->meta_msg)); // 传统 RC 回调处理(如媒体元数据)/* Free the Memory allocated for tAVRC_MSG */} else if (bt_rc_ctrl_callbacks != NULL) { // Sink + CT + TG 场景(如绝对音量控制)/* This is case of Sink + CT + TG(for abs vol)) */log::verbose("BTA_AV_META_MSG_EVT code:{} label:{} opcode {} ctype {}",p_data->meta_msg.code, p_data->meta_msg.label,p_data->meta_msg.p_msg->hdr.opcode,p_data->meta_msg.p_msg->hdr.ctype);log::verbose("company_id:0x{:x} len:{} handle:{}",p_data->meta_msg.company_id, p_data->meta_msg.len,p_data->meta_msg.rc_handle);switch (p_data->meta_msg.p_msg->hdr.opcode) {case AVRC_OP_VENDOR: / 按响应/命令分类处理供应商消息if ((p_data->meta_msg.code >= AVRC_RSP_NOT_IMPL) &&(p_data->meta_msg.code <= AVRC_RSP_INTERIM)) {/* Its a response */handle_avk_rc_metamsg_rsp(&(p_data->meta_msg));} else if (p_data->meta_msg.code <= AVRC_CMD_GEN_INQ) {/* Its a command  */handle_avk_rc_metamsg_cmd(&(p_data->meta_msg));}break;// 3. 浏览消息处理case AVRC_OP_BROWSE: // 按命令/响应分类处理浏览消息if (p_data->meta_msg.p_msg->hdr.ctype == AVRC_CMD) {handle_avk_rc_metamsg_cmd(&(p_data->meta_msg)); // 处理浏览命令(如目录查询)} else if (p_data->meta_msg.p_msg->hdr.ctype == AVRC_RSP) {handle_avk_rc_metamsg_rsp(&(p_data->meta_msg));  // 处理浏览响应(如查询结果)}break;}} else {log::error("Neither CTRL, nor TG is up, drop meta commands");}} break;default:log::verbose("Unhandled RC event : 0x{:x}", event);}
}

btif_rc_handler 函数充当了一个 RC(Remote Control,远程控制)事件的处理枢纽,它接收蓝牙音频 / 视频相关的事件类型(event)以及对应的事件数据指针(p_data)作为参数,依据不同的事件类型,特别是针对 BTA_AV_META_MSG_EVT 事件进行处理,通过调用不同的处理函数来应对诸如命令、响应等不同情况,同时进行相应的日志记录,以确保蓝牙远程控制相关的消息事件能够按照既定逻辑被正确处理,保障蓝牙音频 / 视频远程控制功能的正常运作。 

AVRC_Ctrl_ParsResponse

packages/modules/Bluetooth/system/stack/avrc/avrc_pars_ct.cc
/********************************************************************************* Function         AVRC_Ctrl_ParsResponse** Description      This function is a parse response for AVRCP Controller.** Returns          AVRC_STS_NO_ERROR, if the message in p_data is parsed*                  successfully.*                  Otherwise, the error code defined by AVRCP 1.4*******************************************************************************/
tAVRC_STS AVRC_Ctrl_ParsResponse(tAVRC_MSG* p_msg, tAVRC_RESPONSE* p_result,uint8_t* p_buf, uint16_t* buf_len) {tAVRC_STS status = AVRC_STS_INTERNAL_ERR;if (p_msg && p_result) {switch (p_msg->hdr.opcode) {case AVRC_OP_VENDOR: /*  0x00    Vendor-dependent commands */status =avrc_ctrl_pars_vendor_rsp(&p_msg->vendor, p_result, p_buf, buf_len);break;case AVRC_OP_BROWSE: /* 0xff Browse commands */status = avrc_pars_browse_rsp(&p_msg->browse, p_result);break;default:log::error("unknown opcode:0x{:x}", p_msg->hdr.opcode);break;}p_result->rsp.opcode = p_msg->hdr.opcode;p_result->rsp.status = status;}return status;
}

AVRC_Ctrl_ParsResponse 是 AVRCP(音频 / 视频远程控制协议)控制器的 响应解析核心函数,负责根据操作码(opcode)解析不同类型的 AVRCP 响应消息(如供应商特定响应、浏览响应),并将解析结果填充到 tAVRC_RESPONSE 结构体中。其核心逻辑是通过操作码分发到具体解析函数,确保协议消息的正确解析与状态反馈。

操作码与功能映射

操作码功能分类解析函数典型场景
AVRC_OP_VENDOR供应商特定命令 / 响应avrc_ctrl_pars_vendor_rsp绝对音量调节、元数据交互
AVRC_OP_BROWSE浏览命令 / 响应avrc_pars_browse_rsp设备间文件目录查询

handle_avk_rc_metamsg_rsp

packages/modules/Bluetooth/system/btif/src/btif_rc.cc
/***************************************************************************** Function         handle_avk_rc_metamsg_rsp** Description      Handle RC metamessage response** Returns          void***************************************************************************/
static void handle_avk_rc_metamsg_rsp(tBTA_AV_META_MSG* pmeta_msg) {tAVRC_RESPONSE avrc_response = {0};uint8_t scratch_buf[512] = {0};  // this variable is unuseduint16_t buf_len;tAVRC_STS status;btif_rc_device_cb_t* p_dev = NULL;log::verbose("opcode: {} rsp_code: {}", pmeta_msg->p_msg->hdr.opcode,pmeta_msg->code);p_dev = btif_rc_get_device_by_handle(pmeta_msg->rc_handle);status = AVRC_Ctrl_ParsResponse(pmeta_msg->p_msg, &avrc_response, scratch_buf,&buf_len);if ((AVRC_OP_VENDOR == pmeta_msg->p_msg->hdr.opcode) &&(pmeta_msg->code >= AVRC_RSP_NOT_IMPL) &&(pmeta_msg->code <= AVRC_RSP_INTERIM)) {log::verbose("parse status {} pdu = {} rsp_status = {}", status,avrc_response.pdu, pmeta_msg->p_msg->vendor.hdr.ctype);switch (avrc_response.pdu) {case AVRC_PDU_REGISTER_NOTIFICATION:handle_notification_response(pmeta_msg, &avrc_response.reg_notif);if (pmeta_msg->code == AVRC_RSP_INTERIM) {/* Don't free the transaction Id */clear_cmd_timeout(p_dev, pmeta_msg->label);return;}break;
...log::verbose("release transaction {}", pmeta_msg->label);release_transaction(p_dev, pmeta_msg->label);
}

处理蓝牙音频 / 视频远程控制(RC)相关的消息响应(metamessage response)。对传入的消息响应相关数据进行提取和分析,调用相关函数解析响应内容,依据操作码、响应代码等关键信息进行不同情况的分支处理,比如针对特定的厂商操作码以及响应类型范围进行特定逻辑处理,同时通过日志记录关键信息,最后还会执行释放相关资源(如交易相关资源)的操作,以此来确保对蓝牙 RC 元消息响应的正确处理以及资源的合理管理,保障蓝牙远程控制功能在响应处理方面的正常运行。 

handle_notification_response

packages/modules/Bluetooth/system/btif/src/btif_rc.cc
/***************************************************************************** Function         handle_notification_response** Description      Main handler for notification responses to registered events*                  1. Register for unregistered event(in interim response path)*                  2. After registering for all supported events, start*                     retrieving application settings and values*                  3. Reregister for events on getting changed response*                  4. Run play status timer for getting position when the*                     status changes to playing*                  5. Get the Media details when the track change happens*                     or track change interim response is received with*                     valid track id*                  6. HAL callback for play status change and application*                     setting change* Returns          None***************************************************************************/
static void handle_notification_response(tBTA_AV_META_MSG* pmeta_msg,tAVRC_REG_NOTIF_RSP* p_rsp) {btif_rc_device_cb_t* p_dev =btif_rc_get_device_by_handle(pmeta_msg->rc_handle);if (p_dev == NULL) {log::error("p_dev NULL");return;}if (btif_av_src_sink_coexist_enabled() &&p_rsp->event_id == AVRC_EVT_VOLUME_CHANGE) {log::error("legacy TG don't handle absolute volume change. leave it to new avrcp");return;}const uint32_t* attr_list = get_requested_attributes_list(p_dev);const uint8_t attr_list_size = get_requested_attributes_list_size(p_dev);if (pmeta_msg->code == AVRC_RSP_INTERIM) {btif_rc_supported_event_t* p_event;list_node_t* node;log::verbose("Interim response: 0x{:2X}", p_rsp->event_id);switch (p_rsp->event_id) {case AVRC_EVT_PLAY_STATUS_CHANGE:get_play_status_cmd(p_dev);do_in_jni_thread(FROM_HERE,base::BindOnce(bt_rc_ctrl_callbacks->play_status_changed_cb,p_dev->rc_addr,(btrc_play_status_t)p_rsp->param.play_status));break;case AVRC_EVT_TRACK_CHANGE:if (rc_is_track_id_valid(p_rsp->param.track) != true) {break;} else {uint8_t* p_data = p_rsp->param.track;BE_STREAM_TO_UINT64(p_dev->rc_playing_uid, p_data);get_play_status_cmd(p_dev);get_metadata_attribute_cmd(attr_list_size, attr_list,p_dev);}break;case AVRC_EVT_APP_SETTING_CHANGE:break;case AVRC_EVT_NOW_PLAYING_CHANGE:do_in_jni_thread(FROM_HERE,base::BindOnce(bt_rc_ctrl_callbacks->now_playing_contents_changed_cb,p_dev->rc_addr));break;case AVRC_EVT_AVAL_PLAYERS_CHANGE:log::verbose("AVRC_EVT_AVAL_PLAYERS_CHANGE");do_in_jni_thread(FROM_HERE,base::BindOnce(bt_rc_ctrl_callbacks->available_player_changed_cb,p_dev->rc_addr));break;case AVRC_EVT_ADDR_PLAYER_CHANGE:do_in_jni_thread(FROM_HERE,base::BindOnce(bt_rc_ctrl_callbacks->addressed_player_changed_cb,p_dev->rc_addr, p_rsp->param.addr_player.player_id));break;case AVRC_EVT_PLAY_POS_CHANGED:do_in_jni_thread(FROM_HERE,base::BindOnce(bt_rc_ctrl_callbacks->play_position_changed_cb,p_dev->rc_addr, 0, p_rsp->param.play_pos));break;case AVRC_EVT_UIDS_CHANGE:break;case AVRC_EVT_TRACK_REACHED_END:case AVRC_EVT_TRACK_REACHED_START:case AVRC_EVT_BATTERY_STATUS_CHANGE:case AVRC_EVT_SYSTEM_STATUS_CHANGE:default:log::error("Unhandled interim response: 0x{:2X}", p_rsp->event_id);return;}list_foreach(p_dev->rc_supported_event_list,iterate_supported_event_list_for_interim_rsp,&p_rsp->event_id);node = list_begin(p_dev->rc_supported_event_list);while (node != NULL) {p_event = (btif_rc_supported_event_t*)list_node(node);if ((p_event != NULL) && (p_event->status == eNOT_REGISTERED)) {register_for_event_notification(p_event, p_dev);break;}node = list_next(node);p_event = NULL;}/* Registered for all events, we can request application settings */if (p_event == NULL && !p_dev->rc_app_settings.query_started) {/* we need to do this only if remote TG supports* player application settings*/p_dev->rc_app_settings.query_started = true;if (p_dev->rc_features & BTA_AV_FEAT_APP_SETTING) {list_player_app_setting_attrib_cmd(p_dev);} else {log::verbose("App setting not supported, complete procedure");rc_ctrl_procedure_complete(p_dev);}}} else if (pmeta_msg->code == AVRC_RSP_CHANGED) {btif_rc_supported_event_t* p_event;list_node_t* node;log::verbose("Notification completed: 0x{:2X}", p_rsp->event_id);node = list_begin(p_dev->rc_supported_event_list);while (node != NULL) {p_event = (btif_rc_supported_event_t*)list_node(node);if (p_event != NULL && p_event->event_id == p_rsp->event_id) {p_event->status = eNOT_REGISTERED;register_for_event_notification(p_event, p_dev);break;}node = list_next(node);}switch (p_rsp->event_id) {case AVRC_EVT_PLAY_STATUS_CHANGE:/* Start timer to get play status periodically* if the play state is playing.*/do_in_jni_thread(FROM_HERE,base::BindOnce(bt_rc_ctrl_callbacks->play_status_changed_cb,p_dev->rc_addr,(btrc_play_status_t)p_rsp->param.play_status));break;case AVRC_EVT_TRACK_CHANGE:if (rc_is_track_id_valid(p_rsp->param.track) != true) {break;}get_metadata_attribute_cmd(attr_list_size, attr_list, p_dev);break;case AVRC_EVT_APP_SETTING_CHANGE: {btrc_player_settings_t app_settings;uint16_t xx;app_settings.num_attr = p_rsp->param.player_setting.num_attr;for (xx = 0; xx < app_settings.num_attr; xx++) {app_settings.attr_ids[xx] = p_rsp->param.player_setting.attr_id[xx];app_settings.attr_values[xx] =p_rsp->param.player_setting.attr_value[xx];}do_in_jni_thread(FROM_HERE,base::BindOnce(bt_rc_ctrl_callbacks->playerapplicationsetting_changed_cb,p_dev->rc_addr, app_settings));} break;case AVRC_EVT_NOW_PLAYING_CHANGE:break;case AVRC_EVT_AVAL_PLAYERS_CHANGE:break;case AVRC_EVT_ADDR_PLAYER_CHANGE:break;case AVRC_EVT_PLAY_POS_CHANGED:// handle on interimbreak;case AVRC_EVT_UIDS_CHANGE:break;case AVRC_EVT_TRACK_REACHED_END:case AVRC_EVT_TRACK_REACHED_START:case AVRC_EVT_BATTERY_STATUS_CHANGE:case AVRC_EVT_SYSTEM_STATUS_CHANGE:default:log::error("Unhandled completion response: 0x{:2X}", p_rsp->event_id);return;}}
}

执行bt_rc_ctrl_callbacks->play_status_changed_cb函数。这个函数是init的时候注册的一个回调,用于处理播放状态改变的事件。

btavrcp_play_status_changed_callback

packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_avrcp_controller.cpp
static void btavrcp_play_status_changed_callback(const RawAddress& bd_addr, btrc_play_status_t play_status) {log::info("");std::shared_lock<std::shared_timed_mutex> lock(sCallbacks_mutex);CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid()) return;if (!sCallbacksObj) {log::error("sCallbacksObj is null");return;}ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));if (!addr.get()) {log::error("Failed to allocate a new byte array");return;}sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),(jbyte*)&bd_addr.address);sCallbackEnv->CallVoidMethod(sCallbacksObj, method_handleplaystatuschanged,addr.get(), (jbyte)play_status);
}

btavrcp_play_status_changed_callback 函数在蓝牙音频 / 视频远程控制功能中扮演着连接底层 C++ 实现与上层 Java 应用的重要角色,特别是针对播放状态改变这一事件。它通过合理运用线程安全锁、仔细检查回调环境及相关对象有效性、将设备地址转换为适合 Java 层的字节数组形式以及最终通过 JNI 调用 Java 方法传递关键信息等一系列操作,保障了播放状态改变的信息能够准确无误地从底层传递到 Java 层,从而使得整个蓝牙远程控制功能在不同层次间能够协同工作,实现诸如界面与实际播放状态同步更新等业务需求。

informAudioFocusStateNative

 /packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_a2dp_sink.cpp
static void informAudioFocusStateNative(JNIEnv* env, jobject object,jint focus_state) {if (!sBluetoothA2dpInterface) return;sBluetoothA2dpInterface->set_audio_focus_state((uint8_t)focus_state);
}

充当 Java 层与本地(Native)层之间关于音频焦点(Audio Focus)状态信息传递的桥梁。接收从 Java 层传来的表示音频焦点状态的整数值,先检查是否存在有效的蓝牙 A2DP 接口对象,若存在,则调用该接口对应的方法将音频焦点状态值转换并传递给本地层进行相应处理,以此确保本地层能够知晓并依据音频焦点状态来合理调整音频相关的操作,保障音频播放与系统中其他音频相关应用或功能之间的协调运作。

update_audio_focus_state 

packages/modules/Bluetooth/system/btif/src/btif_av.cc
// Updates the final focus state reported by components calling this module
static void update_audio_focus_state(int state) {log::verbose("state={}", state);btif_a2dp_sink_set_focus_state_req((btif_a2dp_sink_focus_state_t)state);
}

btif_a2dp_sink_set_focus_state_req 

packages/modules/Bluetooth/system/btif/src/btif_a2dp_sink.cc
void btif_a2dp_sink_set_focus_state_req(btif_a2dp_sink_focus_state_t state) {log::info("");tBTIF_MEDIA_SINK_FOCUS_UPDATE* p_buf =reinterpret_cast<tBTIF_MEDIA_SINK_FOCUS_UPDATE*>(osi_malloc(sizeof(tBTIF_MEDIA_SINK_FOCUS_UPDATE)));p_buf->focus_state = state;p_buf->hdr.event = BTIF_MEDIA_SINK_SET_FOCUS_STATE;btif_a2dp_sink_cb.worker_thread.DoInThread(FROM_HERE,base::BindOnce(btif_a2dp_sink_command_ready, (BT_HDR_RIGID*)p_buf));
}

发起一个设置音频焦点状态的请求操作,负责在接收到音频焦点状态变化请求时,将该请求封装并发送到工作线程进行进一步处理。

  • 设置p_buf->hdr.eventBTIF_MEDIA_SINK_SET_FOCUS_STATE,表明这是一个设置音频焦点状态的请求。
packages/modules/Bluetooth/system/btif/src/btif_a2dp_sink.cc
static void btif_a2dp_sink_command_ready(BT_HDR_RIGID* p_msg) {log::verbose("event {} {}", p_msg->event, dump_media_event(p_msg->event));switch (p_msg->event) {...case BTIF_MEDIA_SINK_SET_FOCUS_STATE: {btif_a2dp_sink_focus_state_t state =((tBTIF_MEDIA_SINK_FOCUS_UPDATE*)p_msg)->focus_state;btif_a2dp_sink_set_focus_state_event(state);break;}...default:log::error("unknown event {}", p_msg->event);break;}log::verbose("{} DONE", dump_media_event(p_msg->event));osi_free(p_msg);
}

根据传入消息结构体中的事件类型进行相应的处理操作。当接收到的消息事件类型为 BTIF_MEDIA_SINK_SET_FOCUS_STATE时,会提取出对应的音频焦点状态信息,并调用btif_a2dp_sink_set_focus_state_event函数实际处理音频焦点状态的变化。

btif_a2dp_sink_set_focus_state_req 

packages/modules/Bluetooth/system/btif/src/btif_a2dp_sink.cc
static void btif_a2dp_sink_set_focus_state_event(btif_a2dp_sink_focus_state_t state) {log::info("state={}", state);LockGuard lock(g_mutex);log::verbose("setting focus state to {}", state);btif_a2dp_sink_cb.rx_focus_state = state;if (btif_a2dp_sink_cb.rx_focus_state == BTIF_A2DP_SINK_FOCUS_NOT_GRANTED) {fixed_queue_flush(btif_a2dp_sink_cb.rx_audio_queue, osi_free);btif_a2dp_sink_cb.rx_flush = true;} else if (btif_a2dp_sink_cb.rx_focus_state == BTIF_A2DP_SINK_FOCUS_GRANTED) {btif_a2dp_sink_cb.rx_flush = false;}
}

处理音频焦点状态设置相关的业务逻辑。通过加锁机制保证对共享资源访问的线程安全性,然后将接收到的音频焦点状态值更新到相应的成员变量中,最后根据不同的音频焦点状态值(如焦点未授予或已授予)执行不同的操作,例如清理音频队列或者更新相关的标志位,以此来确保系统在音频焦点状态变化时能做出相应的正确响应,保障音频播放等相关功能与音频焦点状态的协调一致。 

informAudioTrackGainNative

/packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_a2dp_sink.cpp
static void informAudioTrackGainNative(JNIEnv* env, jobject object,jfloat gain) {if (!sBluetoothA2dpInterface) return;sBluetoothA2dpInterface->set_audio_track_gain((float)gain);
}

将音频轨道增益(gain)相关的信息从 Java 层传递到Bluedroid层进行处理。

update_audio_track_gain

packages/modules/Bluetooth/system/btif/src/btif_av.cc
// Updates the track gain (used for ducking).
static void update_audio_track_gain(float gain) {log::verbose("gain={:f}", gain);btif_a2dp_sink_set_audio_track_gain(gain);
}

btif_a2dp_sink_set_audio_track_gain

packages/modules/Bluetooth/system/btif/src/btif_a2dp_sink.cc
void btif_a2dp_sink_set_audio_track_gain(float gain) {log::debug("set gain to {:f}", gain);LockGuard lock(g_mutex);#ifdef __ANDROID__BtifAvrcpSetAudioTrackGain(btif_a2dp_sink_cb.audio_track, gain);
#endif
}

通过加锁机制保证在多线程环境下对共享资源访问的线程安全性,最后在满足特定编译条件(__ANDROID__ 宏定义存在,在 Android 平台相关的编译环境下)时,调用相应的函数将增益值传递过去,以此来实现对音频轨道增益的实际设置,确保音频播放的音量等相关属性能够按照传入的增益值进行调整。 

BtifAvrcpSetAudioTrackGain

packages/modules/Bluetooth/system/btif/src/btif_avrcp_audio_track.cc
void BtifAvrcpSetAudioTrackGain(void* handle, float gain) {if (handle == NULL) {log::info("handle is null.");return;}BtifAvrcpAudioTrack* trackHolder = static_cast<BtifAvrcpAudioTrack*>(handle);if (trackHolder != NULL) {const float clampedGain = std::clamp(gain, kMinTrackGain, kMaxTrackGain);if (clampedGain != gain) {log::warn("Out of bounds gain set. Clamping the gain from :{:f} to {:f}",gain, clampedGain);}trackHolder->gain = clampedGain;log::info("Avrcp audio track gain is set to {:f}", trackHolder->gain);}
}

设置音频通路的增益值,同时具备一定的输入合法性检查以及增益值范围限制处理机制。接收一个指向音频轨道相关对象的指针和一个表示增益的浮点数作为参数,首先检查指针是否为空,接着将指针转换为合适的类型并进一步判断对象是否有效,然后对传入的增益值进行范围限制(确保其处于合理的最小和最大增益值范围内),若超出范围则进行相应的日志记录并修正。通过这个函数,可以确保设置的增益值在有效范围内,并更新AVRCP音频通路对象的增益值,从而控制音频设备的音量。

相关文章:

【Bluedroid】A2DP Sink播放流程源码分析(三)

AVCTP消息处理 avrc_msg_cback /packages/modules/Bluetooth/system/stack/avrc/avrc_api.cc /******************************************************************************** Function avrc_msg_cback** Description This is the callback function used…...

概念实践极速入门 - 常用的设计模式 - 简单生活例子

概念实践极速入门 - 常用的设计模式 - 简单生活例子 SOLID 五大设计原则的首字母缩写 单一职责原则 和 开闭原则 就省略啦, 这两个概念很简单, 为了写而写反而容易误导人~* 鼓励大家字面理解&#xff01; // 哎呀还是解释吧 单一(S): 单干一件事; 开闭(O): 拓展开放, 修改关…...

postgres 数据库信息解读 与 sqlshell常用指令介绍

数据库信息&#xff1a; sqlshell Server [localhost]: 192.168.30.101 Database [postgres]: Port [5432]: 5432 Username [postgres]: 用户 postgres 的口令&#xff1a; psql (15.12, 服务器 16.8 (Debian 16.8-1.pgdg1201)) 警告&#xff1a;psql 主版本15,服务器主版本为…...

映射网络路路径和ftp路径原理是什么,如何使用,有什么区别

文章目录 一、原理1. 映射网络路径2. FTP路径 二、使用方法1. 映射网络路径2. FTP路径 三、主要区别1. 协议与功能2. 安全性与权限3. 适用场景 四、如何选择&#xff1f;五、注意事项 映射网络路径&#xff08;如SMB/CIFS或NFS&#xff09;和FTP路径&#xff08;FTP/FTPS/SFTP&…...

微服务3--服务容错

前言&#xff1a;本篇主要介绍服务容错与Sentinel进行限流。 高并发带来的问题 在微服务架构中&#xff0c;我们将业务拆分为一个个的服务&#xff0c;服务与服务之间都可以相互调用&#xff0c;但是由于网络或者说服务器本身的问题&#xff0c;服务不能保证100%可用&#xff…...

4.15redis点评项目下

--->接redis点评项目上 Redis优化秒杀方案 下单流程为&#xff1a;用户请求nginx--->访问tomcat--->查询优惠券--->判断秒杀库存是否足够--->查询订单--->校验是否是一人一单--->扣减库存--->创建订单 以上流程如果要串行执行耗时会很多&#xff0c…...

Web开发-JavaEE应用原生和FastJson反序列化URLDNS链JDBC链Gadget手搓

知识点&#xff1a; 1、安全开发-JavaEE-原生序列化-URLDNS链分析 2、安全开发-JavaEE-FastJson-JdbcRowSetImpl链分析 利用链也叫"gadget chains"&#xff0c;我们通常称为gadget&#xff1a; 1、共同条件&#xff1a;实现Serializable或者Externalizable接口&…...

坚持每日Codeforces三题挑战:Day 3 - 题目详解(2024-04-16,难度:900, 1200, 1200)

每天坚持写三道题第三天 &#xff08;今天写点简单的&#xff0c;剩下去刷力扣了&#xff09; 今日题目: Problem - B - Codeforces 900 Problem - B - Codeforces 1300 Problem - D - Codeforces 1400 题目一: Problem - B - Codeforces 题目大意: 给你一个数组,每次操…...

MySQL5.7递归查询

向下递归查询 SELECT ID,NAME,PARENT_ID,LEVEL_FROM(SELECT ID AS _IDS,(SELECT ID : GROUP_CONCAT(ID)FROM TREE_TABLE WHERE FIND_IN_SET(PARENT_ID,ID) > 0AND REMOVE N) T1,L : L 1 AS LEVEL_FROM TREE_TABLE,(SELECT ID : start, L: 0) T2WHERE ID IS NOT NULL) T3,…...

半导体设备通信标准—secsgem v0.3.0版本使用说明文档(2)之GEM(SEMI 30)

文章目录 1、处理器1.1、事件 2、GEM 合规性2.1、状态模型2.2、 设备加工状态2.3、 文档2.4、 控制 &#xff08;作员启动&#xff09;2.5、 动态事件报告配置2.6、 跟踪数据收集2.7、 报警管理2.8、 远程控制2.9、 设备常量2.10、 工艺配方管理2.11、 物料移动2.12、 设备终端…...

C++异步编程从入门到精通实战:全面指南与实战案例

C异步编程从入门到精通实战&#xff1a;全面指南与实战案例 在当今多核处理器普及的时代&#xff0c;异步编程成为了提升程序性能和响应能力的关键技术。无论是在高频交易系统、实时游戏引擎&#xff0c;还是网络服务器和大型数据处理平台&#xff0c;异步编程都发挥着至关重要…...

驱动开发硬核特训 · Day 13:从 device_create 到 sysfs,设备文件是如何生成的?

&#x1f50d; B站相应的视屏教程&#xff1a; &#x1f4cc; 内核&#xff1a;博文视频 - 备树深度解析&#xff1a;理论 实践全指南&#xff08;含 of 函数与 i.MX8MP 实例&#xff09; 敬请关注&#xff0c;记得标为原始粉丝。 &#x1f527; &#x1f4cc; 本文目标&#…...

肾脏系统触发 “数据包泄漏“ (血尿)与 “元数据校验异常“(蛋白尿)

肾脏系统触发 "数据包泄漏" (血尿)与 "元数据校验异常"(蛋白尿) 用计算机术语来类比。在之前的对话中&#xff0c;肾小球被比作防火墙或过滤器&#xff0c;肾小管则是回收系统。红细胞泄漏通常是因为肾小球的过滤屏障受损&#xff0c;而蛋白尿则可能与肾小…...

密码学(二)流密码

2.1流密码的基本概念 流密码的基本思想是利用密钥 k 产生一个密钥流...&#xff0c;并使用如下规则对明文串 ... 加密&#xff1a;。密钥流由密钥流发生器产生&#xff1a; &#xff0c;这里是加密器中的记忆元件&#xff08;存储器&#xff09;在时刻 i 的状态&#xff0c…...

Python 趣味学习 -数据类型脱口秀速记公式 [特殊字符]

&#x1f3a4; Python数据类型脱口秀速记公式 &#x1f40d; 1️⃣ 四大金刚登场 "Set叔(无序洁癖)、Tuple爷(顽固老头)、List姐(百变女王)、Dict哥(万能钥匙)"2️⃣ 特性对比RAP &#x1f3b6; 内存/作用域&#xff1a; 全局变量 → 函数内修改 → 可变(mutable)会…...

嵌入式Linux设备使用Go语言快速构建Web服务,实现设备参数配置管理方案探究

本文探讨&#xff0c;利用Go语言及gin框架在嵌入式Linux设备上高效搭建Web服务器&#xff0c;以实现设备参数的网页配置。通过gin框架&#xff0c;我们可以在几分钟内创建一个功能完善的管理界面&#xff0c;方便对诸如集中器&#xff0c;集线器等没有界面的嵌入式设备的管理。…...

波束形成(BF)从算法仿真到工程源码实现-第十二节-总结

一、总结 &#xff08;1&#xff09;基于webrtc的非线性波束形成效果较好&#xff0c;复杂度较低&#xff0c;但是波束形成后引入了非线性&#xff0c;导致噪声估计不准确&#xff0c;降噪效果变差。 &#xff08;2&#xff09;MVDR使用噪声协方差矩阵对平稳噪声降噪效果比较…...

【AI】IDEA 集成 AI 工具的背景与意义

一、IDEA 集成 AI 工具的背景与意义 随着人工智能技术的迅猛发展&#xff0c;尤其是大语言模型的不断演进&#xff0c;软件开发行业也迎来了智能化变革的浪潮。对于开发者而言&#xff0c;日常工作中面临着诸多挑战&#xff0c;如代码编写的重复性劳动、复杂逻辑的实现、代码质…...

解释原型链的概念,并说明`Object.prototype.__proto__`的值是什么?

原型链是 JavaScript 中实现继承的核心机制。每个对象都有一个指向其原型对象的私有链接&#xff08;通过 [[Prototype]] 内部属性&#xff09;&#xff0c;而原型对象自身也可能拥有原型&#xff0c;这种链式结构被称为原型链。当访问对象的属性时&#xff0c;若对象自身不存在…...

prototype`和`__proto__`有什么区别?如何手动修改一个对象的原型?

在 JavaScript 中&#xff0c;prototype 和 __proto__ 都与原型链相关&#xff0c;但它们的角色和用途有本质区别&#xff1a; 1. prototype 和 __proto__ 的区别 特性prototype__proto__归属对象仅函数对象拥有&#xff08;如构造函数&#xff09;所有对象默认拥有&#xff0…...

数据挖掘案例-电力负荷预测

今日课程 时间序列预测介绍 电力负荷预测项目开发&#xff08;开发一个基于时间以及历史负荷信息&#xff0c;预测未来负荷的模型&#xff09; 一、时间序列预测简介 1.什么是时序预测 时间序列预测是一种根据历史时间序列数据来预测未来值的方法。 任务比较好理解&#…...

SQL Server中OPENJSON + WITH 来解析JSON

一、概念 OPENJSON 是 SQL Server&#xff08;2016 及更高版本&#xff09; 中引入的一个表值函数&#xff0c;它将 JSON 文本转换为行和列的关系型数据结构。通过添加 WITH 子句&#xff0c;可以明确指定返回数据的结构和类型&#xff0c;实现 JSON 数据到表格数据的精确映射…...

在 Linux 中判断当前网络类型与网卡类型的实用方法(内外网判断 + 网卡分类)

在日常使用 Linux&#xff08;例如 Jetson、树莓派、服务器&#xff09;过程中&#xff0c;我们经常会遇到以下几个问题&#xff1a; 如何知道系统当前是走 有线网络还是无线网络&#xff1f;如何判断是连接了 公网还是内网&#xff1f;169.254.x.x 是什么&#xff1f;为什么我…...

Docker compose入门

目录 Docker Compose简介安装docker compose局限一 适合单机部署&#xff0c;不适合生产环境1. 架构设计目标不同2. 关键功能对比3. 生产环境的核心需求4. 适用场景总结5. 为什么 Compose 不适合生产&#xff1f; Docker Compose 简介 Docker Compose 是一个用于简化多容器Do…...

Docker Search 和 Docker Pull 失效解决

目录 1. Docker Search 1.1 问题描述 1.2 解决方案 1.2.1 方案1 命令行方式 1.2.2 方案2 非命令行方式 2. Docker Pull 2.1 问题描述 2.2 解决方案 2.2.1 替换镜像源 2.2.1.1 编辑镜像源&#xff08;linux&#xff09;版 2.2.1.2 编辑镜像源&#xff08;windows版本…...

Langchain Agent封装的工具

LangChain Agent Tools 参考文档 本文档详细介绍了LangChain框架中可用的Agent工具及其使用方法。这些工具可以赋予AI智能体与外部系统和服务交互的能力&#xff0c;从而构建功能更强大的应用程序。 目录 工具加载方法基础工具文件和系统工具搜索和信息检索工具语言模型增强…...

Windows11删除文件时弹出提示“没有管理员权限”,怎么办?

Windows11删除文件时弹出提示“没有管理员权限”&#xff0c;怎么办&#xff1f; 原因&#xff1a;文件没有读取到完全控制的权限。 解决方法&#xff1a;点击开始-设置-账户&#xff0c;检查Windows是否登录管理员账户&#xff0c;必须登录管理员账户。然后回到电脑桌面&…...

使用HTML + CSS + JS,编写一个台球追分计分器

目录 一.代码 二.效果展示 三.该计分器的优点 一.代码 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><…...

CS5346 - CHARTS: Chart with Point / Bar / Line / Box

文章目录 Chart with Point&#xff08;点图&#xff09;Scatter Chart&#xff08;散点图&#xff09;Pictogram&#xff08;图标&#xff09;Connected Scatter PlotConnected Dot plot&#xff08;连接点图&#xff09;Bubble Chart&#xff08;气泡图&#xff09; Chart wi…...

CNN实现简易教程

一、CNN基础回顾与设计原则 在开始实践之前,我们先快速回顾CNN的核心组件和设计原则,这将帮助你理解后续的代码和设计决策。 1. CNN的核心组件 卷积层(Convolutional Layer):通过卷积核提取局部特征(如边缘、纹理)。主要参数包括: 输入通道数(in_channels):输入数…...

Flask(1): 在windows系统上部署项目1

1 前言 学习python也有段时间了&#xff0c;最近一个小项目要部署&#xff0c;正好把过程写下来。 在程序的结构上我选择了w/s模式&#xff0c;相比于c/s模式&#xff0c;无需考虑客户端的升级&#xff1b;框架我选择了flask&#xff0c;就是冲着轻量级去的&#xff0c;就是插件…...

Zookeeper 可观测性最佳实践

Zookeeper 介绍 ZooKeeper 是一个开源的分布式协调服务&#xff0c;用于管理和协调分布式系统中的节点。它提供了一种高效、可靠的方式来解决分布式系统中的常见问题&#xff0c;如数据同步、配置管理、命名服务和集群管理等。本文介绍通过 DataKit 采集 Zookeeper 指标&#…...

vs2022使用git方法

1、创建git 2、在cmd下执行 git push -f origin master &#xff0c;会把本地代码全部推送到远程&#xff0c;同时会覆盖远程代码。 3、需要设置【Git全局设置】&#xff0c;修改的代码才会显示可以提交&#xff0c;否则是灰色的不能提交。 4、创建的分支&#xff0c;只要点击…...

【探商宝】跨境关税博弈下的技术破局:从头部平台现象看数字贸易体系重构

2025年4月&#xff0c;某头部跨境电商平台在北美市场上演了一场教科书级的技术突围战&#xff1a;其移动应用在72小时内从应用商店总榜300名开外飙升至第2位&#xff0c;单日下载量暴增近10倍。这场现象级爆发的背后&#xff0c;是关税政策与数字技术深度博弈的集中呈现。作为开…...

DeepSeek是否支持动态模态选择?揭秘多模态AI的智能切换能力

什么是动态模态选择&#xff1f; 想象一下你在和AI助手聊天&#xff1a; “帮我看看这张图片里有什么&#xff1f;”——AI切到视觉模式 “把图片内容写成300字总结”——切回文本模式 “再把它翻译成英文语音”——切到语音模式 这种根据任务需求自动切换处理模式的能力就是…...

Qwen2.5-Omni 部署框架选择指南:PyTorch vs. TensorFlow 深度对比

目录 一、核心结论&#xff1a;优先选择 PyTorch 方案 二、框架技术对比 1. 官方支持度 2. 性能基准测试&#xff08;RTX 4090&#xff09; 3. 关键功能支持 三、环境配置详解 1. PyTorch 推荐方案 系统配置 关键依赖 验证CUDA可用性 2. TensorFlow 替代方案&#x…...

全栈工程师角色介绍

全栈工程师&#xff08;Full Stack Engineer&#xff09;是一种综合型技术角色&#xff0c;具备从前端到后端、数据库、服务器运维等多领域的开发能力&#xff0c;并能独立完成产品全生命周期的构建与维护。其核心定义可从以下维度展开&#xff1a; 一、核心定义 技术广度与深…...

从零起步的Kaggle竞赛 - BirdCLEF2025

一个优秀的coder&#xff0c;先从CV工程开始...... 首先复制了 LB 0.804- EfficientNet B0 Pytorch Pipeline | Kaggle 这个notebook并尝试提交&#xff0c;ok&#xff0c;0.804 下载了大佬的代码试图在本地修改模型结构并训练。 以下是大佬的notebook中的代码&#xff0c;可…...

基于CNN+ViT的蔬果图像分类实验

本文只是做一个简单融合的实验&#xff0c;没有任何新颖&#xff0c;大家看看就行了。 1.数据集 本文所采用的数据集为Fruit-360 果蔬图像数据集&#xff0c;该数据集由 Horea Mureșan 等人整理并发布于 GitHub&#xff08;项目地址&#xff1a;Horea94/Fruit-Images-Datase…...

MySQL SQL 执行顺序(理论顺序)

示例 SQL&#xff1a; SELECT name, COUNT(*) FROM users WHERE age > 18 GROUP BY name HAVING COUNT(*) > 1 ORDER BY name ASC LIMIT 10;虽然语句是从 SELECT 写起的&#xff0c;但执行顺序其实是这样的&#xff1a; 执行顺序SQL 子句作用说明①FROM确定查询的…...

用Allan Deviation的方式估计长时间频率偏差

在电路设计中&#xff0c;若需要评估OSC长时间的偏差(秒级别)&#xff0c;观测的时间越多&#xff0c;低频噪声1/f上载的越厉害,如何通过PhaseNoise去有效估计长时间的偏差呢?...

无人机避障与目标识别技术分析!

一、无人机避障技术 1. 技术实现方式 传感器融合&#xff1a; 视觉传感&#xff08;RGB/双目/红外相机&#xff09;&#xff1a;基于SLAM&#xff08;同步定位与地图构建&#xff09;实现环境建模&#xff0c;但依赖光照条件。 激光雷达&#xff08;LiDAR&#xff09;&…...

2025年渗透测试面试题总结-拷打题库01(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 2025年渗透测试面试题总结-拷打题库01 1. PHP爆绝对路径方法&#xff1f; 2. 渗透工具及最常用工具 3…...

大厂面试:六大排序

前言 本篇博客集中了冒泡&#xff0c;选择&#xff0c;二分插入&#xff0c;快排&#xff0c;归并&#xff0c;堆排&#xff0c;六大排序算法 如果觉得对你有帮助&#xff0c;可以点点关注&#xff0c;点点赞&#xff0c;谢谢你&#xff01; 1.冒泡排序 //冒泡排序&#xff…...

Python爬虫第15节-2025今日头条街拍美图抓取实战

目录 一、项目背景与概述 二、环境准备与工具配置 2.1 开发环境要求 2.2 辅助工具配置 三、详细抓取流程解析 3.1 页面加载机制分析 3.2 关键请求识别技巧 3.3 参数规律深度分析 四、爬虫代码实现 五、实现关键 六、法律与道德规范 一、项目概述 在当今互联网时代&a…...

std::map gdb调试ok ,直接运行会crash

在使用 std::map 并且在调试模式下没有问题&#xff0c;但在直接运行时出现崩溃&#xff08;crash&#xff09;的情况&#xff0c;通常是由于以下几个原因引起的&#xff1a; 未初始化的变量使用&#xff1a;在调试模式下&#xff0c;某些变量可能因为调试工具&#xff08;如 G…...

【2025年泰迪杯数据挖掘挑战赛】A题 数据分析+问题建模与求解+Python代码直接分享

目录 2025年泰迪杯数据挖掘挑战赛A题完整论文&#xff1a;建模与求解Python代码1问题一的思路与求解1.1 问题一的思路1.1.1对统计数据进行必要说明&#xff1a;1.1.2统计流程&#xff1a;1.1.3特殊情况的考虑&#xff1a; 1.2 问题一的求解1.2.1代码实现1.2.2 问题一结果代码分…...

git在分支上会退到某个指定的commit

1、在idea上先备份好分支&#xff08;基于现有分支new branch&#xff09; 2、在gitlab管理端删除现有分支 3、在idea中大卡terminal&#xff0c;执行 git log 查看commit log ,找到要会退到的commit唯一码&#xff0c;然后执行git reset 唯一码 4、查看本地代码状态 git st…...

Cursor入门教程-JetBrains过度向

Cursor使用笔记 **前置&#xff1a;**之前博主使用的是JetBrains的IDE&#xff0c;VSCode使用比较少&#xff0c;所以会尽量朝着JetBrains的使用习惯及样式去调整。 一、设置语言为中文 如果刚上手Cursor&#xff0c;那么肯定对Cursor中的众多选项配置项不熟悉&#xff0c;这…...

MySQL之text字段详细分类说明

在 MySQL 中&#xff0c;TEXT 是用来存储大量文本数据的数据类型。TEXT 类型可以存储非常长的字符串&#xff0c;比 VARCHAR 类型更适合存储大块的文本数据。TEXT 数据类型分为以下几个子类型&#xff0c;每个子类型用于存储不同大小范围的文本数据&#xff1a; TINYTEXT: 可以…...