在蓝牙音频分发的技术体系中,AVDTP(音视频分发传输协议)是承上启下的核心层:上接A2DP、AVRCP等应用配置文件,下承L2CAP与基带层,负责音视频流的协商、建立、传输与全生命周期管控。而AVDTP协议的核心骨架,就是流端点(SEP)的状态机。
目录
[1.1 什么是SEP状态机](#1.1 什么是SEP状态机)
[1.2 六大核心状态全景](#1.2 六大核心状态全景)
[2.1 能力收集:无状态约束的查询类操作](#2.1 能力收集:无状态约束的查询类操作)
[2.2 流配置:从空闲到已配置的第一步](#2.2 流配置:从空闲到已配置的第一步)
[2.3 流建立:传输通道就绪,进入已打开态](#2.3 流建立:传输通道就绪,进入已打开态)
[2.4 启动流式传输:数据开始流转](#2.4 启动流式传输:数据开始流转)
[2.5 流暂停:临时停止,保留通道](#2.5 流暂停:临时停止,保留通道)
[2.6 流重配置:运行中修改参数](#2.6 流重配置:运行中修改参数)
[2.7 流释放:正常关闭,回到空闲](#2.7 流释放:正常关闭,回到空闲)
[2.8 流中止:异常重置的终极手段](#2.8 流中止:异常重置的终极手段)
[2.9 内容安全控制:全生命周期的加密交互](#2.9 内容安全控制:全生命周期的加密交互)
[3.1 状态不一致是怎么产生的](#3.1 状态不一致是怎么产生的)
[3.2 规范推荐的解决方案](#3.2 规范推荐的解决方案)
[4.1 基础定义](#4.1 基础定义)
[4.2 状态转移表与处理函数](#4.2 状态转移表与处理函数)
[4.3 实现说明](#4.3 实现说明)
[5.1 为什么要设计这么多状态?不能简化吗?](#5.1 为什么要设计这么多状态?不能简化吗?)
[5.2 为什么重配置必须暂停流?不能热修改吗?](#5.2 为什么重配置必须暂停流?不能热修改吗?)
[5.3 为什么Abort不等待对端响应就释放资源?](#5.3 为什么Abort不等待对端响应就释放资源?)
如果把一条蓝牙音视频流比作一趟列车,状态机就是这套列车运行的调度系统:什么时候出站配置、什么时候驶上轨道建链、什么时候全速运行传输、什么时候临时停靠暂停、什么时候到站销毁关闭、遇到紧急情况如何逼停中止,都由状态机统一调度。吃透状态机,是理解AVDTP协议运行逻辑、排查蓝牙音频连接故障、开发兼容协议栈的必经之路。
本文从状态机的基础概念出发,逐一拆解六大核心状态的定义与转移规则,覆盖正常流程、异常处理、工程实现等多个维度,带你彻底掌握AVDTP的流生命周期管控逻辑。
一、状态机基础:SEP的角色与生命周期总览
1.1 什么是SEP状态机
在AVDTP的体系中,所有音视频流的能力都以SEP(流端点)的形式对外暴露。一个SEP对应一套具备特定能力的音视频端点,比如一个支持SBC编码的音频源SEP、一个支持AAC编码的音频宿SEP、一个支持H.264的视频源SEP。每个SEP都是独立的逻辑实体,拥有自己的标识符(SEID)、能力集与状态机。
SEP状态机,就是用来描述单个SEP当前运行阶段、定义合法操作与状态转移规则的逻辑模型。它就像SEP的状态身份证,决定了当前SEP能响应哪些命令、能执行哪些操作、下一步能进入哪些状态。
在每一次信令交互中,参与的双方分别承担两个角色:
INT (发起方):主动发起信令命令的一方,主导本次事务的推进。
ACP (接收方):接收命令并给出响应的一方,被动响应本次事务。
需要特别注意的是,INT/ACP是事务级角色,和流方向的SRC(源)/SNK(宿)角色没有绑定关系。绝大多数流操作可以由任意一方发起,比如启动流可以由源端发起,也可以由宿端发起;只有延迟报告是个例外------延迟报告永远由SNK作为INT发起,SRC作为ACP接收,因为延迟是由接收端的缓冲、解码产生的。
1.2 六大核心状态全景
AVDTP协议定义了六个稳定的核心状态,覆盖了SEP从创建到销毁的完整生命周期。每个状态都有明确的进入条件、允许的操作集合与资源分配状态。

从资源分配的视角看,正向流程是资源逐步分配的过程:空闲态→配置态(分配逻辑资源)→打开态(分配通道与缓冲区)→传输态(全速运行) ;反向回收则是逐步释放资源:传输态→打开态(暂停,保留通道)→关闭中→空闲态。
|------------------------|----------|-------------------------|--------------------------------|
| 状态标识符 | 状态名称 | 核心含义 | 资源分配情况 |
| AVDTP_STATE_IDLE | 空闲态 | SEP初始化后的默认状态,未被任何流占用 | 仅分配SEP本身的逻辑资源,没有流上下文、没有L2CAP通道 |
| AVDTP_STATE_CONFIGURED | 已配置态 | 双方已完成流能力协商,参数达成一致 | 分配流上下文、流句柄,未分配L2CAP传输通道 |
| AVDTP_STATE_OPEN | 已打开态 | 所有所需的L2CAP传输通道已建立完成,流就绪 | 媒体、报告、恢复等对应通道全部建立,缓冲区已分配 |
| AVDTP_STATE_STREAMING | 流式传输态 | 音视频数据正在传输中 | 通道处于活跃状态,媒体数据持续收发 |
| AVDTP_STATE_CLOSING | 关闭中态 | 正在执行正常关闭流程,等待释放资源 | 正在断开L2CAP通道,回收流上下文资源 |
| AVDTP_STATE_ABORTING | 中止中态 | 正在执行强制中止流程,异常重置状态 | 强制断开所有相关通道,立即回收所有资源 |
这个状态流转的逻辑,和日常使用音乐APP的体验完全对应:打开APP连接耳机对应配置加建链,最终进入OPEN态;点击播放执行START,进入STREAMING态;点击暂停执行SUSPEND,回到OPEN态;退出APP断开连接执行CLOSE,回到IDLE态。
二、全流程状态转移详解
理解了基础状态之后,逐一拆解每个流程的状态转移逻辑,分别从INT和ACP两侧的视角,讲清楚每一步的状态变化、触发条件与背后的设计逻辑。
2.1 能力收集:无状态约束的查询类操作
能力收集是所有流操作的前置步骤,包括流发现(Discover)、获取基本能力(Get Capabilities)、获取全部能力(Get All Capabilities)三类操作。

协议原文:Collect Capabilities, which includes Discover and Get Capabilities, procedures can be performed in any state.
这类操作的核心特点是:纯查询,不修改 SEP 的状态,不占用SEP资源。因此它们可以在任意状态下执行,不会触发状态转移。
流发现(Discover):INT向ACP查询其所有可用的SEP列表,返回每个SEP的SEID、类型(SRC/SNK)、媒体类型、是否正在被使用。这是建立流的第一步,用来找到对端符合要求的SEP。
获取能力(Get Capabilities / Get All Capabilities):针对特定的SEID,查询该SEP支持的全部服务能力。旧版的Get Capabilities只返回基本能力,Get All Capabilities返回完整的能力集,包括延迟报告、头压缩等扩展能力。
举个实际的例子:当手机正在用蓝牙播放音乐,SEP处于STREAMING状态时,依然可以在开发者选项里查询耳机的蓝牙编解码能力。这个查询操作不会中断音乐播放,也不会改变SEP的状态,这就是能力收集的无状态特性。
2.2 流配置:从空闲到已配置的第一步
流配置是流建立的第一个实质性步骤,完成的是参数协商的工作:INT把自己期望的编解码格式、传输服务选项发给ACP,双方确认参数一致,为后续建链做好准备。
(1) INT 侧(发起配置的一方)状态转移

-
初始状态:IDLE
-
触发条件:上层应用发起流配置请求,选定对端的某个SEID与配置参数。
-
中间过程:INT构建设置配置命令,通过信令通道发给ACP。在收到响应之前,INT的SEP状态保持IDLE不变。
-
成功转移 :收到ACP返回的接受响应,状态从IDLE → CONFIGURED。
此时INT会分配本地流句柄,建立本地SEID与远程SEID的映射关系,把协商好的参数存入流上下文。
5. 失败处理:收到ACP返回的拒绝响应,状态保持IDLE不变,同时向上层返回对应的错误码。
常见错误码包括SEP已被占用、不支持的服务类别、参数长度错误等。
(2)ACP侧(接收配置的一方)状态转移

-
初始状态:IDLE
-
触发条件:收到INT发来的设置配置命令。
-
中间过程:ACP先校验命令参数的合法性,再把配置参数上报给上层应用,由应用决定是否接受该配置。
-
成功转移 :上层应用接受配置,ACP回复接受响应,状态从IDLE → CONFIGURED。
ACP会分配本地的流上下文资源,保存协商好的配置参数,锁定该SEP,不再接受其他设备的配置请求。
5. 失败处理:参数校验失败或者应用拒绝配置,回复带错误码的拒绝响应,状态保持IDLE。
(3)补充:配置后的延迟报告交互
在进入CONFIGURED状态后,如果双方配置了延迟报告能力,SNK端会作为INT发起一次延迟报告命令,把自身的初始缓冲延迟值上报给SRC端。
协议原文:Delay reporting enables synchronized playback of audio and video by providing delay reports from a SEP. A SNK that supports Delay Reporting shall always configure Delay Reporting when the Delay Reporting capability is offered by the SRC.
这个交互是音视频同步的基础:SRC端根据SNK上报的延迟,调整音视频的发送时间差,保证接收端的音画同步。整个交互过程不会改变SEP的状态,只是CONFIGURED状态下的一次信令数据交换。后续如果SNK的缓冲延迟发生变化,比如调整了抖动缓冲区大小,也会随时发起延迟报告更新数值。
2.3 流建立:传输通道就绪,进入已打开态
配置完成只是达成了参数共识,真正承载媒体数据的L2CAP通道还没有建立。流建立(Open)流程的核心工作,就是完成所有所需L2CAP通道的连接,让流进入随时可以传输数据的就绪状态。
(1)INT侧状态转移

-
初始状态:CONFIGURED
-
触发条件:上层发起打开流的请求。
-
中间过程:INT构造打开流命令发给ACP,收到接受响应后,开始按照优先级依次建立对应的L2CAP传输通道。
通道建立优先级严格遵循:媒体通道 > 报告通道 > 恢复通道,必须按顺序建立,前一个成功了再建下一个。
4. 成功转移 :所有所需的L2CAP通道全部建立完成,状态从CONFIGURED → OPEN,通知上层流打开成功。
5. 失败处理:收到拒绝响应,或者某一个通道建立失败,状态保持CONFIGURED,上报错误给上层。
(2)ACP侧状态转移

-
初始状态:CONFIGURED
-
触发条件:收到打开流命令。
-
中间过程:ACP上报上层应用,同意后回复接受响应,然后等待INT侧发起L2CAP通道连接,按顺序完成通道建立。
-
成功转移 :所有通道建立完成,状态从CONFIGURED → OPEN。
(3)复用模式下的特殊处理
如果配置了复用模式,多个传输会话可以共享同一个L2CAP通道,不需要为每个会话单独建链。这种情况下,Open流程只需要建立还不存在的TCID对应的通道,已经存在的通道直接复用即可,大大降低了建链的开销。
协议原文:When the multiplexing mode is not configured, a new L2CAP channel is always established and uniquely mapped to that transport session.
非复用模式下,每个传输会话对应一个独立的L2CAP通道,媒体、报告、恢复数据在物理通道上完全隔离,互不干扰,优点是稳定可靠,缺点是通道多了开销大,适合单流的场景。复用模式的优点是通道少、开销低,适合多流并发的场景,比如同时传输音频和视频。
2.4 启动流式传输:数据开始流转
流打开之后,通道已经就绪,但媒体数据还没有开始传输。启动(Start)流程就是通知对端做好数据收发准备,然后正式开始媒体流的传输。
协议支持任意一方作为INT发起启动命令,既可以由SRC发起,也可以由SNK发起,两种场景下的状态转移逻辑完全一致。
(1)INT侧状态转移

-
初始状态:OPEN
-
触发条件:上层发起启动流的请求。
-
中间过程:构造启动流命令发给ACP,等待响应。
-
成功转移 :收到接受响应,状态从OPEN → STREAMING。

如果INT是SRC角色,此时开始编码并发送媒体数据包;如果是SNK角色,准备好接收缓冲区,等待接收数据。
5. 失败处理:收到拒绝响应,状态保持OPEN,上报错误。
(2)ACP侧状态转移

-
初始状态:OPEN
-
触发条件:收到启动流命令。
-
中间过程:上报上层应用,同意后回复接受响应。
-
成功转移 :回复响应后,状态从OPEN → STREAMING,准备对应的数据收发。

(3)批量启动的原子性
Start命令支持批量操作:一条命令里可以携带多个SEID,同时启动多个流。ACP会按照命令里SEID的顺序逐个处理,只要有一个SEID启动失败,就会立即停止处理,返回失败的SEID和错误码,已经处理成功的SEID会回退到OPEN状态,保证整个操作的原子性,避免出现部分启动成功、部分启动失败的不一致情况。
协议原文:The ACP shall process the SEPs in the order they are listed in the Start Stream Command and stop processing when the first error occurs.
2.5 流暂停:临时停止,保留通道
当用户临时暂停音视频播放时,不需要把整个流通道都断开销毁------重新建链的开销很大,会导致恢复播放时出现明显延迟。AVDTP设计了暂停(Suspend)流程,临时停止数据传输,但保留所有L2CAP通道和流上下文,方便快速恢复。
(1)状态转移逻辑
和Start流程类似,Suspend也支持任意一方发起,支持批量操作,原子性处理规则和Start一致。

INT侧:初始状态STREAMING,发起Suspend命令,收到响应后,状态从STREAMING → OPEN,停止媒体数据收发。
ACP侧:初始状态STREAMING,收到Suspend命令,同意后回复响应,状态从STREAMING → OPEN,停止数据收发。

暂停之后,所有的通道、缓冲区、配置参数都保留不变,用户点击恢复播放时,只需要发一条Start命令,几毫秒就能恢复传输,体验上几乎没有延迟。
这里对比一下暂停和关闭的核心区别:
|-------------|-----------------------|-------------|----------|--------------|
| 操作 | 状态变化 | L2CAP通道 | 流上下文 | 恢复速度 |
| 暂停(Suspend) | STREAMING → OPEN | 保留 | 保留 | 极快,毫秒级 |
| 关闭(Close) | STREAMING/OPEN → IDLE | 释放 | 释放 | 慢,秒级,需重新配置建链 |
2.6 流重配置:运行中修改参数
在流处于OPEN状态(暂停后)时,可以修改部分流参数,不需要重新走完整的配置-建链流程,这就是流重配置(Reconfigure)。
(1)重配置的限制
重配置不是所有参数都能改,协议做了严格的限制:
只能修改应用服务能力:比如媒体编解码参数(采样率、声道数、码率)、内容保护参数等。
不能修改传输服务能力:比如是否开启恢复服务、报告服务、头压缩、复用模式等,这些和底层通道绑定的参数不能改。
只能在OPEN状态执行:必须先暂停流,回到OPEN状态才能重配置,不能在STREAMING传输中直接修改参数。
如果尝试修改传输服务能力,ACP会直接返回无效能力错误码。
协议原文:Reconfigure is only permitted for application service capabilities.
(2)为什么要有这些限制?
-
传输服务能力和底层的L2CAP通道、会话映射是强绑定的,比如原来没开恢复服务,现在要开,就需要新建一条恢复通道,这涉及到底层通道的建立,已经超出了重配置的范畴,不如重新走配置流程。
-
传输中直接修改编解码参数,会导致两端的编码器、解码器状态不一致,出现音视频卡顿、花屏、爆音甚至解码失败。所以必须暂停流,两边都停止数据收发,同步修改参数后再恢复传输,保证播放的连贯性。
(3)状态转移逻辑

INT侧:初始状态OPEN,发起Reconfigure命令,携带要修改的参数。收到接受响应后,状态保持OPEN,更新本地流配置参数。
ACP侧:初始状态OPEN,收到Reconfigure命令,校验参数合法性,上报上层同意后,回复响应,状态保持OPEN,更新本地配置。
如果重配置失败,状态保持原来的OPEN,所有参数不生效,不会出现部分修改的情况。
2.7 流释放:正常关闭,回到空闲
当音视频传输结束,正常退出时,执行流释放(Close)流程,有序地断开所有传输通道,释放所有资源,SEP回到IDLE空闲状态,可以被其他流重新使用。
(1)INT侧状态转移

-
初始状态:OPEN 或者 STREAMING
-
触发条件:上层发起关闭流的请求。
-
中间过程 :INT构造关闭流命令发给ACP,发出命令后,状态立刻从当前状态转移到CLOSING,开始准备释放本地资源。
-
成功转移 :收到ACP的接受响应后,INT开始断开所有和该流相关的L2CAP通道,所有通道断开完成后,状态从CLOSING → IDLE,通知上层关闭完成,释放所有流上下文资源。
(2)ACP侧状态转移

-
初始状态:OPEN 或者 STREAMING
-
触发条件:收到关闭流命令。
-
中间过程 :收到命令后立刻转移到CLOSING状态,通知上层应用。上层处理完成后,回复接受响应,开始释放本地资源。
-
成功转移 :等待INT侧断开所有通道后,状态从CLOSING → IDLE。
(3)防死锁超时机制
Close流程有一个重要的保护机制:如果INT发出Close命令后,没有在3秒内完成通道的释放,ACP可以主动发起Abort流程,强制重置两边的状态,释放资源。
协议原文:If the INT does not close the transport channels within 3 seconds the ACP device may initiate the ABORT procedure to reset the states on both sides.
这个机制是为了防止发起方异常卡死,导致接收方的SEP资源一直被占用,无法被其他流使用。3秒的超时时间,既给了正常流程足够的执行时间,又能快速处理异常情况。
2.8 流中止:异常重置的终极手段
正常流程走不通的时候,比如信令超时、对端无响应、状态不一致、出现严重错误,就需要用到Abort(中止)命令,强制终止流,重置两边的状态。
Abort是AVDTP里最硬核的操作,它最大的特点是:可以在任意状态下发起,无论当前SEP处于什么状态,都能接收和处理Abort命令。
(1)状态转移逻辑

-
INT侧 :在任意状态下,上层发起中止请求,或者本地超时、错误触发中止。构造中止命令发给ACP,发出后立刻进入ABORTING 状态,开始强制释放所有相关的L2CAP通道和本地资源。收到ACP的Abort响应后,等待所有通道释放完成,状态从ABORTING → IDLE。
-
ACP侧 :在任意状态下收到中止命令,首先校验SEID是否合法。如果SEID无效,直接丢弃命令,不做响应;如果SEID合法,立刻进入ABORTING 状态,通知上层,回复Abort响应,强制释放所有本地资源,完成后回到IDLE状态。
(2)特殊情况:IDLE状态收到Abort
即使SEP已经处于IDLE空闲状态,收到Abort命令也要正常回复响应,但不需要改变状态------因为本来就是空闲的。这保证了Abort命令的幂等性,不管对方当前处于什么状态,发Abort都不会出错,总能得到响应,不会出现命令石沉大海的情况。
协议原文:AVDTP_ABORT_CMD can be sent or received in IDLE state. In the event that an AVDTP_ABORT_CMD is received in IDLE state, ACP or INT shall reply with an AVDTP_ABORT_RSP, no state change is required.
Abort是整个状态机的兜底机制,所有无法正常推进的流程,所有异常情况,都可以用Abort来重置,保证系统永远可以从错误中恢复,不会出现状态机卡死、资源泄漏的问题。在实际的协议栈实现中,绝大多数异常处理最终都会走到Abort流程。
2.9 内容安全控制:全生命周期的加密交互
内容安全,也就是常说的DRM(数字版权管理),相关的信令交互可以在除了IDLE、CLOSING、ABORTING之外的所有状态执行,也就是CONFIGURED、OPEN、STREAMING状态都可以交换安全控制数据。
这是因为内容保护的协商是贯穿整个流生命周期的:配置阶段协商加密方式和密钥,传输过程中可能需要更新密钥、刷新授权,这些都可以通过安全控制命令完成,不需要暂停或者中断流的传输。
执行安全控制命令不会改变SEP的状态,只是当前状态下的一次数据交互,属于带内的控制信令。
三、状态不一致问题与可靠性保障
理想情况下,INT和ACP的状态应该是同步的,每一条命令和响应都对应一次同步的状态转移。但在实际的无线环境中,信令数据包可能丢失、延迟、乱序,很容易导致两边的状态不一致。
3.1 状态不一致是怎么产生的
举个最常见的场景:
INT向ACP发送了Set Config命令,ACP成功收到了,处理完回复了接受响应,自己进入了CONFIGURED状态。但是这个响应包在传输过程中因为无线干扰丢失了,INT一直没收到响应,超时之后认为配置失败,自己还停留在IDLE状态。
这时候就出现了状态不一致:ACP认为已经配置好了,INT认为还没配置。接下来INT如果再发Set Config命令,ACP就会返回SEP已占用错误;如果INT直接发Open命令,ACP又会因为状态不匹配而拒绝。整个流就卡死了,用户的直观感受就是蓝牙连不上了,需要重启蓝牙。
3.2 规范推荐的解决方案
针对状态不一致的问题,AVDTP规范给出了四层防护方案,从底层到上层逐步保障可靠性。
1. 信令通道禁用自动刷新
最基础的优化,是把信令L2CAP通道的刷新超时设置为无限大。
默认情况下,蓝牙基带的数据包有个超时时间,超过时间没传成功就会自动丢弃,也就是Flush。把信令通道的这个超时设为无限,基带就会一直重传信令数据包,直到成功为止,从底层减少信令丢包的概率。
协议原文:It is recommended to set the L2CAP flush timeout to infinite for the signaling connection so that signaling messages are not automatically flushed.
2. 开启L2CAP增强重传模式
如果蓝牙控制器支持L2CAP增强重传模式(ERTM),建议给信令通道开启这个模式。
ERTM在L2CAP层实现了CRC校验、序号、重传、流量控制等机制,比基带层的重传更可靠,能彻底保证信令数据包的可靠交付。
对应的,媒体通道因为要保证实时性,不需要重传,可以用流式模式,丢包了直接丢弃,靠上层的纠错或者错误隐藏来处理。
协议原文:If L2CAP Enhanced Retransmission Mode is supported, implementations may activate it for the AVDTP signaling channel to improve reliability.
3. 用Abort命令强制重置
如果已经出现了状态不一致,最彻底的解决方式就是任何一方发起Abort命令,强制双方都回到IDLE状态,然后重新开始流程。
这也是为什么Abort被设计成可以在任意状态发起------就是为了处理这种状态不一致的异常情况,给系统一个恢复出厂设置的机会。
4. 冲突避免与随机延迟重传
还有一种特殊的冲突场景:两边同时向对方发起同一个命令,比如INT正在等ACP的Set Config响应,结果收到了ACP发来的Set Config命令------说明两边同时想配置对方的SEP。
这种情况下,如果两边都等待对方响应,就会死锁。规范给出的解决方案是:收到冲突命令的一方可以直接拒绝这个命令,然后随机延迟一段时间后,重传自己的命令。随机延迟可以避免两边同时重传再次冲突,保证最终有一方能成功。
协议原文:In case an INT receives a request for the same command that it is expecting a response for, it may reject the command and may use a random delay for an AVDTP level retransmission to avoid deadlock.
四、工程实现:状态机代码示例
讲了这么多理论,我们来看一个简化的AVDTP状态机的C语言实现,帮助大家理解状态机在实际代码中是怎么落地的。
4.1 基础定义
首先定义状态枚举和事件枚举:
// SEP状态枚举
typedef enum {
AVDTP_STATE_IDLE = 0, // 空闲态
AVDTP_STATE_CONFIGURED, // 已配置态
AVDTP_STATE_OPEN, // 已打开态
AVDTP_STATE_STREAMING, // 流式传输态
AVDTP_STATE_CLOSING, // 关闭中态
AVDTP_STATE_ABORTING, // 中止中态
AVDTP_STATE_MAX
} avdtp_state_t;
// 事件枚举,对应上层请求和底层信令接收
typedef enum {
AVDTP_EVT_SET_CONFIG_REQ = 0, // 上层发起配置请求
AVDTP_EVT_SET_CONFIG_RSP, // 收到配置响应
AVDTP_EVT_OPEN_REQ, // 上层发起打开请求
AVDTP_EVT_OPEN_RSP, // 收到打开响应
AVDTP_EVT_START_REQ, // 上层发起启动请求
AVDTP_EVT_START_RSP, // 收到启动响应
AVDTP_EVT_SUSPEND_REQ, // 上层发起暂停请求
AVDTP_EVT_SUSPEND_RSP, // 收到暂停响应
AVDTP_EVT_CLOSE_REQ, // 上层发起关闭请求
AVDTP_EVT_CLOSE_RSP, // 收到关闭响应
AVDTP_EVT_ABORT_REQ, // 上层发起中止请求
AVDTP_EVT_ABORT_RSP, // 收到中止响应
AVDTP_EVT_MAX
} avdtp_event_t;
然后定义SEP的结构体,保存当前状态和其他上下文信息:
typedef struct {
uint8_t seid; // 本地SEID
uint8_t remote_seid; // 远程SEID
avdtp_state_t state; // 当前状态
uint16_t stream_handle; // 流句柄
// 其他配置参数:编解码、服务能力、通道标识符等
} avdtp_sep_t;
4.2 状态转移表与处理函数
工业级的状态机实现一般都用查表法,把状态转移规则固化在二维数组里,逻辑清晰,便于维护和排查问题。
// 状态转移表:[当前状态][事件] = 下一个状态
// AVDTP_STATE_MAX表示无效转移,不改变状态
static const avdtp_state_t state_trans_table[AVDTP_STATE_MAX][AVDTP_EVT_MAX] = {
// IDLE状态
[AVDTP_STATE_IDLE] = {
[AVDTP_EVT_SET_CONFIG_RSP] = AVDTP_STATE_CONFIGURED,
[AVDTP_EVT_ABORT_RSP] = AVDTP_STATE_IDLE,
},
// CONFIGURED状态
[AVDTP_STATE_CONFIGURED] = {
[AVDTP_EVT_OPEN_RSP] = AVDTP_STATE_OPEN,
[AVDTP_EVT_ABORT_REQ] = AVDTP_STATE_ABORTING,
},
// OPEN状态
[AVDTP_STATE_OPEN] = {
[AVDTP_EVT_START_RSP] = AVDTP_STATE_STREAMING,
[AVDTP_EVT_SUSPEND_RSP] = AVDTP_STATE_OPEN,
[AVDTP_EVT_CLOSE_REQ] = AVDTP_STATE_CLOSING,
[AVDTP_EVT_ABORT_REQ] = AVDTP_STATE_ABORTING,
},
// STREAMING状态
[AVDTP_STATE_STREAMING] = {
[AVDTP_EVT_SUSPEND_RSP] = AVDTP_STATE_OPEN,
[AVDTP_EVT_CLOSE_REQ] = AVDTP_STATE_CLOSING,
[AVDTP_EVT_ABORT_REQ] = AVDTP_STATE_ABORTING,
},
// CLOSING状态
[AVDTP_STATE_CLOSING] = {
[AVDTP_EVT_CLOSE_RSP] = AVDTP_STATE_IDLE,
[AVDTP_EVT_ABORT_REQ] = AVDTP_STATE_ABORTING,
},
// ABORTING状态
[AVDTP_STATE_ABORTING] = {
[AVDTP_EVT_ABORT_RSP] = AVDTP_STATE_IDLE,
},
};
然后是核心的状态机处理函数,负责执行状态转移、调用进入/退出动作:
// 状态退出动作:离开某个状态时执行的清理工作
static void avdtp_state_exit_action(avdtp_sep_t *sep, avdtp_state_t state)
{
switch (state) {
case AVDTP_STATE_STREAMING:
// 停止媒体数据收发,关闭传输定时器
avdtp_media_stop(sep);
break;
case AVDTP_STATE_OPEN:
// 可选:暂停通道保活
break;
case AVDTP_STATE_CLOSING:
case AVDTP_STATE_ABORTING:
// 释放L2CAP通道资源
avdtp_l2cap_release_all(sep);
break;
default:
break;
}
}
// 状态进入动作:进入某个状态时执行的初始化工作
static void avdtp_state_entry_action(avdtp_sep_t *sep, avdtp_state_t state)
{
switch (state) {
case AVDTP_STATE_CONFIGURED:
// 初始化流上下文,分配缓冲区
avdtp_stream_context_init(sep);
break;
case AVDTP_STATE_OPEN:
// 通道建立完成,准备媒体传输
avdtp_media_prepare(sep);
break;
case AVDTP_STATE_STREAMING:
// 启动媒体传输定时器,开始收发数据
avdtp_media_start(sep);
break;
case AVDTP_STATE_IDLE:
// 重置流上下文,释放所有资源
avdtp_stream_context_reset(sep);
break;
default:
break;
}
}
// 状态机事件处理主函数
avdtp_state_t avdtp_state_machine_handle(avdtp_sep_t *sep, avdtp_event_t event)
{
if (sep == NULL || event >= AVDTP_EVT_MAX) {
return AVDTP_STATE_MAX;
}
avdtp_state_t current_state = sep->state;
avdtp_state_t next_state = state_trans_table[current_state][event];
// 无效转移,不改变状态,返回错误
if (next_state == AVDTP_STATE_MAX) {
return current_state;
}
// 状态没变化,不用执行动作
if (next_state == current_state) {
return current_state;
}
// 执行旧状态的退出动作
avdtp_state_exit_action(sep, current_state);
// 执行状态转移
sep->state = next_state;
// 执行新状态的进入动作
avdtp_state_entry_action(sep, next_state);
return next_state;
}
4.3 实现说明
这个示例是简化版的INT视角状态机,实际的协议栈实现会更复杂:
-
需要区分INT和ACP两个视角,事件处理逻辑不同;
-
每个事件需要携带参数,比如错误码、配置参数;
-
需要配套定时器管理,处理各种超时场景;
-
需要和上层应用、下层L2CAP的接口做联动。
但核心的设计思想是一致的:用查表法实现状态转移,把业务逻辑和状态转移逻辑解耦,代码结构清晰,易于调试和维护。排查问题的时候,只要打印出当前状态和收到的事件,就能快速定位是不是非法状态转移导致的问题。
五、常见设计疑问解答
5.1 为什么要设计这么多状态?不能简化吗?
经常有同学问,不就是传个音频吗,为什么要分IDLE、CONFIGURED、OPEN、STREAMING这么多状态?直接未连接和连接中两个状态不行吗?
其实这是蓝牙协议的设计哲学:精细化资源管理,低功耗优先。蓝牙设备很多都是电池供电的耳机、音箱,功耗非常敏感。状态拆分越细,资源分配就越精准:
IDLE状态只保留最基础的SEP信息,几乎不占资源;
CONFIGURED只分配逻辑上下文,不占无线通道资源;
OPEN状态才分配L2CAP通道和缓冲区,但还没启动高速传输,功耗较低;
STREAMING状态才是全速传输,功耗最高。
用户使用的时候,大部分时间可能处于暂停状态(OPEN),这时候可以降低链路功耗,不用一直保持高速传输。如果只有两个状态,暂停的时候也得保持全速传输,功耗就上去了。
5.2 为什么重配置必须暂停流?不能热修改吗?
理论上热修改编解码参数是可以做的,但蓝牙音频的实时性要求很高,两端的编码器、解码器必须严格同步。如果在传输中直接修改参数,很容易出现参数更新不同步的情况:发送端已经改成48kHz采样率了,接收端还在用44.1kHz解码,就会出现爆音、变调、花屏。
而且蓝牙的传输是有延迟的,命令发出去到对端收到有几十毫秒的时差,这段时间里的数据包怎么处理?是丢弃还是用旧参数解码?处理不好就会出现播放卡顿。
所以规范直接要求必须暂停后再修改,看似麻烦,实则保证了兼容性和播放稳定性------所有设备都按同一个规则来,就不会出现奇奇怪怪的兼容问题。
5.3 为什么Abort不等待对端响应就释放资源?
Close流程是等收到响应再释放通道,为什么Abort流程发了命令就直接开始释放资源?
因为Abort是异常处理,追求的是快和必成,而不是有序性。当你需要用Abort的时候,往往已经出现了异常,比如对端已经没响应了,这时候等响应很可能永远等不到。所以Abort的设计逻辑是:我发命令通知你一声,然后我这边直接释放资源重置状态;你收到了就也重置,没收到就算了,反正我这边已经恢复了,大不了下次重连的时候再同步。
这种设计在分布式系统里很常见:异常重置的时候,优先保证本地可用性,不依赖对端的响应。
六、测验
问题:简述AVDTP中SEP的六大核心状态及各自的核心含义。
参考答案:
AVDTP的SEP共有六大核心状态,覆盖流的完整生命周期:
-
IDLE(空闲态):初始默认状态,SEP未被占用,仅保留基础逻辑资源,没有流上下文和L2CAP通道。可执行发现、查询能力、发起配置等操作。
-
CONFIGURED(已配置态):双方已完成流能力协商,参数达成一致,分配了流上下文和流句柄,但尚未建立L2CAP传输通道。
-
OPEN(已打开态):所有所需的L2CAP传输通道已建立完成,缓冲区已分配,流处于就绪状态,随时可以启动传输。
-
STREAMING(流式传输态):音视频媒体数据正在通过通道传输,是流的活跃工作状态。
-
CLOSING(关闭中态):正在执行正常关闭流程,正在断开通道、释放资源,完成后回到IDLE态。
-
ABORTING(中止中态):正在执行强制中止流程,异常情况下触发,强制断开所有通道、释放所有资源,完成后回到IDLE态,可从任意状态进入。
问题:AVDTP中出现INT与ACP状态不一致的原因是什么?协议给出了哪些解决方案?
参考答案:
产生原因:
AVDTP信令运行在L2CAP通道上,无线环境下数据包可能因干扰、距离过远而丢失或延迟。如果命令或响应包丢失,会导致一侧状态已经转移,另一侧还停留在原状态,出现状态不一致。例如配置响应丢失,ACP进入CONFIGURED态,INT仍处于IDLE态。
协议给出的解决方案:
-
信令通道禁用自动刷新:将信令L2CAP通道的刷新超时设为无限大,基带持续重传信令包,从底层减少丢包概率。
-
开启L2CAP增强重传模式(ERTM):在L2CAP层实现可靠传输,通过CRC校验、序号、重传机制保证信令可靠交付。
-
Abort命令强制重置:状态不一致时,任意一方发起Abort命令,强制双方回到IDLE态,重新开始流程,是最彻底的兜底方案。
-
冲突随机延迟重传:双方同时发起同一条命令出现死锁时,收到冲突命令的一方拒绝命令,随机延迟后重传自身命令,避免死锁。
问题:AVDTP的流重配置(Reconfigure)有哪些限制?为什么要有这些限制?
参考答案:
限制:
-
参数范围限制:只能修改应用服务能力(如媒体编解码参数、内容保护参数),不能修改传输服务能力(如是否开启恢复、报告、复用模式等)。
-
状态限制:只能在OPEN状态下执行,必须先暂停流,不能在STREAMING传输状态下直接修改。
原因:
-
传输服务能力与底层通道强绑定:传输服务对应L2CAP通道的建立和会话映射,修改这类参数需要重建通道,超出了重配置的范畴,应重新走完整配置流程。
-
保证播放稳定性:传输中直接修改编解码参数,会导致两端编码器/解码器状态不同步,出现爆音、花屏、卡顿等问题。暂停后修改参数,两端同步更新后再恢复传输,保证播放连贯,也保证了不同设备间的兼容性。