【Bluedroid】A2DP Sink播放流程源碼分析(二)

接上一篇繼續分析:【Bluedroid】A2DP Sink播放流程源碼分析(一)_安卓a2dp sink播放流程-CSDN博客

AVDTP接收端(Sink)流事件處理

bta_av_sink_data_cback?是 Bluedroid 中 A2DP Sink 角色的?AVDTP 數據回調函數,負責處理接收端的音頻數據事件,將底層接收到的音頻數據傳遞給上層模塊。

bta_av_sink_data_cback

packages/modules/Bluetooth/system/bta/av/bta_av_aact.cc
/********************************************************************************* Function         bta_av_sink_data_cback** Description      This is the AVDTP callback function for sink stream events.** Returns          void*******************************************************************************/
void bta_av_sink_data_cback(uint8_t handle, BT_HDR* p_pkt, uint32_t time_stamp,uint8_t m_pt) {int index = 0;tBTA_AV_SCB* p_scb;log::verbose("avdt_handle: {} pkt_len=0x{:x}  offset = 0x{:x} number of frames 0x{:x} ""sequence number 0x{:x}",handle, p_pkt->len, p_pkt->offset,*((uint8_t*)(p_pkt + 1) + p_pkt->offset), p_pkt->layer_specific);// 1. 查找流控制塊(SCB)/* Get SCB and correct sep type */for (index = 0; index < BTA_AV_NUM_STRS; index++) {p_scb = bta_av_cb.p_scb[index]; // 存儲流連接的狀態(如角色、句柄、SEP 類型等)if ((p_scb->avdt_handle == handle) &&(p_scb->seps[p_scb->sep_idx].tsep == AVDT_TSEP_SNK)) {break;}}if (index == BTA_AV_NUM_STRS) {/* cannot find correct handler */osi_free(p_pkt);return;}// 2. 觸發上層回調p_pkt->event = BTA_AV_SINK_MEDIA_DATA_EVT; // 設置事件類型為 Sink 媒體數據事件// 上層回調:通過 p_app_sink_data_cback 將數據傳遞給 A2DP Sink 應用層(如音頻解碼、播放模塊),觸發后續處理(如數據解析、緩沖區寫入)p_scb->seps[p_scb->sep_idx].p_app_sink_data_cback(p_scb->PeerAddress(), BTA_AV_SINK_MEDIA_DATA_EVT, (tBTA_AV_MEDIA*)p_pkt);/* Free the buffer: a copy of the packet has been delivered */osi_free(p_pkt);
}

bta_av_sink_data_cback?用于處理藍牙 AVDTP接收端(Sink)流事件的回調函數。主要作用是在接收到相關的數據包后,根據數據包對應的句柄等信息找到對應的處理上下文(通過查找?SCB),然后對數據包進行一些必要的處理,如設置事件類型,并調用相應的應用層回調函數將數據包及相關事件信息傳遞給上層應用進行進一步處理,最后釋放數據包所占用的內存,確保整個接收端流事件處理流程的完整性以及內存資源的合理管理。?

關鍵作用

  1. 數據通路橋梁:連接 AVDTP 層與上層應用,將原始音頻數據轉換為 Sink 角色可處理的事件。
  2. 狀態校驗:通過 SCB 篩選確保僅處理?Sink 角色的合法流連接,避免跨角色數據誤處理。
  3. 錯誤隔離:未找到 SCB 時及時釋放內存,防止無效指針操作導致的崩潰。

bta_av_sink_media_callback (BTA_AV_SINK_MEDIA_DATA_EVT

packages/modules/Bluetooth/system/btif/src/btif_av.cc
// TODO: All processing should be done on the JNI thread
static void bta_av_sink_media_callback(const RawAddress& peer_address,tBTA_AV_EVT event,tBTA_AV_MEDIA* p_data) {log::verbose("event={}", event);switch (event) {case BTA_AV_SINK_MEDIA_DATA_EVT: { // 當接收到媒體數據時觸發BtifAvPeer* peer = btif_av_sink_find_peer(peer_address);if (peer != nullptr && peer->IsActivePeer()) {int state = peer->StateMachine().StateId();if ((state == BtifAvStateMachine::kStateStarted) ||(state == BtifAvStateMachine::kStateOpened)) {uint8_t queue_len = btif_a2dp_sink_enqueue_buf((BT_HDR*)p_data);log::verbose("Packets in Sink queue {}", queue_len);}}break;}...default:break;}
}

bta_av_sink_media_callback?是一個回調函數,主要用于處理藍牙音頻 / 視頻(AV)接收端(Sink)相關的媒體事件。依據接收到的不同媒體事件類型來執行相應操作,目的是協調藍牙 A2DP 接收端在接收媒體數據時與系統內其他部分的交互,比如根據接收端狀態決定是否將接收到的數據入隊等操作,以此保障藍牙音頻 / 視頻接收流程能正常進行。

btif_a2dp_sink_enqueue_buf?
packages/modules/Bluetooth/system/btif/src/btif_a2dp_sink.cc
uint8_t btif_a2dp_sink_enqueue_buf(BT_HDR* p_pkt) {// 1. 加鎖與刷新檢查LockGuard lock(g_mutex);if (btif_a2dp_sink_cb.rx_flush) /* Flush enabled, do not enqueue */return fixed_queue_length(btif_a2dp_sink_cb.rx_audio_queue);log::verbose("+");// 2. 分配內存并復制數據包/* Allocate and queue this buffer */BT_HDR* p_msg =reinterpret_cast<BT_HDR*>(osi_malloc(sizeof(*p_msg) + p_pkt->len));memcpy(p_msg, p_pkt, sizeof(*p_msg));p_msg->offset = 0;memcpy(p_msg->data, p_pkt->data + p_pkt->offset, p_pkt->len);// 將新的消息結構體添加到接收音頻隊列 rx_audio_queue 中fixed_queue_enqueue(btif_a2dp_sink_cb.rx_audio_queue, p_msg);// 3. 隊列長度檢查與處理if (fixed_queue_length(btif_a2dp_sink_cb.rx_audio_queue) ==MAX_INPUT_A2DP_FRAME_QUEUE_SZ) { // 隊列已滿osi_free(fixed_queue_try_dequeue(btif_a2dp_sink_cb.rx_audio_queue));uint8_t ret = fixed_queue_length(btif_a2dp_sink_cb.rx_audio_queue);return ret;}// 4. 解碼啟動檢查// Avoid other checks if alarm has already been initialized.if (btif_a2dp_sink_cb.decode_alarm == nullptr &&fixed_queue_length(btif_a2dp_sink_cb.rx_audio_queue) >=MAX_A2DP_DELAYED_START_FRAME_COUNT) {log::verbose("Initiate decoding. Current focus state:{}",btif_a2dp_sink_cb.rx_focus_state);if (btif_a2dp_sink_cb.rx_focus_state == BTIF_A2DP_SINK_FOCUS_GRANTED) {btif_a2dp_sink_audio_handle_start_decoding();}}return fixed_queue_length(btif_a2dp_sink_cb.rx_audio_queue);
}

處理藍牙 A2DP接收端(Sink)的數據入隊操作。通過合理的加鎖機制保障多線程環境下資源訪問的安全性,根據刷新機制決定是否入隊數據,在入隊過程中進行內存分配、數據復制以及隊列長度控制等操作,同時還能根據特定條件觸發音頻解碼相關操作,確保接收端音頻數據能夠正確地緩存、管理以及適時地進行解碼播放,對于維持整個藍牙音頻接收和播放流程的順暢以及資源的合理利用有著重要意義。

fixed_queue_enqueue?
packages/modules/Bluetooth/system/osi/src/fixed_queue.cc
void fixed_queue_enqueue(fixed_queue_t* queue, void* data) {CHECK(queue != NULL);CHECK(data != NULL);//等待入隊信號量。如果隊列已滿(即入隊信號量的計數為0),則當前線程將阻塞在這里,直到有其他線程從隊列中移除了元素并調用了?semaphore_post(queue->enqueue_sem)semaphore_wait(queue->enqueue_sem);{std::lock_guard<std::mutex> lock(*queue->mutex);list_append(queue->list, data); // 將數據添加到隊列的列表中}// 增加出隊信號量的計數。表示隊列中有新的元素可供出隊操作,如果之前有線程在等待出隊信號量(即隊列為空且線程嘗試從隊列中取出元素),則這些線程現在可以繼續執行semaphore_post(queue->dequeue_sem);
}

向一個固定隊列(fixed_queue_t?類型的隊列)中添加元素(通過?void* data?參數傳入要添加的數據指針),在添加過程中,通過嚴格的參數檢查、合理的信號量控制以及互斥鎖保護下的隊列元素添加操作,在多線程環境下實現了安全、可靠的隊列入隊功能。通過控制入隊和出隊信號量的等待與釋放,還能有效地協調多個線程對隊列的并發使用,避免出現數據競爭、不一致等并發問題,保障了整個固定隊列數據結構在多線程系統中的正常運行,對于需要在并發環境下進行數據緩存、排隊處理等應用場景有著重要的支撐作用。

btif_a2dp_sink_audio_handle_start_decoding

packages/modules/Bluetooth/system/btif/src/btif_a2dp_sink.cc
// Must be called while locked.
static void btif_a2dp_sink_audio_handle_start_decoding() {log::info("");if (btif_a2dp_sink_cb.decode_alarm != nullptr)return;  // Already started decoding#ifdef __ANDROID__BtifAvrcpAudioTrackStart(btif_a2dp_sink_cb.audio_track);
#endif//創建一個新的周期性定時器,并將其地址存儲在?btif_a2dp_sink_cb.decode_alarm?中btif_a2dp_sink_cb.decode_alarm = alarm_new_periodic("btif.a2dp_sink_decode");if (btif_a2dp_sink_cb.decode_alarm == nullptr) {log::error("unable to allocate decode alarm");return;}// 設置定時器的觸發間隔為?BTIF_SINK_MEDIA_TIME_TICK_MS(20,定時器觸發的毫秒間隔)// 當定時器觸發時,將調用?btif_decode_alarm_cb?回調函數(傳遞?nullptr?作為參)alarm_set(btif_a2dp_sink_cb.decode_alarm, BTIF_SINK_MEDIA_TIME_TICK_MS,btif_decode_alarm_cb, nullptr);
}

負責啟動藍牙 A2DP(接收端(Sink)的音頻解碼相關操作。在調用時要求處于加鎖狀態,以確保在多線程環境下操作相關共享資源(如?btif_a2dp_sink_cb?結構體相關成員)的安全性。首先通過檢查避免重復啟動解碼操作,然后針對安卓平臺進行相應的音頻播放前置操作(在__ANDROID__環境下),接著創建周期性的定時器(或稱為“alarm”)并設置好相關參數和回調函數,以此來建立起一個定時執行音頻解碼任務的機制,保障音頻數據能夠在合適的時間間隔下持續地被解碼處理,為后續的音頻播放等流程提供解碼后的數據支持。

下面分析SBC編碼寫入audiotrack的過程,其它編碼格式的如法炮制。?

btif_decode_alarm_cb

packages/modules/Bluetooth/system/btif/src/btif_a2dp_sink.cc
static void btif_decode_alarm_cb(void* /* context */) {LockGuard lock(g_mutex);btif_a2dp_sink_cb.worker_thread.DoInThread(FROM_HERE, base::BindOnce(btif_a2dp_sink_avk_handle_timer));
}

btif_decode_alarm_cb?函數主要作為一個回調函數來使用,其核心功能是在被觸發時,先通過加鎖機制確保線程安全地訪問共享資源,然后安排在特定的工作線程中執行另一個函數?btif_a2dp_sink_avk_handle_timer,以此實現一種異步的、線程安全的操作觸發機制,用于在特定的定時或事件觸發場景下,讓相關的音頻處理邏輯(由?btif_a2dp_sink_avk_handle_timer?函數具體實現)能夠在合適的線程環境中有序執行,保障音頻相關功能的正確運行。

btif_a2dp_sink_avk_handle_timer?

packages/modules/Bluetooth/system/btif/src/btif_a2dp_sink.cc
static void btif_a2dp_sink_avk_handle_timer() {// 1. 加鎖操作LockGuard lock(g_mutex);// 2. 檢查隊列是否為空BT_HDR* p_msg; // 用于后續存儲從隊列中取出的數據包if (fixed_queue_is_empty(btif_a2dp_sink_cb.rx_audio_queue)) {log::verbose("empty queue");return;}// 3. 檢查焦點狀態/* Don't do anything in case of focus not granted */if (btif_a2dp_sink_cb.rx_focus_state == BTIF_A2DP_SINK_FOCUS_NOT_GRANTED) {log::verbose("skipping frames since focus is not present");return;}// 4. 檢查刷新標志/* Play only in BTIF_A2DP_SINK_FOCUS_GRANTED case */if (btif_a2dp_sink_cb.rx_flush) {// 清空接收音頻隊列,并釋放隊列中每個元素的內存fixed_queue_flush(btif_a2dp_sink_cb.rx_audio_queue, osi_free);return;}// 5. 處理隊列中的數據包log::verbose("process frames begin");while (true) { // 使用 while 循環不斷從隊列中取出數據包,直到隊列為空(即 fixed_queue_try_dequeue 返回 NULL)p_msg = (BT_HDR*)fixed_queue_try_dequeue(btif_a2dp_sink_cb.rx_audio_queue);if (p_msg == NULL) {break;}log::verbose("number of packets in queue {}",fixed_queue_length(btif_a2dp_sink_cb.rx_audio_queue));/* Queue packet has less frames */btif_a2dp_sink_handle_inc_media(p_msg); // 處理取出的音頻數據包osi_free(p_msg);}log::verbose("process frames end");
}

btif_a2dp_sink_avk_handle_timer?函數是一個定時器處理函數,用于處理 A2DP Sink 端接收到的音頻數據包隊列。在定時器觸發時被調用,對 A2DP Sink 端的音頻數據包隊列進行檢查和處理。根據隊列狀態、焦點狀態和刷新標志來決定是直接返回、清空隊列還是逐個處理隊列中的數據包,確保音頻數據的處理符合系統的狀態和要求。

btif_a2dp_sink_handle_inc_media

packages/modules/Bluetooth/system/btif/src/btif_a2dp_sink.cc
// Must be called while locked.
static void btif_a2dp_sink_handle_inc_media(BT_HDR* p_msg) {if ((btif_av_get_peer_sep() == AVDT_TSEP_SNK) ||(btif_a2dp_sink_cb.rx_flush)) {log::verbose("state changed happened in this tick");return;}CHECK(btif_a2dp_sink_cb.decoder_interface != nullptr);if (!btif_a2dp_sink_cb.decoder_interface->decode_packet(p_msg)) {log::error("decoding failed");}
}

確保當前狀態允許的情況下,調用解碼器接口來解碼接收到的A2DP音頻數據包。保障了音頻數據在合適的條件下能夠被合理地進行解碼操作,對于維持音頻播放等后續功能的正常運行有著重要意義。?

a2dp_sbc_decoder_decode_packet
packages/modules/Bluetooth/system/stack/a2dp/a2dp_sbc_decoder.cc
bool a2dp_sbc_decoder_decode_packet(BT_HDR* p_buf) {// 1. 輸入數據準備uint8_t* data = p_buf->data + p_buf->offset;size_t data_size = p_buf->len;if (data_size == 0) {log::error("Empty packet");return false;}size_t num_frames = data[0] & 0xf; // 提取幀數量(低4位)data += 1;  // 跳過幀數量字段,指向實際音頻數據data_size -= 1;// 2. 逐幀解碼const OI_BYTE* oi_data = data; // 轉換為 OI 庫所需的字節類型uint32_t oi_size = data_size;  // 剩余待解碼數據大小size_t out_avail = sizeof(a2dp_sbc_decoder_cb.decode_buf); // 輸出緩沖區總大小int16_t* out_ptr = a2dp_sbc_decoder_cb.decode_buf; // 輸出緩沖區指針(PCM 數據)for (size_t i = 0; i < num_frames; ++i) {uint32_t out_size = out_avail;  // 當前幀解碼所需的輸出空間// 解碼庫調用OI_STATUS status = OI_CODEC_SBC_DecodeFrame(&a2dp_sbc_decoder_cb.decoder_context,  // 解碼上下文(包含編解碼參數)&oi_data,                             // 輸入數據指針(按幀遞增)&oi_size,                             // 剩余輸入數據大小(按幀遞減)out_ptr,                             // 輸出 PCM 數據指針&out_size                            // 實際解碼出的 PCM 數據大小);if (!OI_SUCCESS(status)) {log::error("Decoding failure: {}", status);return false;  // 解碼失敗,終止處理}out_avail -= out_size;       // 更新剩余輸出空間out_ptr += out_size / sizeof(*out_ptr);  // 移動輸出指針(按 int16 單位)
}// 3. 解碼結果回調size_t out_used = (out_ptr - a2dp_sbc_decoder_cb.decode_buf) * sizeof(*out_ptr);// 上層回調:通過 decode_callback 將解碼后的 PCM 數據傳遞給上層(如音頻播放模塊),觸發后續處理(如寫入音頻輸出設備)a2dp_sbc_decoder_cb.decode_callback(reinterpret_cast<uint8_t*>(a2dp_sbc_decoder_cb.decode_buf),  // PCM 數據起始地址out_used                                                     // 實際使用的字節數);
}

a2dp_sbc_decoder_decode_packet?是?A2DP Sink 端 SBC(Subband Coding,子帶編碼)編解碼器的核心解碼函數,負責將接收到的 SBC 格式音頻數據包解碼為 PCM 數據,供上層音頻播放模塊使用。其核心邏輯包括?數據包解析逐幀解碼?和?結果回調

OI_CODEC_SBC_DecodeFrame
packages/modules/Bluetooth/system/embdrv/sbc/decoder/srce/decoder-sbc.c
OI_STATUS OI_CODEC_SBC_DecodeFrame(OI_CODEC_SBC_DECODER_CONTEXT* context,const OI_BYTE** frameData,uint32_t* frameBytes, int16_t* pcmData,uint32_t* pcmBytes) {OI_STATUS status;OI_UINT framelen;uint8_t crc;TRACE(("+OI_CODEC_SBC_DecodeFrame"));// 1. 同步字查找(Syncword Detection)TRACE(("Finding syncword"));status = FindSyncword(context, frameData, frameBytes);if (!OI_SUCCESS(status)) {return status;}// 2. 頭部解析與參數校驗/* Make sure enough data remains to read the header. */if (*frameBytes < SBC_HEADER_LEN) {TRACE(("-OI_CODEC_SBC_DecodeFrame: OI_CODEC_SBC_NOT_ENOUGH_HEADER_DATA"));return OI_CODEC_SBC_NOT_ENOUGH_HEADER_DATA;}if (context->mSbcEnabled) {/** There is no parameter embedded in mSBC's header as the parameters are* fixed unlike the general SBC. We only need the packet's crc for mSBC.*/context->common.frameInfo.crc = (*frameData)[3]; // mSBC 僅需 CRC} else {TRACE(("Reading Header"));OI_SBC_ReadHeader(&context->common, *frameData); // 普通 SBC 解析完整頭部}/** Some implementations load the decoder into RAM and use overlays for 4 vs 8* subbands. We need* to ensure that the SBC parameters for this frame are compatible with the* restrictions imposed* by the loaded overlays.*/// // 參數校驗:子帶數量、通道數、PCM 步長等if (context->limitFrameFormat &&(context->common.frameInfo.subbands != context->restrictSubbands)) {ERROR(("SBC parameters incompatible with loaded overlay"));return OI_STATUS_INVALID_PARAMETERS;}if (context->common.frameInfo.nrof_channels > context->common.maxChannels) {ERROR(("SBC parameters incompatible with number of channels specified during ""reset"));return OI_STATUS_INVALID_PARAMETERS;}if (context->common.pcmStride < 1 || context->common.pcmStride > 2) {ERROR(("PCM stride not set correctly during reset"));return OI_STATUS_INVALID_PARAMETERS;}/** At this point a header has been read. However, it's possible that we found* a false syncword,* so the header data might be invalid. Make sure we have enough bytes to read* in the* CRC-protected header, but don't require we have the whole frame. That way,* if it turns out* that we're acting on bogus header data, we don't stall the decoding process* by waiting for* data that we don't actually need.*/framelen = OI_CODEC_SBC_CalculateFramelen(&context->common.frameInfo);if (*frameBytes < framelen) {TRACE(("-OI_CODEC_SBC_DecodeFrame: OI_CODEC_SBC_NOT_ENOUGH_BODY_DATA"));return OI_CODEC_SBC_NOT_ENOUGH_BODY_DATA; // 數據不足}TRACE(("Calculating checksum"));// 3. 幀長度計算與數據完整性校驗crc = OI_SBC_CalculateChecksum(&context->common.frameInfo, *frameData);if (crc != context->common.frameInfo.crc) {TRACE(("CRC Mismatch:  calc=%02x read=%02x\n", crc,context->common.frameInfo.crc));TRACE(("-OI_CODEC_SBC_DecodeFrame: OI_CODEC_SBC_CHECKSUM_MISMATCH"));return OI_CODEC_SBC_CHECKSUM_MISMATCH; // CRC 校驗失敗}// 4. 比特池合法性檢查/** Make sure the bitpool values are sane.*/if ((context->common.frameInfo.bitpool < SBC_MIN_BITPOOL) &&!context->common.frameInfo.enhanced) {ERROR(("Bitpool too small: %d (must be >= 2)",context->common.frameInfo.bitpool));return OI_STATUS_INVALID_PARAMETERS; }if (context->common.frameInfo.bitpool >OI_SBC_MaxBitpool(&context->common.frameInfo)) {ERROR(("Bitpool too large: %d (must be <= %ld)",context->common.frameInfo.bitpool,OI_SBC_MaxBitpool(&context->common.frameInfo)));return OI_STATUS_INVALID_PARAMETERS;}// 5. 音頻數據解碼(核心邏輯)/** Now decode the SBC data. Partial decode is not yet implemented for an SBC* stream, so pass FALSE to decode body to have it enforce the old rule that* you have to decode a whole packet at a time.*/status = DecodeBody(context, *frameData + SBC_HEADER_LEN, pcmData, pcmBytes,FALSE);if (OI_SUCCESS(status)) {*frameData += framelen; // 移動數據指針到下一幀*frameBytes -= framelen; // 更新剩余數據大小}TRACE(("-OI_CODEC_SBC_DecodeFrame: %d", status));// 6. mSBC 特殊處理/* mSBC is designed with 8 bits of zeros at the end for padding. */if (context->mSbcEnabled) {*frameBytes -= 1;}return status;
}

OI_CODEC_SBC_DecodeFrame?是?SBC 音頻解碼的核心函數,負責對單個 SBC 音頻幀進行解碼,將編碼后的音頻數據轉換為 PCM 格式。其核心流程包括?同步字查找頭部解析參數校驗CRC 校驗?和?音頻數據解碼,是 A2DP Sink 端音頻解碼的關鍵環節。兼顧了普通 SBC 和 mSBC(Modified SBC,改進型SBC)兩種模式,通過狀態機和上下文管理實現高效的幀級解碼。理解此函數有助于分析音頻解碼中的常見問題(如雜音、解碼失敗),并為編解碼器優化(如提升解碼速度、降低功耗)提供切入點。

繼續回到a2dp_sbc_decoder_decode_packet分析回調處理。

btif_a2dp_sink_on_decode_complete?

packages/modules/Bluetooth/system/btif/src/btif_a2dp_sink.cc
static void btif_a2dp_sink_on_decode_complete(uint8_t* data, uint32_t len) {
#ifdef __ANDROID__BtifAvrcpAudioTrackWriteData(btif_a2dp_sink_cb.audio_track,reinterpret_cast<void*>(data), len);
#endif
}

btif_a2dp_sink_on_decode_complete?作為一個回調函數,用于在音頻數據解碼完成后進行后續的處理操作。在 Android 平臺下,調用BtifAvrcpAudioTrackWriteData函數,將解碼后的音頻數據寫入到音頻通路。

BtifAvrcpAudioTrackWriteData

packages/modules/Bluetooth/system/btif/src/btif_avrcp_audio_track.cc
int BtifAvrcpAudioTrackWriteData(void* handle, void* audioBuffer,int bufferLength) {// 1. 參數校驗與初始化BtifAvrcpAudioTrack* trackHolder = static_cast<BtifAvrcpAudioTrack*>(handle);CHECK(trackHolder != NULL);CHECK(trackHolder->stream != NULL); // 確保音頻流句柄有效aaudio_result_t retval = -1;// 2. 調試數據 Dump(可選)
#if (DUMP_PCM_DATA == TRUE)if (outputPcmSampleFile) { // 將接收到的音頻數據直接寫入文件,用于調試階段分析原始音頻數據fwrite((audioBuffer), 1, (size_t)bufferLength, outputPcmSampleFile);}
#endif// 3. 樣本大小計算// 根據音頻格式(如 16 位 PCM、浮點型)返回單個樣本的字節大小。例如:// 16 位 PCM 立體聲:每個樣本 2 字節 × 2 通道 = 4 字節 / 幀。// 32 位浮點立體聲:每個樣本 4 字節 × 2 通道 = 8 字節 / 幀。size_t sampleSize = sampleSizeFor(trackHolder);// 4. 數據轉碼與分段寫入int transcodedCount = 0;do {// 轉碼:將輸入數據(如 SBC 解碼后的 PCM)轉為 AAudio 所需的浮點格式transcodedCount += transcodeToPcmFloat(((uint8_t*)audioBuffer) + transcodedCount,  // 輸入數據指針(按轉碼進度遞增)bufferLength - transcodedCount,             // 剩余待轉碼數據長度trackHolder                                 // 軌道句柄(含編碼格式、通道數等信息));// 寫入 AAudio 流:參數為流句柄、目標緩沖區、樣本數量、超時時間retval = AAudioStream_write(trackHolder->stream,          // AAudio 流句柄trackHolder->buffer,          // 轉碼后的浮點 PCM 緩沖區transcodedCount / (sampleSize * trackHolder->channelCount),  // 樣本數量(總字節數 / 單樣本字節數)kTimeoutNanos                // 寫入超時時間(納秒級));log::verbose("Track.cpp: btWriteData len = {} ret = {}", bufferLength,retval);} while (transcodedCount < bufferLength);return transcodedCount;
}

BtifAvrcpAudioTrackWriteData?是?AVRCP 音頻軌道的數據寫入函數,負責將接收到的音頻數據轉換為可播放的 PCM 格式,并通過 AAudio 框架寫入音頻流,最終輸出到音頻設備。其核心邏輯包括?數據轉碼AAudio 流寫入?和?調試數據 Dump

AAudio ?|? Android NDK ?|? Android Developers

?流程圖

a2dp sink起播和播放的流程圖

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/75891.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/75891.shtml
英文地址,請注明出處:http://en.pswp.cn/web/75891.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

抗量子算法驗證工具

抗量子算法計算工具 抗量子算法驗證工具ML-KEMML-DSASLH-DSA 抗量子算法驗證工具 2024年末&#xff0c;美國NIST陸續公布了FIPS-203、FIPS-204、FIPS-205算法標準文檔&#xff0c;抽空學習了一下&#xff0c;做了個算法計算工具。 ML-KEM ML-DSA SLH-DSA 需要的朋友可留言交流…

2025年PMP考試有哪些變化?難點在哪里?

PMP&#xff08;項目管理專業人士資格認證&#xff09;考試因其廣泛的行業認可度和實用性&#xff0c;成為許多專業人士提升職業競爭力的重要選擇。然而&#xff0c;對于初次接觸PMP考試的考生來說&#xff0c;其廣度與深度的平衡、理論與實踐的結合&#xff0c;以及跨文化思維…

Docker學習筆記-docker安裝、刪除

一、在centOS 7中docker的默認安裝目錄 # Docker 主配置文件目錄 ls /etc/docker# Docker 數據目錄&#xff08;鏡像、容器、卷等&#xff09; ls /var/lib/docker# Docker 可執行文件路徑 which docker # 輸出類似 /usr/bin/docker 二、docker文件目錄說明 目錄/文件用途/…

MATLAB求和∑怎么用?

MATLAB求和∑怎么用&#xff1f; 一&#xff1a;題目&#xff1a;求下列方程的和 二、代碼如下 1.syms函數 &#xff08;方法一) 代碼如下&#xff08;示例&#xff09;&#xff1a; 1. syms x 2. symsum((x.^22*x).^3,1,100) 3. 2.直接用循環 (方法二) 代碼如下&am…

每日算法-鏈表(2.兩數相加、24.兩兩交換鏈表中的節點、143.重排鏈表)

一.兩數相加 1.1題目描述 1.2題解思路 定義兩個指針l1,l2依次遍歷兩個鏈表&#xff0c;用變量add存儲l1加l2的值&#xff0c;將add的個位數取出來充當新節點的值&#xff0c;然后將add的個位數刪去&#xff0c;即add /10&#xff0c;循環此操作。 重點分析&#xff1a; 1.跟…

Flutter學習 滾動組件(1):ListView基本使用

目錄 一、ListView構造方法1.1 常規方法1.2 ListView.builder1.3 ListView.separated 二、自定義ListView樣式和布局&#xff1a;三、ListView性能優化&#xff1a;總結&#xff1a; 一、ListView構造方法 主要以下幾種方法&#xff1a; 常規方法&#xff0c;直接使用默認的構…

ESLint常見錯誤

1、Strings must use singlequote —— 字符串必須使用單引號 2、Extra semicolon semi——額外的分號&#xff1a;一行語句結尾不能添加分號 3、Unexpected trailing comma —— 行尾多了一個逗號 4、Newline required at end of file but not found ——文件結尾必須要新加…

Windows進行磁盤分區/擴容

Windows進行磁盤分區/擴容 導航 文章目錄 Windows進行磁盤分區/擴容導航分區教程壓縮卷教程 用Windows自帶的磁盤管理進行分區/擴容&#xff0c;但有個東西需要說明下是&#xff1a; 物理特性限制 磁盤分區的物理特性決定了擴容操作的方向。在磁盤上&#xff0c;數據是線性存儲…

獲取類路徑

分析 String pathThread.currentThread().getContextClassLoader().getResource("log").getPath(); 這行代碼用于獲取類路徑(classpath)下名為"log"的資源的文件系統路徑&#xff0c;我來詳細解析它的執行過程和潛在問題&#xff1a; 1. 代碼分解解析 j…

安裝fvm可以讓電腦同時管理多個版本的flutter、flutter常用命令、vscode連接模擬器

打開 PowerShellfvm安裝 dart pub global activate fvm安裝完成后&#xff0c;如果顯示FVM無法識別&#xff0c;那么需要去添加環境變量path添加這個&#xff1a;C:\Users\Administrator\AppData\Local\Pub\Cache\bin 常用命令 fvm releases 查看用戶可以裝的flutter版本fvm l…

Kaggle-Disaster Tweets-(二分類+NLP+模型融合)

Disaster Tweets 題意&#xff1a; 就是給出一個dataframe包含text這一列代表著文本&#xff0c;文本會有一些詞&#xff0c;問對于每條記錄中的text是真關于災難的還是假關于災難的。 比如我們說今天作業真多&#xff0c;這真是一場災難。實際上這個災難只是我們調侃而言的。…

Flutter 2025 Roadmap

2025 這個路線圖是有抱負的。它主要代表了我們這些在谷歌工作的人收集的內容。到目前為止&#xff0c;非Google貢獻者的數量超過了谷歌雇傭的貢獻者&#xff0c;所以這并不是一個詳盡的列表&#xff0c;列出了我們希望今年Flutter能夠出現的所有令人興奮的新事物&#xff01;在…

如何通過API接口獲取淘寶商品價格?實操講解

要通過API接口獲取淘寶商品價格&#xff0c;需使用淘寶開放平臺&#xff08;Taobao Open Platform, TOP&#xff09;提供的商品詳情API&#xff08;如taobao.item.get或taobao.item_get&#xff09;。以下是完整的實操步驟&#xff1a; 一、前期準備 注冊淘寶開放平臺賬號 訪問…

按鍵精靈安卓/ios腳本輔助工具開發教程:如何把界面配置保存到服務器

在使用按鍵精靈工具輔助的時候&#xff0c;多配置的情況下&#xff0c;如果保存現有的配置&#xff0c;并且讀取&#xff0c;尤其是游戲中多種任務并行情況下&#xff0c;更是需要界面進行保存&#xff0c;簡單分享來自紫貓插件的配置保存服務器寫法。 界面例子&#xff1a; …

DP34 【模板】前綴和 -- 前綴和

目錄 一&#xff1a;題目 二&#xff1a;算法原理 三&#xff1a;代碼實現 一&#xff1a;題目 題目鏈接&#xff1a;【模板】前綴和_牛客題霸_牛客網 二&#xff1a;算法原理 三&#xff1a;代碼實現 #include <iostream> #include <vector> using namespac…

關于我的服務器

最近我買了臺騰訊云服務器&#xff0c;然后新手小白只會用寶塔。。。 安裝完之后默認的端口是8888&#xff0c;打開面板就會提示我有風險。然后 我改了端口之后&#xff0c;怎么都打不開。 于是 學到了幾句命令可以使用&#xff1a; //查看端口是否已經修改成功 cat www/se…

機器學習常用算法總結

1. 概述 機器學習的定義是對于某類任務T和性能度量P&#xff0c;如果一個計算機程序在T上其性能P隨著經驗E而自我完善&#xff0c;那么我們就稱這個系統從經驗E中學習&#xff0c;機器學習是人工智能的一種方法&#xff0c;它通過在大量數據中學習隱藏的規則&#xff0c;模式和…

ns-3中UDP飽和流發包時間間隔設置最合理值

ns3的官方手冊很全&#xff0c;相關書籍也是有的&#xff0c;官網先貼在這里&#xff1a; ns-3 | a discrete-event network simulator for internet systemsa discrete-event network simulator for internet systemshttps://www.nsnam.org/相關的腳本介紹也都有一些&#xf…

Windsurf代碼依賴檢查導入

目錄 1. 在全局配置中根據需求設置檢查的文件&#xff0c;以python為例 2. 執行命令生成requirements.txt文件&#xff0c;此操作可以將當前代碼所需的依賴全部寫入 3. 在Cascade對話窗口輸入detect-dependencies查詢 4. 根據查詢出來的結果優化requirements.txt&#xff0c;可…

復變函數摘記3

復變函數摘記3 5. 留數5.1 可去奇點、極點、本性奇點5.2 零點與極點的關系5.3 在無窮遠點處的情形5.4 留數 5. 留數 \quad 如果函數 f ( z ) f(z) f(z) 在 z 0 z_0 z0? 及 z 0 z_0 z0? 的鄰域內處處可導&#xff0c;那么稱 f ( z ) f(z) f(z) 在點 z 0 z_0 z0? 處解析。…