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

【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起播和播放的流程图

相关文章:

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

接上一篇继续分析&#xff1a;【Bluedroid】A2DP Sink播放流程源码分析(一)_安卓a2dp sink播放流程-CSDN博客 AVDTP接收端&#xff08;Sink&#xff09;流事件处理 bta_av_sink_data_cback 是 Bluedroid 中 A2DP Sink 角色的 AVDTP 数据回调函数&#xff0c;负责处理接收端的…...

【Code】《代码整洁之道》笔记-Chapter16-重构SerialDate

第16章 重构SerialDate 如果你找到JCommon类库&#xff0c;深入该类库&#xff0c;其中有个名为org.jfree.date的程序包。在该程序包中&#xff0c;有个名为SerialDate的类&#xff0c;我们即将剖析这个类。 SerialDate的作者是David Gilbert。David显然是一位经验丰富、能力…...

redis 内存中放哪些数据?

在 Java 开发中,Redis 作为高性能内存数据库,通常用于存储高频访问、低延迟要求、短期有效或需要原子操作的数据。以下是 Redis 内存中常见的数据类型及对应的使用场景,适合面试回答: 1. 缓存数据(高频访问,降低数据库压力) 用户会话(Session):存储用户登录状态、临时…...

【Python使用】嘿马云课堂web完整实战项目第4篇:封装异常处理,封装JSON返回值【附代码文档】

教程总体简介&#xff1a;项目概述 项目背景 项目的功能构架 项目的技术架构 CMS 什么是CMS CMS需求分析与工程搭建 静态门户工程搭建 SSI服务端包含技术 页面预览开发 4 添加“页面预览”链接 页面发布 需求分析 技术方案 测试 环境搭建 数据字典 服务端 前端 数据模型 页面原…...

「数据可视化 D3系列」入门第三章:深入理解 Update-Enter-Exit 模式

深入理解 Update-Enter-Exit 模式 一、数据绑定三态&#xff1a;Update、Enter、Exit三种状态的直观理解 二、基础概念1. Update 选区 - 处理已有元素2. Enter 选区 - 处理新增数据3. Exit 选区 - 处理多余元素 三、完整工作流程四、三种状态的底层原理数据绑定过程解析键函数&…...

中间件--ClickHouse-5--架构设计(分布式架构,列式压缩存储、并行计算)

1、整体架构设计 ClickHouse 采用MPP&#xff08;大规模并行处理&#xff09;架构&#xff0c;支持分布式计算和存储&#xff0c;其核心设计目标是高性能列式分析。 &#xff08;1&#xff09;、存储层 列式存储&#xff1a; 数据按列存储&#xff08;而非传统行式存储&#…...

AgentGPT 在浏览器中组装、配置和部署自主 AI 代理 入门介绍

AI MCP 系列 AgentGPT-01-入门介绍 Browser-use 是连接你的AI代理与浏览器的最简单方式 AI MCP(大模型上下文)-01-入门介绍 AI MCP(大模型上下文)-02-awesome-mcp-servers 精选的 MCP 服务器 AI MCP(大模型上下文)-03-open webui 介绍 是一个可扩展、功能丰富且用户友好的…...

【开源项目】Excel手撕AI算法深入理解(三):Backpropagation、mamba、RNN

项目源码地址&#xff1a;https://github.com/ImagineAILab/ai-by-hand-excel.git 一、Backpropagation 1. 反向传播的本质 反向传播是通过链式法则计算损失函数对网络参数的梯度的高效算法&#xff0c;目的是用梯度下降优化参数。其核心思想是&#xff1a; 前向传播&#xf…...

uniapp的通用页面及组件基本封装

1.基本布局页面 适用于自定义Navbar头部 <template><view class"bar" :style"{height : systemInfo.statusBarHeight px, background: param.barBgColor }"></view><view class"headBox" :style"{ height: param.h…...

Ubuntu和Debian 操作系统的同与异

首先需要说明&#xff1a;Ubuntu 是基于 Debian 操作系统开发的。它们之间的关系如下 起源与发展&#xff1a;Debian 是一个社区驱动的开源 Linux 发行版&#xff0c;始于 1993 年&#xff0c;是最早的 Linux 发行版之一&#xff0c;以其稳定性和自由软件政策著称。Ubuntu 是基…...

【android bluetooth 协议分析 21】【ble 介绍 1】【什么是RPA】

通俗易懂地讲解一下 BLE&#xff08;低功耗蓝牙&#xff09;中的 Resolvable Private Address&#xff08;RPA&#xff0c;可解析私有地址&#xff09;。 1. 一句话理解 RPA 是一种“临时的、隐私保护的蓝牙设备地址”&#xff0c;别人无法随便追踪你&#xff0c;但“授权的设…...

狂神SQL学习笔记九:MyISAM 和 lnnoDB 区别

show create database school –查看创建数据库的语句 show create table student – 查看student数据表的定义语句 desc student –显示表的结构 MYISAMINNODB事务支持不支持支持数据行锁定不支持支持行锁定外键不支持支持全文索引支持不支持表空间的大小较小较大&#x…...

深度学习--神经网络的构造

在当今数字化时代&#xff0c;深度学习已然成为人工智能领域中最为耀眼的明星。而神经网络作为深度学习的核心架构&#xff0c;其构造方式决定了模型的性能与应用效果。本文将深入探讨深度学习神经网络的构造&#xff0c;带您领略这一前沿技术的奥秘。​ 一、神经网络基础概念…...

Jenkins 代理自动化-dotnet程序

两种方式 容器部署 本地部署 容器部署 可自动实现&#xff0c;服务器重启&#xff0c;容器自动运行 主要将dockerfile 写好 本地部署 1.服务器重启自动运行代理 参考下面的链接&#xff0c;只是把程序换成 java程序&#xff0c;提前确认好需要的jdk版本 Ubuntu20.04 设置开机…...

【区块链+ 人才服务】“CERX Network”——基于 FISCO BCOS 的研学资源交换网络 | FISCO BCOS 应用案例

CERX Network &#xff08;Consortium-based Education Resource Exchanging Network&#xff09; 是定位于面向高校科学研究与教学 的分布式研学资产交换网络&#xff0c; 构建一个用于数据、 算法模型、 论文和课程的研学资源价值流转平台。项目以 FISCO BCOS 联盟链为底层平…...

中间件--ClickHouse-6--SQL基础(类似Mysql,存在差异)

ClickHouse语言类似Mysql&#xff0c;如果熟悉Mysql&#xff0c;那么学习ClickHouse的语言还是比较容易上手的。 1、建表语法&#xff08;CREATE TABLE&#xff09; &#xff08;1&#xff09;、表引擎&#xff08;Engine&#xff09; MySQL&#xff1a; 默认使用 InnoDB 引…...

[MSPM0开发]MSPM0G3507番外一:关于使用外部高速晶振HFXT后程序可能不运行的问题

一、问题描述 如下图所示&#xff0c;MSPM0G3507时钟树配置为使用外部HFXT&#xff08;外部高速晶振&#xff09;作为HSCLK时钟源。 配置结果MCLK 40MHz。 另外配置PB22为输出模式&#xff0c;控制外部LED亮灭。 在main.c中主要代码如下&#xff1a; 主要完成延时并翻转LED控…...

2025年计算机领域重大技术突破与行业动态综述

——前沿技术重塑未来&#xff0c;开发者如何把握机遇&#xff1f; 2025年第一季度&#xff0c;全球计算机领域迎来多项里程碑式进展&#xff0c;从量子计算到人工智能&#xff0c;从芯片设计到网络安全&#xff0c;技术革新与产业融合持续加速。本文梳理近三个月内最具影响力…...

我的机器学习之路(初稿)

文章目录 一、机器学习定义二、核心三要素三、算法类型详解1. 监督学习&#xff08;带标签数据&#xff09;2. 无监督学习&#xff08;无标签数据&#xff09;3. 强化学习&#xff08;决策优化&#xff09;(我之后主攻的方向) 四、典型应用场景五、学习路线图六、常见误区警示七…...

交易模式革新:Eagle Trader APP上线,助力自营交易考试效率提升

近年来&#xff0c;金融行业随着投资者需求的日益多样化&#xff0c;衍生出了众多不同的交易方式。例如&#xff0c;为了帮助新手小白建立交易基础&#xff0c;诞生了各类跟单社区&#xff1b;而与此同时&#xff0c;一种备受瞩目的交易方式 —— 自营交易模式&#xff0c;正吸…...

emotn ui桌面tv版官网-emotn ui桌面使用教程

在智能电视和盒子的使用中&#xff0c;出色的桌面系统能大幅提升体验&#xff0c;Emotn UI桌面TV版便是其中的佼佼者。 访问Emotn UI桌面TV版官网&#xff0c;首页简洁清晰&#xff0c;“产品介绍”“下载中心”等板块一目了然。官网对其功能优势详细阐述&#xff0c;在“下载中…...

Django之modelform使用

Django新增修改数据功能优化 目录 1.新增数据功能优化 2.修改数据功能优化 在我们做数据优化处理之前, 我们先回顾下传统的写法, 是如何实现增加修改的。 我们需要在templates里面新建前端的页面, 需要有新增还要删除, 比如说员工数据的新增, 那需要有很多个输入框, 那html…...

Hadoop:大数据时代的基石

在当今数字化浪潮中&#xff0c;数据量呈爆炸式增长&#xff0c;企业和组织面临着前所未有的数据处理挑战。从社交媒体的海量信息到物联网设备的实时数据&#xff0c;如何高效地存储、管理和分析这些数据成为了一个关键问题。Apache Hadoop 作为大数据处理领域的核心框架&#…...

定制开发还是源码搭建?如何快速上线同城外卖跑腿APP?

在“万物皆可同城配送”的时代&#xff0c;同城外卖跑腿APP成为众多创业者和本地服务商的热门选择。无论是打造本地生活服务平台&#xff0c;还是拓展快送业务&#xff0c;拥有一款功能完善、体验流畅的外卖跑腿APP&#xff0c;已经成为进入市场的标配。 然而&#xff0c;对于…...

How AI could empower any business - Andrew Ng

How AI could empower any business - Andrew Ng References 人工智能如何为任何业务提供支持 empower /ɪmˈpaʊə(r)/ vt. 授权&#xff1b;给 (某人) ...的权力&#xff1b;使控制局势&#xff1b;增加 (某人的) 自主权When I think about the rise of AI, I’m reminded …...

SpringBoot-基础特性

1.SpringApplication 1.1.自定义banner 类路径添加banner.txt或设置spring.banner.location就可以定制 banner 1.2.自定义 SpringApplication import org.springframework.boot.Banner; import org.springframework.boot.SpringApplication; import org.springframework.bo…...

系统环境变量有什么实际作用,为什么要配置它

系统环境变量有什么实际作用,为什么要配置它 系统环境变量具有以下重要实际作用: 指定程序路径:操作系统通过环境变量来知晓可执行文件、库文件等的存储位置例如,当你在命令提示符或终端中输入一个命令时,系统会根据环境变量PATH中指定的路径去查找对应的可执行文件。如果…...

C++ | STL之list详解:双向链表的灵活操作与高效实践

引言 std::list 是C STL中基于双向链表实现的顺序容器&#xff0c;擅长高效插入和删除操作&#xff0c;尤其适用于频繁修改中间元素的场景。与std::vector不同&#xff0c;std::list的内存非连续&#xff0c;但提供了稳定的迭代器和灵活的元素管理。本文将全面解析std::list的…...

Spring Cloud 服务间调用深度解析

前言 在构建微服务架构时&#xff0c;服务间的高效通信是至关重要的。Spring Cloud 提供了一套完整的解决方案来实现服务间的调用、负载均衡、服务发现等功能。本文将深入探讨 Spring Cloud 中服务之间的调用机制&#xff0c;并通过源码片段和 Mermaid 图表帮助读者更好地理解…...

什么是时间复杂度和空间复杂度?

什么是时间复杂度和空间复杂度? 时间复杂度:衡量代码运行时间随输入规模增大而增长的速度。简单来说,就是“代码跑多快”。 空间复杂度:衡量代码运行时额外占用的内存空间随输入规模增大而增长的速度。简单来说,就是“代码用多少内存”。 我们通常用 大 O 表示法(Big O N…...

算法思想之分治-快排

欢迎拜访&#xff1a;雾里看山-CSDN博客 本篇主题&#xff1a;算法思想之分治-快排 发布时间&#xff1a;2025.4.15 隶属专栏&#xff1a;算法 目录 算法介绍核心步骤优化策略 例题颜色分类题目链接题目描述算法思路代码实现 排序数组题目链接题目描述算法思路代码实现 数组中的…...

25.4.15学习总结

问题&#xff1a; 邮箱验证码通过公钥加密后发到前端&#xff0c;在前端用私钥解密验证可行吗&#xff1f; 结论&#xff1a; 在前端使用私钥解密通过公钥加密的邮箱验证码在技术上是可行的&#xff0c;但存在严重的安全风险&#xff0c;不建议采用。 问题分析 非对称加密的…...

小程序获取用户总结(全)

获取方式 目前小程序获取用户一共有3中(自己接触到的),但由于这个API一直在改,所以不确定后期是否有变动,还是要多关注官方公告。 方式一 使用wx.getUserInfo 实例: wxml 文件<button open-type="getUserInfo" bindgetuserinfo="onGetUserInfo&quo…...

如何成为一名嵌入式软件工程师?

如何成为一名嵌入式软件工程师&#xff1f; 01明确岗位的角色与定位 嵌入式软件工程师主要负责开发运行在特定硬件平台上的软件&#xff0c;这些软件通常与硬件紧密集成&#xff0c;以实现特定的功能。 不仅需要精通编程语言&#xff08;如C/C、Java等&#xff09;和软件开发工…...

机器人发展未来两年会有突破吗?

未来两年,机器人技术将在芯片、编码器、材料、加工工艺和AI等核心领域迎来系统性突破,推动行业从专用化向通用化转型。以下从技术路径、产业动态和商业化前景三个维度展开分析,结合权威数据与技术趋势,构建机器人技术演进的全景框架。 一、芯片技术:3nm制程与存算一体架构…...

【grafana原生告警中心配置飞书机器人告警】

在grafana中的connect point中使用webhook的方式推送到飞书&#xff0c;始终无法触发告警&#xff0c;原因是grafana推送的格式飞书不识别&#xff0c;现有两种方式 1.使用中转服务 使用flask搭建一个服务&#xff0c;grafana告警先通过webhook发送到web服务中&#xff0c;格…...

HTTP HTTPS RSA

推荐阅读 小林coding HTTP篇 文章目录 HTTP 80HTTP 响应码1xx&#xff1a;信息性状态码&#xff08;Informational&#xff09;2xx&#xff1a;成功状态码&#xff08;Success&#xff09;3xx&#xff1a;重定向状态码&#xff08;Redirection&#xff09;4xx&#xff1a;客户端…...

【机器学习】如何正确下载sklearn包

TOC 直接pip install sklearn时&#xff0c;报错 sklearn的包&#xff0c;实际上叫scikit-learn pip install scikit-learn发现成功了&#xff1a; 总结 下载sklearn包的语句&#xff1a;pip install scikit-learn 完成。...

【Python进阶】断言(assert)的十大核心应用场景解析

目录 前言&#xff1a;技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键技术模块技术选型对比 二、实战演示环境配置要求核心代码实现&#xff08;10个案例&#xff09;案例1&#xff1a;参数合法性检查案例2&#xff1a;不变…...

关于汽车辅助驾驶不同等级、技术对比、传感器差异及未来发展方向的详细分析

以下是关于汽车辅助驾驶不同等级、技术对比、传感器差异及未来发展方向的详细分析&#xff1a; 一、汽车辅助驾驶等级详解 根据SAE&#xff08;国际自动机工程师学会&#xff09;的标准&#xff0c;自动驾驶分为 L0到L5 六个等级&#xff1a; 1. L0&#xff08;无自动化&…...

STM32 HAL库之WDG示例代码

独立看门狗&#xff08;IWDG&#xff09; 初始化独立看门狗&#xff0c;在main.c中的 MX_IWDG_Init();&#xff0c;也就是iwdg.c中的初始化代码 void MX_IWDG_Init(void) {/* USER CODE BEGIN IWDG_Init 0 *//* USER CODE END IWDG_Init 0 *//* USER CODE BEGIN IWDG_Init 1 …...

【差分隐私相关概念】瑞丽差分隐私(RDP)命题10

命题10证明中的最后一个不等号成立&#xff0c;关键在于将事件 A A A上的积分与Rnyi散度 D α ( P ∥ Q ) D_\alpha(P \parallel Q) Dα​(P∥Q)的定义联系起来&#xff0c;并通过积分放缩得到上界。具体推导如下&#xff1a; Rnyi散度的定义&#xff1a; D α ( P ∥ Q ) 1 …...

Android 开发 如何生成系统签名

在源码中拿到安全文件 文件路径 lagvm/LINUX/android/build/target/product/security如下两个文件 platform.pk8 platform.x509.pem 使用Android studio生成一个jks Android studio 顶部 buildGenerate Signed Bundle or APKapkcrate new记住 记住alias 和password linux下…...

(EtherCAT 转 EtherNet/IP)EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

型号 协议转换通信网关 EtherCAT 转 EtherNet/IP MS-GW12 概述 MS-GW12 是 EtherCAT 和 EtherNet/IP 协议转换网关&#xff0c;为用户提供两种不同通讯协议的 PLC 进行数据交互的解决方案&#xff0c;可以轻松容易将 EtherNet/IP 网络接入 EtherCAT 网络中&#xff0c;方便…...

适合stm32 前端adc使用的放大器芯片

在 STM32 前端 ADC 应用中&#xff0c;合适的放大器芯片需具备低噪声、高精度、低失调电压等特性。以下为你推荐几款常用的放大器芯片&#xff1a; 低功耗、高精度型 OPA2333 特点&#xff1a;这是一款微功耗、零漂移运算放大器&#xff0c;失调电压极低&#xff0c;仅为 2.5…...

《Ethical Implications of ChatGPT in Higher Education: A Scoping Review》全文翻译

《Ethical Implications of ChatGPT in Higher Education: A Scoping Review》 ChatGPT在高等教育中的伦理影响&#xff1a;一项范围界定性综述 摘要 本范围界定性综述探讨了在高等教育中使用ChatGPT所引发的伦理挑战。通过回顾近期发表的英文、中文和日文的学术文章&#x…...

day26 学习笔记

文章目录 前言一、边缘填充1.边界复制2.边界反射3.边界常数4.边界包裹5.代码示例 二、透视变换三、颜色加法 前言 通过今天的学习&#xff0c;我掌握了OpenCV中有关边缘填充&#xff0c;透视变换以及颜色加法的相关概念和操作 一、边缘填充 当我们对图像进行仿射变换后往往会发…...

LVGL Animation Image(Animimg)控件详解

一、Animation Image&#xff08;Animimg&#xff09;控件详解 1. 概述 功能&#xff1a;Animimg 是 LVGL 中用于显示动画图像的控件。特点&#xff1a;支持从多个静态图像创建动画效果。 2. 创建和初始化 创建方法&#xff1a;lv_obj_t * lv_animimg_create(lv_obj_t * pa…...

【unity游戏开发入门到精通——UGUI】GraphicRaycaster图形射线投射器组件

注意&#xff1a;考虑到UGUI的内容比较多&#xff0c;我将UGUI的内容分开&#xff0c;并全部整合放在【unity游戏开发——UGUI】专栏里&#xff0c;感兴趣的小伙伴可以前往逐一查看学习。 文章目录 前言Graphic Raycaster参数1、Ignore Reversed Graphics&#xff1a;是否忽略反…...

WPF GDI 画 晶圆Mapping图

效果图 UI代码 <Window x:Class="WpfWaferMapping.Window3"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expre…...