在蓝牙音频传输领域,A2DP(Advanced Audio Distribution Profile,高级音频分发协议)扮演着关键角色,它能够实现高质量音频(如立体声音乐)通过蓝牙在不同设备间的传输。在 Android 系统所采用的 Bluedroid 蓝牙协议栈里,A2DP Source 承担着音频流发送的重任,将音频数据传输至 A2DP Sink(像车载音响、蓝牙耳机等接收设备)。
一、概述
Bluedroid作为Android系统中的蓝牙协议栈,其架构组件各司其职,共同实现了蓝牙通信的功能。其中;
- BTIF(Bluetooth Interface)作为Bluedroid与上层Java框架之间的桥梁,提供了丰富多样的调用方法,使得上层应用能够便捷地与蓝牙协议栈进行通信;
- BTA(Bluetooth Application)专注于实现各类蓝牙 profile 的业务逻辑,包括 A2DP 相关逻辑处理;
- BTU(Bluetooth Upper Layer)则在 BTA 与 HCI(Host Controller Interface)之间构建起信息传递的桥梁,确保数据和命令能够在不同层次间准确流转;
- BTM(Bluetooth Manager)主要负责蓝牙配对以及链路管理工作,保障蓝牙连接的建立与维护;
- HCI 负责与蓝牙硬件进行底层通信,实现软件指令与硬件操作的对接;
- BTE(Bluetooth Engine)作为 Bluedroid 的核心处理部分,整合了 BTA、BTU、BTM 和 HCI 等模块,是整个蓝牙协议栈运行的关键支撑。
A2DP Source播放流程涉及多个步骤,以下是基于Bluedroid源码对其流程的梳理。
1.1. 初始化阶段
- 启动BluetoothAdapter服务:系统会调用BluetoothAdapter的enable方法以启动蓝牙服务。如果Bluedroid尚未初始化,则会先进行初始化。
- 绑定服务:在BluetoothManagerService中,通过绑定服务的方式连接到BluetoothAdapterService和GattService等蓝牙相关服务。
- 加载蓝牙库:BluetoothAdapterService在初始化过程中会加载蓝牙相关的库文件(如libbluetooth.so),这些库文件包含了实现蓝牙协议栈所需的代码。
- 创建接口:通过 dlopen 和 dlsym 等函数,从已加载的库文件中获取蓝牙接口的指针。这些接口指针就像是通往蓝牙协议栈各个功能模块的 “入口钥匙”,上层应用或其他服务可以通过这些指针调用蓝牙相关的 API,实现诸如设备搜索、连接建立、数据传输等操作。
- 启动核心服务:在BluetoothAdapterService中,会启动包括A2DP在内的核心蓝牙服务。通常是通过发送消息给服务处理线程,并调用相应的服务启动函数来实现的。
- 初始化A2DP模块:这一步会创建和初始化A2DP相关的模块或对象,例如初始化一些用于管理A2DP连接状态、音频编解码器(codec)配置、数据传输缓冲等方面的内部数据结构和对象。
- 控制通道的建立:
- 在系统初始化过程中,Bluedroid通过
uipc_init
函数初始化必要的变量,并启动主线程uipc_read_task
来处理控制通道的事件。 - 接着,调用
uipc_open
函数建立控制通道,并注册回调函数btif_a2dp_ctrl_cb
。这个回调函数将负责处理来自控制通道的各种命令和事件。
- 在系统初始化过程中,Bluedroid通过
1.2. 配置阶段
- 设置编解码器优先级:A2DP支持多种音频编解码器,如SBC(Subband Coding)、AAC(Advanced Audio Coding)等。在初始化过程中,会根据设备的硬件能力和用户设置(如果有)来确定各种编解码器的优先级顺序。例如,如果设备的硬件对 AAC 编解码器有更好的支持,且用户未明确指定编解码器偏好,那么可能会将 AAC 的优先级设置得较高。这种优先级设置会影响到后续与 A2DP Sink 设备进行音频能力协商时编解码器的选择,确保选择出最适合当前设备和用户需求的音频编码方式。
- 注册回调函数:为了能够在A2DP连接建立、音频传输过程中出现各种事件(如连接成功、连接断开、音频播放暂停等)时进行相应的处理,会注册一系列回调函数。这些回调函数通常会与上层应用(如音乐播放应用)或系统的其他相关部分进行交互。
- 处理函数的注册:在
btif_media_thread_init
函数中,通过UIPC_Open
注册了btif_a2dp_ctrl_cb
作为控制通道的处理函数。意味着,当控制通道接收到命令或事件时,将调用btif_a2dp_ctrl_cb
进行处理。 - 创建状态机:A2DP服务内部会创建一个状态机来管理A2DP的连接状态和数据传输。这个状态机会根据接收到的命令和事件来更新A2DP的状态,并触发相应的操作。
1.3. 连接与播放阶段
-
设备发现与选择:在A2DP Source播放之前,蓝牙设备需要处于可被发现的状态。用户通过蓝牙设置界面搜索附近的蓝牙设备,并从搜索结果中选择要连接的蓝牙设备(即A2DP Sink)。
-
发起连接请求:上层应用会通过合适的接口向Bluedroid中的A2DP相关模块发起连接请求,过程涉及到构建连接请求数据结构、设置相关连接参数等操作。
-
建立L2CAP通道:A2DP协议是基于L2CAP(逻辑链路控制和适配协议)之上的,所以首先要建立与A2DP Sink之间的L2CAP通道。L2CAP模块会根据目标设备地址等信息,向蓝牙底层发送连接建立请求,并经过蓝牙链路层的一系列交互(如链路建立、参数协商等),最终成功建立起L2CAP通道。
-
进行A2DP能力协商:在L2CAP通道建立好之后,A2DP相关模块会开始进行A2DP连接的建立操作,向A2DP Sink发送A2DP连接请求消息。对方设备收到请求后会进行响应,双方开始进行A2DP能力的协商,包括各自支持的音频编码格式、声道数量、采样率范围等音频相关的能力信息。这些信息会通过消息交互进行交换和协商,以确定最终在本次连接中使用的音频配置参数。
-
配置AVDTP:A2DP连接建立后,还需要通过AVDTP(Audio/Video Distribution Transport Protocol)协议来进一步配置和协商音频流相关的传输细节,例如音频数据的传输模式(是可靠传输还是尽力传输等模式)、缓冲区大小设置等。
-
音频数据准备:
- 解码器初始化:一旦A2DP和AVDTP相关的配置协商完成,A2DP Source端会根据协商好的音频编码格式来初始化音频解码器(尽管在A2DP Source端主要是编码过程,但这里的解码器初始化可能是指准备编码相关的资源或参数)。
- 音频轨道创建:A2DP Source端会基于协商确定的音频参数(如采样率、声道数等),利用安卓音频API(如AAudio)来配置并创建音频流,将音频流及其他相关参数信息封装到自定义的音频轨道结构体中。
- 播放流程阶段:
- 数据发送接口的调用:
- 上层的音频接口通过调用A2DP HAL(Hardware Abstraction Layer)中的
out_write
函数来发送命令和数据。 - 在
out_write
函数中,会根据音频流的状态进行相应处理。如果音频流处于suspended
状态,则直接返回失败;如果处于stopped
或standby
状态,则会调用start_audio_datapath
函数来建立音频路径(audio patch)。
- 上层的音频接口通过调用A2DP HAL(Hardware Abstraction Layer)中的
- 音频路径的建立:
start_audio_datapath
函数的主要任务是下发启动命令和建立socket连接。- 通过
a2dp_command
函数下发A2DP_CTRL_CMD_START
命令,该命令会被之前注册的btif_a2dp_ctrl_cb
回调函数处理。 - 同时,通过
skt_connect
函数建立socket连接,用于后续的数据传输。
- 命令的发送与处理:
a2dp_command
函数将命令发送到之前建立好的控制通道的socket中,并等待接收返回的消息以确认命令的执行情况。btif_a2dp_ctrl_cb
回调函数根据接收到的不同命令进行相应处理。例如,当收到A2DP_CTRL_CMD_START
命令时,会进行一些状态检查,并准备进入音频数据的传输阶段。
- 数据传输:
- 当音频路径建立成功且socket连接建立后,就可以通过
skt_write
函数将音频数据发送到音频路径中。 - 音频数据经过蓝牙协议栈的处理后,最终通过蓝牙硬件发送到Sink端(如蓝牙耳机或车载音响)。
- 当音频路径建立成功且socket连接建立后,就可以通过
- 数据发送接口的调用:
1.4. 关键函数与模块作用
out_write
函数:作为音频数据发送的入口函数,根据音频流的不同状态进行相应处理,决定是否建立 audio patch 以及发送音频数据。start_audio_datapath
函数:负责建立 audio patch,包括下发启动命令和建立 socket 连接,为音频数据传输做准备。a2dp_command
函数:实现向控制通道的 socket 发送命令,并等待接收确认消息,确保命令的可靠传输。btif_a2dp_ctrl_cb
函数:作为控制通道的回调处理函数,根据接收到的不同事件和命令,进行相应的处理和操作,如处理A2DP_CTRL_CMD_START
命令、连接状态变化等。
1.5. 状态监测与异常处理
- 状态监测:在整个A2DP Source播放过程中,会有专门的模块或函数持续监测连接的状态,比如链路是否正常、是否有数据传输中断、音频播放是否出现卡顿等情况。
- 异常处理:如果在A2DP Source播放的任何一个阶段出现异常情况(如链路连接失败、协议协商超时、音频数据接收错误等),源码中都会有相应的异常处理机制启动,包括输出错误日志信息、尝试重新发起连接或协商操作、向上层应用或用户反馈错误提示等。
Bluedroid中A2DP Source播放流程源码涵盖了从初始化、配置到连接播放以及状态监测与异常处理等多个环节的详细逻辑处理。这些环节紧密配合、相互依赖,通过一系列的协议交互、数据处理以及状态管理等操作来实现稳定的蓝牙音频播放功能。
二、源码分析
从A2dp连接开始分析,上层应用会通过setActiveDeviceNative接口向Bluedroid中的A2DP相关模块发起连接请求,过程涉及到构建连接请求数据结构、设置相关连接参数等操作。
2.1. setActiveDeviceNative
packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_a2dp.cpp
static jboolean setActiveDeviceNative(JNIEnv* env, jobject /* object */,
jbyteArray address) {
log::info("sBluetoothA2dpInterface: {}", fmt::ptr(sBluetoothA2dpInterface));
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sBluetoothA2dpInterface) {
log::error("Failed to get the Bluetooth A2DP Interface");
return JNI_FALSE;
}
jbyte* addr = env->GetByteArrayElements(address, nullptr);
RawAddress bd_addr = RawAddress::kEmpty;
if (addr) {
bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
}
bt_status_t status = sBluetoothA2dpInterface->set_active_device(bd_addr);
if (status != BT_STATUS_SUCCESS) {
log::error("Failed A2DP set_active_device, status: {}",
bt_status_text(status));
}
env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
setActiveDeviceNative
是一个 JNI(Java Native Interface)函数,它是供 Java 代码调用的本地 C++ 函数。
2.2. src_set_active_sink
packages/modules/Bluetooth/system/btif/src/btif_av.cc
static bt_status_t src_set_active_sink(const RawAddress& peer_address) {
log::verbose("Peer {}", ADDRESS_TO_LOGGABLE_CSTR(peer_address));
if (!btif_av_source.Enabled()) {
log::warn("BTIF AV Source is not enabled");
return BT_STATUS_NOT_READY;
}
std::promise<void> peer_ready_promise;
std::future<void> peer_ready_future = peer_ready_promise.get_future();
bt_status_t status = do_in_main_thread(
FROM_HERE, base::BindOnce(&set_active_peer_int,
AVDT_TSEP_SNK, // peer_sep
peer_address, std::move(peer_ready_promise)));
if (status == BT_STATUS_SUCCESS) {
peer_ready_future.wait();
} else {
log::error("BTIF AV Source fails to change peer");
}
return status;
}
src_set_active_sink函数的主要目的是设置蓝牙音频 / 视频(AV)传输中的接收端(Sink),也就是指定当前音频数据要发送到的目标蓝牙设备(例如切换当前正在用于音频播放的蓝牙耳机或者车载音响等接收设备),并根据操作的执行情况返回相应的 bt_status_t
类型的状态值,用于指示操作是成功还是遇到了何种类型的问题。
set_active_peer_int
packages/modules/Bluetooth/system/btif/src/btif_av.cc
// Set the active peer
static void set_active_peer_int(uint8_t peer_sep,
const RawAddress& peer_address,
std::promise<void> peer_ready_promise) {
log::warn("peer_sep={} ({}) peer_address={}",
(peer_sep == AVDT_TSEP_SRC) ? "Source" : "Sink", peer_sep,
ADDRESS_TO_LOGGABLE_CSTR(peer_address));
BtifAvPeer* peer = nullptr;
if (peer_sep == AVDT_TSEP_SNK) {
if (!btif_av_src_sink_coexist_enabled() || (btif_av_src_sink_coexist_enabled() &&
btif_av_both_enable() && (btif_av_sink.FindPeer(peer_address) == nullptr))) {
btif_av_source.SetActivePeer(peer_address,
std::move(peer_ready_promise));
log::error("Error setting {} as active Sink peer",
ADDRESS_TO_LOGGABLE_CSTR(peer_address));
}
return;
}
if (peer_sep == AVDT_TSEP_SRC) {
if (!btif_av_src_sink_coexist_enabled() || (btif_av_src_sink_coexist_enabled() &&
btif_av_both_enable() && (btif_av_source.FindPeer(peer_address) == nullptr))) {
if (!btif_av_sink.SetActivePeer(peer_address,
std::move(peer_ready_promise))) {
log::error("Error setting {} as active Source peer",
ADDRESS_TO_LOGGABLE_CSTR(peer_address));
}
}
return;
}
// If reached here, we could not set the active peer
log::error("Cannot set active {} peer to {}: peer not {}",
(peer_sep == AVDT_TSEP_SRC) ? "Source" : "Sink",
ADDRESS_TO_LOGGABLE_CSTR(peer_address),
(peer == nullptr) ? "found" : "connected");
peer_ready_promise.set_value();
}
该函数主要用于设置蓝牙音频 / 视频(AV)传输中的活动对等端(Peer),根据传入的 peer_sep
参数来区分是设置音频源(Source)端还是音频接收端(Sink)为活动状态,并且通过 peer_address
参数指定具体的蓝牙设备地址,同时利用 std::promise<void>
来传递操作完成的信号。
处理接收端(Sink):
- 如果
peer_sep
等于AVDT_TSEP_SNK
,表示要设置的是接收端。- 检查是否启用了A2DP源和接收端共存(
btif_av_src_sink_coexist_enabled()
),以及是否同时启用了A2DP的源和接收端功能(btif_av_both_enable()
)。- 如果未启用共存或启用了但找不到对应的源端对等端(
btif_av_source.FindPeer(peer_address) == nullptr
),则尝试将目标设备设置为活跃的接收端对等端。处理发送端(Source):
- 如果
peer_sep
等于AVDT_TSEP_SRC
,表示要设置的是发送端。- 同样检查共存和同时启用条件。
- 如果找不到对应的接收端对等端(
btif_av_sink.FindPeer(peer_address) == nullptr
),则尝试将目标设备设置为活跃的发送端对等端。
SetActivePeer
packages/modules/Bluetooth/system/btif/src/btif_av.cc
/**
* Set the active peer.
*
* @param peer_address the active peer address or RawAddress::kEmpty to
* reset the active peer
* @return true on success, otherwise false
*/
bool SetActivePeer(const RawAddress& peer_address,
std::promise<void> peer_ready_promise) {
log::info("peer: {}", ADDRESS_TO_LOGGABLE_STR(peer_address));
if (active_peer_ == peer_address) {
peer_ready_promise.set_value();
return true; // Nothing has changed
}
if (peer_address.IsEmpty()) {
log::verbose("peer address is empty, shutdown the Audio source");
if (!btif_av_src_sink_coexist_enabled() ||
(btif_av_src_sink_coexist_enabled() &&
btif_av_sink_active_peer().IsEmpty())) {
if (!bta_av_co_set_active_peer(peer_address)) {
log::warn("unable to set active peer to empty in BtaAvCo");
}
}
btif_a2dp_source_end_session(active_peer_);
std::promise<void> shutdown_complete_promise;
std::future<void> shutdown_complete_future =
shutdown_complete_promise.get_future();
btif_a2dp_source_shutdown(std::move(shutdown_complete_promise));
using namespace std::chrono_literals;
if (shutdown_complete_future.wait_for(1s) ==
std::future_status::timeout) {
log::error("Timed out waiting for A2DP source shutdown to complete.");
}
active_peer_ = peer_address;
peer_ready_promise.set_value();
return true;
}
if (btif_av_src_sink_coexist_enabled()) btif_av_sink_delete_active_peer();
BtifAvPeer* peer = FindPeer(peer_address);
if (peer == nullptr || !peer->IsConnected()) {
log::error("Error setting {} as active Source peer",
ADDRESS_TO_LOGGABLE_STR(peer_address));
peer_ready_promise.set_value();
return false;
}
if (!btif_a2dp_source_restart_session(active_peer_, peer_address,
std::move(peer_ready_promise))) {
// cannot set promise but need to be handled within restart_session
return false;
}
active_peer_ = peer_address;
return true;
}
该函数的主要作用根据传入的设备地址情况(是特定地址还是空地址来表示重置等不同情况),函数会执行相应的逻辑来更新活动对等端的设置,并且在操作完成后通过 peer_ready_promise
传递完成信号,最后返回一个布尔值来指示整个设置操作是否成功。
btif_a2dp_source_restart_session
packages/modules/Bluetooth/system/btif/src/btif_a2dp_source.cc
bool btif_a2dp_source_restart_session(const RawAddress& old_peer_address,
const RawAddress& new_peer_address,
std::promise<void> peer_ready_promise) {
log::info("old_peer_address={} new_peer_address={} state={}",
ADDRESS_TO_LOGGABLE_STR(old_peer_address),
ADDRESS_TO_LOGGABLE_STR(new_peer_address),
btif_a2dp_source_cb.StateStr());
CHECK(!new_peer_address.IsEmpty());
// Must stop first the audio streaming
btif_a2dp_source_stop_audio_req();
// If the old active peer was valid, end the old session.
// Otherwise, time to startup the A2DP Source processing.
if (!old_peer_address.IsEmpty()) {
btif_a2dp_source_end_session(old_peer_address);
} else {
btif_a2dp_source_startup();
}
// Start the session.
btif_a2dp_source_start_session(new_peer_address,
std::move(peer_ready_promise));
// If audio was streaming before, DON'T start audio streaming, but leave the
// control to the audio HAL.
return true;
}
该函数旨在重新启动蓝牙 A2DP Source的会话,主要涉及从旧的对等端(Peer)切换到新的对等端的相关操作。在这个过程中,进行必要的状态检查、停止当前音频流、处理旧会话的结束(如果有旧的有效对等端)、启动新会话等一系列操作,最后返回一个布尔值来表示函数执行过程是否顺利,同时利用 std::promise<void>
来传递操作完成的信号,方便外部代码知晓操作进度。
btif_a2dp_source_startup
packages/modules/Bluetooth/system/btif/src/btif_a2dp_source.cc
bool btif_a2dp_source_startup(void) {
log::info("state={}", btif_a2dp_source_cb.StateStr());
if (btif_a2dp_source_cb.State() != BtifA2dpSource::kStateOff) {
log::error("A2DP Source media task already running");
return false;
}
btif_a2dp_source_cb.Reset();
btif_a2dp_source_cb.SetState(BtifA2dpSource::kStateStartingUp);
btif_a2dp_source_cb.tx_audio_queue = fixed_queue_new(SIZE_MAX);
// Schedule the rest of the operations
btif_a2dp_source_thread.DoInThread(
FROM_HERE, base::BindOnce(&btif_a2dp_source_startup_delayed));
return true;
}
该函数主要负责启动蓝牙 A2DP Source端相关的初始化操作。先检查当前 A2DP Source端的状态,只有在处于关闭状态(kStateOff
)时才会进行后续的启动流程,包括重置相关状态、设置初始状态、创建音频数据传输队列以及安排后续延迟执行的操作等。
btif_a2dp_source_startup_delayed
packages/modules/Bluetooth/system/btif/src/btif_a2dp_source.cc
static void btif_a2dp_source_startup_delayed() {
log::info("state={}", btif_a2dp_source_cb.StateStr());
if (!btif_a2dp_source_thread.EnableRealTimeScheduling()) {
#if defined(__ANDROID__)
log::fatal("unable to enable real time scheduling");
#endif
}
if (!bluetooth::audio::a2dp::init(&btif_a2dp_source_thread)) {
if (btif_av_is_a2dp_offload_enabled()) {
// TODO: BluetoothA2dp@1.0 is deprecated
log::warn("Using BluetoothA2dp HAL");
} else {
log::warn("Using legacy HAL");
btif_a2dp_control_init();
}
}
btif_a2dp_source_cb.SetState(BtifA2dpSource::kStateRunning);
}
该函数作为蓝牙 A2DP Source端启动流程中延迟执行的一部分,主要负责完成一些关键的初始化操作以及状态更新工作。会尝试进行实时调度相关设置、音频相关模块的初始化等操作,并根据初始化结果进行相应的日志记录和状态切换,最终将 A2DP 源端的状态设置为运行状态(kStateRunning
),标志着启动流程进入了一个新的阶段。
btif_a2dp_control_init
packages/modules/Bluetooth/system/btif/src/btif_a2dp_control.cc
void btif_a2dp_control_init(void) {
log::error("btif_a2dp_control_init");
a2dp_uipc = UIPC_Init();
UIPC_Open(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, btif_a2dp_ctrl_cb, A2DP_CTRL_PATH);
}
该函数通过调用底层的通信初始化函数以及打开特定通道并注册回调函数的操作,来建立起与 A2DP 控制功能相关的通信链路和事件处理机制,为后续的 A2DP 控制命令传输、状态管理等操作奠定基础。
初始化 UIPC:
a2dp_uipc = UIPC_Init();
调用UIPC_Init
函数来初始化用户空间进程间通信(UIPC)机制。这个机制可能用于在不同进程或线程之间传递 A2DP 控制消息。a2dp_uipc
是一个全局变量,用于存储 UIPC 的句柄或上下文。打开 UIPC 通道:
UIPC_Open(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, btif_a2dp_ctrl_cb, A2DP_CTRL_PATH);
调用UIPC_Open
函数来打开一个特定的 UIPC 通道,用于 A2DP 控制消息的传递。UIPC_CH_ID_AV_CTRL
是通道的标识符,表示这是一个用于音频/视频控制的通道。btif_a2dp_ctrl_cb
是一个回调函数,当 UIPC 通道接收到消息时,该函数将被调用。这个回调函数负责处理接收到的 A2DP 控制消息。A2DP_CTRL_PATH
是 UIPC 通道的路径("/data/misc/bluedroid/.a2dp_ctrl"),用于在系统中唯一标识这个通道。
btif_a2dp_source_start_session
packages/modules/Bluetooth/system/btif/src/btif_a2dp_source.cc
bool btif_a2dp_source_start_session(const RawAddress& peer_address,
std::promise<void> peer_ready_promise) {
log::info("peer_address={} state={}", ADDRESS_TO_LOGGABLE_STR(peer_address),
btif_a2dp_source_cb.StateStr());
btif_a2dp_source_setup_codec(peer_address);
if (btif_a2dp_source_thread.DoInThread(
FROM_HERE,
base::BindOnce(&btif_a2dp_source_start_session_delayed, peer_address,
std::move(peer_ready_promise)))) {
return true;
} else {
// cannot set promise but triggers crash
log::fatal("peer_address={} state={} fails to context switch",
ADDRESS_TO_LOGGABLE_STR(peer_address),
btif_a2dp_source_cb.StateStr());
return false;
}
}
该函数的主要目的是启动蓝牙 A2DP Source端与指定对等端(Peer)之间的会话。它会先进行一些准备工作,比如设置音频编解码器相关配置,然后尝试在特定线程中安排执行后续延迟的操作,根据能否成功安排这些操作来返回相应的布尔值,以表示启动会话操作是否成功发起。
btif_a2dp_source_setup_codec
packages/modules/Bluetooth/system/btif/src/btif_a2dp_source.cc
static void btif_a2dp_source_setup_codec(const RawAddress& peer_address) {
log::info("peer_address={} state={}", ADDRESS_TO_LOGGABLE_CSTR(peer_address),
btif_a2dp_source_cb.StateStr());
// Check to make sure the platform has 8 bits/byte since
// we're using that in frame size calculations now.
CHECK(CHAR_BIT == 8);
btif_a2dp_source_audio_tx_flush_req();
btif_a2dp_source_thread.DoInThread(
FROM_HERE,
base::BindOnce(&btif_a2dp_source_setup_codec_delayed, peer_address));
}
该函数主要负责为蓝牙 A2DP Source端设置音频编解码器相关的配置。先进行一些必要的准备工作,如进行字节位数的检查以确保平台符合编解码器相关计算的预期,然后刷新音频发送队列(为了清理之前残留的数据等),最后安排在特定线程中执行后续延迟的编解码器设置相关操作。
btif_a2dp_source_setup_codec_delayed
packages/modules/Bluetooth/system/btif/src/btif_a2dp_source.cc
static void btif_a2dp_source_setup_codec_delayed(
const RawAddress& peer_address) {
log::info("peer_address={} state={}", ADDRESS_TO_LOGGABLE_CSTR(peer_address),
btif_a2dp_source_cb.StateStr());
tA2DP_ENCODER_INIT_PEER_PARAMS peer_params;
bta_av_co_get_peer_params(peer_address, &peer_params);
if (!bta_av_co_set_active_peer(peer_address)) {
log::error("Cannot stream audio: cannot set active peer to {}",
ADDRESS_TO_LOGGABLE_CSTR(peer_address));
return;
}
btif_a2dp_source_cb.encoder_interface = bta_av_co_get_encoder_interface();
if (btif_a2dp_source_cb.encoder_interface == nullptr) {
log::error("Cannot stream audio: no source encoder interface");
return;
}
A2dpCodecConfig* a2dp_codec_config = bta_av_get_a2dp_current_codec();
if (a2dp_codec_config == nullptr) {
log::error("Cannot stream audio: current codec is not set");
return;
}
btif_a2dp_source_cb.encoder_interface->encoder_init(
&peer_params, a2dp_codec_config, btif_a2dp_source_read_callback,
btif_a2dp_source_enqueue_callback);
// Save a local copy of the encoder_interval_ms
btif_a2dp_source_cb.encoder_interval_ms =
btif_a2dp_source_cb.encoder_interface->get_encoder_interval_ms();
if (bluetooth::audio::a2dp::is_hal_enabled()) {
bluetooth::audio::a2dp::setup_codec();
}
}
该函数作为蓝牙 A2DP Source端设置编解码器相关操作中延迟执行的部分,主要负责与对等端(Peer)协商获取编解码器配置参数、设置活动对等端、获取并验证编码器相关接口、初始化编码器以及根据音频硬件抽象层(HAL)启用情况进一步配置编解码器等工作,整个流程旨在确保编解码器能够正确配置,为后续稳定的蓝牙音频传输奠定基础。
bta_av_co_get_encoder_interface
packages/modules/Bluetooth/system/btif/co/bta_av_co.cc
const tA2DP_ENCODER_INTERFACE* bta_av_co_get_encoder_interface(void) {
return bta_av_co_cb.GetSourceEncoderInterface();
}
该函数的主要作用是获取蓝牙 A2DP相关的编码器接口。直接调用了 bta_av_co_cb
对象的 GetSourceEncoderInterface
成员函数,并返回其结果,这个结果是一个指向 tA2DP_ENCODER_INTERFACE
类型的指针,外部代码通过获取这个接口指针,就能进一步利用该接口提供的功能来操作编码器,比如进行编码器的初始化、获取编码器相关参数等操作,是实现蓝牙音频编码相关功能的重要一环。
GetSourceEncoderInterface
packages/modules/Bluetooth/system/btif/co/bta_av_co.cc
const tA2DP_ENCODER_INTERFACE* BtaAvCo::GetSourceEncoderInterface() {
std::lock_guard<std::recursive_mutex> lock(peer_cache_->codec_lock_);
return A2DP_GetEncoderInterface(bta_av_legacy_state_.getCodecConfig());
}
该函数是 BtaAvCo
类的成员函数,其主要目的是获取用于蓝牙 A2DP Source端的编码器接口。它通过使用互斥锁来保证在多线程环境下对相关资源访问的安全性,然后调用另一个函数 A2DP_GetEncoderInterface
并传入合适的编解码器配置参数,最终返回获取到的指向 tA2DP_ENCODER_INTERFACE
类型的编码器接口指针,以供外部代码进一步操作编码器来进行音频编码相关的工作,是实现蓝牙音频编码功能中获取关键接口的重要步骤。
A2DP_GetEncoderInterface
packages/modules/Bluetooth/system/stack/a2dp/a2dp_codec_config.cc
const tA2DP_ENCODER_INTERFACE* A2DP_GetEncoderInterface(
const uint8_t* p_codec_info) {
tA2DP_CODEC_TYPE codec_type = A2DP_GetCodecType(p_codec_info);
log::verbose("codec_type = 0x{:x}", codec_type);
if (::bluetooth::audio::a2dp::provider::supports_codec(
A2DP_SourceCodecIndex(p_codec_info))) {
return A2DP_GetEncoderInterfaceExt(p_codec_info);
}
switch (codec_type) {
case A2DP_MEDIA_CT_SBC:
return A2DP_GetEncoderInterfaceSbc(p_codec_info);
#if !defined(EXCLUDE_NONSTANDARD_CODECS)
case A2DP_MEDIA_CT_AAC:
return A2DP_GetEncoderInterfaceAac(p_codec_info);
case A2DP_MEDIA_CT_NON_A2DP:
return A2DP_VendorGetEncoderInterface(p_codec_info);
#endif
default:
break;
}
log::error("unsupported codec type 0x{:x}", codec_type);
return NULL;
}
该函数的核心功能是根据传入的编解码器信息(以字节数组形式表示,通过 p_codec_info
参数传递)来获取对应的蓝牙 A2DP编码器接口。它首先确定编解码器的类型,然后依据不同的情况(如是否支持特定编解码器、编解码器类型具体是什么等)来返回相应的编码器接口指针,如果遇到不支持的编解码器类型,则输出错误日志并返回 NULL。
- 获取编解码器类型:调用
A2DP_GetCodecType
函数来获取传入的编解码器信息的类型。- 检查编解码器支持:检查当前系统是否支持传入的编解码器。如果支持,则调用
A2DP_GetEncoderInterfaceExt
函数来获取编码器接口。- 根据编解码器类型获取编码器接口:
- 使用
switch
语句根据codec_type
的值来选择合适的函数来获取编码器接口。
- 对于标准的 SBC 编解码器,调用
A2DP_GetEncoderInterfaceSbc
。- 如果定义了
EXCLUDE_NONSTANDARD_CODECS
宏,则不会包含对 AAC 和非 A2DP 编解码器的支持。- 对于 AAC 编解码器(如果未排除),调用
A2DP_GetEncoderInterfaceAac
。- 对于非 A2DP 编解码器(如果未排除),调用
A2DP_VendorGetEncoderInterface
。- 错误处理:如果传入的编解码器类型不被支持,则返回
NULL
。
A2DP_GetEncoderInterfaceAac
packages/modules/Bluetooth/system/stack/a2dp/a2dp_aac.cc
const tA2DP_ENCODER_INTERFACE* A2DP_GetEncoderInterfaceAac(
const uint8_t* p_codec_info) {
if (!A2DP_IsSourceCodecValidAac(p_codec_info)) return NULL;
return &a2dp_encoder_interface_aac;
}
该函数的主要作用是获取用于蓝牙 A2DP中 AAC 编解码器的编码器接口。首先会验证传入的 AAC 编解码器信息是否有效,若有效则返回一个指向特定 tA2DP_ENCODER_INTERFACE
类型的 AAC 编码器接口的指针,若无效则返回 NULL
,以此来为后续针对 AAC 编解码器的音频编码操作提供正确的接口支持或者反馈无效的情况。
a2dp_aac_encoder_init
packages/modules/Bluetooth/system/stack/a2dp/a2dp_aac_encoder.cc
void a2dp_aac_encoder_init(const tA2DP_ENCODER_INIT_PEER_PARAMS* p_peer_params,
A2dpCodecConfig* a2dp_codec_config,
a2dp_source_read_callback_t read_callback,
a2dp_source_enqueue_callback_t enqueue_callback) {
uint8_t codec_info[AVDT_CODEC_SIZE];
if (!a2dp_codec_config->copyOutOtaCodecConfig(codec_info)) {
log::error("Cannot update the codec encoder for {}: invalid codec config",
a2dp_codec_config->name().c_str());
return;
}
uint16_t mtu = adjust_effective_mtu(*p_peer_params);
int max_bit_rate =
A2DP_ComputeMaxBitRateAac(codec_info, mtu - A2DP_AAC_MAX_PREFIX_SIZE) /
8 * 8;
int bit_rate = std::min(A2DP_GetBitRateAac(codec_info) / 8 * 8, max_bit_rate);
tA2DP_SAMPLE_RATE sample_rate = A2DP_GetTrackSampleRateAac(codec_info);
tA2DP_CHANNEL_COUNT channel_count = A2DP_GetTrackChannelCountAac(codec_info);
tA2DP_BITS_PER_SAMPLE bits_per_sample =
a2dp_codec_config->getAudioBitsPerSample();
int pcm_samples_per_frame = codec_intf.prepare_context(
sample_rate, channel_count, bit_rate, bits_per_sample, mtu);
if (pcm_samples_per_frame < 0) {
log::error("Failed to prepare context: {}", pcm_samples_per_frame);
codec_intf.clear_context();
return; // TODO(b/294165759): need to return an error
}
uint32_t encoder_interval_ms = pcm_samples_per_frame * 1000 / sample_rate;
a2dp_aac_encoder_cb = tA2DP_AAC_ENCODER_CB{
.read_callback = read_callback,
.enqueue_callback = enqueue_callback,
.TxAaMtuSize = mtu,
.peer_params = *p_peer_params,
.timestamp = bluetooth::os::GenerateRandom(), // (RFC 6416)
.feeding_params =
{
.sample_rate = sample_rate,
.bits_per_sample = bits_per_sample,
.channel_count = channel_count,
},
.aac_feeding_state =
tA2DP_AAC_FEEDING_STATE{
.bytes_per_tick = (sample_rate * bits_per_sample / 8 *
channel_count * encoder_interval_ms) /
1000,
},
.pcm_samples_per_frame = static_cast<uint32_t>(pcm_samples_per_frame),
.encoder_interval_ms = encoder_interval_ms,
.stats =
a2dp_aac_encoder_stats_t{
.session_start_us = bluetooth::common::time_get_os_boottime_us(),
},
};
}
该函数主要负责初始化蓝牙 A2DP中的 AAC 编码器相关的参数和状态信息。从传入的编解码器配置、对等端参数等信息中提取关键参数,进行一系列计算和初始化操作,包括调整有效最大传输单元(MTU)、计算比特率、准备编码器上下文等,最后构建并填充 tA2DP_AAC_ENCODER_CB
结构体类型的 a2dp_aac_encoder_cb
变量,用于后续 AAC 编码器的相关操作。
btif_a2dp_source_start_session_delayed
packages/modules/Bluetooth/system/btif/src/btif_a2dp_source.cc
static void btif_a2dp_source_start_session_delayed(
const RawAddress& peer_address, std::promise<void> peer_ready_promise) {
log::info("peer_address={} state={}", ADDRESS_TO_LOGGABLE_STR(peer_address),
btif_a2dp_source_cb.StateStr());
if (btif_a2dp_source_cb.State() != BtifA2dpSource::kStateRunning) {
log::error("A2DP Source media task is not running");
peer_ready_promise.set_value();
return;
}
if (bluetooth::audio::a2dp::is_hal_enabled()) {
bluetooth::audio::a2dp::start_session();
bluetooth::audio::a2dp::set_remote_delay(btif_av_get_audio_delay());
BluetoothMetricsLogger::GetInstance()->LogBluetoothSessionStart(
bluetooth::common::CONNECTION_TECHNOLOGY_TYPE_BREDR, 0);
} else {
BluetoothMetricsLogger::GetInstance()->LogBluetoothSessionStart(
bluetooth::common::CONNECTION_TECHNOLOGY_TYPE_BREDR, 0);
}
peer_ready_promise.set_value();
}
该函数作为蓝牙 A2DP Source端启动会话流程中延迟执行的部分,主要负责在合适的状态下进行实际的会话启动相关操作,包括检查 A2DP 源端的运行状态、根据音频硬件抽象层(HAL)是否启用执行相应的音频会话启动操作、设置音频延迟相关参数以及记录蓝牙会话启动的相关指标等,最后通过 peer_ready_promise
发送操作完成的信号,告知外部代码会话启动相关操作已完成。
在安卓系统中,当用户选择通过蓝牙设备(如蓝牙耳机)来播放音频时,涉及多个层次的组件协作,音频数据从应用层经过一系列处理,最终会通过 AudioFlinger
中的 openOutput
函数来完成向耳机输出音频的关键步骤。这个过程涵盖了从音频源获取音频数据、进行必要的格式转换和处理、通过蓝牙协议传输,再到适配耳机等输出设备进行播放的完整链路,而 openOutput
函数在其中承担着打开合适的音频输出通道、配置相关参数以及协调底层硬件等重要任务。
2.3. AudioFlinger::openOutput
/frameworks/av/services/audioflinger/AudioFlinger.cpp
status_t AudioFlinger::openOutput(const media::OpenOutputRequest& request,
media::OpenOutputResponse* response)
{
audio_module_handle_t module = VALUE_OR_RETURN_STATUS(
aidl2legacy_int32_t_audio_module_handle_t(request.module));
audio_config_t halConfig = VALUE_OR_RETURN_STATUS(
aidl2legacy_AudioConfig_audio_config_t(request.halConfig, false /*isInput*/));
audio_config_base_t mixerConfig = VALUE_OR_RETURN_STATUS(
aidl2legacy_AudioConfigBase_audio_config_base_t(request.mixerConfig, false/*isInput*/));
sp<DeviceDescriptorBase> device = VALUE_OR_RETURN_STATUS(
aidl2legacy_DeviceDescriptorBase(request.device));
audio_output_flags_t flags = VALUE_OR_RETURN_STATUS(
aidl2legacy_int32_t_audio_output_flags_t_mask(request.flags));
audio_io_handle_t output;
ALOGI("openOutput() this %p, module %d Device %s, SamplingRate %d, Format %#08x, "
"Channels %#x, flags %#x",
this, module,
device->toString().c_str(),
halConfig.sample_rate,
halConfig.format,
halConfig.channel_mask,
flags);
audio_devices_t deviceType = device->type();
const String8 address = String8(device->address().c_str());
if (deviceType == AUDIO_DEVICE_NONE) {
return BAD_VALUE;
}
Mutex::Autolock _l(mLock);
sp<ThreadBase> thread = openOutput_l(module, &output, &halConfig,
&mixerConfig, deviceType, address, flags);
if (thread != 0) {
uint32_t latencyMs = 0;
if ((flags & AUDIO_OUTPUT_FLAG_MMAP_NOIRQ) == 0) {
PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
latencyMs = playbackThread->latency();
// notify client processes of the new output creation
playbackThread->ioConfigChanged(AUDIO_OUTPUT_OPENED);
// the first primary output opened designates the primary hw device if no HW module
// named "primary" was already loaded.
AutoMutex lock(mHardwareLock);
if ((mPrimaryHardwareDev == nullptr) && (flags & AUDIO_OUTPUT_FLAG_PRIMARY)) {
ALOGI("Using module %d as the primary audio interface", module);
mPrimaryHardwareDev = playbackThread->getOutput()->audioHwDev;
mHardwareStatus = AUDIO_HW_SET_MODE;
mPrimaryHardwareDev->hwDevice()->setMode(mMode);
mHardwareStatus = AUDIO_HW_IDLE;
}
} else {
MmapThread *mmapThread = (MmapThread *)thread.get();
mmapThread->ioConfigChanged(AUDIO_OUTPUT_OPENED);
}
response->output = VALUE_OR_RETURN_STATUS(legacy2aidl_audio_io_handle_t_int32_t(output));
response->config = VALUE_OR_RETURN_STATUS(
legacy2aidl_audio_config_t_AudioConfig(halConfig, false /*isInput*/));
response->latencyMs = VALUE_OR_RETURN_STATUS(convertIntegral<int32_t>(latencyMs));
response->flags = VALUE_OR_RETURN_STATUS(
legacy2aidl_audio_output_flags_t_int32_t_mask(flags));
return NO_ERROR;
}
return NO_INIT;
}
该函数是 AudioFlinger
类中的一个成员函数,主要负责打开音频输出设备并进行相关的配置和初始化操作。它接收包含音频输出请求相关信息的 media::OpenOutputRequest
参数以及用于返回响应信息的 media::OpenOutputResponse
指针参数,基于请求中的模块、配置、设备描述、标志等信息,在内部进行一系列处理,如获取正确的设备类型、加锁保护共享资源、调用内部函数打开输出设备、处理延迟计算、配置变更通知以及根据不同情况设置主硬件设备等操作,最后将处理结果填充到响应结构体中并返回相应的状态值,以告知调用者操作是否成功以及相关的输出信息。
openOutput
-->openOutput_l
-->findSuitableHwDev_l
-->audio_interfaces[i]
--> loadHwModule_l
-->openDevice
-->loadAudioInterface
-->AUDIO_HARDWARE_MODULE_ID(DevicesFactory.cpp)
-->hal_module_methods(audio_a2dp_hw.cc)
--> adev_open(audio_a2dp_hw.cc)
packages/modules/Bluetooth/system/audio_a2dp_hw/src/audio_a2dp_hw.cc
static int adev_open(const hw_module_t* module, const char* name,
hw_device_t** device) {
struct a2dp_audio_device* adev;
INFO(" adev_open in A2dp_hw module");
FNLOG();
if (strcmp(name, AUDIO_HARDWARE_INTERFACE) != 0) {
ERROR("interface %s not matching [%s]", name, AUDIO_HARDWARE_INTERFACE);
return -EINVAL;
}
adev = (struct a2dp_audio_device*)calloc(1, sizeof(struct a2dp_audio_device));
if (!adev) return -ENOMEM;
adev->mutex = new std::recursive_mutex;
adev->device.common.tag = HARDWARE_DEVICE_TAG;
adev->device.common.version = AUDIO_DEVICE_API_VERSION_2_0;
adev->device.common.module = (struct hw_module_t*)module;
adev->device.common.close = adev_close;
adev->device.init_check = adev_init_check;
adev->device.set_voice_volume = adev_set_voice_volume;
adev->device.set_master_volume = adev_set_master_volume;
adev->device.set_mode = adev_set_mode;
adev->device.set_mic_mute = adev_set_mic_mute;
adev->device.get_mic_mute = adev_get_mic_mute;
adev->device.set_parameters = adev_set_parameters;
adev->device.get_parameters = adev_get_parameters;
adev->device.get_input_buffer_size = adev_get_input_buffer_size;
adev->device.open_output_stream = adev_open_output_stream;
adev->device.close_output_stream = adev_close_output_stream;
adev->device.open_input_stream = adev_open_input_stream;
adev->device.close_input_stream = adev_close_input_stream;
adev->device.dump = adev_dump;
adev->output = NULL;
*device = &adev->device.common;
return 0;
}
该函数是用于打开音频设备,在音频硬件抽象层(HAL)模块中起着关键作用。函数接收表示硬件模块、设备名称以及用于返回设备指针的参数,主要完成创建并初始化一个 a2dp_audio_device
结构体类型的音频设备对象,设置该对象相关的属性和函数指针(对应各种音频设备操作函数),最后将初始化好的设备对象指针通过参数返回给调用者。整个过程旨在为后续的音频设备相关操作(如音量设置、模式设置、流的打开与关闭等)准备好可用的设备对象。
2.4. adev_open_output_stream
packages/modules/Bluetooth/system/audio_a2dp_hw/src/audio_a2dp_hw.cc
static int adev_open_output_stream(struct audio_hw_device* dev,
UNUSED_ATTR audio_io_handle_t handle,
UNUSED_ATTR audio_devices_t devices,
UNUSED_ATTR audio_output_flags_t flags,
struct audio_config* config,
struct audio_stream_out** stream_out,
UNUSED_ATTR const char* address)
{
struct a2dp_audio_device* a2dp_dev = (struct a2dp_audio_device*)dev;
struct a2dp_stream_out* out;
int ret = 0;
INFO("opening output");
// protect against adev->output and stream_out from being inconsistent
std::lock_guard<std::recursive_mutex> lock(*a2dp_dev->mutex);
out = (struct a2dp_stream_out*)calloc(1, sizeof(struct a2dp_stream_out));
if (!out) return -ENOMEM;
out->stream.common.get_sample_rate = out_get_sample_rate;
out->stream.common.set_sample_rate = out_set_sample_rate;
out->stream.common.get_buffer_size = out_get_buffer_size;
out->stream.common.get_channels = out_get_channels;
out->stream.common.get_format = out_get_format;
out->stream.common.set_format = out_set_format;
out->stream.common.standby = out_standby;
out->stream.common.dump = out_dump;
out->stream.common.set_parameters = out_set_parameters;
out->stream.common.get_parameters = out_get_parameters;
out->stream.common.add_audio_effect = out_add_audio_effect;
out->stream.common.remove_audio_effect = out_remove_audio_effect;
out->stream.get_latency = out_get_latency;
out->stream.set_volume = out_set_volume;
out->stream.write = out_write;
out->stream.get_render_position = out_get_render_position;
out->stream.get_presentation_position = out_get_presentation_position;
/* initialize a2dp specifics */
a2dp_stream_common_init(&out->common);
// Make sure we always have the feeding parameters configured
btav_a2dp_codec_config_t codec_config;
btav_a2dp_codec_config_t codec_capability;
if (a2dp_read_output_audio_config(&out->common, &codec_config,
&codec_capability,
true /* update_stream_config */) < 0) {
ERROR("a2dp_read_output_audio_config failed");
ret = -1;
goto err_open;
}
// a2dp_read_output_audio_config() opens the socket control path (or fails)
/* set output config values */
if (config != nullptr) {
// Try to use the config parameters and send it to the remote side
// TODO: Shall we use out_set_format() and similar?
if (config->format != 0) out->common.cfg.format = config->format;
if (config->sample_rate != 0) out->common.cfg.rate = config->sample_rate;
if (config->channel_mask != 0)
out->common.cfg.channel_mask = config->channel_mask;
if ((out->common.cfg.format != 0) || (out->common.cfg.rate != 0) ||
(out->common.cfg.channel_mask != 0)) {
if (a2dp_write_output_audio_config(&out->common) < 0) {
ERROR("a2dp_write_output_audio_config failed");
ret = -1;
goto err_open;
}
// Read again and make sure we use the same parameters as the remote side
if (a2dp_read_output_audio_config(&out->common, &codec_config,
&codec_capability,
true /* update_stream_config */) < 0) {
ERROR("a2dp_read_output_audio_config failed");
ret = -1;
goto err_open;
}
}
config->format = out_get_format((const struct audio_stream*)&out->stream);
config->sample_rate =
out_get_sample_rate((const struct audio_stream*)&out->stream);
config->channel_mask =
out_get_channels((const struct audio_stream*)&out->stream);
INFO(
"Output stream config: format=0x%x sample_rate=%d channel_mask=0x%x "
"buffer_sz=%zu",
config->format, config->sample_rate, config->channel_mask,
out->common.buffer_sz);
}
*stream_out = &out->stream;
a2dp_dev->output = out;
DEBUG("success");
/* Delay to ensure Headset is in proper state when START is initiated from
* DUT immediately after the connection due to ongoing music playback. */
usleep(250000);
return 0;
err_open:
a2dp_stream_common_destroy(&out->common);
free(out);
*stream_out = NULL;
a2dp_dev->output = NULL;
ERROR("failed");
return ret;
}
该函数用于打开音频输出流,主要完成创建并初始化音频输出流对象、设置流对象的各种属性和操作函数指针、根据配置参数与远程设备进行音频配置协商、处理可能出现的错误情况以及最终返回初始化好的音频输出流对象给调用者等工作,整个过程旨在为后续音频数据通过该输出流向外部设备(如蓝牙耳机)进行播放做好准备。
- 设置输出流接口:填充
out->stream.common
结构体成员,包括获取和设置采样率、缓冲区大小、通道数、格式等函数指针,以及待机、dump、设置和获取参数、添加和移除音频效果等函数。设置特定的A2DP输出流函数,如获取延迟、设置音量、写入数据以及获取渲染和呈现位置。- 初始化A2DP特定部分:调用
a2dp_stream_common_init
函数来初始化A2DP输出流的通用部分。- 读取和设置音频配置:
- 读取A2DP输出音频配置,并检查是否成功。这一步也打开了与控制路径的套接字连接。
- 如果传入了有效的
config
参数,则尝试使用这些配置参数,并通过a2dp_write_output_audio_config
函数将它们发送到远程设备。- 再次读取A2DP输出音频配置,以确保与远程设备使用的参数一致。
- 更新
config
结构体中的格式、采样率和通道掩码,以反映实际使用的配置。- 返回输出流指针:将
out->stream
的地址赋值给*stream_out
,这样调用者就可以通过这个指针访问输出流。将out
赋值给a2dp_dev->output
,以便后续使用。- 延迟处理:使用
usleep
函数引入一个短暂的延迟(250毫秒),以确保在连接后立即从被测设备(DUT)启动时,耳机处于正确状态。这可能是为了处理正在进行的音乐播放等场景。
a2dp_read_output_audio_config
packages/modules/Bluetooth/system/audio_a2dp_hw/src/audio_a2dp_hw.cc
static int a2dp_read_output_audio_config(
struct a2dp_stream_common* common, btav_a2dp_codec_config_t* codec_config,
btav_a2dp_codec_config_t* codec_capability, bool update_stream_config) {
struct a2dp_config stream_config;
if (a2dp_command(common, A2DP_CTRL_GET_OUTPUT_AUDIO_CONFIG) < 0) {
ERROR("get a2dp output audio config failed");
return -1;
}
// Receive the current codec config
if (a2dp_ctrl_receive(common, &codec_config->sample_rate,
sizeof(btav_a2dp_codec_sample_rate_t)) < 0) {
return -1;
}
if (a2dp_ctrl_receive(common, &codec_config->bits_per_sample,
sizeof(btav_a2dp_codec_bits_per_sample_t)) < 0) {
return -1;
}
if (a2dp_ctrl_receive(common, &codec_config->channel_mode,
sizeof(btav_a2dp_codec_channel_mode_t)) < 0) {
return -1;
}
// Receive the current codec capability
if (a2dp_ctrl_receive(common, &codec_capability->sample_rate,
sizeof(btav_a2dp_codec_sample_rate_t)) < 0) {
return -1;
}
if (a2dp_ctrl_receive(common, &codec_capability->bits_per_sample,
sizeof(btav_a2dp_codec_bits_per_sample_t)) < 0) {
return -1;
}
if (a2dp_ctrl_receive(common, &codec_capability->channel_mode,
sizeof(btav_a2dp_codec_channel_mode_t)) < 0) {
return -1;
}
// Check the codec config sample rate
switch (codec_config->sample_rate) {
case BTAV_A2DP_CODEC_SAMPLE_RATE_44100:
stream_config.rate = 44100;
break;
case BTAV_A2DP_CODEC_SAMPLE_RATE_48000:
stream_config.rate = 48000;
break;
case BTAV_A2DP_CODEC_SAMPLE_RATE_88200:
stream_config.rate = 88200;
break;
case BTAV_A2DP_CODEC_SAMPLE_RATE_96000:
stream_config.rate = 96000;
break;
case BTAV_A2DP_CODEC_SAMPLE_RATE_176400:
stream_config.rate = 176400;
break;
case BTAV_A2DP_CODEC_SAMPLE_RATE_192000:
stream_config.rate = 192000;
break;
case BTAV_A2DP_CODEC_SAMPLE_RATE_NONE:
default:
ERROR("Invalid sample rate: 0x%x", codec_config->sample_rate);
return -1;
}
// Check the codec config bits per sample
switch (codec_config->bits_per_sample) {
case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16:
stream_config.format = AUDIO_FORMAT_PCM_16_BIT;
break;
case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24:
stream_config.format = AUDIO_FORMAT_PCM_24_BIT_PACKED;
break;
case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_32:
stream_config.format = AUDIO_FORMAT_PCM_32_BIT;
break;
case BTAV_A2DP_CODEC_BITS_PER_SAMPLE_NONE:
default:
ERROR("Invalid bits per sample: 0x%x", codec_config->bits_per_sample);
return -1;
}
// Check the codec config channel mode
switch (codec_config->channel_mode) {
case BTAV_A2DP_CODEC_CHANNEL_MODE_MONO:
stream_config.channel_mask = AUDIO_CHANNEL_OUT_MONO;
stream_config.is_stereo_to_mono = true;
break;
case BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO:
stream_config.channel_mask = AUDIO_CHANNEL_OUT_STEREO;
stream_config.is_stereo_to_mono = false;
break;
case BTAV_A2DP_CODEC_CHANNEL_MODE_NONE:
default:
ERROR("Invalid channel mode: 0x%x", codec_config->channel_mode);
return -1;
}
if (stream_config.is_stereo_to_mono) {
stream_config.channel_mask = AUDIO_CHANNEL_OUT_STEREO;
}
// Update the output stream configuration
if (update_stream_config) {
common->cfg.rate = stream_config.rate;
common->cfg.channel_mask = stream_config.channel_mask;
common->cfg.is_stereo_to_mono = stream_config.is_stereo_to_mono;
common->cfg.format = stream_config.format;
common->buffer_sz = audio_a2dp_hw_stream_compute_buffer_size(
codec_config->sample_rate, codec_config->bits_per_sample,
codec_config->channel_mode);
if (common->cfg.is_stereo_to_mono) {
// We need to fetch twice as much data from the Audio framework
common->buffer_sz *= 2;
}
}
INFO(
"got output codec config (update_stream_config=%s): "
"sample_rate=0x%x bits_per_sample=0x%x channel_mode=0x%x",
update_stream_config ? "true" : "false", codec_config->sample_rate,
codec_config->bits_per_sample, codec_config->channel_mode);
INFO(
"got output codec capability: sample_rate=0x%x bits_per_sample=0x%x "
"channel_mode=0x%x",
codec_capability->sample_rate, codec_capability->bits_per_sample,
codec_capability->channel_mode);
return 0;
}
该函数主要用于读取 A2DP输出音频的相关配置信息,包括获取当前的编解码器配置(codec_config
)、编解码器能力(codec_capability
),并依据获取到的配置信息进行一系列有效性检查和相应的处理,最后根据传入的参数决定是否更新音频流的配置(update_stream_config
)。这个函数在蓝牙音频传输相关的系统中起着关键作用,确保音频输出能按照正确的配置参数进行,保障音频播放的质量和效果。
- 发送获取配置命令:首先,向A2DP控制器发送一个命令,以获取当前的输出音频配置。
- 接收编解码器配置:通过
a2dp_ctrl_receive
函数接收采样率、每样本位数和通道模式等编解码器配置信息。- 接收编解码器能力:同样地,接收编解码器的能力信息,包括支持的采样率、每样本位数和通道模式。
- 验证和转换配置:
- 检查采样率,并将其转换为对应的数值。
- 检查每样本位数,并设置音频格式。
- 检查通道模式,并设置通道掩码和是否将立体声转换为单声道的标志。
- 更新流配置:如果
update_stream_config
为真,则根据接收到的配置更新A2DP流的配置,并计算缓冲区大小。如果需要将立体声转换为单声道,缓冲区大小将加倍。
a2dp_command
packages/modules/Bluetooth/system/audio_a2dp_hw/src/audio_a2dp_hw.cc
static int a2dp_command(struct a2dp_stream_common* common, tA2DP_CTRL_CMD cmd) {
char ack;
DEBUG("A2DP COMMAND %s", audio_a2dp_hw_dump_ctrl_event(cmd));
if (common->ctrl_fd == AUDIO_SKT_DISCONNECTED) {
INFO("starting up or recovering from previous error: command=%s",
audio_a2dp_hw_dump_ctrl_event(cmd));
a2dp_open_ctrl_path(common);
if (common->ctrl_fd == AUDIO_SKT_DISCONNECTED) {
ERROR("failure to open ctrl path: command=%s",
audio_a2dp_hw_dump_ctrl_event(cmd));
return -1;
}
}
/* send command */
ssize_t sent;
OSI_NO_INTR(sent = send(common->ctrl_fd, &cmd, 1, MSG_NOSIGNAL));
if (sent == -1) {
ERROR("cmd failed (%s): command=%s", strerror(errno),
audio_a2dp_hw_dump_ctrl_event(cmd));
skt_disconnect(common->ctrl_fd);
common->ctrl_fd = AUDIO_SKT_DISCONNECTED;
return -1;
}
/* wait for ack byte */
if (a2dp_ctrl_receive(common, &ack, 1) < 0) {
ERROR("A2DP COMMAND %s: no ACK", audio_a2dp_hw_dump_ctrl_event(cmd));
return -1;
}
DEBUG("A2DP COMMAND %s DONE STATUS %d", audio_a2dp_hw_dump_ctrl_event(cmd),
ack);
if (ack == A2DP_CTRL_ACK_INCALL_FAILURE) {
ERROR("A2DP COMMAND %s error %d", audio_a2dp_hw_dump_ctrl_event(cmd), ack);
return ack;
}
if (ack != A2DP_CTRL_ACK_SUCCESS) {
ERROR("A2DP COMMAND %s error %d", audio_a2dp_hw_dump_ctrl_event(cmd), ack);
return -1;
}
return 0;
}
该函数主要用于发送命令给对应的设备(蓝牙耳机等通过 A2DP 连接的远程设备),并等待接收相应的确认(ACK)信息以确保命令被成功接收和处理。关键点在于它处理与A2DP设备的控制命令交互,包括发送命令、等待响应以及处理响应。使用了套接字编程来与A2DP设备进行通信,并通过检查ACK值来确定命令是否成功执行。如果命令失败,函数会适当地清理资源(如断开套接字连接)并返回错误代码。
- 检查控制套接字状态:如果
common->ctrl_fd
(控制套接字的文件描述符)是AUDIO_SKT_DISCONNECTED
(套接字未连接),则尝试通过a2dp_open_ctrl_path
函数重新打开控制路径。如果重新打开后仍然未连接,则打印错误信息并返回-1。- 发送命令:使用
send
函数向控制套接字发送命令。OSI_NO_INTR
宏用于禁用中断,以确保send
函数不会被中断。如果发送失败,则打印错误信息,断开套接字连接,将common->ctrl_fd
设置为AUDIO_SKT_DISCONNECTED
,并返回-1。- 等待响应:使用
a2dp_ctrl_receive
函数等待A2DP设备的响应(ACK字节)。如果未收到响应,则打印错误信息并返回-1。- 处理响应:如果ACK是
A2DP_CTRL_ACK_INCALL_FAILURE
(表示在通话中失败),则打印错误信息并返回ACK值。如果ACK不是A2DP_CTRL_ACK_SUCCESS
(表示成功),则打印错误信息并返回-1。
a2dp_open_ctrl_path
packages/modules/Bluetooth/system/audio_a2dp_hw/src/audio_a2dp_hw.cc
static void a2dp_open_ctrl_path(struct a2dp_stream_common* common) {
int i;
if (common->ctrl_fd != AUDIO_SKT_DISCONNECTED) return; // already connected
/* retry logic to catch any timing variations on control channel */
for (i = 0; i < CTRL_CHAN_RETRY_COUNT; i++) {
/* connect control channel if not already connected */
if ((common->ctrl_fd = skt_connect(
A2DP_CTRL_PATH, AUDIO_STREAM_CONTROL_OUTPUT_BUFFER_SZ)) >= 0) {
/* success, now check if stack is ready */
if (check_a2dp_ready(common) == 0) break;
ERROR("error : a2dp not ready, wait 250 ms and retry");
usleep(250000);
skt_disconnect(common->ctrl_fd);
common->ctrl_fd = AUDIO_SKT_DISCONNECTED;
}
/* ctrl channel not ready, wait a bit */
usleep(250000);
}
}
该函数的主要目的是打开 A2DP相关音频流的控制路径,也就是建立与远程设备(例如蓝牙耳机等通过 A2DP 连接的设备)之间用于发送控制命令和接收反馈的通信通道。它针对可能出现的连接失败情况采用了重试机制,在每次重试时尝试进行连接操作,若连接成功还会进一步检查 A2DP 相关的准备状态,只有当准备状态也符合要求时才认定控制路径打开成功,整个过程旨在确保可靠地建立起有效的控制路径,为后续 A2DP 命令交互等操作奠定基础。
- 检查控制通道状态:如果
common->ctrl_fd
不是AUDIO_SKT_DISCONNECTED
(控制通道已经连接),则函数立即返回,不执行任何操作。- 重试逻辑:使用一个循环来尝试连接控制通道,循环次数由
CTRL_CHAN_RETRY_COUNT
定义。这个宏可能表示在放弃之前尝试连接的次数。- 连接控制通道:在循环内部,使用
skt_connect
函数尝试连接到A2DP设备的控制通道。如果连接成功(即skt_connect
返回的文件描述符非负),则检查A2DP堆栈是否准备就绪。- 检查A2DP堆栈状态:使用
check_a2dp_ready
函数检查A2DP堆栈是否准备就绪。通过发送特定的命令或查询设备状态来确定A2DP是否可用。如果A2DP协议栈准备就绪(check_a2dp_ready
返回0),则跳出循环。- 处理连接失败:如果A2DP堆栈未准备就绪或连接失败,则打印错误信息,等待250毫秒(
usleep(250000)
),然后断开套接字连接,并将common->ctrl_fd
设置为AUDIO_SKT_DISCONNECTED
以表示控制通道未连接。- 等待并重试:在每次尝试连接之后(无论成功还是失败),都等待250毫秒,然后再次尝试连接,直到达到重试次数限制。
skt_connect
packages/modules/Bluetooth/system/audio_a2dp_hw/src/audio_a2dp_hw.cc
/*****************************************************************************
*
* bluedroid stack adaptation
*
****************************************************************************/
static int skt_connect(const char* path, size_t buffer_sz) {
int ret;
int skt_fd;
int len;
INFO("connect to %s (sz %zu)", path, buffer_sz);
skt_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (osi_socket_local_client_connect(
skt_fd, path, ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM) < 0) {
ERROR("failed to connect (%s)", strerror(errno));
close(skt_fd);
return -1;
}
len = buffer_sz;
ret =
setsockopt(skt_fd, SOL_SOCKET, SO_SNDBUF, (char*)&len, (int)sizeof(len));
if (ret < 0) ERROR("setsockopt failed (%s)", strerror(errno));
ret =
setsockopt(skt_fd, SOL_SOCKET, SO_RCVBUF, (char*)&len, (int)sizeof(len));
if (ret < 0) ERROR("setsockopt failed (%s)", strerror(errno));
/* Socket send/receive timeout value */
struct timeval tv;
tv.tv_sec = SOCK_SEND_TIMEOUT_MS / 1000;
tv.tv_usec = (SOCK_SEND_TIMEOUT_MS % 1000) * 1000;
ret = setsockopt(skt_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
if (ret < 0) ERROR("setsockopt failed (%s)", strerror(errno));
tv.tv_sec = SOCK_RECV_TIMEOUT_MS / 1000;
tv.tv_usec = (SOCK_RECV_TIMEOUT_MS % 1000) * 1000;
ret = setsockopt(skt_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
if (ret < 0) ERROR("setsockopt failed (%s)", strerror(errno));
INFO("connected to stack fd = %d", skt_fd);
return skt_fd;
}
skt_connect 函数主要用于创建并配置一个本地套接字(socket)连接,其目标是与指定路径对应的服务或端点建立可靠的通信链路,函数接收要连接的路径以及缓冲区大小作为参数,在内部完成套接字的创建、连接操作,并对套接字的发送缓冲区、接收缓冲区以及发送和接收超时时间等关键属性进行设置,最终返回创建并配置好的套接字文件描述符(如果连接成功),或者返回 -1
表示连接失败。
- 创建套接字:调用
socket
函数创建一个本地(AF_LOCAL
)流(SOCK_STREAM
)套接字,并将返回的文件描述符存储在skt_fd
中。- 连接到套接字:使用
osi_socket_local_client_connect
函数尝试连接到指定的套接字路径。这个函数可能是特定于操作系统或框架的,用于处理抽象命名空间内的套接字连接。如果连接失败,则打印错误信息,关闭套接字,并返回-1。- 设置套接字选项:使用
setsockopt
函数设置套接字的发送(SO_SNDBUF
)和接收(SO_RCVBUF
)缓冲区大小。接着,设置套接字的发送(SO_SNDTIMEO
)和接收(SO_RCVTIMEO
)超时值。这些值由SOCK_SEND_TIMEOUT_MS
和SOCK_RECV_TIMEOUT_MS
宏定义,表示以毫秒为单位的超时时间。- 返回套接字文件描述符:函数返回套接字文件描述符
skt_fd
。如果连接失败,则返回-1。
建立了一个socket连接,连接的path是"/data/misc/bluedroid/.a2dp_data",后续audio传下来的数据只要写到common->audio_fd就可以了。
音频数据生成到通过这个机制发送到蓝牙设备的大致过程:
- 音频源产生数据:应用层(如音乐播放器应用)读取音频文件(比如 MP3 文件),通过解码器将其解码为原始的音频采样数据(例如 PCM 格式数据),这些音频数据准备好后会传递给系统的音频框架层。
- 音频框架处理:安卓系统中的音频框架(
AudioFlinger
组件)接收到音频数据后,可能会进行一系列操作,如根据当前系统音频状态(是否有其他音频正在播放、音量设置等情况)对音频数据进行混音(如果需要)、调整音量、转换为适合蓝牙传输的音频格式(如果和原始格式不同)等处理,将音频数据处理成符合A2DP
协议要求的格式和参数状态。 - 数据传递到
common->audio_fd
:经过音频框架处理后的音频数据,会通过相应的路由机制找到对应的common->audio_fd
,然后将音频数据写入到这个文件描述符所关联的缓冲区或者传输通道中,这个过程中会遵循一些事先约定好的协议或者规范,比如按照固定的数据包大小写入、添加特定的标识信息等,确保数据能够被正确识别和后续处理。 - 底层处理与蓝牙传输:一旦音频数据写入到
common->audio_fd
,蓝牙协议栈以及相关硬件驱动程序就开始发挥作用。它们会对数据进行进一步封装,添加A2DP
协议相关的头部信息(包含音频流标识、时间戳等用于同步和识别的信息),如果有加密要求还会进行加密处理,然后将数据通过蓝牙无线链路以合适的方式(比如按照一定的传输速率、频率等参数)发送到已配对的蓝牙耳机等蓝牙音频设备上。 - 蓝牙设备接收与播放:蓝牙耳机接收到通过蓝牙链路传输过来的音频数据后,其内部的音频处理模块会对数据进行解封装、解密(如果有加密)、解码(将数据转换为可以驱动扬声器发声的模拟信号等形式)等操作,最终通过耳机的扬声器将音频播放出来,让用户能够听到声音。
这种基于特定 socket 连接路径以及通过
common->audio_fd
来传输音频数据的机制,是安卓系统中实现蓝牙音频播放功能的一个重要环节,涉及多个层次、多个组件之间的紧密协作,每个环节都对最终音频能否准确、高质量地传输到蓝牙设备并播放起着关键作用。
2.5. out_write
packages/modules/Bluetooth/system/audio_a2dp_hw/src/audio_a2dp_hw.cc
/*****************************************************************************
*
* audio output callbacks
*
****************************************************************************/
static ssize_t out_write(struct audio_stream_out* stream, const void* buffer,
size_t bytes) {
struct a2dp_stream_out* out = (struct a2dp_stream_out*)stream;
int sent = -1;
size_t write_bytes = bytes;
DEBUG("write %zu bytes (fd %d)", bytes, out->common.audio_fd);
std::unique_lock<std::recursive_mutex> lock(*out->common.mutex);
if (out->common.state == AUDIO_A2DP_STATE_SUSPENDED ||
out->common.state == AUDIO_A2DP_STATE_STOPPING) {
DEBUG("stream suspended or closing");
goto finish;
}
/* only allow autostarting if we are in stopped or standby */
if ((out->common.state == AUDIO_A2DP_STATE_STOPPED) ||
(out->common.state == AUDIO_A2DP_STATE_STANDBY)) {
if (start_audio_datapath(&out->common) < 0) {
goto finish;
}
} else if (out->common.state != AUDIO_A2DP_STATE_STARTED) {
ERROR("stream not in stopped or standby");
goto finish;
}
// Mix the stereo into mono if necessary
if (out->common.cfg.is_stereo_to_mono) {
const size_t frames = bytes / audio_stream_out_frame_size(stream);
int16_t* src = (int16_t*)buffer;
int16_t* dst = (int16_t*)buffer;
for (size_t i = 0; i < frames; i++, dst++, src += 2) {
*dst = (int16_t)(((int32_t)src[0] + (int32_t)src[1]) >> 1);
}
write_bytes /= 2;
DEBUG("stereo-to-mono mixing: write %zu bytes (fd %d)", write_bytes,
out->common.audio_fd);
}
lock.unlock();
sent = skt_write(out->common.audio_fd, buffer, write_bytes);
lock.lock();
if (sent == -1) {
skt_disconnect(out->common.audio_fd);
out->common.audio_fd = AUDIO_SKT_DISCONNECTED;
if ((out->common.state != AUDIO_A2DP_STATE_SUSPENDED) &&
(out->common.state != AUDIO_A2DP_STATE_STOPPING)) {
out->common.state = AUDIO_A2DP_STATE_STOPPED;
} else {
ERROR("write failed : stream suspended, avoid resetting state");
}
goto finish;
}
finish:;
const size_t frames = bytes / audio_stream_out_frame_size(stream);
out->frames_rendered += frames;
out->frames_presented += frames;
lock.unlock();
// If send didn't work out, sleep to emulate write delay.
if (sent == -1) {
const int us_delay = calc_audiotime_usec(out->common.cfg, bytes);
DEBUG("emulate a2dp write delay (%d us)", us_delay);
usleep(us_delay);
}
return bytes;
}
out_write 函数是音频输出的回调函数之一,主要负责负责将音频数据写入到对应的音频输出流中,以便通过蓝牙将音频发送到外部设备(如蓝牙耳机)进行播放。它接收音频流对象指针、指向音频数据缓冲区的指针以及要写入的数据字节数作为参数,在函数内部会进行状态检查、必要的音频数据处理(如立体声转单声道混合)、实际的数据写入操作以及对写入失败等情况的处理,同时还会更新与音频播放进度相关的一些统计信息,旨在保障音频数据能够按照正确的流程和状态被可靠地写入并传输,进而实现音频的正常播放。
- 互斥锁:使用
std::unique_lock
和std::recursive_mutex
来保护对共享资源的访问。确保了在修改音频流状态时不会有并发访问。- 状态检查:检查音频流的状态。如果流处于挂起或停止状态,则不写入数据。
- 自动启动:如果流处于停止或待机状态,并且允许自动启动,则尝试启动音频数据路径。
- 立体声到单声道转换:如果配置要求将立体声转换为单声道,则执行转换。涉及到遍历缓冲区中的样本,将每对立体声样本的平均值作为单声道样本。
- 解锁和写入:在写入数据之前解锁互斥锁,以避免死锁。写入后,重新锁定互斥锁。
- 写入错误处理:如果写入失败(
skt_write
返回-1),则断开套接字连接,更新音频流状态,并跳转到finish
标签。- 更新帧计数器:无论写入是否成功,都会更新已渲染和已呈现的帧计数器。
- 重新锁定和解锁:在处理完写入逻辑后,释放互斥锁。
- 写入失败后的延迟模拟:如果写入失败,计算一个延迟时间(基于配置和要写入的字节数),并使用
usleep
函数模拟这个延迟。- 返回值:函数返回尝试写入的字节数(注意:即使写入失败,也返回原始请求的字节数,这可能是一个错误,因为通常应该返回实际写入的字节数或-1表示错误)。
start_audio_datapath
packages/modules/Bluetooth/system/audio_a2dp_hw/src/audio_a2dp_hw.cc
static int start_audio_datapath(struct a2dp_stream_common* common) {
INFO("state %d", common->state);
int oldstate = common->state;
common->state = AUDIO_A2DP_STATE_STARTING;
int a2dp_status = a2dp_command(common, A2DP_CTRL_CMD_START);
if (a2dp_status < 0) {
ERROR("Audiopath start failed (status %d)", a2dp_status);
goto error;
} else if (a2dp_status == A2DP_CTRL_ACK_INCALL_FAILURE) {
ERROR("Audiopath start failed - in call, move to suspended");
goto error;
}
/* connect socket if not yet connected */
if (common->audio_fd == AUDIO_SKT_DISCONNECTED) {
common->audio_fd = skt_connect(A2DP_DATA_PATH, common->buffer_sz);
if (common->audio_fd < 0) {
ERROR("Audiopath start failed - error opening data socket");
goto error;
}
}
common->state = (a2dp_state_t)AUDIO_A2DP_STATE_STARTED;
/* check to see if delay reporting is enabled */
enable_delay_reporting = delay_reporting_enabled();
return 0;
error:
common->state = (a2dp_state_t)oldstate;
return -1;
}
该函数主要负责启动音频数据路径,在基于 A2DP
的蓝牙音频传输场景下,要进行一系列操作来准备好音频数据从本地设备传输到远程蓝牙音频设备(如蓝牙耳机)的通道和相关状态。它接收一个指向 a2dp_stream_common
结构体的指针(其中包含了音频流相关的公共信息,如当前状态、音频文件描述符、缓冲区大小等关键元素),函数内部会更新状态、发送启动命令、建立必要的套接字连接(如果尚未连接),并根据操作结果进行相应的状态维护和错误处理,最终返回表示启动成功(返回 0
)或失败(返回 -1
)的状态码,旨在确保音频数据路径能够正确开启,为后续音频数据的可靠传输奠定基础。
- 状态更新:保存当前状态到
oldstate
变量,并将状态更新为AUDIO_A2DP_STATE_STARTING
,表示音频数据路径正在启动。- 发送A2DP控制命令:
- 调用
a2dp_command
函数发送一个启动命令(A2DP_CTRL_CMD_START
)到A2DP控制器。- 如果
a2dp_command
返回一个小于0的值,表示启动失败。- 如果
a2dp_command
返回A2DP_CTRL_ACK_INCALL_FAILURE
,表示由于当前正在通话中而无法启动音频路径。- 套接字连接:
- 检查
audio_fd
(音频文件描述符)是否已断开连接(AUDIO_SKT_DISCONNECTED
)。- 如果是,则调用
skt_connect
函数尝试连接到A2DP数据路径,并传入数据缓冲区大小。- 如果
skt_connect
返回一个小于0的值,表示连接失败。- 状态更新为启动完成:将状态更新为
AUDIO_A2DP_STATE_STARTED
,表示音频数据路径已成功启动。- 延迟报告:调用
delay_reporting_enabled
函数检查是否启用了延迟报告,并将结果保存到enable_delay_reporting
变量中(。- 成功返回:函数返回0,表示音频数据路径已成功启动。
- 错误处理:
- 如果在启动过程中遇到任何错误,将状态恢复为之前保存的
oldstate
。- 函数返回-1,表示启动失败。
a2dp_command( A2DP_CTRL_CMD_START)
a2dp_command前文有分析,这里不在详细展开。
2.6. btif_a2dp_ctrl_cb(UIPC_RX_DATA_READY_EVT)
packages/modules/Bluetooth/system/btif/src/btif_a2dp_control.cc
static void btif_a2dp_ctrl_cb(tUIPC_CH_ID /* ch_id */, tUIPC_EVENT event) {
// Don't log UIPC_RX_DATA_READY_EVT by default, because it
// could be very chatty when audio is streaming.
if (event == UIPC_RX_DATA_READY_EVT) {
log::verbose("A2DP-CTRL-CHANNEL EVENT {}", dump_uipc_event(event));
} else {
log::warn("A2DP-CTRL-CHANNEL EVENT {}", dump_uipc_event(event));
}
switch (event) {
...
case UIPC_RX_DATA_READY_EVT:
btif_a2dp_recv_ctrl_data();
break;
default:
log::error("### A2DP-CTRL-CHANNEL EVENT {} NOT HANDLED ###", event);
break;
}
}
该函数是一个回调函数,用于处理与 A2DP
控制通道相关的事件。它接收控制通道的标识以及具体的事件类型作为参数,根据不同的事件类型进行相应的处理。当事件类型为 UIPC_RX_DATA_READY_EVT
时,调用 btif_a2dp_recv_ctrl_data
函数以便及时获取并处理控制通道上接收到的新数据,保证 A2DP
控制信息的交互顺畅,维持音频播放等相关操作与远程设备之间的协调一致。
btif_a2dp_recv_ctrl_data
packages/modules/Bluetooth/system/btif/src/btif_a2dp_control.cc
static void btif_a2dp_recv_ctrl_data(void) {
tA2DP_CTRL_CMD cmd = A2DP_CTRL_CMD_NONE;
int n;
uint8_t read_cmd = 0; /* The read command size is one octet */
n = UIPC_Read(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, &read_cmd, 1);
cmd = static_cast<tA2DP_CTRL_CMD>(read_cmd);
/* detach on ctrl channel means audioflinger process was terminated */
if (n == 0) {
log::warn("CTRL CH DETACHED");
UIPC_Close(*a2dp_uipc, UIPC_CH_ID_AV_CTRL);
return;
}
// Don't log A2DP_CTRL_GET_PRESENTATION_POSITION by default, because it
// could be very chatty when audio is streaming.
if (cmd == A2DP_CTRL_GET_PRESENTATION_POSITION) {
log::verbose("a2dp-ctrl-cmd : {}", audio_a2dp_hw_dump_ctrl_event(cmd));
} else {
log::warn("a2dp-ctrl-cmd : {}", audio_a2dp_hw_dump_ctrl_event(cmd));
}
a2dp_cmd_pending = cmd;
switch (cmd) {
...
case A2DP_CTRL_CMD_START:
btif_a2dp_command_ack(btif_a2dp_control_on_start());
break;
...
default:
log::error("UNSUPPORTED CMD ({})", cmd);
btif_a2dp_command_ack(A2DP_CTRL_ACK_FAILURE);
break;
}
// Don't log A2DP_CTRL_GET_PRESENTATION_POSITION by default, because it
// could be very chatty when audio is streaming.
if (cmd == A2DP_CTRL_GET_PRESENTATION_POSITION) {
log::verbose("a2dp-ctrl-cmd : {} DONE", audio_a2dp_hw_dump_ctrl_event(cmd));
} else {
log::warn("a2dp-ctrl-cmd : {} DONE", audio_a2dp_hw_dump_ctrl_event(cmd));
}
}
该函数主要负责接收 A2DP
控制通道上的数据,并根据接收到的数据进行相应的处理和响应。它首先尝试从指定的控制通道读取命令数据,然后依据不同的命令类型执行不同的操作,比如调用对应的功能函数并发送确认信息,同时还会对一些特殊情况(如控制通道断开等)进行处理,旨在确保 A2DP
控制通道上的命令交互能够正确、有序地进行,进而保障整个蓝牙音频系统基于 A2DP
协议的正常运行。
读取控制命令:通过
UIPC_Read
函数从A2DP的UIPC控制通道读取一个字节的数据到read_cmd
中,n
存储实际读取的字节数。处理控制通道断开:如果
n
等于0,表示控制通道已经断开,意味着发送命令的audioflinger
进程已经终止。此时,会关闭A2DP的UIPC控制通道。处理控制命令:根据接收到的命令类型(
cmd
),执行相应的处理函数,并通过btif_a2dp_command_ack
函数发送命令响应。
- 对于
A2DP_CTRL_CMD_START
命令,调用btif_a2dp_control_on_start
函数处理启动请求,并通过btif_a2dp_command_ack
函数发送响应。
UIPC_Read(A2DP_CTRL_CMD_START)
从A2DP的UIPC控制通道读取一个字节的数据到read_cmd
中,n
存储实际读取的字节数。
btif_a2dp_control_on_start
packages/modules/Bluetooth/system/btif/src/btif_a2dp_control.cc
static tA2DP_CTRL_ACK btif_a2dp_control_on_start() {
/*
* Don't send START request to stack while we are in a call.
* Some headsets such as "Sony MW600", don't allow AVDTP START
* while in a call, and respond with BAD_STATE.
*/
if (!bluetooth::headset::IsCallIdle()) {
log::warn("A2DP command start while call state is busy");
return A2DP_CTRL_ACK_INCALL_FAILURE;
}
if (btif_av_stream_ready()) {
/* Setup audio data channel listener */
UIPC_Open(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, btif_a2dp_data_cb,
A2DP_DATA_PATH);
/*
* Post start event and wait for audio path to open.
* If we are the source, the ACK will be sent after the start
* procedure is completed, othewise send it now.
*/
btif_av_stream_start();
if (btif_av_get_peer_sep() == AVDT_TSEP_SRC) return A2DP_CTRL_ACK_SUCCESS;
}
if (btif_av_stream_started_ready()) {
/*
* Already started, setup audio data channel listener and ACK
* back immediately.
*/
UIPC_Open(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, btif_a2dp_data_cb,
A2DP_DATA_PATH);
return A2DP_CTRL_ACK_SUCCESS;
}
log::warn("A2DP command start while AV stream is not ready");
return A2DP_CTRL_ACK_FAILURE;
}
该函数主要处理A2DP控制模块中用于处理启动A2DP音频流请求相关操作,根据当前系统的一些状态条件,如通话状态、音频流准备情况等,来决定是否能够成功启动 A2DP
音频传输,并返回相应的确认信息(A2DP_CTRL_ACK
类型的枚举值,用于告知调用者启动操作的结果),同时在启动过程中还涉及到一些与音频数据通道相关的设置操作,比如打开音频数据通道并设置对应的回调函数,旨在确保 A2DP
启动操作能够按照合适的系统状态和流程来正确执行,保障音频数据后续可以顺利通过蓝牙进行传输。
- 检查通话状态:
- 函数首先检查当前是否处于通话状态(通过
bluetooth::headset::IsCallIdle
函数)。- 如果处于通话状态,则返回
A2DP_CTRL_ACK_INCALL_FAILURE
作为响应,表示在通话期间无法启动A2DP。- 检查AV流是否准备好:通过
btif_av_stream_ready
函数检查A/V(音频/视频)流是否准备好。
- 如果准备好,则打开A2DP的音频数据通道监听器(通过
UIPC_Open
函数),并设置回调函数btif_a2dp_data_cb
和路径A2DP_DATA_PATH
。- 然后,通过
btif_av_stream_start
函数启动A/V流。- 如果当前设备是音频流的源(通过
btif_av_get_peer_sep
函数检查,返回AVDT_TSEP_SRC
表示是源),则直接返回A2DP_CTRL_ACK_SUCCESS
作为响应,表示启动成功。- 检查AV流是否已启动并准备好:如果A/V流已经启动并准备好(通过
btif_av_stream_started_ready
函数检查),则同样打开音频数据通道监听器,并立即返回A2DP_CTRL_ACK_SUCCESS
作为响应。- 处理失败情况:如果上述条件都不满足,则表示A2DP命令启动时A/V流未准备好。返回
A2DP_CTRL_ACK_FAILURE
作为响应,表示启动失败。
UIPC_Open(A2DP_DATA_PATH)
调用 UIPC_Open
函数来打开音频数据通道,并设置对应的回调函数(btif_a2dp_data_cb
),以便后续在音频数据通道有数据到达或者其他相关事件发生时,能够通过这个回调函数进行相应的处理,保障音频数据的接收和处理流程能够正常建立起来。A2DP_DATA_PATH
表示音频数据通道对应的路径信息,用于指定通道的连接目标等情况。
2.7. btif_av_stream_start
启动A/V流
packages/modules/Bluetooth/system/btif/src/btif_av.cc
void btif_av_stream_start(void) {
log::info("");
btif_av_source_dispatch_sm_event(btif_av_source_active_peer(),
BTIF_AV_START_STREAM_REQ_EVT);
}
btif_av_stream_start
函数主要用于启动音频 / 视频(AV)流的传输过程,通过调用 btif_av_source_dispatch_sm_event
函数向相关模块发送一个特定的事件(BTIF_AV_START_STREAM_REQ_EVT
),以此来触发后续一系列与启动 AV
流相关的操作。
btif_av_source_dispatch_sm_event(BTIF_AV_START_STREAM_REQ_EVT
)
packages/modules/Bluetooth/system/btif/src/btif_av.cc
static void btif_av_source_dispatch_sm_event(const RawAddress& peer_address,
btif_av_sm_event_t event) {
BtifAvEvent btif_av_event(event, nullptr, 0);
log::verbose("peer_address={} event={}",
ADDRESS_TO_LOGGABLE_CSTR(peer_address),
btif_av_event.ToString());
do_in_main_thread(FROM_HERE, base::BindOnce(&btif_av_handle_event,
AVDT_TSEP_SNK, // peer_sep
peer_address, kBtaHandleUnknown,
btif_av_event));
}
该函数的主要作用是构建一个 BtifAvEvent
类型的事件对象,然后将这个事件传递给主线程去处理,通过调用 do_in_main_thread
函数结合绑定的 btif_av_handle_event
回调函数来实现,旨在确保与音频 / 视频(AV)流相关的事件能够在主线程的合适环境下按照既定的逻辑进行处理,保障整个蓝牙 AV 流传输相关操作的顺序性和稳定性,避免多线程环境下可能出现的并发问题等。
btif_av_handle_event
packages/modules/Bluetooth/system/btif/src/btif_av.cc
/**
* Process BTIF or 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 peer_address the peer address if known, otherwise RawAddress::kEmpty
* @param bta_handle the BTA handle for the peer if known, otherwise
* kBtaHandleUnknown
* @param btif_av_event the corresponding event
*/
static void btif_av_handle_event(uint8_t peer_sep,
const RawAddress& peer_address,
tBTA_AV_HNDL bta_handle,
const BtifAvEvent& btif_av_event) {
log::debug("Handle event peer_address={} bta_handle=0x{:x}",
ADDRESS_TO_LOGGABLE_CSTR(peer_address), bta_handle);
BtifAvPeer* peer = nullptr;
// Find the peer
if (btif_av_src_sink_coexist_enabled()) {
peer = btif_av_handle_both_peer(peer_sep, peer_address, bta_handle);
} else {
if (peer_address != RawAddress::kEmpty) {
if (peer_sep == AVDT_TSEP_SNK) {
peer = btif_av_source.FindOrCreatePeer(peer_address, bta_handle);
} else if (peer_sep == AVDT_TSEP_SRC) {
peer = btif_av_sink.FindOrCreatePeer(peer_address, bta_handle);
}
} else if (bta_handle != kBtaHandleUnknown) {
if (peer_sep == AVDT_TSEP_SNK) {
peer = btif_av_source.FindPeerByHandle(bta_handle);
} else if (peer_sep == AVDT_TSEP_SRC) {
peer = btif_av_sink.FindPeerByHandle(bta_handle);
}
}
}
if (peer == nullptr) {
log::error(
"jni_thread: Cannot find or create {} peer for peer_address={} "
"bta_handle=0x{:x} : event dropped: {}",
peer_stream_endpoint_text(peer_sep),
ADDRESS_TO_LOGGABLE_CSTR(peer_address), bta_handle,
btif_av_event.ToString());
return;
}
peer->StateMachine().ProcessEvent(btif_av_event.Event(),
btif_av_event.Data());
}
该函数主要用于处理与蓝牙音频 / 视频(BTIF、BTA AV 或 BTA AVRCP 相关)的各类事件,处理操作在 JNI 线程上进行。首先会根据传入的参数(对端设备的角色、地址、蓝牙句柄以及具体的事件信息等)尝试查找对应的对端设备对象(BtifAvPeer
类型),如果能找到或成功创建该对象,就将接收到的事件传递给这个对端设备对象内部的状态机进行处理,通过状态机根据事件类型执行相应的逻辑,从而实现对不同事件的针对性处理,保障蓝牙音频 / 视频传输过程中各类事件能够按照正确的流程被响应,维持整个系统的正常运行。
- 查找对等设备:
- 检查是否启用了源和接收端共存功能(
btif_av_src_sink_coexist_enabled()
)。如果启用了,则调用btif_av_handle_both_peer
来查找或创建对等设备对象。- 如果没有启用共存功能,则根据
peer_address
和bta_handle
的值,以及peer_sep
的类型(源或接收端),分别调用btif_av_source.FindOrCreatePeer
或btif_av_sink.FindOrCreatePeer
(对于接收端)以及btif_av_source.FindPeerByHandle
或btif_av_sink.FindPeerByHandle
(通过句柄查找)来查找或创建对等设备对象。- 处理找不到对等设备的情况:如果无法找到或创建对等设备对象,则返回。
- 处理事件:如果成功找到了对等设备对象,则调用该对象的
StateMachine().ProcessEvent
方法来处理事件。
BtifAvStateMachine::StateOpened::ProcessEvent
packages/modules/Bluetooth/system/btif/src/btif_av.cc
bool BtifAvStateMachine::StateOpened::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()));
if ((event == BTA_AV_REMOTE_CMD_EVT) &&
peer_.CheckFlags(BtifAvPeer::kFlagRemoteSuspend) &&
(p_av->remote_cmd.rc_id == AVRC_ID_PLAY)) {
log::verbose("Peer {} : Resetting remote suspend flag on RC PLAY",
ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()));
peer_.ClearFlags(BtifAvPeer::kFlagRemoteSuspend);
}
switch (event) {
...
case BTIF_AV_START_STREAM_REQ_EVT: {
log::info("Peer {} : event={} flags={}",
ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()),
BtifAvEvent::EventName(event), peer_.FlagsToString());
if (p_data) {
const btif_av_start_stream_req_t* p_start_steam_req =
static_cast<const btif_av_start_stream_req_t*>(p_data);
log::info("Stream use_latency_mode={}",
p_start_steam_req->use_latency_mode ? "true" : "false");
peer_.SetUseLatencyMode(p_start_steam_req->use_latency_mode);
}
BTA_AvStart(peer_.BtaHandle(), peer_.UseLatencyMode());
peer_.SetFlags(BtifAvPeer::kFlagPendingStart);
} break;
...
default:
log::warn("Peer {} : Unhandled event={}",
ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()),
BtifAvEvent::EventName(event));
return false;
}
return true;
}
该函数是 BtifAvStateMachine
中 StateOpened
状态下用于处理各类事件的函数。它接收一个事件类型标识(event
)以及与事件相关的数据指针(p_data
)作为参数,针对特定的事件类型(如 BTA_AV_REMOTE_CMD_EVT
和 BTIF_AV_START_STREAM_REQ_EVT
等)执行相应的处理操作,对于未处理的事件则记录警告日志并返回 false
,处理成功的事件返回 true
,旨在确保在 StateOpened
状态下,不同的事件能够按照预定的规则和逻辑得到恰当的响应,保障蓝牙音频 / 视频流相关操作的正确执行。
事件分发:使用
switch
语句根据事件类型进行分发处理。
- 对于
BTIF_AV_START_STREAM_REQ_EVT
(开始流请求事件):
- 检查
p_data
是否非空。如果不为空,将其转换为btif_av_start_stream_req_t
类型的指针,并从中获取是否使用延迟模式的标志。- 调用
peer_.SetUseLatencyMode
方法设置对等设备是否使用延迟模式。- 调用
BTA_AvStart
函数尝试开始流传输,传入对等设备的BTA句柄和是否使用延迟模式的标志。- 设置对等设备的
BtifAvPeer::kFlagPendingStart
(待开始)标志,表示开始流的请求已经发出,等待确认。
2.8. BTA_AvStart
packages/modules/Bluetooth/system/bta/av/bta_av_api.cc
/*******************************************************************************
*
* Function BTA_AvStart
*
* Description Start audio/video stream data transfer.
*
* Returns void
*
******************************************************************************/
void BTA_AvStart(tBTA_AV_HNDL handle, bool use_latency_mode) {
log::info(
"Starting audio/video stream data transfer bta_handle:{}, "
"use_latency_mode:{}",
handle, use_latency_mode ? "true" : "false");
tBTA_AV_DO_START* p_buf =
(tBTA_AV_DO_START*)osi_malloc(sizeof(tBTA_AV_DO_START));
p_buf->hdr.event = BTA_AV_API_START_EVT;
p_buf->hdr.layer_specific = handle;
p_buf->use_latency_mode = use_latency_mode;
bta_sys_sendmsg(p_buf);
}
该函数的主要作用是启动音频 / 视频(AV)流数据的传输操作。它接收对端设备的蓝牙句柄(handle
)以及是否使用延迟模式(use_latency_mode
)这两个参数,先态分配内存来构建一个特定的消息结构体(tBTA_AV_DO_START
),用于封装启动相关的关键信息(如事件类型、句柄、延迟模式等),最后通过调用 bta_sys_sendmsg
函数将这个消息结构体发送出去,以此来触发系统中其他相关模块执行后续的实际启动音频 / 视频流数据传输的具体操作,是整个蓝牙音频 / 视频流传输启动流程中的一个重要环节。
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
是蓝牙系统中用于向BTA(蓝牙应用层)发送GKI(通用内核接口)消息的函数。
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(蓝牙应用层)事件的函数。主要负责接收一个 BT_HDR_RIGID*
类型的消息指针(包含了事件相关信息),从消息中提取出子系统的标识(id
),然后根据这个标识去验证并调用对应的子系统事件处理程序来处理该消息,如果消息对应的子系统事件处理程序不存在(即未注册),则记录相应的提示信息,最后根据处理结果决定是否释放消息所占用的内存空间,以此确保 BTA
系统中各类事件能够按照注册的子系统及其对应的处理逻辑得到妥善处理,同时合理管理内存资源。
bta_av_hdl_event(BTA_AV_API_START_EVT)
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;
}
该函数是高级音频 / 视频(AV)的主要事件处理函数,用于接收并处理与蓝牙音频 / 视频相关的各类事件消息(通过 BT_HDR_RIGID*
类型的消息指针传入)。根据消息中事件的类型(通过判断事件值所在的范围来区分),分别进行不同的处理操作,比如针对非状态机事件调用相应的处理函数、对状态机事件调用状态机执行函数,以及对特定的流状态机事件调用对应的执行函数等,处理完事件后返回 true
,通常用于指示可以释放传入的消息所占用的内存,以此确保蓝牙 AV 相关的各类事件都能按照其性质和对应的处理逻辑在系统中得到妥善处理,同时合理管理消息内存资源。
bta_av_non_state_machine_event
packages/modules/Bluetooth/system/bta/av/bta_av_main.cc
static void bta_av_non_state_machine_event(uint16_t event,
tBTA_AV_DATA* p_data) {
switch (event) {
...
case BTA_AV_API_START_EVT:
bta_av_api_to_ssm(p_data);
break;
...
}
bta_av_non_state_machine_event
用于处理高级音频 / 视频(AV)相关的非状态机事件。当接收到 BTA_AV_API_START_EVT
类型的事件时,会调用 bta_av_api_to_ssm
函数,将与启动相关的操作从 API 层面传递到流状态机(SSM)相关的处理逻辑中去,以便后续能够基于流状态机的机制来进一步推进音频 / 视频流的启动流程,保障启动操作能按照系统既定的逻辑顺利进行。
bta_av_api_to_ssm
packages/modules/Bluetooth/system/bta/av/bta_av_main.cc
/*******************************************************************************
*
* Function bta_av_api_to_ssm
*
* Description forward the API request to stream state machine
*
*
* Returns void
*
******************************************************************************/
static void bta_av_api_to_ssm(tBTA_AV_DATA* p_data) {
uint16_t event =
p_data->hdr.event - BTA_AV_FIRST_A2S_API_EVT + BTA_AV_FIRST_A2S_SSM_EVT;
tBTA_AV_HNDL handle = p_data->hdr.layer_specific;
tBTA_AV_SCB* p_scb = bta_av_hndl_to_scb(handle);
if (p_scb != nullptr) {
bta_av_ssm_execute(p_scb, event, p_data);
}
}
bta_av_api_to_ssm函数的主要作用是将 API层面的请求转发到流状态机(Stream State Machine,SSM)中进行处理,以此来实现从上层 API 发起的操作(比如启动音频 / 视频流传输等相关请求)能够顺利传递并由流状态机按照其内部逻辑和规则来执行后续的流程,确保整个蓝牙音频 / 视频系统不同层次之间的操作衔接顺畅,保障相关功能(如音频 / 视频流的启动、控制等)能够正确实现。
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
函数是Bluetooth A/V堆栈中处理SSM事件的入口点。首先检查传入的流控制块是否有效,然后调用 bta_av_better_stream_state_machine函数来处理实际的状态机逻辑。这是Bluetooth A/V系统中事件处理和状态管理的一个重要环节,确保了音频/视频流的正确行为和交互。
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_OPEN_SST:
switch (event) {
...
case BTA_AV_AP_START_EVT:
event_handler1 = &bta_av_do_start;
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);
}
}
该函数根据流状态机当前所处的状态(通过 p_scb->state
获取)以及接收到的事件(event
参数)来确定相应的事件处理函数,并执行对应的操作。
bta_av_do_start
packages/modules/Bluetooth/system/bta/av/bta_av_aact.cc
/*******************************************************************************
*
* Function bta_av_do_start
*
* Description Start stream.
*
* Returns void
*
******************************************************************************/
void bta_av_do_start(tBTA_AV_SCB* p_scb, tBTA_AV_DATA* p_data) {
log::info(
"A2dp stream start peer:{} sco_occupied:{} av_role:0x{:x} started:{} "
"wait:0x{:x}",
ADDRESS_TO_LOGGABLE_CSTR(p_scb->PeerAddress()),
logbool(bta_av_cb.sco_occupied), p_scb->role, logbool(p_scb->started),
p_scb->wait);
if (bta_av_cb.sco_occupied) {
log::warn("A2dp stream start failed");
bta_av_start_failed(p_scb, p_data);
return;
}
if (p_scb->started) {
p_scb->role |= BTA_AV_ROLE_START_INT;
if (p_scb->wait != 0) {
log::warn(
"peer {} start stream request ignored: already waiting: "
"sco_occupied:{} role:0x{:x} started:{} wait:0x{:x}",
ADDRESS_TO_LOGGABLE_CSTR(p_scb->PeerAddress()),
logbool(bta_av_cb.sco_occupied), p_scb->role, logbool(p_scb->started),
p_scb->wait);
return;
}
if (p_scb->role & BTA_AV_ROLE_SUSPEND) {
notify_start_failed(p_scb);
} else {
if (p_data) {
bta_av_set_use_latency_mode(p_scb, p_data->do_start.use_latency_mode);
}
bta_av_start_ok(p_scb, NULL);
}
return;
}
if ((p_scb->role & BTA_AV_ROLE_START_INT) != 0) {
log::warn(
"peer {} start stream request ignored: already initiated: "
"sco_occupied:{} role:0x{:x} started:{} wait:0x{:x}",
ADDRESS_TO_LOGGABLE_CSTR(p_scb->PeerAddress()),
logbool(bta_av_cb.sco_occupied), p_scb->role, logbool(p_scb->started),
p_scb->wait);
return;
}
p_scb->role |= BTA_AV_ROLE_START_INT;
bta_sys_busy(BTA_ID_AV, bta_av_cb.audio_open_cnt, p_scb->PeerAddress());
/* disallow role switch during streaming, only if we are the central role
* i.e. allow role switch, if we are peripheral.
* It would not hurt us, if the peer device wants us to be central
* disable sniff mode unconditionally during streaming */
tHCI_ROLE cur_role;
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_for(p_scb->PeerAddress());
}
uint16_t result = AVDT_StartReq(&p_scb->avdt_handle, 1);
if (result != AVDT_SUCCESS) {
log::error("AVDT_StartReq failed for peer {} result:{}",
ADDRESS_TO_LOGGABLE_CSTR(p_scb->PeerAddress()), result);
bta_av_start_failed(p_scb, p_data);
} else if (p_data) {
bta_av_set_use_latency_mode(p_scb, p_data->do_start.use_latency_mode);
}
log::info(
"peer {} start requested: sco_occupied:{} role:0x{:x} started:{} "
"wait:0x{:x}",
ADDRESS_TO_LOGGABLE_CSTR(p_scb->PeerAddress()),
logbool(bta_av_cb.sco_occupied), p_scb->role, logbool(p_scb->started),
p_scb->wait);
}
bta_av_do_start函数的主要功能是启动音频 / 视频流(具体为 A2dp
流相关),在启动过程中,依据多种不同的条件判断来决定具体的启动操作流程,比如检查是否存在 SCO
占用、流是否已经启动、启动是否已经被初始化等情况,根据不同情况执行相应的后续步骤,包括处理启动失败、设置相关参数、向底层模块发起启动请求等操作。
函数处理流程梳理:
SCO占用检查:如果SCO通道被占用,则记录一条警告日志,调用
bta_av_start_failed
函数处理启动失败的情况,并返回。已启动检查:如果流已经启动,则根据当前角色和等待状态决定是否忽略启动请求或更新角色、设置延迟模式,并调用
bta_av_start_ok
函数表示启动成功。启动请求已发起检查:如果启动请求已经由当前设备发起(通过检查
BTA_AV_ROLE_START_INT
标志),则记录一条警告日志并返回,因为不需要重复发起启动请求。设置启动请求:将
BTA_AV_ROLE_START_INT
标志添加到当前角色中,表示启动请求是由当前设备发起的。系统繁忙状态:调用
bta_sys_busy
函数标记系统为繁忙状态,用于管理蓝牙系统的并发操作。角色切换和嗅探模式:根据当前设备是对等设备的中心角色还是外围角色,调用
BTM_block_role_switch_and_sniff_mode_for
或BTM_block_sniff_mode_for
函数来阻止角色切换和/或嗅探模式。为了确保在流传输期间连接的稳定性。发起AVDT启动请求:调用
AVDT_StartReq
函数向对等设备发起A2DP流的启动请求。如果请求失败,则记录一条错误日志并调用bta_av_start_failed
函数处理失败情况。设置延迟模式:如果
p_data
非空且AVDT启动请求成功,则根据p_data->do_start.use_latency_mode
的值设置延迟模式。
AVDT_StartReq
packages/modules/Bluetooth/system/stack/avdt/avdt_api.cc
/*******************************************************************************
*
* Function AVDT_StartReq
*
* Description Start one or more stream endpoints. This initiates the
* transfer of media packets for the streams. All stream
* endpoints must previously be opened. When the streams
* are started, an AVDT_START_CFM_EVT is sent to the
* application via the control callback function for each
* stream.
*
*
* Returns AVDT_SUCCESS if successful, otherwise error.
*
******************************************************************************/
uint16_t AVDT_StartReq(uint8_t* p_handles, uint8_t num_handles) {
AvdtpScb* p_scb = NULL;
tAVDT_CCB_EVT evt;
uint16_t result = AVDT_SUCCESS;
int i;
log::verbose("num_handles={}", num_handles);
if ((num_handles == 0) || (num_handles > AVDT_NUM_SEPS)) {
result = AVDT_BAD_PARAMS;
} else {
/* verify handles */
for (i = 0; i < num_handles; i++) {
p_scb = avdt_scb_by_hdl(p_handles[i]);
if (p_scb == NULL) {
result = AVDT_BAD_HANDLE;
break;
}
}
}
if (result == AVDT_SUCCESS) {
if (p_scb->p_ccb == NULL) {
result = AVDT_BAD_HANDLE;
} else {
/* send event to ccb */
memcpy(evt.msg.multi.seid_list, p_handles, num_handles);
evt.msg.multi.num_seps = num_handles;
avdt_ccb_event(p_scb->p_ccb, AVDT_CCB_API_START_REQ_EVT, &evt);
}
}
if (result != AVDT_SUCCESS) {
if ((num_handles == 0) || (num_handles > AVDT_NUM_SEPS)) {
log::error("result={} num_handles={} invalid", result, num_handles);
} else {
log::error("result={} avdt_handle={}", result,
(i < num_handles ? p_handles[i] : p_handles[num_handles - 1]));
}
}
return result;
}
AVDT_StartReq函数用于启动一个或多个流端点(stream endpoints),其目的是发起媒体数据包在这些流上的传输操作。在调用此函数前,所有相关的流端点都应该已经被打开。如果启动成功,会通过控制回调函数针对每个流向应用程序发送 AVDT_START_CFM_EVT
事件,函数最终会返回表示操作成功与否的结果(AVDT_SUCCESS
表示成功,其他值表示出现错误),以此确保流端点的启动流程按照既定规则执行,保障媒体数据能在相应的流上正确进行传输。
参数检查:检查
num_handles
的值是否在有效范围内内(即大于0且小于等于AVDT_NUM_SEPS
,后者是一个定义了最大流端点数量的常量)。如果不在有效范围内,则函数返回AVDT_BAD_PARAMS
错误。句柄验证:对于
p_handles
数组中的每个句柄,使用avdt_scb_by_hdl
函数查找对应的AvdtpScb结构(Stream Control Block)。如果找不到对应的结构,则函数返回AVDT_BAD_HANDLE
错误,并停止进一步的处理。CCB检查:如果所有句柄都有效,函数接着检查与第一个有效句柄关联的AvdtpScb结构中的
p_ccb
指针(指向对应的连接控制块,Connection Control Block)。如果p_ccb
为空,则函数同样返回AVDT_BAD_HANDLE
错误。发送事件到CCB:如果所有检查都通过,函数将构建一个事件,包含要启动的流端点句柄列表和数量,并将该事件发送到
p_ccb
指向的CCB。这是通过调用avdt_ccb_event
函数实现的。
avdt_ccb_event
packages/modules/Bluetooth/system/stack/avdt/avdt_ccb.cc
/*******************************************************************************
*
* Function avdt_ccb_event
*
* Description State machine event handling function for ccb
*
*
* Returns Nothing.
*
******************************************************************************/
void avdt_ccb_event(AvdtpCcb* p_ccb, uint8_t event, tAVDT_CCB_EVT* p_data) {
tAVDT_CCB_ST_TBL state_table;
uint8_t action;
int i;
#if (AVDT_DEBUG == TRUE)
log::verbose("CCB ccb={} event={} state={} p_ccb={}", avdt_ccb_to_idx(p_ccb),
avdt_ccb_evt_str[event], avdt_ccb_st_str[p_ccb->state],
fmt::ptr(p_ccb));
#endif
/* look up the state table for the current state */
state_table = avdt_ccb_st_tbl[p_ccb->state];
/* set next state */
if (p_ccb->state != state_table[event][AVDT_CCB_NEXT_STATE]) {
p_ccb->state = state_table[event][AVDT_CCB_NEXT_STATE];
}
/* execute action functions */
for (i = 0; i < AVDT_CCB_ACTIONS; i++) {
action = state_table[event][i];
log::verbose("event={} state={} action={}", avdt_ccb_evt_str[event],
avdt_ccb_st_str[p_ccb->state], action);
if (action != AVDT_CCB_IGNORE) {
(*avdtp_cb.p_ccb_act[action])(p_ccb, p_data);
} else {
break;
}
}
}
avdt_ccb_event函数是控制回调块(CCB
)的状态机事件处理函数,用于处理针对 CCB
的各类事件。它接收控制回调块指针(p_ccb
)、事件类型(event
)以及包含事件相关数据的指针(p_data
)作为参数,首先会根据当前 CCB
的状态查找对应的状态表,然后依据状态表来更新 CCB
的状态,并通过循环执行相应的动作函数(这些动作函数根据状态表中的定义来确定),以此实现 CCB
在不同状态下针对不同事件的准确处理,保障与音频 / 视频流控制相关的逻辑能按照既定的状态机机制有序运行。
查找状态表:使用当前CCB的状态作为索引,从
avdt_ccb_st_tbl
状态表中查找对应的状态表项。这个状态表项包含了对于当前状态和事件,下一个状态以及要执行的动作数组。设置下一个状态:如果当前状态与状态表中为该事件指定的下一个状态不同,则更新CCB的状态。
执行动作函数:遍历状态表中为该事件指定的动作数组。对于每个动作,如果它不是
AVDT_CCB_IGNORE
,则调用与该动作关联的回调函数。这些回调函数在avdtp_cb.p_ccb_act
数组中定义,并传入CCB和事件数据作为参数。
- 回调函数的执行是状态机处理事件的核心部分,它们根据事件类型执行相应的操作,如发送AVDTP信令、更新内部状态、处理错误等。
packages/modules/Bluetooth/system/stack/avdt/avdt_ccb_act.cc
/*******************************************************************************
*
* Function avdt_ccb_snd_start_cmd
*
* Description This function is called to send a start command to the
* peer. It verifies that all requested streams are in the
* proper state. If so, it sends a start command. Otherwise
* send ourselves back a start reject.
*
*
* Returns void.
*
******************************************************************************/
void avdt_ccb_snd_start_cmd(AvdtpCcb* p_ccb, tAVDT_CCB_EVT* p_data) {
int i;
AvdtpScb* p_scb;
tAVDT_MSG avdt_msg;
uint8_t seid_list[AVDT_NUM_SEPS];
log::verbose("");
/* make copy of our seid list */
memcpy(seid_list, p_data->msg.multi.seid_list, p_data->msg.multi.num_seps);
/* verify all streams in the right state */
avdt_msg.hdr.err_param =
avdt_scb_verify(p_ccb, AVDT_VERIFY_OPEN, p_data->msg.multi.seid_list,
p_data->msg.multi.num_seps, &avdt_msg.hdr.err_code);
if (avdt_msg.hdr.err_param == 0) {
log::verbose("AVDT_SIG_START");
/* set peer seid list in messsage */
avdt_scb_peer_seid_list(&p_data->msg.multi);
/* send command */
avdt_msg_send_cmd(p_ccb, seid_list, AVDT_SIG_START, &p_data->msg);
} else {
/* failed; send ourselves a reject for each stream */
for (i = 0; i < p_data->msg.multi.num_seps; i++) {
p_scb = avdt_scb_by_hdl(seid_list[i]);
if (p_scb != NULL) {
log::verbose("AVDT_SCB_MSG_START_REJ_EVT: i={}", i);
tAVDT_SCB_EVT avdt_scb_evt;
avdt_scb_evt.msg.hdr = avdt_msg.hdr;
avdt_scb_event(p_scb, AVDT_SCB_MSG_START_REJ_EVT, &avdt_scb_evt);
}
}
}
}
avdt_ccb_snd_start_cmd函数主要用于向对端发送启动命令,在发送之前,它会先对所有请求的流进行验证,检查它们是否处于合适的状态。如果所有流的状态都符合要求,就向对端发送启动命令;若有流的状态不符合要求,则针对每个流发送一个启动拒绝(Start Reject)消息,以此确保启动操作是在流具备相应条件的基础上进行,保障音频 / 视频流启动流程的正确性和可靠性。
复制SEID列表:将传入的SEID列表复制到本地变量
seid_list
中,以便后续处理。验证流的状态:调用
avdt_scb_verify
函数来检查所有请求的流是否都处于打开(Open)状态。该函数会返回第一个不符合条件的流的SEID(如果有的话),以及一个错误代码。如果没有流不符合条件(即返回值为0),则继续发送启动命令。发送启动命令:
- 如果所有流都符合条件,则首先调用
avdt_scb_peer_seid_list
函数来设置对等端的SEID列表(涉及到一些内部映射或转换)。- 然后,调用
avdt_msg_send_cmd
函数发送启动命令,包括SEID列表和命令类型(AVDT_SIG_START
)。发送启动拒绝事件:
- 如果有流不符合条件,则对每个不符合条件的流执行一个循环。
- 在循环中,使用
avdt_scb_by_hdl
函数通过SEID查找对应的流控制块(SCB)。- 如果找到了SCB,则构造一个启动拒绝事件(
AVDT_SCB_MSG_START_REJ_EVT
),并调用avdt_scb_event
函数来处理该事件。向应用层报告错误或采取其他恢复措施。
avdt_msg_send_cmd
packages/modules/Bluetooth/system/stack/avdt/avdt_msg.cc
/*******************************************************************************
*
* Function avdt_msg_send_cmd
*
* Description This function is called to send a command message. The
* sig_id parameter indicates the message type, p_params
* points to the message parameters, if any. It gets a buffer
* from the AVDTP command pool, executes the message building
* function for this message type. It then queues the message
* in the command queue for this CCB.
*
*
* Returns Nothing.
*
******************************************************************************/
void avdt_msg_send_cmd(AvdtpCcb* p_ccb, void* p_scb, uint8_t sig_id,
tAVDT_MSG* p_params) {
uint8_t* p;
uint8_t* p_start;
BT_HDR* p_buf = (BT_HDR*)osi_malloc(AVDT_CMD_BUF_SIZE);
/* set up buf pointer and offset */
p_buf->offset = AVDT_MSG_OFFSET;
p_start = p = (uint8_t*)(p_buf + 1) + p_buf->offset;
/* execute parameter building function to build message */
(*avdt_msg_bld_cmd[sig_id - 1])(&p, p_params);
/* set len */
p_buf->len = (uint16_t)(p - p_start);
/* now store scb hdls, if any, in buf */
if (p_scb != NULL) {
p = (uint8_t*)(p_buf + 1);
/* for start and suspend, p_scb points to array of handles */
if ((sig_id == AVDT_SIG_START) || (sig_id == AVDT_SIG_SUSPEND)) {
memcpy(p, (uint8_t*)p_scb, p_buf->len);
}
/* for all others, p_scb points to scb as usual */
else {
*p = avdt_scb_to_hdl((AvdtpScb*)p_scb);
}
}
/* stash sig, label, and message type in buf */
p_buf->event = sig_id;
AVDT_BLD_LAYERSPEC(p_buf->layer_specific, AVDT_MSG_TYPE_CMD, p_ccb->label);
/* increment label */
p_ccb->label = (p_ccb->label + 1) % 16;
/* queue message and trigger ccb to send it */
fixed_queue_enqueue(p_ccb->cmd_q, p_buf);
avdt_ccb_event(p_ccb, AVDT_CCB_SENDMSG_EVT, NULL);
}
avdt_msg_send_cmd主要用于发送命令消息,根据传入的参数确定消息类型(通过 sig_id
参数)以及相关消息参数(通过 p_params
参数),首先会从 AVDTP
命令池中获取一个缓冲区,然后调用相应的消息构建函数来构建具体的消息内容,接着设置消息的长度、存储相关的流控制块句柄(如果有的话)、填充消息的标识等信息,最后将构建好的消息放入对应的命令队列中,并触发控制回调块(CCB
)发送该消息,以此实现按照指定的消息类型和参数要求,正确地构建并发送命令消息,保障音频 / 视频流相关的控制命令能准确传达和执行。
构建消息:根据
sig_id
查找并调用相应的消息构建函数(存储在avdt_msg_bld_cmd
数组中),传入消息数据的指针和参数结构。这些构建函数负责将命令参数格式化为适合发送的二进制格式。设置消息长度:根据消息数据指针
p
和起始位置p_start
计算消息长度,并设置缓冲区的len
字段。存储SCB句柄:如果
p_scb
不为NULL
,则根据命令类型将其内容复制到缓冲区中。对于AVDT_SIG_START
和AVDT_SIG_SUSPEND
命令,p_scb
指向一个包含多个SCB句柄的数组,直接复制;对于其他命令,p_scb
指向单个SCB,将其句柄转换为字节并存储。设置消息头:将命令标识符、消息类型和CCB标签存储在缓冲区的
event
和layer_specific
字段中。更新CCB标签:将CCB的标签递增,并取模16以确保标签值在0到15之间循环。
排队消息并触发发送事件:将构建好的消息缓冲区加入到CCB的命令队列中,并调用
avdt_ccb_event
函数触发一个发送消息事件(AVDT_CCB_SENDMSG_EVT
),以便后续处理发送逻辑。
后面的发送流程同 A2dp连接流程中对应的函数,这里不再详细分析。
2.9. AVDTP_START
上面一系列流程最后对应HCI的AVDTP_START。
AVDTP(AUDIO/VIDEO DISTRIBUTION TRANSPORT PROTOCOL),即音频/视频分发传输协议,指定了音频或视频分发的传输协议。它通过蓝牙空中传输流媒体音频或视频,其中音频和视频数据流需要同步的数据传输能力。
AVDTP_START是AVDTP协议中的一个重要信令,用于启动音频或视频流的传输。当设备A(作为源设备)希望向设备B(作为接收设备)传输音频或视频流时,它会发送AVDTP_START信令给设备B。这个信令标志着音频或视频流的传输即将开始。
AVDTP_START的信令流程:
- 设备A发送AVDTP_START信令:设备A作为源设备,在准备好音频或视频流后,会向设备B发送AVDTP_START信令。这个信令包含了流的相关信息,如流端点ID、媒体类型等。
- 设备B接收并处理AVDTP_START信令:设备B作为接收设备,在接收到AVDTP_START信令后,会进行一系列的处理。这包括验证信令的有效性、检查流端点ID和媒体类型是否与设备B的配置相匹配等。
- 设备B返回确认:如果设备B成功处理了AVDTP_START信令,并且准备好接收音频或视频流,它会向设备A发送一个确认信令。这个确认信令表明设备B已经准备好开始接收数据。
- 开始传输音频或视频流:在设备A收到设备B的确认后,它会开始传输音频或视频流。这个流会通过之前建立的蓝牙连接传输到设备B。
AVDTP_START在蓝牙音频传输中的应用: 在蓝牙音频传输中,AVDTP_START信令起着至关重要的作用。它标志着音频流的传输即将开始,并且确保了源设备和接收设备之间的同步。以下是AVDTP_START在蓝牙音频传输中的一些关键应用:
- 启动音频流:当用户希望开始播放音乐或视频时,源设备会发送AVDTP_START信令给接收设备,从而启动音频流的传输。
- 同步音频流:AVDTP_START信令确保了源设备和接收设备之间的音频流是同步的。这意味着音频数据会按照正确的顺序和时间间隔传输到接收设备。
- 支持多种音频格式:AVDTP协议支持多种音频格式,如SBC、AAC和LHDC等。通过AVDTP_START信令,源设备和接收设备可以协商并选择最适合当前传输的音频格式。
AVDTP_START的注意事项:
- 确保设备兼容性:在使用AVDTP_START信令之前,需要确保源设备和接收设备都支持AVDTP协议,并且具有兼容的音频格式和编解码器。
- 处理冲突和抢占:在蓝牙音频传输中,可能会出现多个设备同时尝试抢占音频流的情况。此时,需要妥善处理冲突和抢占问题,以确保音频流的稳定传输。
- 监控和管理音频流:在音频流传输过程中,需要监控和管理音频流的状态和质量。这包括检测和处理音频流的丢包、延迟和抖动等问题。
AVDTP_START是AVDTP协议中的一个重要信令,用于启动音频或视频流的传输。在蓝牙音频传输中,它起着至关重要的作用,确保了源设备和接收设备之间的同步和音频流的稳定传输。
2.10. AVDTP_START_ACCEPT
2.11.
avdt_l2c_data_ind_cback
packages/modules/Bluetooth/system/stack/avdt/avdt_l2c.cc
/*******************************************************************************
*
* Function avdt_l2c_data_ind_cback
*
* Description This is the L2CAP data indication callback function.
*
*
* Returns void
*
******************************************************************************/
void avdt_l2c_data_ind_cback(uint16_t lcid, BT_HDR* p_buf) {
AvdtpTransportChannel* p_tbl;
/* look up info for this channel */
p_tbl = avdt_ad_tc_tbl_by_lcid(lcid);
if (p_tbl != NULL) {
avdt_ad_tc_data_ind(p_tbl, p_buf);
} else /* prevent buffer leak */
osi_free(p_buf);
}
该函数是 L2CAP
(逻辑链路控制和适配协议)数据指示回调函数,其主要作用是当接收到 L2CAP
层传来的数据指示时做出相应处理。
函数处理流程梳理:
- 查找通道信息:根据传入
lcid
参数,调用avdt_ad_tc_tbl_by_lcid
函数查找与该 LCID 关联的 AVDTP(Audio/Video Distribution Transport Protocol)传输通道信息。
avdt_ad_tc_tbl_by_lcid
函数返回一个指向AvdtpTransportChannel
结构体的指针,该结构体包含了与该 LCID 相关的通道信息。- 处理接收到的数据:
- 如果找到了对应的通道信息(即
p_tbl != NULL
),则调用avdt_ad_tc_data_ind
函数,传入找到的通道信息指针p_tbl
和包含数据的缓冲区指针p_buf
。avdt_ad_tc_data_ind
函数负责进一步处理接收到的数据,例如解析数据、更新状态等。- 防止内存泄漏:
- 如果没有找到对应的通道信息(即
p_tbl == NULL
),则调用osi_free
函数释放p_buf
指向的缓冲区。- 这一步是为了防止内存泄漏,因为如果缓冲区没有被正确释放,它将会一直占用内存空间。
avdt_ad_tc_data_ind
packages/modules/Bluetooth/system/stack/avdt/avdt_ad.cc
/*******************************************************************************
*
* Function avdt_ad_tc_data_ind
*
* Description This function is called by the L2CAP interface layer when
* incoming data is received from L2CAP. It looks up the CCB
* or SCB for the channel and routes the data accordingly.
*
*
* Returns Nothing.
*
******************************************************************************/
void avdt_ad_tc_data_ind(AvdtpTransportChannel* p_tbl, BT_HDR* p_buf) {
AvdtpCcb* p_ccb;
AvdtpScb* p_scb;
/* store type (media, recovery, reporting) */
p_buf->layer_specific = avdt_ad_tcid_to_type(p_tbl->tcid);
/* if signaling channel, handle control message */
if (p_tbl->tcid == 0) {
p_ccb = avdt_ccb_by_idx(p_tbl->ccb_idx);
avdt_msg_ind(p_ccb, p_buf);
return;
}
/* if media or other channel, send event to scb */
p_scb = avdtp_cb.ad.LookupAvdtpScb(*p_tbl);
if (p_scb == nullptr) {
log::error("Cannot find AvdtScb entry: ccb_idx:{} tcid:{}", p_tbl->ccb_idx,
p_tbl->tcid);
osi_free(p_buf);
log::error("buffer freed");
return;
}
avdt_scb_event(p_scb, AVDT_SCB_TC_DATA_EVT, (tAVDT_SCB_EVT*)&p_buf);
}
avdt_ad_tc_data_ind函数是由 L2CAP
接口层在接收到传入数据时调用的,其核心作用在于根据接收到数据的传输通道相关信息,查找对应的控制回调块(CCB
)或者流控制块(SCB
),然后按照不同的情况来路由这些接收到的数据,比如对于信令通道的数据当作控制消息进行处理,对于媒体或其他通道的数据则向相应的流控制块发送对应事件,以此确保从 L2CAP
层接收到的数据能在音频 / 视频分发传输协议(AVDTP
)相关的模块中得到正确的处理和流转,保障整个音频 / 视频流相关的数据传输流程的顺畅性和正确性。
设置数据类型:通过
avdt_ad_tcid_to_type
函数将通道ID(tcid
)转换为相应的数据类型(媒体、恢复、报告等),并将这个类型存储在接收到的数据缓冲区(p_buf
)的layer_specific
字段中。处理信令通道数据:如果接收到的数据来自信令通道(即
tcid
为0),则通过avdt_ccb_by_idx
函数根据ccb_idx
查找对应的CCB,并将接收到的数据作为控制消息通过avdt_msg_ind
函数传递给该CCB进行处理。处理媒体或其他通道数据:如果接收到的数据来自媒体或其他类型的通道(即
tcid
不为0),则通过avdtp_cb.ad.LookupAvdtpScb
函数查找对应的SCB(流控制块)。如果找不到对应的SCB,则释放接收到的数据缓冲区,并返回。如果找到了对应的SCB,则通过avdt_scb_event
函数将接收到的数据作为事件(AVDT_SCB_TC_DATA_EVT
)传递给该SCB进行处理。
avdt_msg_ind
packages/modules/Bluetooth/system/stack/avdt/avdt_msg.cc
/*******************************************************************************
*
* Function avdt_msg_ind
*
* Description This function is called by the adaption layer when an
* incoming message is received on the signaling channel.
* It parses the message and sends an event to the appropriate
* SCB or CCB for the message.
*
*
* Returns Nothing.
*
******************************************************************************/
void avdt_msg_ind(AvdtpCcb* p_ccb, BT_HDR* p_buf) {
AvdtpScb* p_scb;
uint8_t* p;
bool ok = true;
bool handle_rsp = false;
bool gen_rej = false;
uint8_t label;
uint8_t pkt_type;
uint8_t msg_type;
uint8_t sig = 0;
tAVDT_MSG msg{};
AvdtpSepConfig cfg{};
uint8_t err;
uint8_t evt = 0;
uint8_t scb_hdl;
/* reassemble message; if no message available (we received a fragment) return
*/
p_buf = avdt_msg_asmbl(p_ccb, p_buf);
if (p_buf == NULL) {
return;
}
p = (uint8_t*)(p_buf + 1) + p_buf->offset;
/* parse the message header */
AVDT_MSG_PRS_HDR(p, label, pkt_type, msg_type);
log::verbose("msg_type={}, sig={}", msg_type, sig);
/* set up label and ccb_idx in message hdr */
msg.hdr.label = label;
msg.hdr.ccb_idx = avdt_ccb_to_idx(p_ccb);
/* verify msg type */
if (msg_type == AVDT_MSG_TYPE_GRJ) {
log::warn("Dropping msg msg_type={}", msg_type);
ok = false;
}
/* check for general reject */
else if ((msg_type == AVDT_MSG_TYPE_REJ) &&
(p_buf->len == AVDT_LEN_GEN_REJ)) {
gen_rej = true;
if (p_ccb->p_curr_cmd != NULL) {
msg.hdr.sig_id = sig = (uint8_t)p_ccb->p_curr_cmd->event;
evt = avdt_msg_rej_2_evt[sig - 1];
msg.hdr.err_code = AVDT_ERR_NSC;
msg.hdr.err_param = 0;
}
} else /* not a general reject */
{
/* get and verify signal */
AVDT_MSG_PRS_SIG(p, sig);
msg.hdr.sig_id = sig;
if ((sig == 0) || (sig > AVDT_SIG_MAX)) {
log::warn("Dropping msg sig={} msg_type:{}", sig, msg_type);
ok = false;
/* send a general reject */
if (msg_type == AVDT_MSG_TYPE_CMD) {
avdt_msg_send_grej(p_ccb, sig, &msg);
}
}
}
if (ok && !gen_rej) {
/* skip over header (msg length already verified during reassembly) */
p_buf->len -= AVDT_LEN_TYPE_SINGLE;
/* set up to parse message */
if ((msg_type == AVDT_MSG_TYPE_RSP) && (sig == AVDT_SIG_DISCOVER)) {
/* parse discover rsp message to struct supplied by app */
msg.discover_rsp.p_sep_info = (tAVDT_SEP_INFO*)p_ccb->p_proc_data;
msg.discover_rsp.num_seps = p_ccb->proc_param;
} else if ((msg_type == AVDT_MSG_TYPE_RSP) &&
((sig == AVDT_SIG_GETCAP) || (sig == AVDT_SIG_GET_ALLCAP))) {
/* parse discover rsp message to struct supplied by app */
msg.svccap.p_cfg = (AvdtpSepConfig*)p_ccb->p_proc_data;
} else if ((msg_type == AVDT_MSG_TYPE_RSP) && (sig == AVDT_SIG_GETCONFIG)) {
/* parse get config rsp message to struct allocated locally */
msg.svccap.p_cfg = &cfg;
} else if ((msg_type == AVDT_MSG_TYPE_CMD) && (sig == AVDT_SIG_SETCONFIG)) {
/* parse config cmd message to struct allocated locally */
msg.config_cmd.p_cfg = &cfg;
} else if ((msg_type == AVDT_MSG_TYPE_CMD) && (sig == AVDT_SIG_RECONFIG)) {
/* parse reconfig cmd message to struct allocated locally */
msg.reconfig_cmd.p_cfg = &cfg;
}
/* parse message; while we're at it map message sig to event */
if (msg_type == AVDT_MSG_TYPE_CMD) {
msg.hdr.err_code = err =
(*avdt_msg_prs_cmd[sig - 1])(&msg, p, p_buf->len);
evt = avdt_msg_cmd_2_evt[sig - 1];
} else if (msg_type == AVDT_MSG_TYPE_RSP) {
msg.hdr.err_code = err =
(*avdt_msg_prs_rsp[sig - 1])(&msg, p, p_buf->len);
evt = avdt_msg_rsp_2_evt[sig - 1];
} else /* msg_type == AVDT_MSG_TYPE_REJ */
{
err = avdt_msg_prs_rej(&msg, p, p_buf->len, sig);
evt = avdt_msg_rej_2_evt[sig - 1];
}
/* if parsing failed */
if (err != 0) {
log::warn("Parsing failed sig={} err=0x{:x}", sig, err);
/* if its a rsp or rej, drop it; if its a cmd, send a rej;
** note special case for abort; never send abort reject
*/
ok = false;
if ((msg_type == AVDT_MSG_TYPE_CMD) && (sig != AVDT_SIG_ABORT)) {
avdt_msg_send_rej(p_ccb, sig, &msg);
}
}
}
/* if its a rsp or rej, check sent cmd to see if we're waiting for
** the rsp or rej. If we didn't send a cmd for it, drop it. If
** it does match a cmd, stop timer for the cmd.
*/
if (ok) {
if ((msg_type == AVDT_MSG_TYPE_RSP) || (msg_type == AVDT_MSG_TYPE_REJ)) {
if ((p_ccb->p_curr_cmd != NULL) && (p_ccb->p_curr_cmd->event == sig) &&
(AVDT_LAYERSPEC_LABEL(p_ccb->p_curr_cmd->layer_specific) == label)) {
/* stop timer */
alarm_cancel(p_ccb->idle_ccb_timer);
alarm_cancel(p_ccb->ret_ccb_timer);
alarm_cancel(p_ccb->rsp_ccb_timer);
/* clear retransmission count */
p_ccb->ret_count = 0;
/* later in this function handle ccb event */
handle_rsp = true;
} else {
ok = false;
log::warn("Cmd not found for rsp sig={} label={}", sig, label);
}
}
}
if (ok) {
/* if it's a ccb event send to ccb */
if (evt & AVDT_CCB_MKR) {
tAVDT_CCB_EVT avdt_ccb_evt;
avdt_ccb_evt.msg = msg;
avdt_ccb_event(p_ccb, (uint8_t)(evt & ~AVDT_CCB_MKR), &avdt_ccb_evt);
}
/* if it's a scb event */
else {
/* Scb events always have a single seid. For cmd, get seid from
** message. For rej and rsp, get seid from p_curr_cmd.
*/
if (msg_type == AVDT_MSG_TYPE_CMD) {
scb_hdl = msg.single.seid;
} else {
scb_hdl = *((uint8_t*)(p_ccb->p_curr_cmd + 1));
}
/* Map seid to the scb and send it the event. For cmd, seid has
** already been verified by parsing function.
*/
if (evt) {
p_scb = avdt_scb_by_hdl(scb_hdl);
if (p_scb != NULL) {
tAVDT_SCB_EVT avdt_scb_evt;
avdt_scb_evt.msg = msg;
avdt_scb_event(p_scb, evt, &avdt_scb_evt);
}
}
}
}
/* free message buffer */
osi_free(p_buf);
/* if its a rsp or rej, send event to ccb to free associated
** cmd msg buffer and handle cmd queue
*/
if (handle_rsp) {
avdt_ccb_event(p_ccb, AVDT_CCB_RCVRSP_EVT, NULL);
}
}
avdt_msg_ind主要负责解析消息内容,并依据消息的类型、信号标识等信息,将相应的事件发送到合适的流控制块(SCB
)或者控制回调块(CCB
)进行后续处理。在整个过程中,会涉及消息的重组、头部解析、类型验证、参数解析以及根据不同情况进行的错误处理、事件发送和资源释放等操作,以此保障信令通道消息能在音频 / 视频分发传输协议(AVDTP
)相关的模块中得到正确的流转和处理,确保音频 / 视频流传输过程中的信令交互准确无误。
消息重组:通过
avdt_msg_asmbl
函数尝试重组接收到的消息。如果当前没有完整的消息可用(即只接收到了消息的片段),则函数直接返回。解析消息头:使用
AVDT_MSG_PRS_HDR
解析消息头,获取消息的标签(label
)、包类型(pkt_type
)和消息类型(msg_type
)。设置消息头信息:将解析出的标签和通过
avdt_ccb_to_idx
函数获取的CCB索引设置到消息头中。验证消息类型:
- 如果消息类型是通用拒绝(
AVDT_MSG_TYPE_GRJ
),则记录警告日志并设置ok
为false
。- 如果消息类型是拒绝(
AVDT_MSG_TYPE_REJ
)且长度为通用拒绝的长度,则处理通用拒绝消息,并根据需要设置相应的错误代码和事件。处理非通用拒绝消息:
- 解析并验证信号(
sig
)。- 根据消息类型和信号,准备解析消息所需的结构体,并调用相应的解析函数。
- 如果解析失败,则根据消息类型和信号决定是否发送拒绝消息。
处理响应或拒绝消息:
- 检查当前CCB是否有等待的命令与接收到的响应或拒绝消息匹配。
- 如果匹配,则取消相关定时器,并设置
handle_rsp
为true
以表示需要处理响应事件。- 如果不匹配,则将
ok
设置为false
。根据事件类型发送事件:
- 如果事件是CCB事件(通过检查事件标志位),则构造
avdt_ccb_evt
结构体并调用avdt_ccb_event
函数处理。- 如果事件是SCB事件,则根据消息类型获取SEID(Stream Endpoint Identifier),查找对应的SCB,并构造
avdt_scb_evt
结构体调用avdt_scb_event
函数处理。释放消息缓冲区:无论处理结果如何,最后都会释放接收到的消息缓冲区。
处理响应或拒绝后的后续操作:如果
handle_rsp
为true
,则调用avdt_ccb_event
函数处理响应事件,释放与命令相关的缓冲区,并处理命令队列。
avdt_ccb_event( AVDT_CCB_RCVRSP_EVT)
avdt_ccb_event函数前面有分析过,一样的套路。这里不再展开。
avdt_ccb_hdl_start_rsp
packages/modules/Bluetooth/system/stack/avdt/avdt_ccb_act.cc
/*******************************************************************************
*
* Function avdt_ccb_hdl_start_rsp
*
* Description This function is called when a start response or reject
* is received from the peer. Using the SEIDs stored in the
* current command message, it sends a start response or start
* reject event to each SCB associated with the command.
*
*
* Returns void.
*
******************************************************************************/
void avdt_ccb_hdl_start_rsp(AvdtpCcb* p_ccb, tAVDT_CCB_EVT* p_data) {
uint8_t event;
int i;
uint8_t* p;
AvdtpScb* p_scb;
/* determine rsp or rej event */
event = (p_data->msg.hdr.err_code == 0) ? AVDT_SCB_MSG_START_RSP_EVT
: AVDT_SCB_MSG_START_REJ_EVT;
/* get to where seid's are stashed in current cmd */
p = (uint8_t*)(p_ccb->p_curr_cmd + 1);
/* little trick here; length of current command equals number of streams */
for (i = 0; i < p_ccb->p_curr_cmd->len; i++) {
p_scb = avdt_scb_by_hdl(p[i]);
if (p_scb != NULL) {
avdt_scb_event(p_scb, event, (tAVDT_SCB_EVT*)&p_data->msg);
}
}
}
该函数主要在接收到对端发来的启动响应(start response
)或者启动拒绝(start reject
)消息时被调用。其功能核心在于依据当前命令消息中存储的流端点标识符(Stream Endpoint Identifier,SEIDs
),来向与该命令相关联的各个流控制块(Stream Control Block, SCB)发送对应的启动响应事件或者启动拒绝事件,以此确保相关的流控制块能够知晓启动操作在对端的处理结果,进而采取后续相应的处理措施,保障音频 / 视频流启动相关流程的正确推进。
- 获取SEID列表:从当前命令(
p_ccb->p_curr_cmd
)中获取SEID列表。这里有个小技巧,当前命令的长度等于流的数量。SEID列表紧随命令头部之后,因此通过(uint8_t*)(p_ccb->p_curr_cmd + 1)
获取SEID列表的指针。- 遍历SEID列表:使用一个循环遍历所有的SEID。对于每个SEID,通过
avdt_scb_by_hdl
函数根据SEID获取对应的SCB指针。- 发送事件:如果找到了对应的SCB(
p_scb != NULL
),则调用avdt_scb_event
函数向该SCB发送之前确定的事件(启动响应或启动拒绝),并传递相关的事件数据。
avdt_scb_event(AVDT_SCB_MSG_START_RSP_EVT)
packages/modules/Bluetooth/system/stack/avdt/avdt_scb.cc
/*******************************************************************************
*
* Function avdt_scb_event
*
* Description State machine event handling function for scb
*
*
* Returns Nothing.
*
******************************************************************************/
void avdt_scb_event(AvdtpScb* p_scb, uint8_t event, tAVDT_SCB_EVT* p_data) {
tAVDT_SCB_ST_TBL state_table;
uint8_t action;
#if (AVDT_DEBUG == TRUE)
log::verbose("SCB hdl={} event={}/{} state={} p_avdt_scb={} scb_index={}",
avdt_scb_to_hdl(p_scb), event, avdt_scb_evt_str[event],
avdt_scb_st_str[p_scb->state], fmt::ptr(p_scb),
p_scb->stream_config.scb_index);
#endif
/* Check that we only send AVDT_SCB_API_WRITE_REQ_EVT to the active stream
* device */
uint8_t num_st_streams = 0;
int ccb_index = -1;
int scb_index = -1;
for (int i = 0; i < AVDT_NUM_LINKS; i++) {
for (int j = 0; j < AVDT_NUM_SEPS; j++) {
AvdtpScb* p_avdt_scb = &avdtp_cb.ccb[i].scb[j];
if (p_avdt_scb->allocated &&
avdt_scb_st_tbl[p_avdt_scb->state] == avdt_scb_st_stream) {
num_st_streams++;
ccb_index = i;
scb_index = j;
} else {
p_avdt_scb->curr_stream = false;
}
}
}
if (num_st_streams == 1) {
avdtp_cb.ccb[ccb_index].scb[scb_index].curr_stream = true;
} else if (num_st_streams > 1 && !p_scb->curr_stream &&
event == AVDT_SCB_API_WRITE_REQ_EVT) {
log::error("ignore AVDT_SCB_API_WRITE_REQ_EVT");
avdt_scb_free_pkt(p_scb, p_data);
return;
}
/* set current event */
p_scb->curr_evt = event;
/* look up the state table for the current state */
state_table = avdt_scb_st_tbl[p_scb->state];
/* set next state */
if (p_scb->state != state_table[event][AVDT_SCB_NEXT_STATE]) {
p_scb->state = state_table[event][AVDT_SCB_NEXT_STATE];
}
/* execute action functions */
for (int i = 0; i < AVDT_SCB_ACTIONS; i++) {
action = state_table[event][i];
if (action != AVDT_SCB_IGNORE) {
(*avdtp_cb.p_scb_act[action])(p_scb, p_data);
} else {
break;
}
}
}
该函数是流控制块(SCB
)的状态机事件处理函数,其主要作用是根据传入的流控制块指针、事件类型以及相关事件数据,来处理流控制块的状态转换和相应的动作执行。在处理过程中,会进行一些前置条件检查(比如针对特定事件向活动流设备发送的限制检查),然后依据当前流控制块的状态查找对应的状态表,根据状态表来更新状态,并执行相应的动作函数,以此实现流控制块在不同事件触发下的状态流转和相关业务逻辑的处理,保障音频 / 视频分发传输协议(AVDTP
)中流相关操作的正确执行。
- 检查活动流:
- 遍历所有CCB(Connection Control Block)和SCB,以查找当前处于“流”状态的SCB数量。
- 如果只有一个SCB处于流状态,则将其标记为当前流。
- 如果有多个SCB处于流状态,并且当前SCB不是当前流,同时接收到的是
AVDT_SCB_API_WRITE_REQ_EVT
事件,则忽略该事件并释放相关数据包。- 设置当前事件:将传入的事件类型保存到SCB的
curr_evt
字段中。- 查找状态表:根据SCB的当前状态,从状态表中获取对应的状态表条目。
- 更新状态:如果当前状态与状态表中定义的下一个状态不同,则更新SCB的状态。
- 执行action函数:
- 遍历状态表中与当前事件相关的所有动作。
- 对于每个动作,如果它不是
AVDT_SCB_IGNORE
,则调用相应的动作函数,并传入SCB和事件数据指针。- 如果遇到
AVDT_SCB_IGNORE
,则停止执行后续动作。
avdt_scb_hdl_start_rsp
android/packages/modules/Bluetooth/system/stack/avdt/avdt_scb_act.cc
/*******************************************************************************
*
* Function avdt_scb_hdl_start_rsp
*
* Description This function calls the application callback with a
* start confirm.
*
* Returns Nothing.
*
******************************************************************************/
void avdt_scb_hdl_start_rsp(AvdtpScb* p_scb, tAVDT_SCB_EVT* p_data) {
(*p_scb->stream_config.p_avdt_ctrl_cback)(
avdt_scb_to_hdl(p_scb),
p_scb->p_ccb ? p_scb->p_ccb->peer_addr : RawAddress::kEmpty,
AVDT_START_CFM_EVT, (tAVDT_CTRL*)&p_data->msg.hdr,
p_scb->stream_config.scb_index);
}
该函数的核心功能在于当接收到启动响应(start response
)相关的事件时,调用应用层的回调函数(application callback
),并向其传递启动确认(start confirm
)相关的关键信息,以此来通知应用层启动操作在协议层面的处理结果,方便应用层根据这个结果进行后续相应的处理,例如进一步调整业务逻辑、更新界面显示等操作,实现协议处理与应用层业务逻辑之间的交互和协同工作。
- 获取SCB的句柄:使用
avdt_scb_to_hdl(p_scb)
函数将SCB指针转换为句柄,这个句柄是应用程序用来识别不同SCB的。- 获取对等设备的地址:
- 如果SCB有关联的连接控制块(Connection Control Block, CCB),则使用
p_scb->p_ccb->peer_addr
获取对等设备的地址。- 如果没有关联的CCB,则使用
RawAddress::kEmpty
表示没有地址信息。- 调用应用程序的回调函数:
- 使用SCB中的
stream_config.p_avdt_ctrl_cback
回调函数指针。- 传递的参数包括:SCB的句柄、对等设备的地址、启动确认事件类型(
AVDT_START_CFM_EVT
)、指向事件消息头部的指针(这里使用了类型转换,将p_data->msg.hdr
转换为tAVDT_CTRL*
类型),以及SCB的索引。
2.12. bta_av_proc_stream_evta(AVDT_START_CFM_EVT
)
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) {
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) {
...
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);
}
该函数主要用于构建(compose
)流事件相关的消息结构体,并根据传入的不同参数情况进行一些数据处理、赋值以及事件类型判断等操作,最终通过调用 bta_sys_sendmsg
函数将构建好的包含流事件信息的消息结构体发送出去,其目的是在蓝牙音频 / 视频(AV
)相关的系统中,把与音频 / 视频流相关的事件信息按照统一的格式进行整理并传递给其他需要处理这些事件的模块,保障整个蓝牙音频 / 视频流相关业务流程中的事件信息流转顺畅。
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
的目的是将一个消息发送到BTA(Bluetooth Application Layer)系统的主要处理线程或任务中。是Bluedroid协议栈中用于优化消息传递机制的一部分,确保BTA能够高效地处理来自不同API函数或回调函数的消息。
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事件处理器的一部分。用于处理来自不同子系统的BTA事件。 前文多次有分析到,不再赘述。
bta_av_hdl_event(BTA_AV_STR_START_OK_EVT)
bta_av_hdl_event前文多次有分析到,不再详细展开。
bta_av_start_ok
bta_av_start_ok在【Bluedroid】A2DP SINK播放流程源码分析-CSDN博客中有详细分析。
bta_av_source_callback
packages/modules/Bluetooth/system/btif/src/btif_av.cc
static void bta_av_source_callback(tBTA_AV_EVT event, tBTA_AV* p_data) {
BtifAvEvent btif_av_event(event, p_data, sizeof(tBTA_AV));
log::verbose("event={}", btif_av_event.ToString());
do_in_main_thread(
FROM_HERE, base::BindOnce(&btif_av_handle_bta_av_event,
AVDT_TSEP_SNK /* peer_sep */, btif_av_event));
}
该函数是一个回调函数,在特定的蓝牙音频 / 视频(AV
)相关事件发生时被调用,其核心作用在于将接收到的 BTA_AV
相关事件及对应的数据进行封装,通过 do_in_main_thread
机制将后续的事件处理逻辑安排到主线程中执行,调用 btif_av_handle_bta_av_event
函数来处理封装好的蓝牙音频 / 视频事件,以此确保事件处理的相关操作能在主线程这个合适的执行环境下进行,避免多线程相关的问题,保障整个蓝牙音频 / 视频系统中事件处理流程的正确性和稳定性。
btif_av_handle_bta_av_event
packages/modules/Bluetooth/system/btif/src/btif_av.cc
/**
* 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) {
...
case BTA_AV_START_EVT: {
const tBTA_AV_START& start = p_data->start;
bta_handle = start.hndl;
msg = "Stream started";
break;
}
...
}
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);
}
btif_av_handle_bta_av_event
负责处理来自 BTA (Bluetooth Application Layer) AV (Audio/Video) 或 AVRCP (Audio/Video Remote Control Profile) 的事件。这些事件在 JNI (Java Native Interface) 线程中被处理。从传入的参数中提取出如对端设备地址、事件句柄、具体事件类型以及事件相关数据等关键信息,并针对不同的事件类型(通过 switch
语句来区分)进行相应的初步处理,最后调用 btif_av_handle_event
函数来进一步处理该事件,以此确保蓝牙音频 / 视频相关系统中各类事件能够在 JNI
线程得到有序且正确的处理,保障系统整体功能的实现和状态的正确更新。
btif_av_handle_event
btif_av_handle_event在【Bluedroid】A2DP SINK播放流程源码分析-CSDN博客中有详细分析。
BtifAvStateMachine::StateOpened::ProcessEvent(BTA_AV_START_EVT)
packages/modules/Bluetooth/system/btif/src/btif_av.cc
bool BtifAvStateMachine::StateOpened::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()));
if ((event == BTA_AV_REMOTE_CMD_EVT) &&
peer_.CheckFlags(BtifAvPeer::kFlagRemoteSuspend) &&
(p_av->remote_cmd.rc_id == AVRC_ID_PLAY)) {
log::verbose("Peer {} : Resetting remote suspend flag on RC PLAY",
ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()));
peer_.ClearFlags(BtifAvPeer::kFlagRemoteSuspend);
}
switch (event) {
...
case BTA_AV_START_EVT: {
log::info(
"Peer {} : event={} status={} suspending={} initiator={} flags={}",
ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()),
BtifAvEvent::EventName(event), p_av->start.status,
p_av->start.suspending, p_av->start.initiator, peer_.FlagsToString());
if ((p_av->start.status == BTA_SUCCESS) && p_av->start.suspending)
return true;
// If remote tries to start A2DP when DUT is A2DP Source, then Suspend.
// If A2DP is Sink and call is active, then disconnect the AVDTP
// channel.
bool should_suspend = false;
if (peer_.IsSink()) {
if (!peer_.CheckFlags(BtifAvPeer::kFlagPendingStart |
BtifAvPeer::kFlagRemoteSuspend)) {
log::warn("Peer {} : trigger Suspend as remote initiated",
ADDRESS_TO_LOGGABLE_STR(peer_.PeerAddress()));
should_suspend = true;
} else if (!peer_.IsActivePeer()) {
log::warn("Peer {} : trigger Suspend as non-active",
ADDRESS_TO_LOGGABLE_STR(peer_.PeerAddress()));
should_suspend = true;
}
// If peer is A2DP Source, do ACK commands to audio HAL and start
// media task
if (btif_a2dp_on_started(peer_.PeerAddress(), &p_av->start)) {
// Only clear pending flag after acknowledgement
peer_.ClearFlags(BtifAvPeer::kFlagPendingStart);
}
}
// Remain in Open state if status failed
if (p_av->start.status != BTA_AV_SUCCESS) return false;
if (peer_.IsSource() && peer_.IsActivePeer()) {
// Remove flush state, ready for streaming
btif_a2dp_sink_set_rx_flush(false);
btif_a2dp_sink_on_start();
}
if (should_suspend) {
btif_av_source_dispatch_sm_event(peer_.PeerAddress(),
BTIF_AV_SUSPEND_STREAM_REQ_EVT);
}
peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateStarted);
} break;
...
default:
log::warn("Peer {} : Unhandled event={}",
ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()),
BtifAvEvent::EventName(event));
return false;
}
return true;
}
该函数是 BtifAvStateMachine
类中 StateOpened
状态下用于处理事件的成员函数。其核心功能在于根据传入的不同事件类型以及相关的数据指针,在蓝牙音频 / 视频(AV
)系统处于已打开(Opened
)状态时,进行相应的事件处理逻辑,比如针对特定条件下的标志位操作、根据不同情况决定是否进行状态转换、执行与音频硬件抽象层(HAL
)的交互操作以及向其他模块发送特定的事件请求等,以此来确保蓝牙音频 / 视频系统在 Opened
状态下能够正确响应各类事件,维持系统状态的正确流转以及功能的正常实现。
特定事件处理:如果接收到
BTA_AV_REMOTE_CMD_EVT
事件,并且对等设备设置了BtifAvPeer::kFlagRemoteSuspend
标志,且远程命令是播放(AVRC_ID_PLAY
),则清除该标志。事件处理:通过
switch
语句根据事件类型进行不同的处理。这里只关注BTA_AV_START_EVT。
- BTA_AV_START_EVT:处理流启动事件根据启动状态和是否挂起进行不同的处理。
- 如果启动成功且需要挂起,则直接返回
true
(这可能意味着后续会有挂起操作)。- 如果对等设备是接收端(Sink),并且没有设置
kFlagPendingStart
或kFlagRemoteSuspend
标志,或者不是活动连接,则触发挂起。- 如果对等设备是源(Source)且是活动连接,则移除接收端的刷新状态,并调用
btif_a2dp_sink_on_start
方法。- 如果需要挂起,则分发挂起流请求事件。
- 最后,无论是否挂起,都转换到
kStateStarted
状态。
btif_a2dp_on_started
packages/modules/Bluetooth/system/btif/src/btif_a2dp.cc
bool btif_a2dp_on_started(const RawAddress& peer_addr, tBTA_AV_START* p_av_start) {
log::info("## ON A2DP STARTED ## peer {} p_av_start:{}",
ADDRESS_TO_LOGGABLE_STR(peer_addr), fmt::ptr(p_av_start));
if (p_av_start == NULL) {
tA2DP_CTRL_ACK status = A2DP_CTRL_ACK_SUCCESS;
if (!bluetooth::headset::IsCallIdle()) {
log::error("peer {} call in progress, do not start A2DP stream",
ADDRESS_TO_LOGGABLE_STR(peer_addr));
status = A2DP_CTRL_ACK_INCALL_FAILURE;
}
/* just ack back a local start request, do not start the media encoder since
* this is not for BTA_AV_START_EVT. */
if (bluetooth::audio::a2dp::is_hal_enabled()) {
bluetooth::audio::a2dp::ack_stream_started(status);
} else {
btif_a2dp_command_ack(status);
}
return true;
}
log::info("peer {} status:{} suspending:{} initiator:{}",
ADDRESS_TO_LOGGABLE_STR(peer_addr), p_av_start->status,
logbool(p_av_start->suspending), logbool(p_av_start->initiator));
if (p_av_start->status == BTA_AV_SUCCESS) {
if (p_av_start->suspending) {
log::warn("peer {} A2DP is suspending and ignores the started event",
ADDRESS_TO_LOGGABLE_STR(peer_addr));
return false;
}
if (btif_av_is_a2dp_offload_running()) {
btif_av_stream_start_offload();
} else if (bluetooth::audio::a2dp::is_hal_enabled()) {
if (btif_av_get_peer_sep() == AVDT_TSEP_SNK) {
/* Start the media encoder to do the SW audio stream */
btif_a2dp_source_start_audio_req();
}
if (p_av_start->initiator) {
bluetooth::audio::a2dp::ack_stream_started(A2DP_CTRL_ACK_SUCCESS);
return true;
}
} else {
if (p_av_start->initiator) {
btif_a2dp_command_ack(A2DP_CTRL_ACK_SUCCESS);
return true;
}
/* media task is auto-started upon UIPC connection of a2dp audiopath */
}
} else if (p_av_start->initiator) {
log::error("peer {} A2DP start request failed: status = {}",
ADDRESS_TO_LOGGABLE_STR(peer_addr), p_av_start->status);
if (bluetooth::audio::a2dp::is_hal_enabled()) {
bluetooth::audio::a2dp::ack_stream_started(A2DP_CTRL_ACK_FAILURE);
} else {
btif_a2dp_command_ack(A2DP_CTRL_ACK_FAILURE);
}
return true;
}
return false;
}
btif_a2dp_on_started函数主要用于处理与A2DP启动相关的操作及各种情况判断。它接收对端设备地址和包含 A2DP 启动相关信息的结构体指针作为参数,依据这些参数来判断诸如启动状态是否成功、是否处于暂停状态、是否由对端发起等多种情况,进而决定执行不同的操作,例如向音频硬件抽象层(HAL)发送确认信息、启动媒体相关任务、处理启动失败情况等,以此来确保在 A2DP 启动相关事件发生时,蓝牙音频系统能够按照正确的逻辑进行相应处理,维持系统功能的正常运行。
空指针检查:首先检查
p_av_start
是否为NULL
。如果是,表示这是一个本地的启动请求(不是由BTA_AV_START_EVT事件触发的),函数会检查当前是否有电话在进行中。如果有,则记录一个错误日志,并通过A2DP_CTRL_ACK_INCALL_FAILURE
状态进行回应。如果没有电话进行中,则根据HAL(硬件抽象层)是否启用,调用相应的函数进行回应。处理BTA_AV_START事件:
成功启动:如果启动状态为
BTA_AV_SUCCESS
,函数会检查是否处于挂起状态。如果是,则记录一个警告日志并返回false
。如果不是挂起状态,则会根据是否启用了HAL以及当前是否运行A2DP offload,决定是启动媒体编码器还是进行其他操作。如果是发起者,还会根据HAL是否启用,通过不同的方式发送成功回应。启动失败:如果启动状态不是
BTA_AV_SUCCESS
,并且是发起者,函数会记录一个错误日志,并根据HAL是否启用,通过不同的方式发送失败回应。
btif_a2dp_command_ack
packages/modules/Bluetooth/system/btif/src/btif_a2dp_control.cc
void btif_a2dp_command_ack(tA2DP_CTRL_ACK status) {
uint8_t ack = status;
// Don't log A2DP_CTRL_GET_PRESENTATION_POSITION by default, because it
// could be very chatty when audio is streaming.
if (a2dp_cmd_pending == A2DP_CTRL_GET_PRESENTATION_POSITION) {
log::verbose("## a2dp ack : {}, status {} ##",
audio_a2dp_hw_dump_ctrl_event(a2dp_cmd_pending), status);
} else {
log::warn("## a2dp ack : {}, status {} ##",
audio_a2dp_hw_dump_ctrl_event(a2dp_cmd_pending), status);
}
/* Sanity check */
if (a2dp_cmd_pending == A2DP_CTRL_CMD_NONE) {
log::error("warning : no command pending, ignore ack");
return;
}
/* Clear pending */
a2dp_cmd_pending = A2DP_CTRL_CMD_NONE;
/* Acknowledge start request */
if (a2dp_uipc != nullptr) {
UIPC_Send(*a2dp_uipc, UIPC_CH_ID_AV_CTRL, 0, &ack, sizeof(ack));
}
}
该函数主要,用于处理A2DP控制命令的应答。它接收一个表示确认状态的参数,依据当前系统中 A2DP 命令的相关情况(如正在等待处理的命令类型、与统一进程间通信(UIPC)模块的关联情况等),进行合法性检查、状态清理以及向 UIPC 发送确认信息等操作,以此来确保 A2DP 命令的确认流程能够正确执行,保障蓝牙音频系统中相关命令交互的准确性和稳定性。
UIPC_Send
packages/modules/Bluetooth/system/udrv/ulinux/uipc.cc
/*******************************************************************************
**
** Function UIPC_Send
**
** Description Called to transmit a message over UIPC.
**
** Returns true in case of success, false in case of failure.
**
******************************************************************************/
bool UIPC_Send(tUIPC_STATE& uipc, tUIPC_CH_ID ch_id,
UNUSED_ATTR uint16_t msg_evt, const uint8_t* p_buf,
uint16_t msglen) {
LOG_DEBUG("UIPC_Send : ch_id:%d %d bytes", ch_id, msglen);
std::lock_guard<std::recursive_mutex> lock(uipc.mutex);
ssize_t ret;
OSI_NO_INTR(ret = write(uipc.ch[ch_id].fd, p_buf, msglen));
if (ret < 0) {
LOG_ERROR("failed to write (%s)", strerror(errno));
return false;
}
return true;
}
该函数的主要作用是通过用户空间进程间通信(UIPC)发送消息。它接收 UIPC
相关的状态结构体、通道标识、消息事件(虽然当前代码中标记为未使用属性)、消息缓冲区指针以及消息长度等参数,在进行加锁操作以保证线程安全后,尝试将指定缓冲区中的消息按照给定长度通过对应的 UIPC
通道进行发送,并根据发送操作的结果返回成功或失败的标识,以此保障不同进程间能够利用 UIPC
准确地进行消息传递,维持整个系统中进程间通信相关业务流程的正常运转。
线程安全:通过
std::lock_guard<std::recursive_mutex>
和uipc.mutex
确保对UIPC状态的访问是线程安全的。std::recursive_mutex
允许同一个线程多次锁定互斥量而不会导致死锁。发送数据:
- 使用
OSI_NO_INTR
宏调用write
函数,尝试将p_buf
指向的数据写入uipc.ch[ch_id].fd
指定的文件描述符(即UIPC通道的文件描述符)。write
函数的返回值存储在ret
中。如果ret
小于0,表示写入失败。错误处理:如果
write
失败,返回false
成功完成:如果数据成功写入,函数返回
true
。
继续回到StateOpened::ProcessEvent,开始peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateStarted);状态机转到kStateStarted状态。
BtifAvStateMachine::StateStarted::OnEnter
packages/modules/Bluetooth/system/btif/src/btif_av.cc
void BtifAvStateMachine::StateStarted::OnEnter() {
log::verbose("Peer {}", ADDRESS_TO_LOGGABLE_CSTR(peer_.PeerAddress()));
// We are again in started state, clear any remote suspend flags
peer_.ClearFlags(BtifAvPeer::kFlagRemoteSuspend);
btif_a2dp_sink_set_rx_flush(false);
// Report that we have entered the Streaming stage. Usually, this should
// be followed by focus grant. See update_audio_focus_state()
btif_report_audio_state(peer_.PeerAddress(), BTAV_AUDIO_STATE_STARTED);
}
该函数是 BtifAvStateMachine
类中 StateStarted
状态下的OnEnter
处理函数。在系统进入蓝牙音频 / 视频(AV
)相关的 Started
状态时被调用,主要负责执行一些必要的初始化和状态更新操作,比如清除远程暂停相关标志位、设置接收端接收缓冲区的刷新状态、向系统报告音频状态进入已启动(也就是开始流式传输阶段)等,以此来确保系统在进入该状态时各相关设置和状态信息能正确调整,保障蓝牙音频 / 视频功能后续能顺利进行。
- 设置A2DP接收刷新:调用
btif_a2dp_sink_set_rx_flush(false)
来禁用A2DP接收端的刷新。意味着开始正常接收音频数据,而不是在刷新状态下丢弃数据。- 报告音频状态:调用
btif_report_audio_state(peer_.PeerAddress(), BTAV_AUDIO_STATE_STARTED)
来报告音频状态为BTAV_AUDIO_STATE_STARTED
。表示我们已经进入了音频流阶段,对等体已经开始发送音频数据。通常,这应该跟随音频焦点的授予,但具体的焦点管理逻辑可能在其他地方实现(如update_audio_focus_state()
函数)。
btif_report_audio_state
packages/modules/Bluetooth/system/btif/src/btif_av.cc
/**
* Report the audio state of the A2DP connection.
* The state is updated when either the remote ends starts streaming
* (Started state) or whenever it transitions out of Started state
* (to Opened or Streaming state).
*
* @param peer_address the peer address
* @param state the audio state
*/
static void btif_report_audio_state(const RawAddress& peer_address,
btav_audio_state_t state) {
log::info("peer_address={} state={}", ADDRESS_TO_LOGGABLE_CSTR(peer_address),
state);
if (btif_av_both_enable()) {
BtifAvPeer* peer = btif_av_find_peer(peer_address);
if (peer->IsSink()) {
do_in_jni_thread(
FROM_HERE, base::BindOnce(btif_av_source.Callbacks()->audio_state_cb,
peer_address, state));
} else if (peer->IsSource()) {
do_in_jni_thread(FROM_HERE,
base::BindOnce(btif_av_sink.Callbacks()->audio_state_cb,
peer_address, state));
}
return;
}
if (btif_av_source.Enabled()) {
do_in_jni_thread(FROM_HERE,
base::BindOnce(btif_av_source.Callbacks()->audio_state_cb,
peer_address, state));
} else if (btif_av_sink.Enabled()) {
do_in_jni_thread(FROM_HERE,
base::BindOnce(btif_av_sink.Callbacks()->audio_state_cb,
peer_address, state));
}
using android::bluetooth::a2dp::AudioCodingModeEnum;
using android::bluetooth::a2dp::PlaybackStateEnum;
PlaybackStateEnum playback_state = PlaybackStateEnum::PLAYBACK_STATE_UNKNOWN;
switch (state) {
case BTAV_AUDIO_STATE_STARTED:
playback_state = PlaybackStateEnum::PLAYBACK_STATE_PLAYING;
break;
case BTAV_AUDIO_STATE_STOPPED:
playback_state = PlaybackStateEnum::PLAYBACK_STATE_NOT_PLAYING;
break;
default:
break;
}
AudioCodingModeEnum audio_coding_mode =
btif_av_is_a2dp_offload_running()
? AudioCodingModeEnum::AUDIO_CODING_MODE_HARDWARE
: AudioCodingModeEnum::AUDIO_CODING_MODE_SOFTWARE;
log_a2dp_playback_event(peer_address, playback_state, audio_coding_mode);
}
btif_report_audio_state 函数主要用于报告蓝牙 A2DP连接的音频状态。它会在远程端开始流式传输(进入 “Started” 状态)或者从 “Started” 状态转换到其他状态(如 “Opened” 或 “Streaming” 状态)时更新并报告音频状态信息,同时还会根据不同的条件在 JNI 线程中调用相应的回调函数,并处理一些与音频播放状态、编码模式相关的逻辑,以此来确保系统内各相关模块能够及时知晓蓝牙音频连接的状态变化情况。 详细分析见【Bluedroid】A2DP SINK播放流程源码分析-CSDN博客对应的函数。
2.13. 蓝牙和AUDIO之间的接口
蓝牙和audio之间的通信是通过socket,管理socket中的文件是UIPC,UIPC管理两条socket。
A2DP_CTRL_PATH /data/misc/bluedroid/.a2dp_ctrl
A2DP_DATA_PATH /data/misc/bluedroid/.a2dp_data
UIPC是一种用户空间进程间通信机制,它允许不同的进程或线程通过socket进行通信。在蓝牙和音频系统的场景中,UIPC被用来管理两个关键的socket,这两个socket的作用就是接收audio的控制命令和音频数据。
-
A2DP控制socket(对应
/data/misc/bluedroid/.a2dp_ctrl
):在蓝牙协议栈初始化或蓝牙设备打开时创建。这个socket的创建确保了音频系统可以通过它向蓝牙协议栈发送控制命令,如启动、停止音频流,以及配置音频参数等。这些命令通常由音频系统发送到蓝牙协议栈,以控制A2DP会话的行为。 -
A2DP数据socket(对应
/data/misc/bluedroid/.a2dp_data
):在音频系统下发START命令给蓝牙协议栈时创建。这个命令指示蓝牙协议栈开始A2DP会话,并准备接收和发送音频数据。这些数据通常从音频系统获取,并通过蓝牙协议栈发送到远程蓝牙设备。
2.14. Audio的数据发到蓝牙的流程
从启动音频数据传输到通过蓝牙发送 audio
数据涉及到多个函数之间的协作调用,先是通过一些函数发起音频数据路径相关的启动操作并下发对应的控制命令,接着在接收到启动命令后进行通道创建操作,最后利用 skt_write
函数向创建好的 socket发送消息,以此来实现将音频数据发送到蓝牙的功能。
out_write->start_audio_datapath->a2dp_command 下发A2DP_CTRL_CMD_START命令。
btif_a2dp_ctrl_cb->btif_a2dp_recv_ctrl_data-> UIPC_Open(UIPC_CH_ID_AV_AUDIO, btif_a2dp_data_cb)
收到start命令之后,创建A2DP_DATA_PATH通道
调用skt_write向这个socket发送消息。
- out_write 函数调用:这个函数是音频数据输出的起点,它可能由音频框架或应用程序调用,用于将音频数据写入到某个缓冲区或队列中。
- start_audio_datapath 函数调用:当有音频数据需要发送到蓝牙设备时,这个函数被调用以启动音频数据路径。它可能负责配置音频编码器、设置音频参数(如采样率、位深度等),并准备将数据发送到蓝牙协议栈。
- a2dp_command 下发 A2DP_CTRL_CMD_START 命令:在音频数据路径启动后,A2DP 控制命令被下发到蓝牙协议栈。
A2DP_CTRL_CMD_START
命令用于指示蓝牙协议栈开始A2DP会话,并准备接收音频数据。- btif_a2dp_ctrl_cb 回调函数:这是蓝牙协议栈中用于处理A2DP控制命令的回调函数。当蓝牙堆栈接收到
A2DP_CTRL_CMD_START
命令时,它会调用这个函数来处理该命令。- btif_a2dp_recv_ctrl_data 函数:在
btif_a2dp_ctrl_cb
回调函数中,btif_a2dp_recv_ctrl_data
函数被调用以处理接收到的A2DP控制数据。解析控制数据,并根据需要执行相应的操作。- UIPC_Open 函数调用:为了在蓝牙协议栈和音频框架之间建立通信通道,
UIPC_Open
函数被调用。这个函数创建了一个UIPC(Userspace Inter-Process Communication)通道,用于在蓝牙协议栈的A2DP模块和音频框架之间传输音频数据和控制信息。UIPC_CH_ID_AV_AUDIO
是通道的标识符,btif_a2dp_data_cb
是用于处理接收到的音频数据的回调函数。- 创建A2DP_DATA_PATH通道:在成功打开UIPC通道后,A2DP数据路径被创建。这个数据路径用于将音频数据从音频框架传输到蓝牙协议栈,并最终发送到蓝牙设备。
- 调用 skt_write 向socket发送消息:最后,
skt_write
函数被调用以将音频数据写入到之前创建的socket中。这个socket与蓝牙设备的A2DP服务相关联,因此写入的数据将被发送到蓝牙设备。skt_write
函数可能使用阻塞或非阻塞模式来发送数据,具体取决于配置和当前的网络条件。
2.15. skt_write
packages/modules/Bluetooth/system/audio_a2dp_hw/src/audio_a2dp_hw.cc
static int skt_write(int fd, const void* p, size_t len) {
ssize_t sent;
FNLOG();
ts_log("skt_write", len, NULL);
if (WRITE_POLL_MS == 0) {
// do not poll, use blocking send
OSI_NO_INTR(sent = send(fd, p, len, MSG_NOSIGNAL));
if (sent == -1) ERROR("write failed with error(%s)", strerror(errno));
return (int)sent;
}
// use non-blocking send, poll
int ms_timeout = SOCK_SEND_TIMEOUT_MS;
size_t count = 0;
while (count < len) {
OSI_NO_INTR(sent = send(fd, p, len - count, MSG_NOSIGNAL | MSG_DONTWAIT));
if (sent == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
ERROR("write failed with error(%s)", strerror(errno));
return -1;
}
if (ms_timeout >= WRITE_POLL_MS) {
usleep(WRITE_POLL_MS * 1000);
ms_timeout -= WRITE_POLL_MS;
continue;
}
WARN("write timeout exceeded, sent %zu bytes", count);
return -1;
}
count += sent;
p = (const uint8_t*)p + sent;
}
return (int)count;
}
该函数用于向给定的文件描述符(fd
)写入数据,它提供了两种写入方式,一种是在不进行轮询的情况下使用阻塞式发送,另一种是在设置了特定轮询时间(WRITE_POLL_MS
不为 0
时)采用非阻塞式发送并配合轮询机制来确保数据尽可能完整地写入。函数会根据不同的写入策略进行相应操作,并返回实际写入的字节数(成功时)或者返回 -1
表示写入失败,以此来保障数据能可靠地通过文件描述符对应的通信通道(如套接字等)进行传输。
阻塞发送:如果
WRITE_POLL_MS
宏定义的值为0
,则不使用轮询(polling),而是直接通过send
函数以阻塞方式发送数据。send
函数的参数包括文件描述符fd
、数据指针p
、数据长度len
和标志MSG_NOSIGNAL
(防止发送数据时产生 SIGPIPE 信号)。如果发送失败(send
返回-1
),则记录错误信息。非阻塞发送(带轮询):如果
WRITE_POLL_MS
的值不为0
,则使用非阻塞方式发送数据,并结合轮询机制。首先,定义了一个超时时间ms_timeout
,其值为SOCK_SEND_TIMEOUT_MS
宏定义的值。然后,通过一个循环不断尝试发送数据,直到所有数据都被发送完毕或者发生超时。
- 在循环内部,使用
send
函数尝试发送数据,但这次添加了MSG_DONTWAIT
标志以启用非阻塞模式。如果发送失败(send
返回-1
),则检查错误码:
- 如果错误码不是
EAGAIN
或EWOULDBLOCK
(表示资源暂时不可用,通常发生在非阻塞套接字尝试发送数据但缓冲区已满时),则记录错误信息并返回-1
。- 如果错误码是
EAGAIN
或EWOULDBLOCK
,则检查是否还有剩余的超时时间。如果有,则休眠WRITE_POLL_MS
毫秒后继续尝试发送。如果没有剩余超时时间,则记录警告信息并返回-1
。- 如果发送成功(
send
返回的字节数大于0
),则更新已发送的字节数count
和数据指针p
的位置,继续发送剩余的数据。返回发送的字节数:如果所有数据都被成功发送,则返回发送的总字节数。
2.16. btif_a2dp_data_cb
packages/modules/Bluetooth/system/btif/src/btif_a2dp_control.cc
static void btif_a2dp_data_cb(tUIPC_CH_ID /* ch_id */, tUIPC_EVENT event) {
log::warn("BTIF MEDIA (A2DP-DATA) EVENT {}", dump_uipc_event(event));
switch (event) {
case UIPC_OPEN_EVT:
/*
* Read directly from media task from here on (keep callback for
* connection events.
*/
UIPC_Ioctl(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO,
UIPC_REG_REMOVE_ACTIVE_READSET, NULL);
UIPC_Ioctl(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, UIPC_SET_READ_POLL_TMO,
reinterpret_cast<void*>(A2DP_DATA_READ_POLL_MS));
if (btif_av_get_peer_sep() == AVDT_TSEP_SNK) {
/* Start the media task to encode the audio */
btif_a2dp_source_start_audio_req();
}
/* ACK back when media task is fully started */
break;
...
default:
log::error("### A2DP-DATA EVENT {} NOT HANDLED ###", event);
break;
}
}
btif_a2dp_data_cb函数是一个回调函数,用于处理与A2DP
数据相关的UIPC
事件。根据接收到的不同 UIPC
事件类型(如通道打开事件等),在蓝牙音频系统中执行相应的操作,例如对 UIPC
通道进行配置调整、启动媒体任务以进行音频编码、处理事件未被处理的错误情况等,以此来确保 A2DP
数据相关的 UIPC
通信过程中各个事件能得到恰当的响应,保障蓝牙音频功能的正常运行。
事件处理:使用
switch
语句根据event
的值来处理不同的事件。
- UIPC_OPEN_EVT:当接收到UIPC通道打开事件时,执行以下操作:
- 使用
UIPC_Ioctl
函数移除对UIPC_CH_ID_AV_AUDIO
通道的活动读取集(Read Set)的注册,意味着不再从这个通道主动读取数据。- 再次使用
UIPC_Ioctl
设置UIPC_CH_ID_AV_AUDIO
通道的读取轮询超时时间为A2DP_DATA_READ_POLL_MS(10)
毫秒。- 判断当前是否为音频接收器(通过
btif_av_get_peer_sep()
函数获取对方SEP,即Stream End Point,流端点,AVDT_TSEP_SNK
表示接收器)。如果是,则调用btif_a2dp_source_start_audio_req()
函数启动媒体任务来编码音频。
btif_a2dp_source_start_audio_req
packages/modules/Bluetooth/system/btif/src/btif_a2dp_source.cc
void btif_a2dp_source_start_audio_req(void) {
log::info("state={}", btif_a2dp_source_cb.StateStr());
btif_a2dp_source_thread.DoInThread(
FROM_HERE, base::BindOnce(&btif_a2dp_source_audio_tx_start_event));
}
btif_a2dp_source_start_audio_req
通过调用 btif_a2dp_source_audio_tx_start_event
函数执行启动音频传输所需的具体步骤。这种设计模式(在特定线程中执行任务)有助于保持代码的模块化和线程安全。
btif_a2dp_source_set_tx_flush
packages/modules/Bluetooth/system/btif/src/btif_a2dp_source.cc
static void btif_a2dp_source_audio_tx_start_event(void) {
log::info("streaming {} state={}",
btif_a2dp_source_is_streaming() ? "true" : "false",
btif_a2dp_source_cb.StateStr());
if (btif_av_is_a2dp_offload_running()) return;
/* Reset the media feeding state */
CHECK(btif_a2dp_source_cb.encoder_interface != nullptr);
btif_a2dp_source_cb.encoder_interface->feeding_reset();
log::verbose(
"starting timer {} ms",
btif_a2dp_source_cb.encoder_interface->get_encoder_interval_ms());
/* audio engine starting, reset tx suspended flag */
btif_a2dp_source_cb.tx_flush = false;
wakelock_acquire();
btif_a2dp_source_cb.media_alarm.SchedulePeriodic(
btif_a2dp_source_thread.GetWeakPtr(), FROM_HERE,
base::BindRepeating(&btif_a2dp_source_audio_handle_timer),
std::chrono::milliseconds(
btif_a2dp_source_cb.encoder_interface->get_encoder_interval_ms()));
btif_a2dp_source_cb.sw_audio_is_encoding = true;
btif_a2dp_source_cb.stats.Reset();
// Assign session_start_us to 1 when
// bluetooth::common::time_get_os_boottime_us() is 0 to indicate
// btif_a2dp_source_start_audio_req() has been called
btif_a2dp_source_cb.stats.session_start_us =
bluetooth::common::time_get_os_boottime_us();
if (btif_a2dp_source_cb.stats.session_start_us == 0) {
btif_a2dp_source_cb.stats.session_start_us = 1;
}
btif_a2dp_source_cb.stats.session_end_us = 0;
A2dpCodecConfig* codec_config = bta_av_get_a2dp_current_codec();
if (codec_config != nullptr) {
btif_a2dp_source_cb.stats.codec_index = codec_config->codecIndex();
}
}
btif_a2dp_source_set_tx_flush函数主要用于启动蓝牙音频传输模型(A2DP
)源端(Source
)的音频请求相关操作。其核心操作是通过线程相关机制,将具体的音频传输启动事件处理函数绑定并提交到对应的线程(btif_a2dp_source_thread
)中去执行,以此来确保 A2DP
源端音频数据发送相关操作能够在合适的线程环境下有序开展,保障蓝牙音频功能在源端的正常启动与运行。
- 检查A2DP
offload
是否正在运行:if (btif_av_is_a2dp_offload_running()) return;
:如果A2DP offload功能正在运行,则直接返回,不执行后续操作。硬件offload
意味着音频处理由硬件直接完成,而不需要软件编码器的参与。- 重置媒体供给状态:
btif_a2dp_source_cb.encoder_interface->feeding_reset();
:调用编码器接口的feeding_reset
方法来重置媒体数据的供给状态。为了确保在启动新的音频传输之前,编码器处于干净的状态。- 设置定时器:使用
btif_a2dp_source_cb.media_alarm.SchedulePeriodic
方法设置一个周期性定时器,该定时器将定期调用btif_a2dp_source_audio_handle_timer
函数来处理音频传输的定时需求。定时器的间隔由编码器接口的get_encoder_interval_ms
方法提供。- 重置传输挂起标志:
btif_a2dp_source_cb.tx_flush = false;
:将传输挂起标志设置为false
,表示当前没有挂起的传输任务。- 获取唤醒锁:
wakelock_acquire();
:获取一个唤醒锁,以防止设备在音频传输期间进入休眠状态。- 更新统计信息:
- 重置统计信息结构体
btif_a2dp_source_cb.stats
。- 设置会话开始时间
session_start_us
。如果通过bluetooth::common::time_get_os_boottime_us
获取的系统启动时间为0(这通常不应该发生),则将会话开始时间设置为1,以表示已经调用了btif_a2dp_source_start_audio_req
函数。- 将会话结束时间
session_end_us
设置为0,表示会话尚未结束。- 获取当前A2DP编解码器配置,并更新统计信息中的编解码器索引
codec_index
。
btif_a2dp_source_audio_tx_start_event
函数通过重置编码器状态、设置定时器、获取唤醒锁和更新统计信息等步骤来启动A2DP源的音频传输。这个函数是A2DP源模块中音频传输流程的关键部分,确保了音频数据能够按照预期的方式被编码和传输。
btif_a2dp_source_audio_handle_timer
packages/modules/Bluetooth/system/btif/src/btif_a2dp_source.cc
static void btif_a2dp_source_audio_handle_timer(void) {
if (btif_av_is_a2dp_offload_running()) return;
#ifndef TARGET_FLOSS
uint64_t timestamp_us = bluetooth::common::time_get_os_boottime_us();
uint64_t stats_timestamp_us = timestamp_us;
#else
uint64_t timestamp_us = bluetooth::common::time_get_os_monotonic_raw_us();
uint64_t stats_timestamp_us = bluetooth::common::time_get_os_boottime_us();
#endif
log_tstamps_us("A2DP Source tx scheduling timer", timestamp_us);
if (!btif_a2dp_source_is_streaming()) {
log::error("ERROR Media task Scheduled after Suspend");
return;
}
CHECK(btif_a2dp_source_cb.encoder_interface != nullptr);
size_t transmit_queue_length =
fixed_queue_length(btif_a2dp_source_cb.tx_audio_queue);
#ifdef __ANDROID__
ATRACE_INT("btif TX queue", transmit_queue_length);
#endif
if (btif_a2dp_source_cb.encoder_interface->set_transmit_queue_length !=
nullptr) {
btif_a2dp_source_cb.encoder_interface->set_transmit_queue_length(
transmit_queue_length);
}
btif_a2dp_source_cb.encoder_interface->send_frames(timestamp_us);
bta_av_ci_src_data_ready(BTA_AV_CHNL_AUDIO);
update_scheduling_stats(&btif_a2dp_source_cb.stats.tx_queue_enqueue_stats,
stats_timestamp_us,
btif_a2dp_source_cb.encoder_interval_ms * 1000);
}
该函数作为A2DP Source
端音频处理定时器触发时的回调函数,主要负责在定时器触发时执行一系列与音频数据发送相关的操作,包括根据 A2DP
offload运行情况判断是否返回、获取不同情况下的时间戳、检查编码器接口指针有效性、处理发送队列长度相关操作、进行音频帧发送、通知相关模块数据准备就绪以及更新调度统计信息等,以此来确保 A2DP
source端音频数据能够按照定时器设定的节奏持续稳定地进行发送,并做好相应的状态记录和协调工作,保障蓝牙音频功能的正常运行。
- 获取时间戳:
- 根据编译时的宏定义(
TARGET_FLOSS
),选择使用系统启动时间(bluetooth::common::time_get_os_boottime_us
)还是系统单调原始时间(bluetooth::common::time_get_os_monotonic_raw_us
)来获取当前的时间戳timestamp_us
。- 同时,根据是否定义了
TARGET_FLOSS
,stats_timestamp_us
可能被设置为与timestamp_us
相同的时间,或者系统启动时间。- 检查是否正在流媒体:如果
btif_a2dp_source_is_streaming
函数返回false
,表示当前没有正在进行的流媒体传输,则记录错误日志并返回。- 编码器接口非空检查:使用
CHECK
宏确保btif_a2dp_source_cb.encoder_interface
不为nullptr
。- 获取传输队列长度:使用
fixed_queue_length
函数获取传输队列btif_a2dp_source_cb.tx_audio_queue
的长度,并将其存储在transmit_queue_length
变量中。- Android平台上的跟踪:如果定义了
__ANDROID__
宏,则使用ATRACE_INT
函数跟踪传输队列的长度。- 设置传输队列长度:如果编码器接口提供了
set_transmit_queue_length
方法,则调用该方法来设置传输队列的长度。- 发送音频帧:调用编码器接口的
send_frames
方法,将当前时间戳timestamp_us
作为参数传递,以发送音频帧。- 通知AV核心接口数据已准备好:调用
bta_av_ci_src_data_ready
函数,通知AV核心接口音频数据已准备好进行传输,参数BTA_AV_CHNL_AUDIO
指定了音频通道。- 更新统计信息:调用
update_scheduling_stats
函数,使用stats_timestamp_us
作为时间戳,以及编码器间隔(转换为微秒)作为间隔时间来更新传输队列的统计信息。
send_frames(
a2dp_aac_send_frames)
packages/modules/Bluetooth/system/stack/a2dp/a2dp_aac_encoder.cc
void a2dp_aac_send_frames(uint64_t timestamp_us) {
uint8_t nb_frame = 0; //用于记录每次迭代需要发送的帧数
uint8_t nb_iterations = 0; // 用于记录发送所有帧所需的迭代次数。
a2dp_aac_get_num_frame_iteration(&nb_iterations, &nb_frame, timestamp_us);
log::verbose("Sending {} frames per iteration, {} iterations", nb_frame,
nb_iterations);
if (nb_frame == 0) return;
for (uint8_t counter = 0; counter < nb_iterations; counter++) {
// Transcode frame and enqueue
a2dp_aac_encode_frames(nb_frame);
}
}
a2dp_aac_send_frames函数主要用于处理 A2DP中 AAC格式音频帧的发送相关操作。首先获取要发送的音频帧数量以及迭代次数信息,接着在音频帧数量不为 0 的情况下,通过循环调用编码函数来对相应数量的音频帧进行编码并将其加入队列(这里的队列通常是用于后续音频数据传输的缓冲结构),以此来推动 AAC 格式音频数据在 A2DP 机制下的发送流程,保障蓝牙音频传输中 AAC 音频内容能够正确地进行后续处理与传输。
- 获取帧数和迭代次数:调用
a2dp_aac_get_num_frame_iteration
函数,根据传入的时间戳timestamp_us
,计算出nb_frame
和nb_iterations
的值。- 检查帧数:如果
nb_frame
为 0,则直接返回,不进行任何发送操作。- 迭代发送帧:使用一个
for
循环,从 0 开始,直到nb_iterations
(不包括nb_iterations
),在每次循环中:调用a2dp_aac_encode_frames
函数,根据nb_frame
的值进行帧的转码(编码)并将其加入队列。
a2dp_aac_encode_frames
packages/modules/Bluetooth/system/stack/a2dp/a2dp_aac_encoder.cc
static void a2dp_aac_encode_frames(uint8_t nb_frame) {
tA2DP_AAC_ENCODER_PARAMS* p_encoder_params =
&a2dp_aac_encoder_cb.aac_encoder_params;
tA2DP_FEEDING_PARAMS* p_feeding_params = &a2dp_aac_encoder_cb.feeding_params;
uint8_t remain_nb_frame = nb_frame;
uint8_t read_buffer[BT_DEFAULT_BUFFER_SIZE];
int pcm_bytes_per_frame = p_encoder_params->frame_length *
p_feeding_params->channel_count *
p_feeding_params->bits_per_sample / 8;
CHECK(pcm_bytes_per_frame <= static_cast<int>(sizeof(read_buffer)));
// Setup the input buffer
AACENC_BufDesc in_buf_desc;
void* in_buf_vector[1] = {nullptr};
int in_buf_identifiers[1] = {IN_AUDIO_DATA};
int in_buf_sizes[1] = {pcm_bytes_per_frame};
int in_buf_element_sizes[1] = {p_feeding_params->bits_per_sample / 8};
in_buf_desc.numBufs = 1;
in_buf_desc.bufs = in_buf_vector;
in_buf_desc.bufferIdentifiers = in_buf_identifiers;
in_buf_desc.bufSizes = in_buf_sizes;
in_buf_desc.bufElSizes = in_buf_element_sizes;
// Setup the output buffer (partially)
AACENC_BufDesc out_buf_desc;
void* out_buf_vector[1] = {nullptr};
int out_buf_identifiers[1] = {OUT_BITSTREAM_DATA};
int out_buf_sizes[1] = {p_encoder_params->max_encoded_buffer_bytes};
// NOTE: out_buf_element_sizes below is probably unused by the encoder
int out_buf_element_sizes[1] = {p_feeding_params->bits_per_sample / 8};
out_buf_desc.numBufs = 1;
out_buf_desc.bufs = out_buf_vector;
out_buf_desc.bufferIdentifiers = out_buf_identifiers;
out_buf_desc.bufSizes = out_buf_sizes;
out_buf_desc.bufElSizes = out_buf_element_sizes;
CHECK(p_encoder_params->max_encoded_buffer_bytes <=
static_cast<int>(BT_DEFAULT_BUFFER_SIZE - sizeof(BT_HDR)));
AACENC_InArgs aac_in_args;
aac_in_args.numInSamples =
p_encoder_params->frame_length * p_feeding_params->channel_count;
aac_in_args.numAncBytes = 0;
AACENC_OutArgs aac_out_args = {
.numOutBytes = 0, .numInSamples = 0, .numAncBytes = 0};
uint32_t count;
uint32_t total_bytes_read = 0;
int written = 0;
while (nb_frame) {
BT_HDR* p_buf = (BT_HDR*)osi_malloc(BT_DEFAULT_BUFFER_SIZE);
p_buf->offset = A2DP_AAC_OFFSET;
p_buf->len = 0;
p_buf->layer_specific = 0;
a2dp_aac_encoder_cb.stats.media_read_total_expected_packets++;
count = 0;
do {
//
// Read the PCM data and encode it
//
uint32_t bytes_read = 0;
if (a2dp_aac_read_feeding(read_buffer, &bytes_read)) {
uint8_t* packet = (uint8_t*)(p_buf + 1) + p_buf->offset + p_buf->len;
if (!a2dp_aac_encoder_cb.has_aac_handle) {
log::error("invalid AAC handle");
a2dp_aac_encoder_cb.stats.media_read_total_dropped_packets++;
osi_free(p_buf);
return;
}
in_buf_vector[0] = read_buffer;
out_buf_vector[0] = packet + count;
AACENC_ERROR aac_error =
aacEncEncode(a2dp_aac_encoder_cb.aac_handle, &in_buf_desc,
&out_buf_desc, &aac_in_args, &aac_out_args);
if (aac_error != AACENC_OK) {
log::error("AAC encoding error: 0x{:x}", aac_error);
a2dp_aac_encoder_cb.stats.media_read_total_dropped_packets++;
osi_free(p_buf);
return;
}
written = aac_out_args.numOutBytes;
count += written;
p_buf->len += written;
nb_frame--;
p_buf->layer_specific++; // added a frame to the buffer
} else {
log::warn("underflow {}", nb_frame);
a2dp_aac_encoder_cb.aac_feeding_state.counter +=
nb_frame * p_encoder_params->frame_length *
p_feeding_params->channel_count *
p_feeding_params->bits_per_sample / 8;
// no more pcm to read
nb_frame = 0;
}
total_bytes_read += bytes_read;
} while ((written == 0) && nb_frame);
// NOTE: We don't check whether the packet will fit in the MTU,
// because AAC doesn't give us control over the encoded frame size.
// If the packet is larger than the MTU, it will be fragmented before
// transmission.
if (p_buf->len) {
/*
* Timestamp of the media packet header represent the TS of the
* first frame, i.e the timestamp before including this frame.
*/
*((uint32_t*)(p_buf + 1)) = a2dp_aac_encoder_cb.timestamp;
a2dp_aac_encoder_cb.timestamp +=
p_buf->layer_specific * p_encoder_params->frame_length;
uint8_t done_nb_frame = remain_nb_frame - nb_frame;
remain_nb_frame = nb_frame;
if (!a2dp_aac_encoder_cb.enqueue_callback(p_buf, done_nb_frame,
total_bytes_read))
return;
} else {
a2dp_aac_encoder_cb.stats.media_read_total_dropped_packets++;
osi_free(p_buf);
}
}
}
该函数主要负责对指定数量(由参数 nb_frame
指定)的音频帧进行 AAC编码操作,具体实现了音频帧的编码和发送功能。它涉及到设置编码所需的输入输出缓冲区相关信息、初始化编码的输入输出参数,然后在循环中读取音频数据(PCM 格式)进行编码,处理编码过程中的各种情况(如编码错误、数据读取不足等),最后根据编码结果将编码后的数据进行相应的处理(如设置时间戳、入队等操作),以此来实现将原始音频数据转换为 AAC 编码格式的数据,并为后续的音频传输做准备,保障蓝牙音频传输中 AAC 音频内容能够正确地进行编码和流转。
- 获取编码器参数:从全局回调函数结构体
a2dp_aac_encoder_cb
中获取AAC编码器的参数(aac_encoder_params
)和输入参数(feeding_params
)。- 计算PCM每帧的字节数:根据编码器的参数(每帧长度、通道数和每样本位数),计算出每帧PCM数据的字节数(
pcm_bytes_per_frame
)。- 检查缓冲区大小:确保PCM每帧的字节数不超过读取缓冲区的默认大小(
BT_DEFAULT_BUFFER_SIZE
)。- 设置输入和输出缓冲区描述符:
- 初始化输入缓冲区描述符
in_buf_desc
,用于描述要编码的PCM数据。- 初始化输出缓冲区描述符
out_buf_desc
,用于描述编码后的AAC数据(。- 设置AAC编码器的输入参数:初始化
AACENC_InArgs
结构体,设置要编码的样本数。- 设置AAC编码器的输出参数:初始化
AACENC_OutArgs
结构体,用于接收编码后的输出信息。- 编码循环:使用
while
循环,直到所有需要编码的帧都被处理。
- 在每次循环中,分配一个新的
BT_HDR
缓冲区,并设置其偏移量、长度和层特定信息。- 使用
do-while
循环尝试读取PCM数据并进行编码,直到当前帧被成功编码或没有更多PCM数据可读。
- 如果读取PCM数据失败(下溢),则记录警告,并设置编码器的馈送状态计数器。
- 如果编码失败,则记录错误,并释放已分配的缓冲区。
- 如果编码成功,则更新已编码的字节数、缓冲区长度和层特定信息(表示已添加到缓冲区中的帧数)。
- 发送编码后的帧:
- 如果
BT_HDR
缓冲区的长度大于0,则设置媒体数据包头的时间戳,并更新编码器的时间戳。- 计算已完成编码的帧数,并尝试使用回调函数将编码后的帧加入队列。如果加入队列失败,则直接返回。
- 如果
BT_HDR
缓冲区的长度为0(即没有成功编码的帧),则增加丢弃的数据包统计,并释放已分配的缓冲区。
a2dp_aac_read_feeding
packages/modules/Bluetooth/system/stack/a2dp/a2dp_aac_encoder.cc
static bool a2dp_aac_read_feeding(uint8_t* read_buffer, uint32_t* bytes_read) {
uint32_t read_size = a2dp_aac_encoder_cb.aac_encoder_params.frame_length *
a2dp_aac_encoder_cb.feeding_params.channel_count *
a2dp_aac_encoder_cb.feeding_params.bits_per_sample / 8;
a2dp_aac_encoder_cb.stats.media_read_total_expected_reads_count++;
a2dp_aac_encoder_cb.stats.media_read_total_expected_read_bytes += read_size;
/* Read Data from UIPC channel */
uint32_t nb_byte_read =
a2dp_aac_encoder_cb.read_callback(read_buffer, read_size);
a2dp_aac_encoder_cb.stats.media_read_total_actual_read_bytes += nb_byte_read;
*bytes_read = nb_byte_read;
if (nb_byte_read < read_size) {
if (nb_byte_read == 0) return false;
/* Fill the unfilled part of the read buffer with silence (0) */
memset(((uint8_t*)read_buffer) + nb_byte_read, 0, read_size - nb_byte_read);
nb_byte_read = read_size;
}
a2dp_aac_encoder_cb.stats.media_read_total_actual_reads_count++;
return true;
}
该函数主要目的是确保从UIPC通道读取到足够的数据以供AAC编码器使用,如果读取的数据不足,则通过填充静音数据来补足,以保证编码过程的顺利进行。
- 计算读取大小:
read_size
是根据AAC编码器的参数计算出的期望读取的数据大小。它等于帧长度(frame_length
)乘以通道数(channel_count
)再乘以每样本位数(bits_per_sample
)然后除以8(因为1字节=8位)。- 统计信息更新:
- 更新
media_read_total_expected_reads_count
,表示总共期望的读取次数增加。- 更新
media_read_total_expected_read_bytes
,表示总共期望读取的字节数增加。- 从UIPC通道读取数据:
- 调用
read_callback
函数从UIPC通道读取数据到read_buffer
中,读取的大小为read_size
。nb_byte_read
是实际读取到的字节数。- 更新
media_read_total_actual_read_bytes
,表示实际读取到的字节数增加。- 将实际读取的字节数通过
bytes_read
参数返回给调用者。- 处理读取不足的情况:如果实际读取的字节数
nb_byte_read
小于期望的读取大小read_size
,则进行以下处理:
- 将
nb_byte_read
更新为read_size
,表示虽然实际读取的数据不足,但通过填充静音数据达到了期望的大小。- 使用
memset
函数将未填充的部分(即read_buffer
中从nb_byte_read
位置到read_size
位置的部分)填充为0(表示静音)。- 如果
nb_byte_read
为0,表示没有读取到任何数据,函数返回false
。- 更新读取次数统计:更新
media_read_total_actual_reads_count
,表示实际读取次数增加。- 函数返回:函数返回
true
,表示读取操作成功完成(即使部分数据是通过填充静音得到的)。
btif_a2dp_source_read_callback
packages/modules/Bluetooth/system/btif/src/btif_a2dp_source.cc
static uint32_t btif_a2dp_source_read_callback(uint8_t* p_buf, uint32_t len) {
uint32_t bytes_read = 0;
if (bluetooth::audio::a2dp::is_hal_enabled()) {
bytes_read = bluetooth::audio::a2dp::read(p_buf, len);
} else if (a2dp_uipc != nullptr) {
bytes_read = UIPC_Read(*a2dp_uipc, UIPC_CH_ID_AV_AUDIO, p_buf, len);
}
if (btif_a2dp_source_cb.sw_audio_is_encoding && bytes_read < len) {
log::warn("UNDERFLOW: ONLY READ {} BYTES OUT OF {}", bytes_read, len);
btif_a2dp_source_cb.stats.media_read_total_underflow_bytes +=
(len - bytes_read);
btif_a2dp_source_cb.stats.media_read_total_underflow_count++;
btif_a2dp_source_cb.stats.media_read_last_underflow_us =
bluetooth::common::time_get_os_boottime_us();
log_a2dp_audio_underrun_event(btif_av_source_active_peer(),
btif_a2dp_source_cb.encoder_interval_ms,
len - bytes_read);
}
return bytes_read;
}
该函数作为一个回调函数,主要用于从特定数据源读取音频数据,其会根据不同的条件(如硬件抽象层(HAL)是否启用、UIPC
是否为空等情况)来选择相应的读取方式,并且在读取的数据量小于期望读取长度时进行相应的日志记录以及统计信息更新,最后返回实际读取到的字节数,以此来为后续的音频处理流程(比如编码、传输等操作)提供数据支持,同时方便跟踪和分析数据读取过程中出现的数据不足等情况。
- 读取数据:首先,检查A2DP硬件抽象层(HAL)是否已启用。如果已启用,则调用
bluetooth::audio::a2dp::read(p_buf, len)
从HAL读取数据,并将读取的字节数存储在bytes_read
中。如果A2DP HAL未启用但a2dp_uipc
(一个指向UIPC(用户进程间通信)通道的指针)不为空,则通过UIPC从指定的通道(UIPC_CH_ID_AV_AUDIO
)读取数据。- 处理数据读取不足:如果启用了软件音频编码(
btif_a2dp_source_cb.sw_audio_is_encoding
为真),并且实际读取的字节数少于请求的长度,则记录一个警告日志,表示发生了数据下溢(underflow)。
- 更新统计信息,包括总下溢字节数(
media_read_total_underflow_bytes
)、总下溢次数(media_read_total_underflow_count
)和最后一次下溢的时间(以操作系统启动时间为基准,media_read_last_underflow_us
)。- 返回值:函数返回实际读取的字节数(
bytes_read
)。
bta_av_ci_src_data_ready
packages/modules/Bluetooth/system/bta/av/bta_av_ci.cc
/*******************************************************************************
*
* Function bta_av_ci_src_data_ready
*
* Description This function sends an event to the AV indicating that
* the phone has audio stream data ready to send and AV
* should call bta_av_co_audio_source_data_path().
*
* Returns void
*
******************************************************************************/
void bta_av_ci_src_data_ready(tBTA_AV_CHNL chnl) {
BT_HDR_RIGID* p_buf = (BT_HDR_RIGID*)osi_malloc(sizeof(BT_HDR_RIGID));
p_buf->layer_specific = chnl;
p_buf->event = BTA_AV_CI_SRC_DATA_READY_EVT;
bta_sys_sendmsg(p_buf);
}
该函数的主要作用是向蓝牙音频(AV)相关模块发送一个事件消息,用于告知对方手机端已经准备好音频流数据可以进行发送了,并且提示对方应该调用 bta_av_co_audio_source_data_path()
函数来进一步处理音频数据的传输路径等相关事宜,以此来协调蓝牙音频数据从源端到目标端的发送流程,保障音频数据能够在合适的时机通过正确的路径进行传输。
最后,调用 bta_sys_sendmsg
函数将准备好的消息(p_buf
)发送到蓝牙系统的消息队列中。
bta_av_ci_data
packages/modules/Bluetooth/system/bta/av/bta_av_main.cc
/*******************************************************************************
*
* Function bta_av_ci_data
*
* Description Forward the BTA_AV_CI_SRC_DATA_READY_EVT to stream state
* machine.
*
*
* Returns void
*
******************************************************************************/
static void bta_av_ci_data(tBTA_AV_DATA* p_data) {
tBTA_AV_SCB* p_scb;
int i;
uint8_t chnl = (uint8_t)p_data->hdr.layer_specific;
for (i = 0; i < BTA_AV_NUM_STRS; i++) {
p_scb = bta_av_cb.p_scb[i];
if (p_scb && p_scb->chnl == chnl) {
bta_av_ssm_execute(p_scb, BTA_AV_SRC_DATA_READY_EVT, p_data);
}
}
}
该函数主要用于将 BTA_AV_CI_SRC_DATA_READY_EV
转发给流状态机(stream state machine
)进行相应处理。它会遍历相关的状态控制块(SCB
)数组,查找与传入数据对应的通道匹配的状态控制块,然后调用相应的函数将事件和数据传递给流状态机,以此来确保音频数据准备就绪这一事件能够在合适的状态机环境下触发相应的状态转换和后续处理操作,保障蓝牙音频传输流程的正常推进。
bta_av_data_path
packages/modules/Bluetooth/system/bta/av/bta_av_aact.cc
/*******************************************************************************
*
* Function bta_av_data_path
*
* Description Handle stream data path.
*
* Returns void
*
******************************************************************************/
void bta_av_data_path(tBTA_AV_SCB* p_scb, UNUSED_ATTR tBTA_AV_DATA* p_data) {
BT_HDR* p_buf = NULL;
uint32_t timestamp;
bool new_buf = false;
uint8_t m_pt = 0x60;
tAVDT_DATA_OPT_MASK opt;
if (!p_scb->started) return;
if (p_scb->cong) return;
if (p_scb->use_rtp_header_marker_bit) {
m_pt |= AVDT_MARKER_SET;
}
// Always get the current number of bufs que'd up
p_scb->l2c_bufs =
(uint8_t)L2CA_FlushChannel(p_scb->l2c_cid, L2CAP_FLUSH_CHANS_GET);
if (!list_is_empty(p_scb->a2dp_list)) {
p_buf = (BT_HDR*)list_front(p_scb->a2dp_list);
list_remove(p_scb->a2dp_list, p_buf);
/* use q_info.a2dp data, read the timestamp */
timestamp = *(uint32_t*)(p_buf + 1);
} else {
new_buf = true;
/* A2DP_list empty, call co_data, dup data to other channels */
p_buf = p_scb->p_cos->data(p_scb->cfg.codec_info, ×tamp);
if (p_buf) {
/* use the offset area for the time stamp */
*(uint32_t*)(p_buf + 1) = timestamp;
/* dup the data to other channels */
bta_av_dup_audio_buf(p_scb, p_buf);
}
}
if (p_buf) {
if (p_scb->l2c_bufs < (BTA_AV_QUEUE_DATA_CHK_NUM)) {
/* There's a buffer, just queue it to L2CAP.
* There's no need to increment it here, it is always read from
* L2CAP (see above).
*/
/* opt is a bit mask, it could have several options set */
opt = AVDT_DATA_OPT_NONE;
if (p_scb->no_rtp_header) {
opt |= AVDT_DATA_OPT_NO_RTP;
}
//
// Fragment the payload if larger than the MTU.
// NOTE: The fragmentation is RTP-compatibie.
//
size_t extra_fragments_n = 0;
if (p_buf->len > 0) {
extra_fragments_n = (p_buf->len / p_scb->stream_mtu) +
((p_buf->len % p_scb->stream_mtu) ? 1 : 0) - 1;
}
std::vector<BT_HDR*> extra_fragments;
extra_fragments.reserve(extra_fragments_n);
uint8_t* data_begin = (uint8_t*)(p_buf + 1) + p_buf->offset;
uint8_t* data_end = (uint8_t*)(p_buf + 1) + p_buf->offset + p_buf->len;
while (extra_fragments_n-- > 0) {
data_begin += p_scb->stream_mtu;
size_t fragment_len = data_end - data_begin;
if (fragment_len > p_scb->stream_mtu) fragment_len = p_scb->stream_mtu;
BT_HDR* p_buf2 = (BT_HDR*)osi_malloc(BT_DEFAULT_BUFFER_SIZE);
p_buf2->offset = p_buf->offset;
p_buf2->len = 0;
p_buf2->layer_specific = 0;
uint8_t* packet2 =
(uint8_t*)(p_buf2 + 1) + p_buf2->offset + p_buf2->len;
memcpy(packet2, data_begin, fragment_len);
p_buf2->len += fragment_len;
extra_fragments.push_back(p_buf2);
p_buf->len -= fragment_len;
}
if (!extra_fragments.empty()) {
// Reset the RTP Marker bit for all fragments except the last one
m_pt &= ~AVDT_MARKER_SET;
}
AVDT_WriteReqOpt(p_scb->avdt_handle, p_buf, timestamp, m_pt, opt);
for (size_t i = 0; i < extra_fragments.size(); i++) {
if (i + 1 == extra_fragments.size()) {
// Set the RTP Marker bit for the last fragment
m_pt |= AVDT_MARKER_SET;
}
BT_HDR* p_buf2 = extra_fragments[i];
AVDT_WriteReqOpt(p_scb->avdt_handle, p_buf2, timestamp, m_pt, opt);
}
p_scb->cong = true;
} else {
/* there's a buffer, but L2CAP does not seem to be moving data */
if (new_buf) {
/* just got this buffer from co_data,
* put it in queue */
list_append(p_scb->a2dp_list, p_buf);
} else {
/* just dequeue it from the a2dp_list */
if (list_length(p_scb->a2dp_list) < 3) {
/* put it back to the queue */
list_prepend(p_scb->a2dp_list, p_buf);
} else {
/* too many buffers in a2dp_list, drop it. */
bta_av_co_audio_drop(p_scb->hndl, p_scb->PeerAddress());
osi_free(p_buf);
}
}
}
}
}
bta_av_data_path
函数负责从A2DP列表中获取数据缓冲区,根据L2CAP缓冲区的状态决定是否发送数据,处理数据分割和RTP头标记位,以及管理拥塞和缓冲区队列。
- 条件检查:查流是否已启动(
p_scb->started
)和是否存在拥塞(p_scb->cong
),若未启动或存在拥塞,则直接返回。- RTP头标记位处理:若配置为使用RTP头标记位(
p_scb->use_rtp_header_marker_bit
),则将m_pt
的标记位设置为AVDT_MARKER_SET
。- L2CAP缓冲区状态检查:通过
L2CA_FlushChannel
函数获取当前L2CAP通道的缓冲区数量(p_scb->l2c_bufs
)。- 处理A2DP列表:
- 若A2DP列表(
p_scb->a2dp_list
)不为空,则从列表中取出第一个缓冲区(p_buf
),并从列表中移除。- 若A2DP列表为空,则调用
p_scb->p_cos->data
函数获取新的数据缓冲区,并设置时间戳。- 数据缓冲区处理:若存在数据缓冲区(
p_buf
),则根据L2CAP缓冲区的数量决定是否将数据发送到L2CAP。
- 若L2CAP缓冲区数量少于预设阈值(
BTA_AV_QUEUE_DATA_CHK_NUM
),则准备将数据发送到L2CAP。- 根据配置决定是否在RTP头中包含数据选项(如不使用RTP头)。
- 若数据长度超过MTU(最大传输单元),则将其分割为多个片段,并为每个片段分配新的缓冲区。
- 对于除最后一个片段外的所有片段,重置RTP标记位。
- 使用
AVDT_WriteReqOpt
函数将原始缓冲区和所有片段发送到AVDT(A/V Data Transport)层。- 拥塞处理和缓冲区队列管理:
- 设置拥塞标志(
p_scb->cong
)为true
,表示数据正在发送。- 若L2CAP缓冲区数量不少于阈值,但仍有新数据到来,则根据A2DP列表的长度决定是否将数据重新加入队列或丢弃。
L2CA_FlushChannel
packages/modules/Bluetooth/system/stack/l2cap/l2c_api.cc
/*******************************************************************************
*
* Function L2CA_FlushChannel
*
* Description This function flushes none, some or all buffers queued up
* for xmission for a particular CID. If called with
* L2CAP_FLUSH_CHANS_GET (0), it simply returns the number
* of buffers queued for that CID L2CAP_FLUSH_CHANS_ALL (0xffff)
* flushes all buffers. All other values specifies the maximum
* buffers to flush.
*
* Returns Number of buffers left queued for that CID
*
******************************************************************************/
uint16_t L2CA_FlushChannel(uint16_t lcid, uint16_t num_to_flush) {
tL2C_CCB* p_ccb;
tL2C_LCB* p_lcb;
uint16_t num_left = 0, num_flushed1 = 0, num_flushed2 = 0;
p_ccb = l2cu_find_ccb_by_cid(NULL, lcid);
if (!p_ccb || (p_ccb->p_lcb == NULL)) {
log::warn("L2CA_FlushChannel() abnormally returning 0 CID: 0x{:04x}",
lcid);
return (0);
}
p_lcb = p_ccb->p_lcb;
if (num_to_flush != L2CAP_FLUSH_CHANS_GET) {
log::verbose(
"L2CA_FlushChannel (FLUSH) CID: 0x{:04x} NumToFlush: {} QC: {} "
"pFirst: 0x{}",
lcid, num_to_flush, fixed_queue_length(p_ccb->xmit_hold_q),
fmt::ptr(fixed_queue_try_peek_first(p_ccb->xmit_hold_q)));
} else {
log::verbose("L2CA_FlushChannel (QUERY) CID: 0x{:04x}", lcid);
}
/* Cannot flush eRTM buffers once they have a sequence number */
if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_ERTM_MODE) {
// Don't need send enhanced_flush to controller if it is LE transport.
if (p_lcb->transport != BT_TRANSPORT_LE &&
num_to_flush != L2CAP_FLUSH_CHANS_GET) {
/* If the controller supports enhanced flush, flush the data queued at the
* controller */
if (bluetooth::shim::GetController()->SupportsNonFlushablePb() &&
(BTM_GetNumScoLinks() == 0)) {
/* The only packet type defined - 0 - Automatically-Flushable Only */
btsnd_hcic_enhanced_flush(p_lcb->Handle(), 0);
}
}
// Iterate though list and flush the amount requested from
// the transmit data queue that satisfy the layer and event conditions.
for (const list_node_t* node = list_begin(p_lcb->link_xmit_data_q);
(num_to_flush > 0) && node != list_end(p_lcb->link_xmit_data_q);) {
BT_HDR* p_buf = (BT_HDR*)list_node(node);
node = list_next(node);
if ((p_buf->layer_specific == 0) && (p_buf->event == lcid)) {
num_to_flush--;
num_flushed1++;
list_remove(p_lcb->link_xmit_data_q, p_buf);
osi_free(p_buf);
}
}
}
/* If needed, flush buffers in the CCB xmit hold queue */
while ((num_to_flush != 0) && (!fixed_queue_is_empty(p_ccb->xmit_hold_q))) {
BT_HDR* p_buf = (BT_HDR*)fixed_queue_try_dequeue(p_ccb->xmit_hold_q);
osi_free(p_buf);
num_to_flush--;
num_flushed2++;
}
/* If app needs to track all packets, call it */
if ((p_ccb->p_rcb) && (p_ccb->p_rcb->api.pL2CA_TxComplete_Cb) &&
(num_flushed2))
(*p_ccb->p_rcb->api.pL2CA_TxComplete_Cb)(p_ccb->local_cid, num_flushed2);
/* Now count how many are left */
for (const list_node_t* node = list_begin(p_lcb->link_xmit_data_q);
node != list_end(p_lcb->link_xmit_data_q); node = list_next(node)) {
BT_HDR* p_buf = (BT_HDR*)list_node(node);
if (p_buf->event == lcid) num_left++;
}
/* Add in the number in the CCB xmit queue */
num_left += fixed_queue_length(p_ccb->xmit_hold_q);
/* Return the local number of buffers left for the CID */
log::verbose("L2CA_FlushChannel() flushed: {} + {}, num_left: {}",
num_flushed1, num_flushed2, num_left);
/* If we were congested, and now we are not, tell the app */
l2cu_check_channel_congestion(p_ccb);
return (num_left);
}
L2CA_FlushChannel函数主要用于对特定连接标识符(CID
)相关的逻辑链路控制和适配协议(L2CAP
)通道上排队等待传输的缓冲区进行清理操作,清理的范围可以根据传入的参数指定,比如全部清理、按数量清理或者仅查询缓冲区数量等情况。它涉及查找相关的控制块结构体、依据不同条件判断是否进行特定的清理操作、处理不同队列中的缓冲区、通知相关回调函数(如果满足条件)以及统计剩余缓冲区数量等功能,以此来灵活地管理 L2CAP
通道上的缓冲区,保障数据传输的有序性和资源的合理利用。
查找连接控制块(CCB):通过调用
l2cu_find_ccb_by_cid
函数,根据提供的lcid
查找对应的 CCB。如果找不到对应的 CCB 或 CCB 的逻辑连接块(LCB)为空,则记录警告日志并返回 0。检查传输模式:如果传输模式不是 eRTM(Enhanced Retransmission Mode),则根据条件决定是否向控制器发送增强型清空命令。
清空缓冲区:
- 遍历
link_xmit_data_q
队列,根据条件清空指定数量的缓冲区。- 如果还有剩余的
num_to_flush
值,则继续清空xmit_hold_q
队列中的缓冲区。回调通知:如果应用程序注册了传输完成回调函数,并且确实清空了缓冲区,则调用该回调函数。
统计剩余缓冲区数量:遍历
link_xmit_data_q
队列和xmit_hold_q
队列,统计剩余缓冲区数量。检查通道拥塞:如果之前通道处于拥塞状态,现在不再拥塞,则通知应用程序。
bta_av_co_audio_source_data_path
packages/modules/Bluetooth/system/btif/co/bta_av_co.cc
BT_HDR* bta_av_co_audio_source_data_path(const uint8_t* p_codec_info,
uint32_t* p_timestamp) {
return bta_av_co_cb.GetNextSourceDataPacket(p_codec_info, p_timestamp);
}
该函数的主要作用是获取下一个音频源数据的数据包,它充当了一个对外的接口,内部通过调用 bta_av_co_cb
的 GetNextSourceDataPacket
函数来实现获取音频源数据数据包的具体逻辑,最终返回一个 BT_HDR*
类型的指针,指向获取到的包含音频数据以及相关头部信息的数据包结构体,为后续的音频数据处理、传输等操作提供数据来源。
GetNextSourceDataPacket
packages/modules/Bluetooth/system/btif/co/bta_av_co.cc
BT_HDR* BtaAvCo::GetNextSourceDataPacket(const uint8_t* p_codec_info,
uint32_t* p_timestamp) {
BT_HDR* p_buf;
log::verbose("codec: {}", A2DP_CodecName(p_codec_info));
p_buf = btif_a2dp_source_audio_readbuf();
if (p_buf == nullptr) return nullptr;
if (p_buf->offset < 4) {
osi_free(p_buf);
log::error("No space for timestamp in packet, dropped");
return nullptr;
}
/*
* Retrieve the timestamp information from the media packet,
* and set up the packet header.
*
* In media packet, the following information is available:
* p_buf->layer_specific : number of audio frames in the packet
* p_buf->word[0] : timestamp
*/
if (!A2DP_GetPacketTimestamp(p_codec_info, (const uint8_t*)(p_buf + 1),
p_timestamp) ||
!A2DP_BuildCodecHeader(p_codec_info, p_buf, p_buf->layer_specific)) {
log::error("unsupported codec type ({})", A2DP_GetCodecType(p_codec_info));
osi_free(p_buf);
return nullptr;
}
BtaAvCoPeer* active_peer = bta_av_legacy_state_.getActivePeer();
// if offset is 0, the decremental operation may result in
// underflow and OOB access
if (ContentProtectEnabled() && (active_peer != nullptr) &&
active_peer->ContentProtectActive() && p_buf->offset > 0) {
p_buf->len++;
p_buf->offset--;
uint8_t* p = (uint8_t*)(p_buf + 1) + p_buf->offset;
*p = ContentProtectFlag();
}
return p_buf;
}
该函数主要负责获取下一个音频源数据的数据包,并对其进行一系列的合法性检查、信息提取以及可能的特定处理操作(如与内容保护相关的操作),最后返回一个有效的 BT_HDR*
类型指针指向处理后的数据包(若获取及处理成功),或者返回 nullptr
表示出现异常或不符合要求的情况,以此来为蓝牙音频数据传输流程提供符合要求且包含必要信息(如时间戳、正确的编解码器头部等)的音频数据包,保障音频数据能够正确地在系统中流转和处理。
- 读取音频数据包:调用
btif_a2dp_source_audio_readbuf()
尝试从A2DP音频源读取一个数据包。如果返回nullptr
,则表示没有可用的数据包,函数返回nullptr
。- 检查数据包空间:如果数据包的
offset
小于4,表示没有足够的空间来存储时间戳信息。此时,释放数据包并打印错误信息,然后返回nullptr
。- 获取时间戳和构建编解码器头部:
- 调用
A2DP_GetPacketTimestamp
从媒体数据包中获取时间戳信息,并存储在p_timestamp
中。- 调用
A2DP_BuildCodecHeader
根据编解码器信息和数据包中的音频帧数来构建编解码器头部。- 如果这两个操作中的任何一个失败(例如,不支持的编解码器类型),则打印错误信息,释放数据包,并返回
nullptr
。- 内容保护处理:如果启用了内容保护(
ContentProtectEnabled()
),并且当前活动的对等体(active_peer
)也启用了内容保护(active_peer->ContentProtectActive()
),并且数据包的offset
大于0,则执行以下操作:
- 在数据包的正确位置插入内容保护标志(
ContentProtectFlag()
)。- 增加数据包的长度(
p_buf->len++
)并减少offset
(p_buf->offset--
),以便在数据包的前面插入内容保护标志。- 返回处理后的数据包:如果数据包成功处理,则返回指向
BT_HDR
的指针。
btif_a2dp_source_audio_readbuf
packages/modules/Bluetooth/system/btif/src/btif_a2dp_source.cc
BT_HDR* btif_a2dp_source_audio_readbuf(void) {
uint64_t now_us = bluetooth::common::time_get_os_boottime_us();
BT_HDR* p_buf =
(BT_HDR*)fixed_queue_try_dequeue(btif_a2dp_source_cb.tx_audio_queue);
btif_a2dp_source_cb.stats.tx_queue_total_readbuf_calls++;
btif_a2dp_source_cb.stats.tx_queue_last_readbuf_us = now_us;
if (p_buf != nullptr) {
// Update the statistics
update_scheduling_stats(&btif_a2dp_source_cb.stats.tx_queue_dequeue_stats,
now_us,
btif_a2dp_source_cb.encoder_interval_ms * 1000);
}
return p_buf;
}
该函数主要用于从蓝牙音频 A2DP
源端的发送音频队列(tx_audio_queue
)中尝试获取一个音频数据缓冲区(以 BT_HDR*
类型指针指向的结构体形式存在),同时还负责更新与该操作相关的一些统计信息,比如读取缓冲区调用次数、上次读取操作的时间戳以及队列出队相关的调度统计信息等,最后返回获取到的音频数据缓冲区指针(若获取成功)或者 nullptr
(若获取失败),以此来为后续的音频数据处理、传输等流程提供数据来源,并方便对音频数据队列的读取操作情况进行跟踪和分析。
- 获取当前时间:使用
bluetooth::common::time_get_os_boottime_us()
函数获取自系统启动以来的微秒数,并将其存储在now_us
变量中。这个时间戳可能用于后续的性能统计或调试。- 尝试从队列中读取数据包:
- 调用
fixed_queue_try_dequeue(btif_a2dp_source_cb.tx_audio_queue)
尝试从全局或静态的btif_a2dp_source_cb
结构体中的tx_audio_queue
队列中读取一个数据包。这个队列存储了待传输的音频数据包。- 如果成功读取到数据包,
fixed_queue_try_dequeue
将返回一个指向BT_HDR
结构体的指针,该结构体包含了数据包的头部信息。- 更新统计信息:无论是否成功读取到数据包,都会更新
btif_a2dp_source_cb
结构体中的统计信息。
tx_queue_total_readbuf_calls
:记录尝试从队列中读取数据包的总次数。tx_queue_last_readbuf_us
:记录最后一次尝试读取数据包的时间戳。- 如果成功读取到数据包:
- 如果
p_buf
不为nullptr
,表示成功读取到了数据包。- 调用
update_scheduling_stats
函数更新与数据包出队相关的统计信息。这个函数可能接受当前时间戳、编码器间隔(以微秒为单位)等参数,并用于计算数据包出队的延迟、吞吐量等性能指标。- 返回数据包:函数最后返回
p_buf
指针,它可能指向一个有效的BT_HDR
结构体(如果成功读取到数据包),或者为nullptr
(如果没有可用的数据包)。
2.17. AVDT_WriteReqOpt
packages/modules/Bluetooth/system/stack/avdt/avdt_api.cc
/*******************************************************************************
*
* Function AVDT_WriteReqOpt
*
* Description Send a media packet to the peer device. The stream must
* be started before this function is called. Also, this
* function can only be called if the stream is a SRC.
*
* When AVDTP has sent the media packet and is ready for the
* next packet, an AVDT_WRITE_CFM_EVT is sent to the
* application via the control callback. The application must
* wait for the AVDT_WRITE_CFM_EVT before it makes the next
* call to AVDT_WriteReq(). If the applications calls
* AVDT_WriteReq() before it receives the event the packet
* will not be sent. The application may make its first call
* to AVDT_WriteReq() after it receives an AVDT_START_CFM_EVT
* or AVDT_START_IND_EVT.
*
* The application passes the packet using the BT_HDR
* structure.
* This structure is described in section 2.1. The offset
* field must be equal to or greater than AVDT_MEDIA_OFFSET
* (if NO_RTP is specified, L2CAP_MIN_OFFSET can be used).
* This allows enough space in the buffer for the L2CAP and
* AVDTP headers.
*
* The memory pointed to by p_pkt must be a GKI buffer
* allocated by the application. This buffer will be freed
* by the protocol stack; the application must not free
* this buffer.
*
* The opt parameter allows passing specific options like:
* - NO_RTP : do not add the RTP header to buffer
*
* Returns AVDT_SUCCESS if successful, otherwise error.
*
******************************************************************************/
uint16_t AVDT_WriteReqOpt(uint8_t handle, BT_HDR* p_pkt, uint32_t time_stamp,
uint8_t m_pt, tAVDT_DATA_OPT_MASK opt) {
AvdtpScb* p_scb;
tAVDT_SCB_EVT evt;
uint16_t result = AVDT_SUCCESS;
log::verbose("avdt_handle={} timestamp={} m_pt=0x{:x} opt=0x{:x}", handle,
time_stamp, m_pt, opt);
/* map handle to scb */
p_scb = avdt_scb_by_hdl(handle);
if (p_scb == NULL) {
result = AVDT_BAD_HANDLE;
} else {
evt.apiwrite.p_buf = p_pkt;
evt.apiwrite.time_stamp = time_stamp;
evt.apiwrite.m_pt = m_pt;
evt.apiwrite.opt = opt;
avdt_scb_event(p_scb, AVDT_SCB_API_WRITE_REQ_EVT, &evt);
}
log::verbose("result={} avdt_handle={}", result, handle);
return result;
}
该函数的核心功能是向对等设备发送一个媒体数据包,不过需要满足一些前置条件,比如对应的音频流必须已经启动,且调用该函数的音频流需为源端(SRC
)。它负责接收要发送的数据包以及相关的参数信息(如时间戳、特定配置选项等),进行必要的前期准备(如查找对应的流控制块等操作),然后触发相应的事件将数据包传递给协议栈的相关处理逻辑,最终返回操作的结果(成功则返回 AVDT_SUCCESS
,否则返回相应的错误码),以此来保障媒体数据包能按照蓝牙音频视频分发传输协议(AVDT
)的规则准确地发送给对等设备,并且协调好应用层与协议栈之间关于数据包发送的交互逻辑。
- 句柄到SCB的映射:
- 调用
avdt_scb_by_hdl
函数,根据提供的句柄查找对应的AVDTP控制块(SCB,Session Control Block)。- 如果找不到对应的SCB,则返回
AVDT_BAD_HANDLE
错误。- 准备事件数据:如果找到了SCB,则准备一个
tAVDT_SCB_EVT
结构体的事件数据,包括要发送的BT_HDR
结构体指针、时间戳、媒体类型和选项掩码。- 处理事件:调用
avdt_scb_event
函数,将准备好的事件数据发送给SCB进行处理。这个处理包括将数据包发送到对等设备,并等待确认事件(AVDT_WRITE_CFM_EVT
)以便发送下一个数据包。- 返回结果:返回操作的结果代码。
注意事项:
- 在调用
AVDT_WriteReqOpt
之前,必须确保流已经启动(通过接收AVDT_START_CFM_EVT
或AVDT_START_IND_EVT
事件)。- 应用程序必须等待
AVDT_WRITE_CFM_EVT
事件才能再次调用AVDT_WriteReqOpt
。BT_HDR
结构体中的offset
字段必须大于或等于AVDT_MEDIA_OFFSET
(如果未指定NO_RTP
,则可以使用L2CAP_MIN_OFFSET
),以确保为L2CAP和AVDTP头部留出足够的空间。- 传递给
AVDT_WriteReqOpt
的p_pkt
指针必须指向由应用程序分配的GKI(Generic Kernel Interface)缓冲区,该缓冲区将由协议栈释放,应用程序不应释放它。
avdt_scb_event(AVDT_SCB_API_WRITE_REQ_EVT)
packages/modules/Bluetooth/system/stack/avdt/avdt_scb.cc
/*******************************************************************************
*
* Function avdt_scb_event
*
* Description State machine event handling function for scb
*
*
* Returns Nothing.
*
******************************************************************************/
void avdt_scb_event(AvdtpScb* p_scb, uint8_t event, tAVDT_SCB_EVT* p_data) {
tAVDT_SCB_ST_TBL state_table;
uint8_t action;
#if (AVDT_DEBUG == TRUE)
log::verbose("SCB hdl={} event={}/{} state={} p_avdt_scb={} scb_index={}",
avdt_scb_to_hdl(p_scb), event, avdt_scb_evt_str[event],
avdt_scb_st_str[p_scb->state], fmt::ptr(p_scb),
p_scb->stream_config.scb_index);
#endif
/* Check that we only send AVDT_SCB_API_WRITE_REQ_EVT to the active stream
* device */
uint8_t num_st_streams = 0;
int ccb_index = -1;
int scb_index = -1;
for (int i = 0; i < AVDT_NUM_LINKS; i++) {
for (int j = 0; j < AVDT_NUM_SEPS; j++) {
AvdtpScb* p_avdt_scb = &avdtp_cb.ccb[i].scb[j];
if (p_avdt_scb->allocated &&
avdt_scb_st_tbl[p_avdt_scb->state] == avdt_scb_st_stream) {
num_st_streams++;
ccb_index = i;
scb_index = j;
} else {
p_avdt_scb->curr_stream = false;
}
}
}
if (num_st_streams == 1) {
avdtp_cb.ccb[ccb_index].scb[scb_index].curr_stream = true;
} else if (num_st_streams > 1 && !p_scb->curr_stream &&
event == AVDT_SCB_API_WRITE_REQ_EVT) {
log::error("ignore AVDT_SCB_API_WRITE_REQ_EVT");
avdt_scb_free_pkt(p_scb, p_data);
return;
}
/* set current event */
p_scb->curr_evt = event;
/* look up the state table for the current state */
state_table = avdt_scb_st_tbl[p_scb->state];
/* set next state */
if (p_scb->state != state_table[event][AVDT_SCB_NEXT_STATE]) {
p_scb->state = state_table[event][AVDT_SCB_NEXT_STATE];
}
/* execute action functions */
for (int i = 0; i < AVDT_SCB_ACTIONS; i++) {
action = state_table[event][i];
if (action != AVDT_SCB_IGNORE) {
(*avdtp_cb.p_scb_act[action])(p_scb, p_data);
} else {
break;
}
}
}
该函数作为 AvdtpScb
的状态机事件处理函数,主要负责处理传入的事件,依据当前的状态以及一系列规则来更新状态、执行相应的动作函数等操作。它会先进行一些前置的检查(比如判断特定事件是否能发送给活动流设备等情况),然后基于状态表查找并更新状态,最后遍历执行相应的动作函数,以此来推动 AVDT
协议下音频流相关的状态流转以及对应的业务逻辑执行,保障整个蓝牙音频视频分发传输协议(AVDT
)系统能按照正确的逻辑处理各种事件、维持音频流的正常运行。
- 检查活动流:遍历所有AVDTP连接(CCB,Connection Control Block)和流(SCB),计算处于“流”状态的SCB数量。
- 如果只有一个流处于活动状态,则标记它。
- 如果有多个流处于活动状态,并且当前SCB不是活动流,且接收到的是
AVDT_SCB_API_WRITE_REQ_EVT
事件,则忽略该事件并释放相关数据包。- 设置当前事件:将接收到的事件保存到SCB的
curr_evt
字段中。- 查找状态表:根据当前状态查找状态表,以确定下一步的状态和要执行的动作。
- 设置下一个状态:如果当前状态与状态表中为给定事件指定的下一个状态不同,则更新SCB的状态。
- 执行动作函数:
- 遍历状态表中为当前事件指定的所有动作。
- 对于每个非
AVDT_SCB_IGNORE
的动作,调用相应的动作函数,并传递SCB和事件数据作为参数。- 如果遇到
AVDT_SCB_IGNORE
,则停止遍历。
avdt_scb_hdl_write_req
packages/modules/Bluetooth/system/stack/avdt/avdt_scb_act.cc
/*******************************************************************************
*
* Function avdt_scb_hdl_write_req
*
* Description This function frees the media packet currently stored in
* the SCB, if any. Then it builds a new media packet from
* with the passed in buffer and stores it in the SCB.
*
* Returns Nothing.
*
******************************************************************************/
void avdt_scb_hdl_write_req(AvdtpScb* p_scb, tAVDT_SCB_EVT* p_data) {
uint8_t* p;
uint32_t ssrc;
bool add_rtp_header = !(p_data->apiwrite.opt & AVDT_DATA_OPT_NO_RTP);
/* free packet we're holding, if any; to be replaced with new */
if (p_scb->p_pkt != NULL) {
/* this shouldn't be happening */
log::warn("Dropped media packet; congested");
}
osi_free_and_reset((void**)&p_scb->p_pkt);
/* Recompute only if the RTP header wasn't disabled by the API */
if (add_rtp_header) {
bool is_content_protection = (p_scb->curr_cfg.num_protect > 0);
add_rtp_header =
A2DP_UsesRtpHeader(is_content_protection, p_scb->curr_cfg.codec_info);
}
/* Build a media packet, and add an RTP header if required. */
if (add_rtp_header) {
if (p_data->apiwrite.p_buf->offset < AVDT_MEDIA_HDR_SIZE) {
return;
}
ssrc = avdt_scb_gen_ssrc(p_scb);
p_data->apiwrite.p_buf->len += AVDT_MEDIA_HDR_SIZE;
p_data->apiwrite.p_buf->offset -= AVDT_MEDIA_HDR_SIZE;
p_scb->media_seq++;
p = (uint8_t*)(p_data->apiwrite.p_buf + 1) + p_data->apiwrite.p_buf->offset;
UINT8_TO_BE_STREAM(p, AVDT_MEDIA_OCTET1);
UINT8_TO_BE_STREAM(p, p_data->apiwrite.m_pt);
UINT16_TO_BE_STREAM(p, p_scb->media_seq);
UINT32_TO_BE_STREAM(p, p_data->apiwrite.time_stamp);
UINT32_TO_BE_STREAM(p, ssrc);
}
/* store it */
p_scb->p_pkt = p_data->apiwrite.p_buf;
}
该函数主要负责处理写请求相关操作,具体包括先释放当前存储在流控制块(SCB
)中的媒体数据包(如果存在的话),然后依据传入的相关参数以及一些条件判断(如是否添加 RTP
头部等情况)来构建一个新的媒体数据包,最后将构建好的数据包存储到流控制块中,以此来更新流控制块中的媒体数据包内容,确保其符合当前的配置和传输要求,保障音频视频数据在 AVDT
协议下能正确地进行后续处理和传输。
- 变量初始化:
p
:用于指向数据包内部数据的指针。ssrc
:用于存储生成的SSRC(Synchronization Source)标识符。add_rtp_header
:一个布尔值,表示是否需要在数据包中添加RTP(Real-time Transport Protocol)头。它根据传入数据的选项和当前配置来决定。- 释放现有数据包:如果
p_scb->p_pkt
不为空,表示当前SCB中已有一个数据包。函数会记录一个警告(表示数据包被丢弃,可能是因为拥塞),并释放该数据包。- 重新计算是否需要添加RTP头:根据当前配置的内容保护状态和编解码器信息,使用
A2DP_UsesRtpHeader
函数来决定是否确实需要添加RTP头。- 构建媒体数据包:如果需要添加RTP头,并且传入的数据包的偏移量足够小以容纳RTP头,则进行以下操作:
- 使用宏(如
UINT8_TO_BE_STREAM
和UINT16_TO_BE_STREAM
)将RTP头的各个字段(版本、标记、载荷类型、序列号、时间戳和SSRC)写入到数据包中。- 设置指针
p
到数据包中RTP头的起始位置。- 增加媒体序列号。
- 减少数据包的偏移量以在数据前留出空间给RTP头。
- 增加数据包的长度以包含RTP头。
- 存储数据包:将传入的数据包(现在可能已包含RTP头)存储在SCB的
p_pkt
字段中。
avdt_scb_chk_snd_pkt
packages/modules/Bluetooth/system/stack/avdt/avdt_scb_act.cc
/*******************************************************************************
*
* Function avdt_scb_chk_snd_pkt
*
* Description This function checks if the SCB is congested, and if not
* congested it sends a stored media packet, if any. After it
* sends the packet it calls the application callback function
* with a write confirm.
*
* Returns Nothing.
*
******************************************************************************/
void avdt_scb_chk_snd_pkt(AvdtpScb* p_scb, UNUSED_ATTR tAVDT_SCB_EVT* p_data) {
tAVDT_CTRL avdt_ctrl;
BT_HDR* p_pkt;
avdt_ctrl.hdr.err_code = 0;
if (!p_scb->cong) {
if (p_scb->p_pkt != NULL) {
p_pkt = p_scb->p_pkt;
p_scb->p_pkt = NULL;
avdt_ad_write_req(AVDT_CHAN_MEDIA, p_scb->p_ccb, p_scb, p_pkt);
(*p_scb->stream_config.p_avdt_ctrl_cback)(
avdt_scb_to_hdl(p_scb), RawAddress::kEmpty, AVDT_WRITE_CFM_EVT,
&avdt_ctrl, p_scb->stream_config.scb_index);
}
}
}
该函数的主要作用是检查流控制块(SCB
)是否处于拥塞状态,如果没有拥塞且存在存储的媒体数据包,那么就将该数据包发送出去,并且在发送完成后通过调用应用层的回调函数来进行写确认操作,以此告知应用层数据包已成功发送,协调协议层与应用层之间关于媒体数据包发送的交互流程,保障音频视频数据在 AVDT
协议下能有序地完成传输以及相关状态的反馈。
这个函数是处理发送媒体数据包的核心部分,它负责在不拥塞的情况下发送数据包,并通过回调函数通知应用程序操作的结果。
- 发送数据包:如果
p_scb->p_pkt
不为空,表示有一个数据包等待发送。
- 将
p_scb->p_pkt
的值赋给p_pkt
,并将p_scb->p_pkt
设置为NULL
,表示数据包已被取出准备发送。- 调用
avdt_ad_write_req
函数发送数据包。这个函数是AVDTP协议栈中负责数据发送的函数,它接受通道类型、连接控制块指针、会话控制块指针和数据包作为参数。- 调用回调函数:使用
p_scb->stream_config.p_avdt_ctrl_cback
指针调用应用程序提供的回调函数,传递一个包含操作结果的avdt_ctrl
结构体、一个空地址(可能表示没有特定的远端地址与此操作相关联)、一个表示写入确认的事件类型(AVDT_WRITE_CFM_EVT
)、以及SCB的索引。
avdt_ad_write_req
packages/modules/Bluetooth/system/stack/avdt/avdt_ad.cc
/*******************************************************************************
*
* Function avdt_ad_write_req
*
* Description This function is called by a CCB or SCB to send data to a
* transport channel. It looks up the LCID of the channel
* based on the type, CCB, and SCB (if present). Then it
* passes the data to L2CA_DataWrite().
*
*
* Returns AVDT_AD_SUCCESS, if data accepted
* AVDT_AD_CONGESTED, if data accepted and the channel is
* congested
* AVDT_AD_FAILED, if error
*
******************************************************************************/
uint8_t avdt_ad_write_req(uint8_t type, AvdtpCcb* p_ccb, AvdtpScb* p_scb,
BT_HDR* p_buf) {
uint8_t tcid;
/* get tcid from type, scb */
tcid = avdt_ad_type_to_tcid(type, p_scb);
return L2CA_DataWrite(avdtp_cb.ad.rt_tbl[avdt_ccb_to_idx(p_ccb)][tcid].lcid,
p_buf);
}
avdt_ad_write_req函数作为一个关键的中间调用函数,主要负责协调 CCB
、SCB
(流控制块结构体)与传输通道之间的数据发送操作。首先依据传入的参数(如数据类型、通道控制块以及流控制块等信息)查找对应的传输通道标识符(TCID
),然后将这个标识符以及要发送的数据缓冲区指针传递给 L2CA_DataWrite
函数,以此来实现将数据发送到相应的传输通道,最终返回表示操作结果的状态码,告知调用者数据是否被接受、通道是否拥塞或者出现了错误等情况,保障数据能按照 AVDT
协议相关的传输逻辑准确地发送出去。
2.18. L2CA_DataWrite
packages/modules/Bluetooth/system/stack/l2cap/l2c_api.cc
/*******************************************************************************
*
* Function L2CA_DataWrite
*
* Description Higher layers call this function to write data.
*
* Returns L2CAP_DW_SUCCESS, if data accepted, else false
* L2CAP_DW_CONGESTED, if data accepted and the channel is
* congested
* L2CAP_DW_FAILED, if error
*
******************************************************************************/
uint8_t L2CA_DataWrite(uint16_t cid, BT_HDR* p_data) {
log::verbose("L2CA_DataWrite() CID: 0x{:04x} Len: {}", cid, p_data->len);
return l2c_data_write(cid, p_data, L2CAP_FLUSHABLE_CH_BASED);
}
L2CA_DataWrite
函数位于L2CAP(Logical Link Control and Adaptation Protocol Layer)层。L2CAP是蓝牙协议栈中的一层,负责数据段的分割、重组以及提供更高层次协议(如AVDTP)所需的逻辑通道。最后调用内部的 l2c_data_write
函数并传递相应参数来实际执行数据写入的核心逻辑,最终返回表示操作结果的状态码,告知调用者数据是否被接受、通道是否拥塞或者出现了错误等情况,以此来协调高层与 L2CAP
层之间关于数据传输的交互流程,保障数据能按照相应规则准确地写入到对应的通道中。
snoop log:
三、时序图
标签:bluedroid,a2dp,音频,scb,Source,源码,peer,A2DP,btif From: https://blog.csdn.net/weixin_37800531/article/details/143937637