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

【Bluedroid】A2DP SINK播放流程源码分析

在Bluedroid协议栈中,A2DP(Advanced Audio Distribution Profile)SINK播放流程是一个复杂但有序的过程,它涉及多个层次和组件的交互。

一、概述

1.1. 初始化流程

在A2DP SINK播放之前,系统需要进行一系列初始化操作,以确保A2DP SINK服务能够正确运行。这些操作包括启动A2DP SINK服务、注册回调函数、初始化蓝牙接口等。

1.  启动A2DP SINK服务:

在Android系统中,A2DP SINK服务通常由A2dpSinkService类来启动和管理。这个服务在系统启动时或用户请求时启动。

  • 启动服务:当系统启动或用户请求启动A2DP SINK服务时,系统会调用A2dpSinkService类的start()方法。这个方法会负责创建并启动A2DP SINK服务的相关组件。

  • 创建状态机:在start()方法中,会调用A2dpSinkStateMachine.make()方法来创建一个A2DP SINK状态机实例。这个状态机用于管理A2DP SINK服务的各种状态转换,如连接状态、播放状态等。

  • 启动状态机:创建状态机后,需要调用其start()方法来启动状态机,使其开始监控和处理A2DP SINK服务的各种事件。

2.  注册回调函数:

在初始化过程中,需要注册一系列回调函数,以便在接收到蓝牙事件时能够及时处理。

  • 回调函数类型:这些回调函数通常包括连接状态改变回调、音频状态改变回调等。连接状态改变回调用于处理A2DP连接的建立和断开事件,音频状态改变回调用于处理音频数据的接收和播放状态变化事件。

  • 注册方法:这些回调函数会在classInitNative()initNative()等本地方法中注册到蓝牙协议栈中。这些方法通常是在Java层调用本地(Native)层代码时实现的,用于将Java层的回调函数指针传递给本地层的蓝牙协议栈。

3. 初始化蓝牙接口:

在初始化过程中,还需要获取蓝牙接口,并初始化A2DP SINK相关的蓝牙接口和回调函数。

  • 获取蓝牙接口:通过调用getBluetoothInterface()方法,可以获取到蓝牙系统的接口指针。这个接口指针提供了访问蓝牙系统各种服务和功能的方法。

  • 初始化A2DP SINK接口:通过调用get_profile_interface()等方法,可以获取到A2DP SINK相关的蓝牙接口指针。这些接口提供了A2DP SINK服务所需的各种方法和功能,如连接管理、音频数据传输等。

  • 设置回调函数:在获取到A2DP SINK接口后,需要设置一系列回调函数,以便在接收到蓝牙事件时能够及时处理。这些回调函数通常是在A2DP SINK接口初始化时设置的,用于处理连接状态变化、音频数据传输等事件。

4. 其他初始化操作:

除了上述操作外,A2DP SINK初始化流程还可能包括其他操作,如配置音频解码器、初始化音频播放系统等。这些操作的具体实现取决于Android系统的版本和配置,以及A2DP SINK服务的具体实现方式。

A2DP SINK初始化流程是一个复杂而有序的过程,涉及多个组件和层次的交互。通过正确执行这些初始化操作,可以确保A2DP SINK服务能够正常启动并运行,从而为用户提供高质量的音频播放体验。

1.2. 连接流程

在A2DP SINK设备初始化完成后,需要与A2DP SOURCE设备进行配对和连接。这个过程通常包括以下几个详细步骤:

1. 搜索设备:

  • 启用蓝牙功能:用户需要在A2DP SINK设备上启用蓝牙功能,通常通过设备的设置菜单或快捷按钮来完成。

  • 搜索蓝牙设备:一旦蓝牙功能被启用,A2DP SINK设备将开始搜索附近的蓝牙设备。这个过程可能涉及扫描周围的蓝牙信号,并列出所有可用的蓝牙设备。

2. 配对设备:

  • 选择目标设备:在搜索到的蓝牙设备列表中,用户需要选择要与A2DP SINK设备配对的A2DP SOURCE设备。

  • 输入配对码:配对过程通常需要输入一个配对码(如PIN码或密码)。这个配对码用于确保两个设备之间的安全连接。用户需要在A2DP SINK设备上输入在A2DP SOURCE设备上显示的配对码,或者相反。

  • 确认配对:一旦配对码被输入,两个设备将进行配对确认。如果配对成功,它们之间就可以建立蓝牙连接了。

3. 建立A2DP连接:

  • 发起连接请求:在配对成功后,A2DP SINK设备需要向A2DP SOURCE设备发起A2DP连接请求。这个请求通常是通过AVDTP(Audio/Video Distribution Transport Protocol)协议来发送的。AVDTP是一个用于在蓝牙设备之间传输音视频数据的协议。

  • 协商配置:在连接请求被接受之前,两个设备可能需要协商一些连接参数,如音频编解码器、采样率、通道模式等。这些参数的选择通常基于两个设备共同支持的功能和用户需求。

  • 建立连接:一旦协商完成,并且A2DP SOURCE设备接受了连接请求,两个设备之间就会建立A2DP连接。这个连接将用于传输音频数据。

4. 开始音频传输:

  • 数据传输:一旦A2DP连接建立,A2DP SOURCE设备就可以开始将音频数据通过A2DP连接传输到A2DP SINK设备了。这个过程可能涉及音频数据的编码、封装和传输。

  • 解码和播放:A2DP SINK设备在接收到音频数据后,需要对其进行解码和播放。解码过程可能涉及将音频数据从编码格式转换为PCM(Pulse Code Modulation)或其他适合播放的格式。播放过程则涉及将解码后的音频数据传递给音频播放系统,如音频输出设备或扬声器。

5. 连接管理:

  • 监控连接状态:在A2DP连接建立后,A2DP SINK设备需要持续监控连接状态,以确保连接的稳定性和可靠性。如果连接断开或出现问题,A2DP SINK设备可能需要重新发起连接请求或采取其他恢复措施。

  • 处理连接事件:A2DP SINK设备还需要处理各种连接事件,如连接请求、连接断开、连接参数变化等。这些事件可能需要通过回调函数或其他机制来处理和响应。

A2DP SINK与A2DP SOURCE设备的连接流程是一个复杂而有序的过程,涉及多个步骤和组件的交互。通过正确执行这些步骤,可以确保两个设备之间能够建立稳定可靠的A2DP连接,从而为用户提供高质量的音频播放体验。

1.3. 播放流程

在A2DP连接建立后,A2DP SINK设备就可以开始接收并播放来自A2DP SOURCE设备的音频数据了。这个过程通常包括以下几个步骤:

1. 接收音频数据:

  • AVDTP协议传输:A2DP SINK设备会通过AVDTP协议接收来自A2DP SOURCE设备的音频数据。这些数据是以音频帧的形式进行传输的,每个音频帧都包含了编码后的音频数据以及相关的元数据(如时间戳、序列号等)。

  • 数据缓冲:为了确保音频数据的连续性和稳定性,A2DP SINK设备通常会在接收端设置一个数据缓冲区。这个缓冲区用于暂存接收到的音频数据,以便在解码和播放过程中进行平滑处理。

2. 解码音频数据:

  • 解码器处理:在接收到音频数据后,A2DP SINK设备会将其传递给蓝牙协议栈中的解码器进行处理。解码器会根据音频数据的编码格式(如SBC、AAC、aptX等)进行解码操作,将编码后的音频数据转换为PCM(Pulse Code Modulation)格式或其他适合播放的格式。

  • 格式转换:解码后的音频数据可能需要进行格式转换,以符合音频播放系统的输入要求。例如,如果音频播放系统要求输入PCM格式的音频数据,而解码器输出的是其他格式的音频数据,那么就需要进行格式转换操作。

3. 播放音频数据:

  • 音频播放系统:解码后的音频数据会被传递给音频播放系统(如Android的AudioFlinger)进行播放。音频播放系统负责将音频数据转换为模拟信号,并输出到扬声器或其他音频输出设备。

  • 播放控制:在播放过程中,A2DP SINK设备可以通过音频播放系统提供的API或控制接口来实现对播放过程的控制。这些控制操作包括音量调节、播放/暂停、停止、快进/快退等。

  • 同步处理:为了确保音频数据的同步播放,A2DP SINK设备可能需要与A2DP SOURCE设备进行时间同步操作。这通常涉及到音频时钟的同步、音频帧的同步等。

4. 监控和管理:

  • 连接状态监控:在播放过程中,A2DP SINK设备需要持续监控A2DP连接的状态。如果连接断开或出现问题,A2DP SINK设备需要采取相应的恢复措施或重新发起连接请求。

  • 错误处理:在播放过程中,可能会遇到各种错误情况,如音频数据丢失、解码失败等。A2DP SINK设备需要能够检测并处理这些错误情况,以确保播放过程的稳定性和可靠性。

A2DP SINK设备的播放流程是一个复杂而有序的过程,涉及多个步骤和组件的交互。通过正确执行这些步骤,可以确保A2DP SINK设备能够稳定、可靠地接收并播放来自A2DP SOURCE设备的音频数据。

1.4. 断开连接和清理资源

在播放完成后或用户请求断开连接时,A2DP SINK设备需要与A2DP SOURCE设备断开连接,并清理相关资源。这个过程通常包括以下几个步骤:

1. 发送断开连接请求:

  • AVDTP协议发送:A2DP SINK设备通过AVDTP协议向A2DP SOURCE设备发送断开连接请求。这个请求是通知A2DP SOURCE设备,A2DP SINK设备即将结束音频数据的接收和播放,并请求断开两者之间的A2DP连接。

  • 等待确认:在发送断开连接请求后,A2DP SINK设备需要等待A2DP SOURCE设备的确认响应。这个响应表示A2DP SOURCE设备已经接收到断开连接请求,并同意断开连接。

2. 释放资源:

  • 蓝牙接口释放:一旦确认断开连接,A2DP SINK设备需要释放与蓝牙连接相关的资源。这包括关闭蓝牙接口、释放蓝牙连接所占用的内存和处理器资源等。

  • 音频播放系统释放:同时,A2DP SINK设备还需要释放与音频播放系统相关的资源。这包括停止音频播放、释放音频缓冲区、关闭音频解码器等。

  • 其他资源清理:除了蓝牙接口和音频播放系统外,A2DP SINK设备还需要清理其他与连接相关的资源,如网络连接(如果使用了网络传输辅助蓝牙连接)、定时器等。

3. 更新状态:

  • 状态机更新:在释放资源后,A2DP SINK设备需要更新其状态机中的状态。这包括将连接状态从“已连接”更改为“未连接”,并清除与连接相关的任何临时数据或状态信息。

  • 用户界面更新:如果A2DP SINK设备具有用户界面(如显示屏或指示灯),则还需要更新用户界面以反映当前已经断开连接的事实。例如,可以显示一个断开连接的提示信息或熄灭指示灯。

4. 后续处理:

  • 错误处理:如果在断开连接过程中遇到任何错误情况(如无法发送断开连接请求、无法接收到确认响应等),A2DP SINK设备需要能够检测并处理这些错误情况。这可能包括重试断开连接请求、记录错误信息以供后续分析或采取其他适当的恢复措施。

  • 日志记录:为了跟踪和诊断问题,A2DP SINK设备还可以在断开连接和清理资源的过程中记录相关的日志信息。这些日志信息可以包括断开连接的时间、原因、释放的资源类型等。

A2DP SINK设备与A2DP SOURCE设备断开连接和清理资源的过程是一个重要的步骤,它确保了资源的有效利用和系统的稳定性。通过正确执行这些步骤,可以确保A2DP SINK设备在断开连接后能够恢复到初始状态,并准备进行下一次连接和播放操作。

A2DP SINK播放流程是一个复杂但有序的过程,它涉及多个层次和组件的交互。在源码分析过程中,需要仔细理解每个步骤的实现原理和细节,以确保能够正确地实现A2DP SINK播放功能。

二、源码分析

在Bluedroid协议栈中,作为A2DPSINK(即音频接收端),设备能够接收来自A2DP源(如手机或音乐播放器)的音频流。本文从处理AV流相关事件的处理函数bta_av_proc_stream_evt开始的A2DPSINK播放流程源码分析。

2.1.bta_av_proc_stream_evt(AVDT_START_IND_EVT)

bta_av_proc_stream_evt 函数作为整个 A2DP SINK 播放流程中处理 AV 流相关事件的关键入口点。它接收各种类型的事件以及相关的数据信息作为参数,然后依据不同的事件类型进行不同的处理分支判断,就像是一个调度中心,将不同的事件导向对应的具体处理逻辑,以此来驱动整个 A2DP SINK 播放流程的运转,确保在不同的事件触发场景下(比如音频流开始传输、暂停、恢复、结束等情况),系统能够做出正确的响应并维持音频播放的正常秩序。

packages/modules/Bluetooth/system/bta/av/bta_av_aact.cc
/********************************************************************************* Function         bta_av_proc_stream_evt** Description      Utility function to compose stream events.** Returns          void*******************************************************************************/
void bta_av_proc_stream_evt(uint8_t handle, const RawAddress& bd_addr,uint8_t event, tAVDT_CTRL* p_data,uint8_t scb_index) {CHECK_LT(scb_index, BTA_AV_NUM_STRS); //确保索引在有效范围内tBTA_AV_SCB* p_scb = bta_av_cb.p_scb[scb_index];uint16_t sec_len = 0;log::verbose("peer_address: {} avdt_handle: {} event=0x{:x} scb_index={} p_scb={}",ADDRESS_TO_LOGGABLE_CSTR(bd_addr), handle, event, scb_index,fmt::ptr(p_scb));if (p_data) {// 安全数据的长度sec_len,确保它不超过BTA_AV_SECURITY_MAX_LEN(400)if (event == AVDT_SECURITY_IND_EVT) {sec_len = (p_data->security_ind.len < BTA_AV_SECURITY_MAX_LEN)? p_data->security_ind.len: BTA_AV_SECURITY_MAX_LEN;} else if (event == AVDT_SECURITY_CFM_EVT && p_data->hdr.err_code == 0) {sec_len = (p_data->security_cfm.len < BTA_AV_SECURITY_MAX_LEN)? p_data->security_cfm.len: BTA_AV_SECURITY_MAX_LEN;}}if (p_scb) {tBTA_AV_STR_MSG* p_msg =(tBTA_AV_STR_MSG*)osi_malloc(sizeof(tBTA_AV_STR_MSG) + sec_len);/* copy event data, bd addr, and handle to event message buffer */p_msg->hdr.offset = 0;p_msg->bd_addr = bd_addr;p_msg->scb_index = scb_index;log::verbose("stream event bd_addr: {} scb_index: {}",ADDRESS_TO_LOGGABLE_CSTR(p_msg->bd_addr), scb_index);if (p_data != NULL) {memcpy(&p_msg->msg, p_data, sizeof(tAVDT_CTRL));/* copy config params to event message buffer */switch (event) {case AVDT_CONFIG_IND_EVT:p_msg->cfg = *p_data->config_ind.p_cfg;break;case AVDT_SECURITY_IND_EVT:p_msg->msg.security_ind.p_data = (uint8_t*)(p_msg + 1);memcpy(p_msg->msg.security_ind.p_data, p_data->security_ind.p_data,sec_len);break;case AVDT_SECURITY_CFM_EVT:p_msg->msg.security_cfm.p_data = (uint8_t*)(p_msg + 1);if (p_data->hdr.err_code == 0) {memcpy(p_msg->msg.security_cfm.p_data, p_data->security_cfm.p_data,sec_len);}break;case AVDT_SUSPEND_IND_EVT:p_msg->msg.hdr.err_code = 0;break;case AVDT_CONNECT_IND_EVT:p_scb->recfg_sup = true;p_scb->suspend_sup = true;break;default:break;}} else {p_msg->msg.hdr.err_code = 0;}/* look up application event */if ((p_data == NULL) || (p_data->hdr.err_code == 0)) {p_msg->hdr.event = bta_av_stream_evt_ok[event];} else {p_msg->hdr.event = bta_av_stream_evt_fail[event];}p_msg->initiator = false;if (event == AVDT_SUSPEND_CFM_EVT) p_msg->initiator = true;log::verbose("bta_handle:0x{:x} avdt_handle:{}", p_scb->hndl, handle);p_msg->hdr.layer_specific = p_scb->hndl;p_msg->handle = handle;p_msg->avdt_event = event;bta_sys_sendmsg(p_msg);}if (p_data) {bta_av_conn_cback(handle, bd_addr, event, p_data, scb_index);} else {log::error("p_data is null");}
}

bta_av_proc_stream_evt 的主要作用是处理与音频 / 视频(AV)流相关的事件。它通过接收事件相关的各种参数,如句柄、设备地址、事件类型等,根据不同的事件情况进行相应的数据处理和消息构造,最后将构造好的消息发送出去,并在某些情况下调用相关的回调函数,以实现对 AV 流事件的完整处理流程。

  • 消息分配与填充
    • 如果p_scb非空,分配一个tBTA_AV_STR_MSG结构体,其大小等于结构体本身加上之前计算的安全数据长度sec_len

    • 接着,将事件数据、远程设备地址、流句柄和SCB索引复制到新分配的消息缓冲区中。

    • 根据事件类型,将p_data中的相关数据复制到消息缓冲区中的相应位置。

  • 事件处理
    • 根据p_data是否为空以及p_data->hdr.err_code的值,设置消息头中的事件类型。如果p_data为空或错误代码为0,则使用成功事件;否则,使用失败事件。

    • 对于挂起确认(AVDT_SUSPEND_CFM_EVT)事件,将消息的发起者标志设置为true

  • 消息发送:设置消息头中的层特定信息和句柄,然后调用bta_sys_sendmsg函数发送消息。
  • 回调函数调用
    • 如果p_data非空,则调用bta_av_conn_cback函数,传入相关参数。这个函数用于通知应用程序或进行其他连接相关的处理。

    • 如果p_data为空,则记录一个错误日志。

通过这个函数,bluedroid能够响应来自远程设备的AVDTP请求,如配置、安全指示、安全确认、挂起指示等,并维护流的状态和配置。 

1. bta_sys_sendmsg 

packages/modules/Bluetooth/system/bta/sys/bta_sys_main.cc
/********************************************************************************* Function         bta_sys_sendmsg** Description      Send a GKI message to BTA.  This function is designed to*                  optimize sending of messages to BTA.  It is called by BTA*                  API functions and call-in functions.**                  TODO (apanicke): Add location object as parameter for easier*                  future debugging when doing alarm refactor*** Returns          void*******************************************************************************/
void bta_sys_sendmsg(void* p_msg) {if (do_in_main_thread(FROM_HERE,base::BindOnce(&bta_sys_event, static_cast<BT_HDR_RIGID*>(p_msg))) !=BT_STATUS_SUCCESS) {log::error("do_in_main_thread failed");}
}

bta_sys_sendmsg 的主要作用是将一个 GKI(General Kernel Interface,特定的内核接口相关)消息发送给 BTA(Bluetooth Application Layer)。是为了优化向 BTA 发送消息的过程而设计的,通常被 BTA 的 API 函数和回调函数调用。

2. bta_sys_event

packages/modules/Bluetooth/system/bta/sys/bta_sys_main.cc
/********************************************************************************* Function         bta_sys_event** Description      BTA event handler; called from task event handler.*** Returns          void*******************************************************************************/
static void bta_sys_event(BT_HDR_RIGID* p_msg) {bool freebuf = true;log::verbose("Event 0x{:x}", p_msg->event);/* get subsystem id from event */uint8_t id = (uint8_t)(p_msg->event >> 8);/* verify id and call subsystem event handler */if ((id < BTA_ID_MAX) && (bta_sys_cb.reg[id] != NULL)) {freebuf = (*bta_sys_cb.reg[id]->evt_hdlr)(p_msg);} else {log::info("Ignoring receipt of unregistered event id:{}[{}]",BtaIdSysText(static_cast<tBTA_SYS_ID>(id)), id);}if (freebuf) {osi_free(p_msg);}
}

bta_sys_event 是一个 BTA的事件处理函数,它会在任务事件处理程序中被调用,主要负责接收并处理传入的事件消息。通过对事件消息的分析,确定所属子系统,然后调用相应子系统的事件处理程序进行处理,最后根据处理结果决定是否释放事件消息所占用的内存。

  • 获取子系统 ID:通过 uint8_t id = (uint8_t)(p_msg->event >> 8); 从事件的 event 成员值中提取出子系统 ID。将事件值右移 8 位并转换为 uint8_t 类型,得到的结果即为子系统 ID,用于后续确定该事件所属的子系统。
  • 调用相应子系统的事件处理程序:通过 if ((id < BTA_ID_MAX) && (bta_sys_cb.reg[id]!= NULL)) 条件判断来检查提取出的子系统 ID 是否在有效范围内(小于 BTA_ID_MAX)并且对应的子系统注册信息(bta_sys_cb.reg[id])是否不为空。如果这两个条件都满足,说明该子系统已注册且当前事件属于该子系统的处理范围:
    • bta_sys_cb 是一个全局的回调函数数组,用于存储不同子系统的事件处理器指针。
    • 此时调用 (*bta_sys_cb.reg[id]->evt_hdlr)(p_msg) 函数,通过注册信息中的事件处理程序指针(evt_hdlr)来调用相应子系统的事件处理程序,并将事件消息指针 p_msg 传入,以便该子系统的事件处理程序能够根据事件消息中的具体信息进行相应的处理操作。将该调用的返回值赋给 freebuf 变量,用于后续判断是否释放事件消息的内存。
  • 处理未注册事件的情况:如果不满足上述条件,即子系统 ID 不在有效范围内或者对应的子系统注册信息为空,说明接收到的是一个未注册的事件:
    • 进行日志记录,然后直接返回,不再进行后续的针对该事件的特定处理操作,因为未注册事件可能不需要或者无法进行进一步的常规处理。
  • 根据处理结果释放内存:如果 freebuf 为 true,则调用 osi_free 函数释放消息缓冲区 p_msg

3. bta_av_hdl_event

packages/modules/Bluetooth/system/bta/av/bta_av_main.cc
/********************************************************************************* Function         bta_av_hdl_event** Description      Advanced audio/video main event handling function.*** Returns          bool*******************************************************************************/
bool bta_av_hdl_event(const BT_HDR_RIGID* p_msg) {if (p_msg->event > BTA_AV_LAST_EVT) {return true; /* to free p_msg */}if (p_msg->event >= BTA_AV_FIRST_NSM_EVT) {log::verbose("AV nsm event=0x{:x}({})", p_msg->event,bta_av_evt_code(p_msg->event));bta_av_non_state_machine_event(p_msg->event, (tBTA_AV_DATA*)p_msg);} else if (p_msg->event >= BTA_AV_FIRST_SM_EVT &&p_msg->event <= BTA_AV_LAST_SM_EVT) {log::verbose("AV sm event=0x{:x}({})", p_msg->event,bta_av_evt_code(p_msg->event));/* state machine events */bta_av_sm_execute(&bta_av_cb, p_msg->event, (tBTA_AV_DATA*)p_msg);} else {log::verbose("bta_handle=0x{:x}", p_msg->layer_specific);/* stream state machine events */bta_av_ssm_execute(bta_av_hndl_to_scb(p_msg->layer_specific), p_msg->event,(tBTA_AV_DATA*)p_msg);}return true;
}

bta_av_hdl_event 是用于处理高级音频 / 视频(AV)相关事件的主要函数。它根据接收到的事件消息中的事件类型,将事件分发给不同的子处理函数进行针对性处理,最后返回 true,用于指示可以释放相关的消息资源(如内等)。

a. bta_av_ssm_execute
packages/modules/Bluetooth/system/bta/av/bta_av_ssm.cc
/********************************************************************************* Function         bta_av_ssm_execute** Description      Stream state machine event handling function for AV*** Returns          void*******************************************************************************/
void bta_av_ssm_execute(tBTA_AV_SCB* p_scb, uint16_t event,tBTA_AV_DATA* p_data) {if (p_scb == NULL) {/* this stream is not registered */log::verbose("AV channel not registered");return;}bta_av_better_stream_state_machine(p_scb, event, p_data);
}

bta_av_ssm_execute 作为音频 / 视频(AV)流状态机事件的处理函数,主要负责对传入的 AV 流相关的状态机事件进行初步判断,并在确认相关流状态机结构体指针有效后,调用另一个函数来进一步处理具体的流状态机事件以及与之相关的数据,以此推动 AV 流相关状态的转换和处理。 

如果流控制块指针有效,将调用 bta_av_better_stream_state_machine来处理该流状态机事件。

b. bta_av_better_stream_state_machine
packages/modules/Bluetooth/system/bta/av/bta_av_ssm.cc
static void bta_av_better_stream_state_machine(tBTA_AV_SCB* p_scb,uint16_t event,tBTA_AV_DATA* p_data) {uint8_t previous_state = p_scb->state;tBTA_AV_ACT event_handler1 = nullptr;tBTA_AV_ACT event_handler2 = nullptr;switch (p_scb->state) {case BTA_AV_INIT_SST:switch (event) {case BTA_AV_API_OPEN_EVT:p_scb->state = BTA_AV_OPENING_SST;event_handler1 = &bta_av_do_disc_a2dp;break;...case BTA_AV_OPEN_SST:switch (event) {...case BTA_AV_STR_START_OK_EVT:event_handler1 = &bta_av_start_ok;break;...}if (previous_state != p_scb->state) {log::info("peer {} p_scb={:#x}({}) AV event=0x{:x}({}) state={}({}) -> {}({})",ADDRESS_TO_LOGGABLE_CSTR(p_scb->PeerAddress()), p_scb->hndl,fmt::ptr(p_scb), event, bta_av_evt_code(event), previous_state,bta_av_sst_code(previous_state), p_scb->state,bta_av_sst_code(p_scb->state));} else {log::verbose("peer {} p_scb={:#x}({}) AV event=0x{:x}({}) state={}({})",ADDRESS_TO_LOGGABLE_CSTR(p_scb->PeerAddress()), p_scb->hndl,fmt::ptr(p_scb), event, bta_av_evt_code(event), p_scb->state,bta_av_sst_code(p_scb->state));}if (event_handler1 != nullptr) {event_handler1(p_scb, p_data);}if (event_handler2 != nullptr) {event_handler2(p_scb, p_data);}
}

bta_av_better_stream_state_machine 是处理音频 / 视频(AV)流状态机具体逻辑的核心函数。它依据当前流状态机结构体(p_scb)所处的状态以及传入的事件类型(event),通过状态机的方式确定对应的事件处理函数,然后根据状态是否发生变化进行相应的日志记录,最后调用确定好的事件处理函数来执行与 AV 流相关的具体操作,以此推动 AV 流在不同事件下的状态转换和业务处理。 

 调用事件处理函数:如果 event_handler1 不为 nullptr,则调用它来处理事件。

2.2. bta_av_start_ok 

packages/modules/Bluetooth/system/bta/av/bta_av_aact.cc
/********************************************************************************* Function         bta_av_start_ok** Description      Stream started.** Returns          void*******************************************************************************/
void bta_av_start_ok(tBTA_AV_SCB* p_scb, tBTA_AV_DATA* p_data) {bool initiator = false;bool suspend = false;uint8_t new_role = p_scb->role;BT_HDR_RIGID hdr;tHCI_ROLE cur_role;uint8_t local_tsep = p_scb->seps[p_scb->sep_idx].tsep;log::info("peer {} bta_handle:0x{:x} wait:0x{:x} role:0x{:x} local_tsep:{}",ADDRESS_TO_LOGGABLE_CSTR(p_scb->PeerAddress()), p_scb->hndl,p_scb->wait, p_scb->role, local_tsep);p_scb->started = true; //表示流已成功启动if (local_tsep == AVDT_TSEP_SRC) {// The RTP Header marker bit for the A2DP Source encoderA2dpCodecConfig* codec_config =bta_av_get_a2dp_peer_current_codec(p_scb->PeerAddress());CHECK(codec_config != nullptr);p_scb->use_rtp_header_marker_bit = codec_config->useRtpHeaderMarkerBit();}if (p_scb->sco_suspend) {p_scb->sco_suspend = false;}if (new_role & BTA_AV_ROLE_START_INT) initiator = true;/* for A2DP SINK we do not send get_caps */if ((p_scb->avdt_handle == p_scb->seps[p_scb->sep_idx].av_handle) &&(local_tsep == AVDT_TSEP_SNK)) {p_scb->wait &= ~(BTA_AV_WAIT_ACP_CAPS_ON);log::verbose("local SEP type is SNK new wait is 0x{:x}", p_scb->wait);}if (p_scb->wait & BTA_AV_WAIT_ROLE_SW_FAILED) {/* role switch has failed */log::error("peer {} role switch failed: bta_handle:0x{:x} wait:0x{:x}, ""role:0x{:x}",ADDRESS_TO_LOGGABLE_CSTR(p_scb->PeerAddress()), p_scb->hndl,p_scb->wait, p_scb->role);p_scb->wait &= ~BTA_AV_WAIT_ROLE_SW_FAILED;p_data = (tBTA_AV_DATA*)&hdr;hdr.offset = BTA_AV_RS_FAIL;}log::verbose("peer {} wait:0x{:x} use_rtp_header_marker_bit:{}",ADDRESS_TO_LOGGABLE_CSTR(p_scb->PeerAddress()), p_scb->wait,(p_scb->use_rtp_header_marker_bit) ? "true" : "false");if (p_data && (p_data->hdr.offset != BTA_AV_RS_NONE)) {p_scb->wait &= ~BTA_AV_WAIT_ROLE_SW_BITS;if (p_data->hdr.offset == BTA_AV_RS_FAIL) {bta_sys_idle(BTA_ID_AV, bta_av_cb.audio_open_cnt, p_scb->PeerAddress());tBTA_AV bta_av_data = {.start ={.chnl = p_scb->chnl,.hndl = p_scb->hndl,.status = BTA_AV_FAIL_ROLE,.initiator = initiator,.suspending = false,},};(*bta_av_cb.p_cback)(BTA_AV_START_EVT, &bta_av_data);return;}}if (!bta_av_link_role_ok(p_scb, A2DP_SET_ONE_BIT))p_scb->q_tag = BTA_AV_Q_TAG_START;else {/* The wait flag may be set here while we are already central on the link *//* this could happen if a role switch complete event occurred during* reconfig *//* if we are now central on the link, there is no need to wait for the role* switch, *//* complete anymore so we can clear the wait for role switch flag */p_scb->wait &= ~BTA_AV_WAIT_ROLE_SW_BITS;}if (p_scb->wait &(BTA_AV_WAIT_ROLE_SW_RES_OPEN | BTA_AV_WAIT_ROLE_SW_RES_START)) {p_scb->wait |= BTA_AV_WAIT_ROLE_SW_STARTED;p_scb->q_tag = BTA_AV_Q_TAG_START;}if (p_scb->wait) {log::error("peer {} wait:0x{:x} q_tag:{} not started",ADDRESS_TO_LOGGABLE_CSTR(p_scb->PeerAddress()), p_scb->wait,p_scb->q_tag);/* Clear first bit of p_scb->wait and not to return from this point else* HAL layer gets blocked. And if there is delay in Get Capability response* as* first bit of p_scb->wait is cleared hence it ensures bt_av_start_ok is* not called* again from bta_av_save_caps.*/p_scb->wait &= ~BTA_AV_WAIT_ACP_CAPS_ON;}/* tell role manager to check M/S role */bta_sys_conn_open(BTA_ID_AV, p_scb->app_id, p_scb->PeerAddress());bta_sys_busy(BTA_ID_AV, bta_av_cb.audio_open_cnt, p_scb->PeerAddress());if (p_scb->media_type == AVDT_MEDIA_TYPE_AUDIO) {/* in normal logic, conns should be bta_av_cb.audio_count - 1,* However, bta_av_stream_chg is not called to increase* bta_av_cb.audio_count yet.* If the code were to be re-arranged for some reasons, this number may need* to be changed*/p_scb->co_started = bta_av_cb.audio_open_cnt;}/* clear the congestion flag */p_scb->cong = false;if (new_role & BTA_AV_ROLE_START_INT) {new_role &= ~BTA_AV_ROLE_START_INT;} else if ((new_role & BTA_AV_ROLE_AD_ACP) &&(new_role & BTA_AV_ROLE_SUSPEND_OPT)) {suspend = true;}if (!suspend) {p_scb->q_tag = BTA_AV_Q_TAG_STREAM;bta_av_stream_chg(p_scb, true);}{/* If sink starts stream, disable sniff mode here */if (!initiator) {/* If source is the central role, disable role switch during streaming.* Otherwise allow role switch, if source is peripheral.* Because it would not hurt source, if the peer device wants source to be* central.* disable sniff mode unconditionally during streaming */if ((BTM_GetRole(p_scb->PeerAddress(), &cur_role) == BTM_SUCCESS) &&(cur_role == HCI_ROLE_CENTRAL)) {BTM_block_role_switch_and_sniff_mode_for(p_scb->PeerAddress());} else {BTM_block_sniff_mode_fo

相关文章:

【Bluedroid】A2DP SINK播放流程源码分析

在Bluedroid协议栈中,A2DP(Advanced Audio Distribution Profile)SINK播放流程是一个复杂但有序的过程,它涉及多个层次和组件的交互。 一、概述 1.1. 初始化流程 在A2DP SINK播放之前,系统需要进行一系列初始化操作,以确保A2DP SINK服务能够正确运行。这些操作包括启动…...

【什么是Redis?】

Redis&#xff1a;高性能内存数据库的深度探索 在当今这个数据驱动的世界里&#xff0c;数据库的选择直接关系到应用程序的性能、可扩展性和可靠性。在众多数据库解决方案中&#xff0c;Redis以其卓越的性能、丰富的数据结构和灵活的使用场景脱颖而出&#xff0c;成为众多开发…...

TCL大数据面试题及参考答案

Mysql 索引失效的场景 对索引列进行运算或使用函数:当在索引列上进行数学运算、函数操作等,索引可能失效。例如,在存储年龄的列上建立了索引,若查询语句是 “SELECT * FROM table WHERE age + 1 = 20”,这里对索引列 age 进行了加法运算,数据库会放弃使用索引而进行全表扫…...

提高总线数据传输率

提高总线数据传输率是一个涉及多个方面的技术问题&#xff0c;以下是一些有效的方法&#xff1a; 一、提高总线时钟频率 总线时钟频率是影响总线传输速率的重要因素之一。通过提高总线时钟频率&#xff0c;可以缩短每个时钟周期的时间&#xff0c;从而在相同的时间内传输更多…...

_FYAW智能显示控制仪表的简单使用_串口通信

一、简介 该仪表可以实时显示位移传感器的测量值&#xff0c;并可设定阈值等。先谈谈简单的使用方法&#xff0c;通过说明书&#xff0c;我们可以知道长按SET键可以进入参数选择状态&#xff0c;按“↑”“↓”可以选择该组参数的上一个或者下一个参数。 从参数一览中可以看到有…...

图的遍历。

图的遍历这一部分&#xff0c;离不开广度优先和深度优先&#xff0c;如果大家已经学过搜索算法的话&#xff0c;这部分将是易如反掌。 万能搜索算法-CSDN博客 文章中不会提太多离散数学中图的专有名词&#xff0c;因为本篇博客只涉及最简单的图的遍历&#xff0c;故以练习题为主…...

Methode Electronics EDI 需求分析

Methode Electronics 是一家总部位于美国的全球性技术公司&#xff0c;专注于设计和制造用于多个行业的电子和电气组件&#xff0c;产品涵盖汽车、工业、电信、医疗设备以及消费电子等多个领域&#xff0c;提供创新的解决方案。 填写Methode_EDI_Parameters_Template Methode_…...

IT资产管理工具-NetBox

IT资产管理工具-NetBox 推荐一款IT资产管理工具 了解推荐阅读官方中文文档 https://docs.wangluohe.com/introduction/ 硬件要求 ​ - 建议4Core 8G以上&#xff0c;100G存储空间 这里我使用的Linux镜像为 CentOS8-Stream 提前关闭Selinux和防火墙 部署NetBox 一&#…...

uniapp接入BMapGL百度地图

下面代码兼容安卓APP和H5 百度地图官网&#xff1a;控制台 | 百度地图开放平台 应用类别选择《浏览器端》 /utils/map.js 需要设置你自己的key export function myBMapGL1() {return new Promise(function(resolve, reject) {if (typeof window.initMyBMapGL1 function) {r…...

AWTK 最新动态:支持鸿蒙系统(HarmonyOS Next)

HarmonyOS是全球第三大移动操作系统&#xff0c;有巨大的市场潜力&#xff0c;在国产替代的背景下&#xff0c;机会多多&#xff0c;AWTK支持HarmonyOS&#xff0c;让AWTK开发者也能享受HarmonyOS生态的红利。 AWTK全称为Toolkit AnyWhere&#xff0c;是ZLG倾心打造的一套基于C…...

React基础知识一

写的东西太多了&#xff0c;照成csdn文档编辑器都开始卡顿了&#xff0c;所以分篇写。 1.安装React 需要安装下面三个包。 react:react核心包 react-dom:渲染需要用到的核心包 babel:将jsx语法转换成React代码的工具。&#xff08;没使用jsx可以不装&#xff09;1.1 在html中…...

Oracle热备过程中对数据库崩溃的处理方法

引言 在热备过程中如果发生数据库崩溃、断电等情况该如何处理? 如果正在备份 users 表空间的数据文件过程中,此时的数据文件表头 SCN 会被锁定,此时正在复制数据文件时数据库崩溃,系统断电。 从而导致数据文件表头与控制文件中的不一致,导致数据库无法打开,会要求介质恢…...

身份证实名认证API接口助力电商购物安全

亲爱的网购达人们&#xff0c;你们是否曾经因为网络上的虚假信息和诈骗而感到困扰&#xff1f;在享受便捷的网购乐趣时&#xff0c;如何确保交易安全成为了我们共同关注的话题。今天&#xff0c;一起来了解一下翔云身份证实名认证接口如何为电子商务保驾护航&#xff0c;让您的…...

win10 禁止更新

一、winR 输入 regedit 二、输入注册列表路径&#xff1a; &#xff08;1&#xff09;计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings &#xff08;2&#xff09;按照格式&#xff0c;创建文件命名: FlightSettingsMaxPauseDays &#xff08;3&…...

运维百科:网络性能20大关键指标

网络性能评估是确保网络服务质量和用户体验的关键环节。在网络、运维领域中&#xff0c;存在着一系列关键的性能指标&#xff0c;共同构成了衡量网络性能的基础。以下是网络性能的20大关键指标&#xff0c;每个指标都承载着特定的意义和重要性。 1.速率&#xff08;Rate&#…...

java编程开发基础,正则表达式的使用案例Demo

java编程开发基础,正则表达式的使用案例Demo!实际开发中&#xff0c;经常遇到一些字符串&#xff0c;信息的裁剪和提取操作&#xff0c;正则表达式是经常使用的&#xff0c;下面的案例&#xff0c;可以帮助大家快速的了解和熟悉&#xff0c;正则表达式的使用技巧。 package com…...

结构控制

目录​​​​​​​ 1.顺序结构 2.分支结构 2.1.单分支结构 2.2.二分支结构 2.3.多分支结构 2.4.嵌套分支结构 3.循环结构 3.1.while 循环结构 3.2.while...else 循环结构 PS&#xff1a;break 关键字 PS&#xff1a;pass 关键字 3.3.for 循环结构 PS&#xff1a;…...

Go语言中的内存分配与初始化:new与make函数详解

在Go语言中&#xff0c;内存分配和初始化是编程的基础操作。Go提供了两个内置函数new和make&#xff0c;用于不同场景下的内存分配和初始化。理解这两个函数的区别和适用场景对于编写高效、安全的Go代码至关重要。本文将详细介绍new和make函数&#xff0c;并提供示例说明它们的…...

The 2024 ICPC Kunming Invitational Contest

VP链接&#xff1a;https://codeforces.com/gym/105386 B. Gold Medal 签到题。对每一个读入的数 a&#xff0c;先记录已有奖牌数量&#xff0c;即 &#xff0c;再将 a 对 k 取模。然后将 a 数组从大到小排序&#xff0c;将每个不足 k 的数补到 k。如果 m 有剩余&#xff0c;…...

对原jar包解压后修改原class文件后重新打包为jar

文章目录 背景三种修改方式1.POM中移除原jar中依赖的历史版本2.原jar它不使用pom依赖而是直接放在源码中再编译使用JarEditor 插件对源码进行修改(推荐)使用java-decompiler反编译后修改源码覆盖原class&#xff08;不好用-不推荐直接跳过&#xff09;提醒 参考资料-推荐阅读拓…...

【C++】ReadFile概述,及实践使用时ReadFile的速率影响研究

ReadFile 函数概述 ReadFile 是 Windows API 函数&#xff0c;用于从文件或设备&#xff08;如串口、硬盘等&#xff09;中读取数据。它是同步和异步 I/O 操作的基础函数。 函数原型 BOOL ReadFile(_In_ HANDLE hFile, // 文件或设备句柄_Out_write…...

WebGL进阶(十一)层次模型

理论基础&#xff1a; 效果&#xff1a; 源码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"vie…...

Django:从入门到精通

一、Django背景 Django是一个由Python编写的高级Web应用框架&#xff0c;以其简洁性、安全性和高效性而闻名。Django最初由Adrian Holovaty和Simon Willison于2003年开发&#xff0c;旨在简化Web应用的开发过程。作为一个开放源代码项目&#xff0c;Django迅速吸引了大量的开发…...

C++设计模式行为模式———中介者模式

文章目录 一、引言二、中介者模式三、总结 一、引言 中介者模式是一种行为设计模式&#xff0c; 能让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互&#xff0c; 迫使它们通过一个中介者对象进行合作。 中介者模式可以减少对象之间混乱无序的依赖关系&…...

Perfetto学习大全

Perfetto 是一个功能强大的性能分析和追踪工具&#xff0c;主要用于捕获和分析复杂系统中的事件和性能数据&#xff0c;特别是在 Android 和 Linux 环境下。它的核心目标是帮助开发者深入了解系统和应用程序的运行状态&#xff0c;以便优化性能和诊断问题。 Perfetto的主要作用…...

管家婆财贸ERP BR040.销售单明细表变更

最低适用版本&#xff1a; C系列 23.8 插件简要功能说明&#xff1a; 销售明细表支持直接修改单据自由项1-16更多细节描述见下方详细文档 插件操作视频&#xff1a; 进销存类定制插件--销售单明细表变更 1. 应用中心增加菜单【销售单明细表变更】 a. 复制23.8版本销售单明细…...

2411rust,实现特征

原文 在Rust2024中,impl Trait在中位置的默认工作方式有了变化.是为了简化impl Trait,以更好地匹配人们一般的需求. 还添加了一个灵活的语法,让你需要时可完全控制. 从Rust2024开始,一直在更改,何时可在返回位置impl Trait的隐藏类型中使用泛型参数的规则: 1,即对返回位置i…...

SpringBoot3与JUnit5集成测试

你可以在 Spring Boot 3 中轻松设置和运行 JUnit 集成测试。合理使用 Spring 提供的注解和工具&#xff0c;可以确保测试的高效性和可靠性。以下是集成测试的步骤和示例&#xff1a; 1. 添加依赖 在 pom.xml 中添加 Spring Boot Starter Test 依赖&#xff0c;它包含 JUnit 5 …...

工程企业需要什么样的物资管理系统?为什么需要物资管理系统?

一、背景与意义 在工程项目的建设中&#xff0c;无论是高楼大厦的拔地而起&#xff0c;还是高速公路的绵延铺展&#xff0c;物资都是最基础的要素之一。从钢筋水泥到施工机械&#xff0c;任何一种物资的管理不善都可能导致项目延误、成本超支&#xff0c;甚至质量问题。然而&a…...

Vue + Websocket播放PCM(base64转ArrayBuffer、 字符串转ArrayBuffer)

文章目录 引言I 音视频处理相关概念和APIII 案例:基于开源库 pcm-player方式播放借助MediaSource和Audio对象播放音频流。基于原生api AudioContext 播放操作III 格式转换js字符串转ArrayBufferbase64 转 ArrayBufferIV 解决pcm-player分片播放问题引言 需求: 基于webscoket传…...

华为防火墙技术基本概念学习笔记

1.防火墙概述 1.1防火墙与交换机、路由器对比 路由器与交换机的本质是转发&#xff0c;防火墙的本质是控制。 防火墙与路由器、交换机是有区别的。路由器用来连接不同的网络&#xff0c;通过路由协议保证互联互通&#xff0c;确保将报文转发到目的地;交换机则通常用来组建局域…...

Mesh路由组网

Mesh无线网格网络&#xff0c;多跳&#xff08;multi-hop&#xff09;网络&#xff0c;为解决全屋覆盖信号&#xff0c;一般用于家庭网络和小型企业 原理 网关路由器&#xff08;主路由&#xff0c;连接光猫&#xff09;&#xff0c;Mesh路由器&#xff08;子路由&#xff0c;…...

【数据结构】七种常用排序总结

一、七种排序及其讲解 以下为七种排序的讲解&#xff1a; 【数据结构】插入排序——直接插入排序 和 希尔排序 【数据结构】选择排序——选择排序 和 堆排序 【数据结构】交换排序——冒泡排序 和 快速排序 【数据结构】归并排序 —— 递归及非递归解决归并排序 二、排序的…...

【在Linux世界中追寻伟大的One Piece】多线程(一)

目录 1 -> Linux线程概念 1.1 -> 什么是线程 1.2 -> 线程的优点 1.3 -> 线程的缺点 1.4 -> 线程异常 1.5 -> 线程用途 2 -> Linux线程 VS 进程 2.1 -> 线程和进程 2.2 -> 进程的多个线程共享 3 -> Linux线程控制 3.1 -> POSIX线程…...

《Python编程实训快速上手》第十天--处理CSV文件和JSON数据

CSV&#xff1a;简化的电子表格&#xff0c;被保存为纯文本文件 JSON&#xff1a;是一种数据交换格式&#xff0c;易于人阅读和编写&#xff0c;同时也易于机器解析和生成&#xff0c;以JavaScript源代码的形式将信息保存在纯文本文件中 一、csv模块 CSV文件中的每行代表电…...

基于springboot停车场管理系统源码和论文

如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统停车场管理系统信息管理难度大&#xff0c;容错率低&#xff0c;…...

Linux的桌面

Linux的桌面是可以卸载的 的确&#xff0c;Linux并不像Windows&#xff0c;Linux本身是一个基于命令行的操作系统&#xff0c;在内核眼中&#xff0c;桌面只不过是个普通的应用程序&#xff0c;所以&#xff0c;在Linux的桌面中可以完成的事情&#xff0c;命令行中也基本可以完…...

Spring Boot 3.0废弃了JavaEE,改用了Jakarta EE

Spring Boot 3.0废弃了JavaEE&#xff0c;改用了Jakarta EE 历史背景 javax变成Jakarta的主要原因是因为Java EE项目从Oracle转移到了Eclipse Foundation&#xff0c;并改名为Jakarta EE。 JavaEE是从Java 1.2版本开始推出的Java企业级开发平台&#xff0c;最初的名称是J2EE(J…...

java-排序算法汇总

排序算法&#xff1a; 冒泡排序&#xff08;Bubble Sort&#xff09; 选择排序&#xff08;Selection Sort&#xff09; 插入排序&#xff08;Insertion Sort&#xff09; 快速排序&#xff08;Quick Sort&#xff09; 归并排序&#xff08;Merge Sort&#xff09; 堆排序&…...

C0030.Clion中运行提示Process finished with exit code -1073741515 (0xC0000135)解决办法

1.错误提示 2.解决办法 添加环境变量完成之后&#xff0c;重启Clion软件&#xff0c;然后就可以正常调用由mingw编译的opencv库了。...

如何理解JS的 异步

JS是一门单线程的语言&#xff0c;这是因为它运行在浏览器的渲染主线程中&#xff0c;而渲染主线程只有一个。 而渲染主线程承担着诸多的工作&#xff0c;渲染页面、执行JS都在其中运行。 如果使用同步的方式&#xff0c;就极有可能导致主线程产生阻塞&#xff0c;从而导致消…...

vsgithub

VS&GitHub项目联动&#xff08;上传和克隆&#xff09;&#xff0c;创建你的第一个仓库&#xff0c;小白配置_vs上传代码到github-CSDN博客...

Android蓝牙架构,源文件目录/编译方式学习

Android 版本 发布时间 代号&#xff08;Codename&#xff09; Android 1.0 2008年9月23日 无 Android 1.1 2009年2月9日 Petit Four Android 1.5 2009年4月27日 Cupcake Android 1.6 2009年9月15日 Donut Android 2.0 2009年10月26日 Eclair Android 2.1 2…...

10 - Clickhouse集群部署以及副本和分片

目 一、副本 1、简介 2、副本写入流程 3、副本配置步骤 3.1、启动zookeeper集群 3.2、在 hallo100 的/etc/clickhouse-server/config.d 目录下创建一个名为metrika.xml 的配置文件,内容如下&#xff1a; 3.3、在 hallo100 的/etc/clickhouse-server/config.xml 中增加如…...

FreeRTOS的软件定时器与事件标志组

目录 1.FreeRTOS软件定时器 1.1 什么是FreeRTOS软件定时器&#xff1f; 1.2 学习软件定时器的意义 1.3 软件定时器的简介 1.3.1 软件定时器概述 1.3.2 编写回调函数的注意事项 1.4 定时器服务/Daemon 任务 1.4.1 定时器服务任务与队列 1.4.2 定时器相关配置 configUSE_T…...

生产制造领域的多元化模式探索

在当今全球化和信息化的时代背景下&#xff0c;生产制造领域正经历着前所未有的变革。随着消费者需求的多样化、市场竞争的加剧以及技术的不断进步&#xff0c;传统的生产制造模式已经难以满足现代企业的需求。因此&#xff0c;多种生产制造模式应运而生&#xff0c;以适应不同…...

大数据技术之SparkCore

RDD概述 什么是RDD RDD&#xff08;Resilient Distributed Dataset&#xff09;叫做弹性分布式数据集&#xff0c;是Spark中最基本的数据抽象。代码中是一个抽象类&#xff0c;它代表一个弹性的、不可变、可分区、里面的元素可并行计算的集合。 RDD五大特性 RDD编程 RDD的创…...

element-plus的组件数据配置化封装 - table

目录 一、封装的table、table-column组件以及相关ts类型的定义 1、ATable组件的封装 - index.ts 2、ATableColumn组件的封装 - ATableColumn.ts 3、ATable、ATableColumn类型 - interface.ts 二、ATable、ATableColumn组件的使用 三、相关属性、方法的使用以及相关说明 1. C…...

蓝桥杯每日真题 - 第15天

题目&#xff1a;&#xff08;钟表&#xff09; 题目描述&#xff08;13届 C&C B组B题&#xff09; 解题思路&#xff1a; 理解钟表指针的运动&#xff1a; 秒针每分钟转一圈&#xff0c;即每秒转6度。 分针每小时转一圈&#xff0c;即每分钟转6度。 时针每12小时转一圈…...

c#:winform调用bartender实现打印(学习整理笔记)

效果 学习路径 C# winform调用Bartender进行自定义打印、批量打印、检索文件夹中的模板_哔哩哔哩_bilibili 一、初始环境搭建见&#xff1a; c#:winform引入bartender-CSDN博客https://blog.csdn.net/weixin_46001736/article/details/143989473?sharetypeblogdetail&s…...