在蓝牙音视频传输体系中,AVDTP(音视频分发传输协议)是整个传输链路的控制核心,而信令消息则是所有设备交互的载体。两个蓝牙设备要建立稳定的音视频流,必须经过端点发现、能力查询、参数协商、流建立、启动传输等一系列握手流程。这整个过程的所有交互,都离不开两类核心基础:构成信令消息的信息元素 ,以及决定流配置的服务能力。
目录
[1.1 流端点标识符 SEID](#1.1 流端点标识符 SEID)
[1.2 服务能力长度 LOSC](#1.2 服务能力长度 LOSC)
[1.3 流端点类型 TSEP](#1.3 流端点类型 TSEP)
[1.4 信令包数量 NOSP](#1.4 信令包数量 NOSP)
[1.5 占用状态标记 In Use](#1.5 占用状态标记 In Use)
[1.6 信令错误码 ERROR_CODE](#1.6 信令错误码 ERROR_CODE)
[2.1 服务能力的整体结构](#2.1 服务能力的整体结构)
[2.2 基础传输类服务能力](#2.2 基础传输类服务能力)
[2.3 传输增强类服务能力](#2.3 传输增强类服务能力)
[2.4 应用与控制类服务能力](#2.4 应用与控制类服务能力)
[2.5 服务能力的协商流程](#2.5 服务能力的协商流程)
[2.6 常见的协商失败原因与排障](#2.6 常见的协商失败原因与排障)
[3.1 协议栈层次总览:从HCI到AVDTP](#3.1 协议栈层次总览:从HCI到AVDTP)
[3.2 AVDTP Set Configuration 命令逐字节解析](#3.2 AVDTP Set Configuration 命令逐字节解析)
[3.3 AVDTP Set Configuration 响应逐字节解析](#3.3 AVDTP Set Configuration 响应逐字节解析)
[3.4 抓包解析常见错误与排障要点](#3.4 抓包解析常见错误与排障要点)
很多开发者在调试AVDTP连接时,经常遇到协商失败、流建立报错、兼容性差等问题,本质上都是对这两类基础元素的理解不够深入。比如不清楚错误码对应的具体含义,搞不清SEID和Stream Handle的区别,或者不明白服务能力的编码格式,导致抓包之后无法定位问题。本文从最基础的字段定义出发,逐层拆解AVDTP信令的信息元素和服务能力体系,结合实际的协商流程和常见问题,帮你建立完整的知识框架。
一、信令交互的基石:信息元素全解
AVDTP的所有信令消息,无论是命令还是响应,本质上都是由一个个预定义的信息元素拼接而成。这些元素遵循统一的编码规则,每个元素都有明确的含义、长度和取值范围,就像搭积木用的标准零件,按照固定规则组合起来就能构成完整的信令消息。
信息元素覆盖了信令交互的所有维度:标识流端点的SEID、标记服务能力长度的LOSC、区分端点类型的TSEP、统计分片数量的NOSP、标记占用状态的In Use,以及标识错误原因的ERROR_CODE。下面逐个拆解每个元素的定义和作用。
1.1 流端点标识符 SEID

1. 核心定义
SEID全称Stream End Point Identifier,即流端点标识符,它是两个设备在信令交互中用来指代某个流端点的唯一编号。你可以把它理解成酒店的房间号:每个设备上有多个可以提供音视频服务的流端点(SEP),就像酒店有多个房间,每个房间有一个编号,对方要找哪个房间的服务,就报这个编号。

SEID的长度是6比特,理论上可以有64个取值。其中0x00是禁止使用的保留值,0x01到0x3E是合法可用值,0x3F为预留扩展值。也就是说,一个设备在同一条蓝牙连接上,最多可以支持62个不同的流端点,这对于绝大多数音频、视频场景来说完全足够。
2. 作用域 与分配规则
SEID有一个非常重要的特性:它的作用域是设备本地且连接本地的。这句话有两层含义:
第一,SEID由接收方(ACP)的上层应用分配,只在本设备上有意义,不同设备上的SEID没有对应关系;
第二,同一个设备上,和不同的对端设备建立连接时,SEID可以重复使用。比如耳机和手机A连接时用了SEID=1,和手机B连接时也可以用SEID=1,因为这是两条独立的连接,互不干扰。但在同一条连接上,每个SEP的SEID必须唯一,不能重复。
3. INT SEID 与 ACP SEID
在信令交互中,SEID根据角色不同分为两种:
ACP SEID:接收方的流端点ID,这是绝大多数命令里携带的标识。发起方(INT)要操作对方的某个SEP,就必须使用对方分配的SEID,也就是ACP SEID;
INT SEID:发起方自己的流端点ID,这个字段只在Set Configuration命令中携带,用来告诉接收方"我这边用这个编号来指代咱们这条流"。
当Set Configuration执行成功之后,两边就都知道了对方的SEID,后续的所有信令交互(比如Open、Start、Suspend、Close),都用对方的SEID来指代目标流端点。
4. SEID 与 Stream Handle 的区别
很多初学者容易把SEID和Stream Handle(流句柄)搞混,这里做一个明确的区分:
|--------|-------------------|------------------------|
| 维度 | SEID(流端点标识符) | Stream Handle(流句柄) |
| 作用范围 | 空中传输,跨设备使用 | 设备本地使用,不空中传输 |
| 作用 | 两个设备之间信令交互时指代流端点 | 上层应用与本地协议栈交互时指代一条流 |
| 分配方 | 各自设备的应用层分配,双方各有各的 | 本地AVDTP协议栈在配置成功时分配 |
| 唯一性 | 单条连接内唯一,不同连接可复用 | 本地设备全局唯一 |
打个比方:两个公司做业务对接,A公司的对接人叫张三,工号1001;B公司的对接人叫李四,工号2002。两家公司内部沟通的时候,都用工号指代这个人,这就是Stream Handle;而两家公司对接的时候,都称呼对方的名字,这就是SEID。你不需要知道对方公司的工号体系,只要叫对名字就行,内部的工号自己用就好。
在本地协议栈内部,会维护一张映射表,把对端的SEID、本地的SEID和本地的Stream Handle对应起来。上层应用用句柄操作流,协议栈发信令时转换成对端SEID;收到对端信令时,根据对端SEID找到对应的本地句柄,再上报给应用。
1.2 服务能力长度 LOSC
LOSC全称Length of Service Capabilities,即服务能力长度,它是服务能力条目的配套字段,用来标识一个服务能力条目后面的具体内容有多少字节。

LOSC的长度是8比特(1字节),所以单个服务能力条目的内容最长是255字节。这个设计是典型的TLV(Type-Length-Value)编码结构:Type是服务类别,Length是LOSC,Value是具体的能力参数。
TLV结构最大的好处是扩展性强,而且向后兼容。如果老版本的设备收到了一个不认识的服务类别,它可以根据LOSC的值直接跳过对应字节数,去解析下一个条目,不会因为不认识某个字段就导致整个消息解析失败。这也是蓝牙协议设计里非常通用的一种思路。
1.3 流端点类型 TSEP

TSEP全称Type of Stream End Point,用来标识一个流端点是源端还是宿端。它的长度只有1比特,取值非常简单:
0:代表SRC(Source,源端),也就是这个端点是发送音视频数据的,比如手机的音频输出SEP;
1:代表SNK(Sink,宿端),也就是这个端点是接收音视频数据的,比如耳机的音频输入SEP。
这个字段只会出现在Stream End Point Discovery的响应消息里,发起方通过发现命令拿到对方的SEP列表时,同时就能知道每个SEP是发数据的还是收数据的,从而选择自己需要的类型。

这里要注意一个点:一个设备可以同时拥有SRC和SNK类型的SEP,比如带麦的蓝牙耳机,既有作为SNK接收音频的SEP,也有作为SRC发送麦克风数据的SEP。
1.4 信令包数量 NOSP
NOSP全称Number of Signal Packets,即信令包数量,它只用在分片的信令消息中。

我们知道,L2CAP通道有MTU(最大传输单元)的限制,也就是单次传输的数据包不能超过某个长度。如果一个信令消息的内容特别长,比如某个SEP支持非常多的服务能力和编解码器,整个消息长度超过了L2CAP的MTU,那就需要把这个信令消息拆分成多个小包来发送,这就是信令分片。
在分片传输的时候,第一个包叫做起始包(Start Packet),起始包的头部会携带NOSP字段,长度是8比特,用来告诉接收方:这个完整的信令消息总共被拆成了多少个包。接收方收到起始包之后,就知道自己还要收多少个后续的包,等所有包都收齐了,再拼接成完整的消息进行解析。
后续的包分为继续包(Continue Packet)和结束包(End Packet),这些包的头部不需要携带NOSP,因为接收方已经从起始包里知道总数了。
这里还有两个重要规则:
同一个信令消息的所有分片包必须连续发送,中间不能插入其他信令消息的包;
只有一种情况可以中断分片传输:发送Abort命令来终止当前这个分片消息,而且Abort命令的SEID必须和当前分片消息的SEID一致。
1.5 占用状态标记 In Use

In Use字段用来标识一个流端点当前是否已经被占用,长度也是1比特:
0:表示空闲,也就是这个SEP当前没有被任何流使用,可以用来建立新的连接;
1:表示已占用,也就是这个SEP正在被某条流使用,暂时无法再建立新的连接。
这个字段同样只出现在Discovery响应消息里。发起方在发现阶段就能知道哪些SEP是空闲可用的,哪些已经被占了,从而选择空闲的SEP去进行后续的能力查询和配置。

这里有一点需要注意:In Use状态只是查询那一刻的快照,因为蓝牙连接是动态的,可能你查询的时候还是空闲的,等你发配置命令过去的时候,刚好被别的设备占用了。这时候ACP就会返回SEP_IN_USE的错误码,这也是为什么我们做开发的时候不能只靠Discovery的结果来判断,还要处理配置失败的异常情况。
1.6 信令错误码 ERROR_CODE
错误码是AVDTP信令交互中最常用的排障依据,当接收方(ACP)拒绝发起方(INT)的命令时,就会在拒绝响应里携带一个8位的错误码,告诉对方拒绝的具体原因。
错误码不仅会在空中传输,还会通过协议栈的服务接口上报给上层应用,让应用层知道操作失败的原因,从而做出对应的处理,比如换一个配置重试,或者提示用户。
1. 错误码的两大触发场景

应用层拒绝:ACP的AVDTP层成功解析了命令,并且把请求上报给了上层应用,但上层应用因为资源不足、参数不合法等原因,拒绝了这个请求。这时候上层应用会把错误码返回给AVDTP层,AVDTP再封装成拒绝响应发回给INT;
协议栈层拒绝:ACP的AVDTP层收到命令之后,直接就发现了错误,比如SEID不存在、命令格式不对、当前状态不允许这个操作等,这时候AVDTP层不会上报给应用,直接自己生成拒绝响应返回给INT。

这两种场景的处理流程不同,但最终返回给INT的错误码格式是完全一样的,INT不需要区分是哪一层拒绝的,只要根据错误码判断原因即可。
2. 错误码分类与详解
AVDTP的错误码按照功能分为四大类:头部格式错误、载荷格式错误、传输服务能力错误、流程状态错误。下面逐个说明每个错误码的含义和触发场景。


- 0x01 BAD_HEADER_FORMAT:请求消息的头部格式错误。
这个错误代表收到的信令消息的头部不符合规范,比如字段长度不对,或者保留位被设置了非零值等。相当于你给对方发了一封信,信封的格式完全不对,对方连信封都拆不开,直接退给你。
(2)载荷格式错误类
这一类错误都是和消息的内容格式相关的,也是最常见的错误类型。
- 0x11 BAD_LENGTH:消息长度不匹配。
也就是命令的实际长度和预期的长度不一致,比如某个命令应该带4字节的参数,结果发过来的只有2字节,就会报这个错。
- 0x12 BAD_ACP_SEID:无效的ACP SEID。
也就是请求里携带的SEID在ACP设备上根本不存在,比如对方只有SEID 1和2,你发了个SEID 3过去,就会返回这个错误。这是新手很容易犯的错误,比如Discovery还没做就直接发配置命令,或者SEID赋值错了。
- 0x13 SEP_IN_USE:流端点已被占用。
这个错误只会出现在Set Configuration命令的拒绝响应里。当INT试图配置一个已经被其他流占用的SEP时,ACP就会返回这个错误。就像你去酒店订房间,你选的房间已经被别人住了,前台就会告诉你房间已占用。
- 0x14 SEP_NOT_IN_USE:流端点未被使用。
这个错误只会出现在Reconfigure命令的拒绝响应里。重配置操作的前提是流已经被配置并建立好了,处于Open状态。如果你试图对一个根本没配置的SEP发重配置命令,就会收到这个错误。
- 0x17 BAD_SERV_CATEGORY:无效的服务类别。
也就是Set Configuration或者Reconfigure里携带了一个未定义的服务类别编号,比如用了0x09这个保留值,对方不认识,就返回这个错误。
- 0x18 BAD_PAYLOAD_FORMAT:载荷格式错误。
这是一个通用的格式错误,当载荷的格式有问题,但又不属于上面列出的具体格式错误时,就用这个错误码。
- 0x19 NOT_SUPPORTED_COMMAND:不支持的命令。
也就是ACP不认识收到的这个命令标识符,比如老版本的设备不支持Get All Capabilities命令,收到这个命令就会返回这个错误。这也是为什么很多设备会兼容老的Get Capabilities命令,用来做降级适配。
- 0x1A INVALID_CAPABILITIES:无效的能力配置。
这个错误最常见的场景是Reconfigure命令。AVDTP规定,重配置只能修改应用层的能力,也就是媒体编解码器和内容保护,不能修改传输层的能力,比如媒体传输、恢复、多路复用这些。如果你在Reconfigure里试图修改传输层能力,就会返回这个错误。
(3)传输服务能力错误类
这一类错误都和具体的传输服务能力配置相关,当某个服务类别的参数格式不对或者不支持时,就会返回对应的错误码。
- 0x22 BAD_RECOVERY_TYPE:无效的恢复类型。
也就是配置恢复服务的时候,指定了一个不支持的FEC方案类型。
- 0x23 BAD_MEDIA_TRANSPORT_FORMAT:媒体传输能力格式错误。
媒体传输能力本身是没有参数的,LOSC应该是0,如果配置的时候给媒体传输类别加了额外的参数,就会报这个错。
- 0x25 BAD_RECOVERY_FORMAT:恢复服务能力格式错误。
也就是恢复能力的参数格式不对,比如长度不对,或者参数值超出了规定范围。
- 0x26 BAD_ROHC_FORMAT:头压缩能力格式错误。
头压缩能力的参数应该是1字节,如果长度不对或者参数保留位被设置了,就会报这个错。
- 0x27 BAD_CP_FORMAT:内容保护能力格式错误。
内容保护的参数格式不符合规范,比如CP Type是未定义的值。
- 0x28 BAD_MULTIPLEXING_FORMAT:多路复用能力格式错误。
多路复用的参数格式不对,比如TSID或TCID用了保留值,或者会话顺序不对。
- 0x29 UNSUPPORTED_CONFIGURATION:不支持的配置。
这个错误代表配置的参数本身格式是对的,但ACP的硬件或者软件不支持这个配置。比如你要求48kHz的采样率,但耳机只支持44.1kHz,就会返回这个错误。这也是实际开发中最常遇到的错误之一,通常是编解码器参数不匹配导致的。
(4)流程状态错误类
- 0x31 BAD_STATE:状态机错误。
这个错误代表当前SEP的状态不允许执行这个命令。每个AVDTP命令都有对应的合法状态,比如Start命令只能在Open状态执行,Close命令只能在Open或者Streaming状态执行。如果状态不对还强行发命令,就会收到这个错误。
比如流还没配置就直接发Start,或者流已经关闭了还发Suspend,都会触发BAD_STATE错误。这个错误本质上是两边的状态机不同步导致的,常见于信令丢包之后的状态不一致。
3. 本地错误与空中错误的区别
上面讲的所有错误码都是会在空中传输的,也就是ACP发给INT的错误。还有一类错误是本地错误,也就是INT的AVDTP层在发送命令之前就发现了问题,比如参数非法、句柄无效等,这时候AVDTP会直接返回错误给上层应用,不会把命令发出去。

最常见的本地错误是参数错误,比如传入了一个不存在的Stream Handle,或者空指针之类的,协议栈直接就返回失败,不会走空中流程。

排障的时候,首先要区分是本地错误还是空中返回的错误。如果是本地错误,就查自己的调用参数;如果是空中错误,再根据错误码查对端的原因。
二、流协商的核心:服务能力体系详解
如果说信息元素是信令的积木零件,那服务能力就是音视频流谈判的核心筹码。两个设备能不能建立连接、能支持什么样的音质、有没有纠错能力、能不能做音视频同步,全看服务能力的协商结果。
2.1 服务能力的整体结构
1. 通用格式:TLV结构
所有的服务能力条目都遵循统一的TLV三段式结构:

-
第一字节:Service Category(服务类别),也就是Type,标识这是哪一种服务能力;
-
第二字节:LOSC(长度),也就是Length,标识后面的Value部分有多少字节;
-
后续N字节:具体的能力参数,也就是Value,不同的服务类别有不同的参数格式。
这种结构的好处我们前面已经提过,就是扩展性和兼容性极强。新版本可以增加新的服务类别,老设备收到之后可以直接跳过,不影响其他能力的解析。
2. 服务类别总览

目前定义了8种标准的服务类别,编号从0x01到0x08,其余都是保留值。我们可以把这8类分成三层:
基础传输层:媒体传输(0x01),这是所有SEP必须支持的基础;
传输增强层:报告(0x02)、恢复(0x03)、头压缩(0x05)、多路复用(0x06),这些都是用来优化传输质量和效率的,可选支持;
应用与控制层:媒体编解码器(0x07)、内容保护(0x04)、延迟报告(0x08),这些和具体的应用场景相关,由上层Profile决定是否启用。
下面我们逐个拆解每个服务类别的具体格式和技术细节。
2.2 基础传输类服务能力
1. 媒体传输(Media Transport,0x01)
媒体传输是最基础的服务,也是每个SEP必须支持的。它的作用就是提供基础的RTP媒体流传输通道,没有它就谈不上音视频传输。

它的格式非常简单:服务类别0x01,LOSC=0x00,也就是没有任何额外参数。因为只要支持这个类别,就代表支持标准的RTP媒体包传输,不需要额外配置。
可以把它理解成快递的基础配送服务:只要你寄快递,就默认有这个服务,不需要额外加钱,也没有额外选项。
2. 报告服务(Reporting,0x02)
报告服务对应RTCP(RTP控制协议),也就是传输质量统计报告服务。如果配置了这个服务,就会额外建立一个传输会话,用来收发RTCP的发送端报告(SR)和接收端报告(RR)。

它的格式同样很简单:服务类别0x02,LOSC=0x00,没有额外参数。
RTCP报告里包含了很多传输质量指标,比如累计丢包数、丢包率、包抖动、累计收发字节数等。上层应用可以通过这些指标来判断当前链路的质量,从而做出调整,比如降低码率来适应差的信道,或者调整缓冲来应对抖动。
对于普通的音频场景,报告服务不是必须的,但对于视频或者高质量音频场景,报告服务是很有用的,可以用来监控链路状态。
2.3 传输增强类服务能力
1. 恢复服务(Recovery,0x03)
恢复服务也就是前向纠错(FEC)服务,是用来对抗无线链路丢包的重要手段。蓝牙是无线传输,容易受到干扰导致丢包,而实时音视频又不能像文件传输那样靠重传来恢复,因为重传会带来延迟,影响实时性。FEC就是解决这个问题的:发送端在发送媒体包的同时,额外发送一些校验包,接收端如果丢了少量媒体包,可以用校验包把丢失的包恢复出来,不需要重传。
AVDTP的恢复服务是基于RFC 2733标准实现的,这是一种通用的RTP载荷前向纠错方案。
(1)能力参数格式
恢复服务的能力参数有3字节,也就是LOSC=0x03,包含三个字段:

-
恢复类型 :标识使用的FEC方案。目前只定义了
0x01代表RFC 2733,0x00是禁止值,其他都是保留值; -
最大恢复窗口大小(MRWS):接收端最大可以缓存多少个媒体包来做恢复,取值范围是1到24;
-
单校验块最大媒体包数(MNMP):生成一个校验包最多可以用多少个媒体包来计算,取值范围也是1到24。
(2)参数的实际意义
这两个窗口参数直接决定了FEC的恢复能力和带来的延迟开销:
恢复窗口越大,能恢复的丢包数量就越多,但需要的缓存就越大,带来的延迟也越高。因为接收端必须等窗口内的所有包(包括媒体包和校验包)都到齐了,才能做恢复运算,然后再把数据送给解码器。窗口越大,等待的时间就越长,延迟也就越高;
而单个校验码覆盖的媒体包数量,决定了校验包的开销比例。比如每10个媒体包生成1个校验包,开销就是10%;每4个媒体包生成1个校验包,开销就是25%。覆盖的包越少,开销越大,但恢复能力越强,因为丢一个包的概率比丢多个包的概率高。
实际使用中,需要根据链路质量和延迟要求来权衡这两个参数。链路质量差的时候,就用小一点的覆盖数,提高恢复能力;链路质量好的时候,就用大一点的覆盖数,降低开销。
这里还要提一个非系统化编码的特殊用法:正常情况下,我们是既发媒体包也发校验包,叫做系统化编码。而非系统化编码是只发校验包,不发原始媒体包,接收端完全靠校验包来恢复出原始媒体包。这种模式的抗干扰能力极强,但开销也大,一般用在信道条件特别差的场景。使用这种模式的前提是,配置的时候只配置恢复服务,不配置媒体传输服务。
2. 健壮头压缩(Header Compression,0x05)
健壮头压缩简称ROHC,是一种用来压缩RTP数据包头部的技术,目的是节省传输带宽,提高有效载荷的占比。
我们知道,标准的RTP头部有12字节,对于音频来说,比如SBC编码,一个帧可能也就几十字节,12字节的头占比就很高,差不多20%以上。如果能把头压缩掉,就能节省出带宽用来传更多音频数据,或者降低整体的传输速率,节省功耗。
(1)能力参数格式
头压缩服务的能力参数只有1字节,LOSC=0x01,里面包含三个标志位,其余都是保留位:
bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
保留 | 反馈通道 | 恢复包 | 媒体包
-
媒体包位(bit0):表示是否支持对媒体包进行头压缩。1表示支持,0表示不支持;
-
恢复包位(bit1):表示是否支持对恢复包进行头压缩。1表示支持,0表示不支持;
-
反馈通道位(bit2):表示是否支持ROHC反馈通道。1表示支持,0表示不支持。
(2)各标志位的作用
媒体和恢复位很好理解,就是分别控制两种数据包要不要开压缩。一般来说,如果开了恢复服务,恢复包也会一起开压缩,因为恢复包也有RTP头。
反馈通道是ROHC的优化机制。头压缩的原理是发送端和接收端维护一个共同的上下文,发送端只发送和上下文不同的部分。但如果上下文不同步了,接收端就解不出来。有了反馈通道,接收端就可以给发送端发反馈,告诉发送端上下文同步状态,发送端就可以调整压缩策略,比如重新发完整的头来同步上下文。
没有反馈通道的话,发送端只能定期发完整的头来做同步,压缩效率会低一些,但实现更简单。所以反馈通道是可选的,用来提升压缩效率。
这里要注意,AVDTP里的ROHC只压缩RTP头,不压缩IP和UDP头,因为根本没有这两层。所以和标准的ROHC相比,AVDTP用的是简化版本,只处理RTP层的字段。
3. 多路复用服务(Multiplexing,0x06)
多路复用服务是AVDTP里一个非常重要的传输优化机制,它的核心作用是让多个传输会话(媒体、报告、恢复)共享同一个L2CAP通道,从而减少L2CAP通道的数量,提高带宽利用率。
(1)为什么需要多路复用
在基础模式下,每一个传输会话都要占用一个独立的L2CAP通道。比如一条流如果同时开了媒体、报告、恢复三个服务,那就需要建立三个L2CAP通道。如果有两条流,就需要六个通道。
而L2CAP通道是有资源开销的,每个通道都要维护自己的状态、缓冲区、流控等。通道太多的话,不仅浪费内存,还会增加调度开销,而且每个通道单独发包的话,小包太多,协议开销占比高,带宽利用率低。
多路复用就是为了解决这个问题:把多个小的会话打包到同一个L2CAP通道里传输,大通道的利用率更高,也减少了通道数量。
(2)核心概念:TSID 与 TCID
要理解多路复用,首先要搞清楚两个ID的区别:
TSID(Transport Session Identifier,传输会话ID):用来标识一个逻辑传输会话,比如媒体会话、报告会话、恢复会话各是一个TSID。TSID是逻辑层面的,每个流的每个服务都对应一个唯一的TSID;
TCID(Transport Channel Identifier,传输通道ID):用来标识一个物理的L2CAP通道。TCID是物理层面的,一个TCID对应一个真实的L2CAP连接。
多路复用的本质,就是建立TSID到TCID的映射关系,将多个TSID映射到同一个TCID上,也就是多个逻辑会话共享同一个物理通道。
(3)能力参数格式

多路复用的能力参数长度不固定,取决于有多少个传输会话。参数的结构如下:
第一字节:FRAG标志位(最高位bit7) + 保留位(低7位)。FRAG位表示是否支持适配层分片,1表示支持,0表示不支持;
后续每两个字节对应一个传输会话的映射:
第一个字节:TSID(低5位) + 保留位(高3位);
第二个字节:TCID(低5位) + 保留位(高3位)。
会话的顺序是固定的:第一个永远是媒体传输会话,第二个是报告会话(如果有的话),第三个是恢复会话(如果有的话)。顺序不能乱,必须按照这个来。
TSID和TCID的长度都是5位,所以取值范围都是0x00到0x1F。其中0x00是特殊值,用来查询对方的建议值,0x1F是保留值。所以可用的TSID和TCID都是30个,完全足够用。
(4)适配层包头与分片
当多个会话复用到同一个L2CAP通道之后,每个数据包前面都要加一个适配层(AL)的包头,接收端靠这个包头来区分这个包属于哪个TSID,也就是哪个会话。
适配层包头的结构包含四个部分:

-
TSID:5位,用来标识所属的传输会话;
-
F位:1位,分片标记。0表示这是一个完整的包,或者是一个分片包的第一个分段;1表示这是一个分片包的后续分段;
-
LCODE:2位,长度编码,用来标识后面长度字段的格式:
-
00:没有长度字段,这个包是整个L2CAP载荷里的最后一个包,长度可以由L2CAP的总长度算出来; -
01:16位长度字段,后面跟2字节的长度; -
10:9位长度字段,MSB为0,后面跟1字节的长度(低8位); -
11:9位长度字段,MSB为1,后面跟1字节的长度(低8位)。
-
用可变长度的编码是为了节省开销:短包用9位长度就够了,只需要1字节,比16位的省1字节;最后一个包可以不带长度,靠L2CAP总长度来算,最节省。
除了复用,适配层还支持分片功能。也就是如果一个传输层的包太大,超过了当前L2CAP载荷剩下的空间,就可以把它拆成两段,前一段放在当前的L2CAP包里,后一段放在下一个L2CAP包里。这样可以把每个L2CAP包都填得满满的,最大化带宽利用率。
分片就是靠F位来标识的,接收端收到F=0的分段,就知道这是开头,然后收下后续F=1的分段,直到拼成一个完整的包。
(5)多路复用的实际案例
我们可以用一个直观的例子来理解。假设有两条流:
流1:视频流,带报告和恢复服务,共3个会话(TSID1、TSID2、TSID3);
流2:音频流,带报告服务,共2个会话(TSID4、TSID5)。
在基础模式下,需要5个独立的L2CAP通道(TCID1到TCID5),每个通道传一个会话的包,各自为战。
在多路复用模式下,我们可以把它们合并到2个L2CAP通道里:
通道A(TCID1):传输流1的媒体和报告(TSID1、TSID2);
通道B(TCID2):传输流2的媒体和报告,加上流1的恢复(TSID3、TSID4、TSID5)。
这样一来,L2CAP通道的数量从5个减少到了2个,大大降低了资源开销。而且每个通道里可以把多个小包拼在一起发送,减少了协议开销的占比,提高了有效数据的传输效率。
2.4 应用与控制类服务能力
1. 媒体编解码器(Media Codec,0x07)
媒体编解码器能力是和应用层关系最紧密的,它定义了流使用的编码格式,以及编码的具体参数。我们常说的SBC、AAC、aptX这些,都是在这个服务类别里定义的。
(1)能力参数格式
媒体编解码器的能力参数结构如下:

-
媒体类型 :低6位,标识这是音频还是视频,或者其他类型。
0x00代表音频,0x01代表视频,其余保留; -
编解码器类型:1字节,标识具体的编码格式。具体的取值是在蓝牙分配号里定义的,比如SBC是0x00,MPEG1/2音频是0x01,MPEG4音频是0x02,AAC是0x04等;
-
编解码器特定参数:N字节,不同的编解码器有不同的参数格式,比如SBC的参数包括采样频率、声道模式、块长度、子带数量、分配方式、比特池范围等;AAC的参数包括采样率、声道数、码率类型等。
这部分的具体内容是由上层的Profile来定义的,比如A2DP(高级音频分发配置文件)就详细定义了音频编解码器的参数格式。AVDTP只负责承载这些参数,不解释具体含义,这样设计的好处是解耦:传输层和编码层分开,以后增加新的编解码器,只需要更新上层Profile,不需要改AVDTP本身。
(2)协商注意点
编解码器是流协商的核心,也是最容易出问题的地方。协商的时候,INT会从ACP返回的能力里,选择一个双方都支持的编码格式和参数组合,然后通过Set Configuration发给ACP确认。
这里要注意,Get Capabilities返回的是ACP支持的所有能力集合,是一个范围,比如支持44.1kHz和48kHz两种采样率。而Set Configuration里必须选一个确定的值,不能再是范围,因为配置必须是明确的。
如果INT选的参数不在ACP支持的范围内,ACP就会返回UNSUPPORTED_CONFIGURATION错误。
2. 内容保护(Content Protection,0x04)
内容保护服务也就是DRM(数字版权管理),用来保护受版权保护的音视频内容,防止被盗录。比如我们常见的SCMS-T(串行拷贝管理系统-传输)就是一种内容保护方案。
(1)能力参数格式
内容保护的能力参数结构如下:

-
CP类型:2字节,小端模式,也就是先传低字节(LSB),再传高字节(MSB)。这一点和其他字段不一样,要特别注意。CP类型的取值也是在蓝牙分配号里定义的,比如0x0001代表SCMS-T;
-
CP特定参数:N字节,不同的保护方案有不同的参数,没有统一格式。
(2)多条目支持
内容保护是所有服务类别里唯一一个可以出现多个条目的。也就是说,在Get Capabilities的响应里,可以有多个0x04类别的条目,每个条目对应一种支持的内容保护方案。
比如一个设备同时支持SCMS-T和另一种DRM方案,就会返回两个内容保护的能力条目。INT在配置的时候,选择其中一种即可,Set Configuration里只能有一个内容保护条目。
(3)内容保护的交互流程
内容保护的协商分为两步:
能力协商:在Set Configuration的时候选定CP类型和基础参数,这一步和其他服务一样;
控制交互:建立流之后,双方通过Security Control命令来交换具体的控制数据,比如密钥协商、认证信息等。这些数据是透传的,AVDTP不解析,直接交给上层的内容保护模块处理。
Security Control命令可以在流的任何状态下使用,只要SEP已经配置好了就行。
3. 延迟报告(Delay Reporting,0x08)
延迟报告服务是AVDTP 1.3版本新增的功能,主要用来解决音视频同步的问题。

(1)作用与原理
我们知道,音视频同步的关键是让音频和视频的播放时间对齐。但蓝牙音频传输的时候,接收端(比如蓝牙耳机)会有缓冲、解码、渲染的延迟,这个延迟每个设备都不一样,而且可能会动态变化。如果发送端不知道这个延迟是多少,就没办法准确对齐音视频。
延迟报告就是解决这个问题的:接收端(SNK)把自己这边的总延迟告诉发送端(SRC),发送端就可以根据这个延迟来调整视频的播放时间,或者调整音频的发送时间,让两者在播放端对齐。
比如手机播放视频,声音通过蓝牙音箱输出。音箱的缓冲+解码+播放延迟是200ms,音箱就把这个200ms告诉手机,手机就把视频画面延迟200ms再显示,这样用户看到的画面和听到的声音就是同步的。
(2)能力格式与使用规则
延迟报告的能力格式非常简单:服务类别0x08,LOSC=0x00,没有额外参数。只要支持这个功能,就会带上这个类别。
使用规则上有一个强制要求:如果SRC端支持延迟报告能力,并且SNK端也支持,那么SNK端必须启用这个功能。也就是说,只要源端开了,宿端就必须用,不能拒绝。这样设计是为了保证SRC端总能拿到延迟信息,方便做同步。
(3)延迟报告的交互时机
-
第一次报告是在Set Configuration完成之后,Open命令之前,SNK主动给SRC发一个Delay Report命令,带上初始延迟值;
-
后续如果延迟发生了变化,比如缓冲调整了,SNK会随时发送新的延迟报告,更新延迟值;
-
延迟值的单位是0.1毫秒,用两个字节表示,所以最大可以表示6553.5毫秒,足够覆盖所有场景。
2.5 服务能力的协商流程
讲完了所有的服务类别,我们来梳理一下完整的服务能力协商流程,也就是从发现到配置的整个步骤。
整个协商过程分为四步:发现、查询能力、选择配置、确认配置。
(1)第一步:发现SEP(Discovery)
INT向ACP发送Discovery命令,ACP返回自己所有的SEP列表,每个SEP包含SEID、端点类型(SRC/SNK)、占用状态。
INT从列表里选出一个符合自己需求的、空闲的SEP,记录下它的SEID。
(2)第二步:查询能力(Get All Capabilities / Get Capabilities)
INT用第一步拿到的SEID,向ACP发送Get All Capabilities命令,查询这个SEP的全部能力。
ACP收到命令之后,把这个SEP支持的所有服务能力都返回给INT,包括媒体传输、编解码器、各种可选服务等。
如果是老设备不支持Get All Capabilities,就退而求其次用Get Capabilities,只能拿到基础的能力。
(3)第三步:选择配置
INT拿到ACP的能力列表之后,和自己的能力做匹配,选出一个双方都支持的最优配置组合。
比如选一个最高音质的编解码器,开上报告服务,根据链路质量决定开不开FEC等。
这个选择的过程是INT单方面决定的,ACP只需要确认自己支不支持。
(4)第四步:确认配置(Set Configuration)
INT把选好的配置组合封装成Set Configuration命令,发给ACP,同时带上自己的INT SEID。
ACP收到之后,逐项检查每个服务类别和参数,如果都支持,就返回成功响应,两边的SEP都进入Configured状态,协商完成。
如果有不支持的,就返回拒绝响应,带上错误码和第一个失败的服务类别,INT收到之后可以调整配置再试。
整个协商过程是单向决策的,INT主导,ACP只做同意或拒绝的判断。这样设计的好处是流程简单,交互次数少,效率高。
2.6 常见的协商失败原因与排障
实际开发中,服务能力协商失败是很常见的问题,这里总结几个最常见的原因和排查方法。
①SEID错误:最基础也最容易犯的错误,SEID写错了,直接返回BAD_ACP_SEID。排查方法就是抓包看Discovery的响应,确认正确的SEID。
②编解码器参数不匹配:最常见的失败原因,返回UNSUPPORTED_CONFIGURATION。比如手机要求48kHz采样率,但耳机只支持44.1kHz。排查方法就是对比两边支持的参数范围,找到交集。
③服务类别不支持:比如INT要求开恢复服务,但ACP根本不支持,就会返回BAD_SERV_CATEGORY或者UNSUPPORTED_CONFIGURATION。
④重配置修改了传输层能力:Reconfigure的时候改了不允许改的参数,返回INVALID_CAPABILITIES。
⑤状态错误:在错误的状态下发了配置命令,比如流已经开了还发Set Configuration,返回BAD_STATE。
排障的核心手段就是抓包,看空中交互的信令内容,根据错误码定位具体的失败点。只要对服务能力的格式足够熟悉,抓包之后逐字节解析,很快就能找到问题所在。
三、实战:抓包信令逐字节深度解析
在蓝牙音频开发与联调过程中,snoop是定位协商故障、兼容性问题的核心手段。基于真实的A2DP连接场景中的AVDTP Set Configuration交互抓包,从最底层的HCI数据包开始逐层剥离协议封装,逐字节拆解每一位的语义,建立从原始二进制数据到协议业务逻辑的完整映射能力。
本次抓包的场景为:手机作为源端(INT)向蓝牙耳机(ACP)发送流配置命令,协商SBC编码的音频传输参数,耳机校验通过后返回接受响应。
3.1 协议栈层次总览:从HCI到AVDTP
蓝牙音视频信令遵循严格的分层封装规则,一个空中的ACL数据包经过主机控制器上传后,会依次经过L2CAP层、AVDTP层,最终交付给上层的A2DP Profile处理。整个数据包的分层结构与对应长度如下表:
|--------------|----------|----------|---------|
| 协议层级 | 头部长度 | 载荷长度 | 总长度 |
| HCI ACL Data | 4字节 | 20字节 | 24字节 |
| L2CAP PDU | 4字节 | 16字节 | 20字节 |
| AVDTP信令 | 2字节 | 14字节 | 16字节 |
3.1.1 第一层:HCI ACL 数据包头

HCI是主机与蓝牙基带芯片之间的标准接口,所有空中数据包都会封装为HCI格式上传。HCI ACL数据包的头部固定为4字节,格式定义为小端序存储:
第0-1字节:连接句柄(12位) + 分包边界标志(2位) + 广播标志(2位)
第2-3字节:数据总长度(16位),标识HCI头部之后的载荷字节数
逐字节解析:
1.第0字节0x32 + 第1字节0x20:小端序组合为0x2032
低12位:
0x032,对应连接句柄(Connection Handle)0x0032,是本次蓝牙链路的唯一标识第12-13位:
0b10,对应First automatically-flushable packet,标识这是一个可自动刷新数据包的起始分片第14-15位:
0b00,对应Point-to-point,为点对点单播数据包,非广播
2.第2字节0x14 + 第3字节0x00:小端序组合为0x0014 = 十进制20,对应数据总长度20字节,即后续L2CAP PDU的总长度。
HCI头部解析完成,后续的20字节为L2CAP协议数据单元。
3.1.2 第二层:L2CAP PDU 封装
L2CAP是蓝牙的核心适配层,负责为上层协议提供面向连接的逻辑信道,通过PSM(协议/服务复用器)值区分不同的上层协议,AVDTP对应的PSM值为0x0019。
L2CAP PDU的头部固定为4字节,同样采用小端序:
第0-1字节:载荷长度(16位),标识L2CAP头部之后的载荷字节数
第2-3字节:信道ID(16位),标识对应的逻辑信道
逐字节解析:
第0字节
0x10+ 第1字节0x00:小端序组合为0x0010= 十进制16,对应载荷长度16字节,即后续AVDTP消息的总长度。第2字节
0x44+ 第3字节0x00:小端序组合为0x0044,对应目标信道ID 0x0044,这是AVDTP信令的固定信道,标识该L2CAP载荷属于AVDTP协议。
L2CAP头部解析完成,后续的16字节为AVDTP信令消息,也是本次解析的核心。
3.2 AVDTP Set Configuration 命令逐字节解析
AVDTP所有信令消息采用统一的头部格式,Set Configuration命令的整体结构为:2字节信令头 + 2字节端点标识 + 服务能力列表。

3.2.1 第一部分:AVDTP 信令头(2字节)
信令头共2字节,包含事务标签、包类型、消息类型、信号标识符四个核心字段。
(1)第0字节: 0x60 → 二进制 0110 00 00
①高4位 0110 **= 十进制6:**事务标签。这是发起方为每个命令分配的唯一标识,用于匹配命令与对应的响应,接收方在响应中必须原样返回该标签。本次交互的事务标签为6,与抓包解析结果完全一致。
②中间2位 00 :包类型。 00代表Single Packet(单包),表示该信令消息没有分片,完整包含在一个L2CAP包中。本次配置参数较短,无需分片,因此使用单包格式。
③低2位 00 : 消息类型。00代表Command(命令),标识这是一条发起方发送的请求命令。
(2)第1字节: 0x03 → 二进制 00 000011
①高2位 00:保留位,规范要求必须置0,接收方忽略该字段。
②低6位 000011 = 十进制3: 信号标识符,对应AVDTP_SET_CONFIGURATION命令,标识这是一条流配置请求。
信令头解析完成,接下来解析命令载荷部分。
3.2.2 第二部分:端点标识字段(2字节)
Set Configuration命令需要同时指定接收方的流端点(ACP SEID)和发起方的流端点(INT SEID),两个字段各占1字节,格式统一:高2位为保留位(置0),低6位为SEID值。
第2字节: 0x03 → 二进制 00 000011
高2位
00:保留位低6位
000011= 十进制3:ACP SEID,即接收方的流端点编号为3。发起方通过之前的流发现流程获取到该SEID,本次配置针对该音频接收端点。
第3字节: 0x02 → 二进制 00 000010
高2位
00:保留位低6位
000010= 十进制2:INT SEID,即发起方本地分配的流端点编号为2。该编号由发起方上层应用分配,告知接收方后续交互中可以用该SEID指代本条流。
端点标识解析完成,接下来是本次命令的核心:服务能力列表。
3.2.3 第三部分:服务能力列表解析
服务能力列表采用TLV(类型-长度-值)结构,每个能力条目由1字节服务类别(Service Category)、1字节长度(Length of Service Capability)和N字节参数组成。本次配置共包含三个能力条目:媒体传输、媒体编解码器、延迟报告。
(1)条目1:媒体传输(Media Transport)
第4字节: 0x01
服务类别值为0x01,对应Media Transport(媒体传输)。这是所有音视频流必须支持的基础服务,标识该流承载RTP格式的媒体数据,是流传输的核心载体。
第5字节: 0x00
能力长度为0字节,即该服务没有额外参数,只要声明支持即生效。这是所有基础服务的共性,无需额外配置项。
(2)条目2:媒体编解码器(Media Codec)
这是整个配置的核心,决定了音频的编码格式、采样率、码率等关键参数,也是最容易出现协商失败的部分。
第6字节: 0x07
服务类别值为0x07,对应Media Codec(媒体编解码器)。
第7字节: 0x06
能力长度为6字节,即后续6字节为编解码器的具体参数。
媒体编解码器的参数结构遵循A2DP规范定义,分为三部分:
-
第1字节:保留位(2位) + 媒体类型(6位)
-
第2字节:编解码器类型
-
第3-6字节:编解码器特定参数
我们逐字节解析:
第8字节: 0x00 → 二进制 00 000000
-
高2位
00:保留位 -
低6位
000000= 0x00:媒体类型为Audio(音频)。
第9字节: 0x00
编解码器类型为0x00,对应SBC(低复杂度子带编码)。SBC是蓝牙音频的强制编码,所有A2DP设备都必须支持,保证了基础兼容性。
接下来的4字节为SBC编码的特定参数,是SBC协商的核心:
第10字节: 0x28 → 二进制 0010 1000
-
高4位
0010= 0x02:采样频率为44.1kHz。这是最常用的音频采样率,对应CD级音质,也是绝大多数音频内容的原生采样率。 -
低4位
1000= 0x08:声道模式为Joint Stereo(联合立体声)。联合立体声是SBC的高效立体声编码模式,通过利用左右声道的相关性压缩码率,相同码率下主观听感优于双声道独立编码。
第11字节: 0x84 → 二进制 1000 01 00
-
高4位
1000= 0x08:块长度(Block Length)为16。SBC编码以子带块为单位处理,16块是标准配置,平衡编码效率与算法延迟。 -
中间2位
01= 0x01:子带数量(Subbands)为8。8子带是SBC的标准配置,兼顾音质与计算复杂度,绝大多数蓝牙耳机都采用该配置。 -
低2位
00= 0x00:分配方式(Allocation Method)为Loudness(响度分配)。响度分配基于人耳的听觉掩蔽特性分配比特,在相同码率下主观听感优于SNR分配,是消费级音频的主流选择。
第12字节: 0x02
最小比特池值(Min Bitpool Value)为2。比特池是SBC控制码率的核心参数,值越大码率越高、音质越好。最小值2代表该配置支持的最低码率,对应极低码率的基础通话级音质。
第13字节: 0x35 = 十进制53
最大比特池值(Max Bitpool Value)为53。这是SBC 44.1kHz联合立体声的标准高质量配置,对应码率约328kbps,达到近CD级听感,也是绝大多数蓝牙耳机的默认工作配置。
(3)条目3:延迟报告(Delay Reporting)
第14字节: 0x08
服务类别值为0x08,对应Delay Reporting(延迟报告)。这是AVDTP 1.3版本新增的服务,主要用于音视频同步场景。
第15字节: 0x00
能力长度为0字节,该服务无额外参数,只要声明即表示启用。启用后,音频宿端会定期向源端报告自身的缓冲、解码、渲染总延迟,源端可以根据该延迟调整视频播放进度,实现音画同步,避免出现"声画不同步"的问题。
至此,整条Set Configuration命令的16字节全部解析完成。我们可以完整还原出发起方的配置意图:建立一条SBC编码、44.1kHz采样、联合立体声、最大53比特池的高质量音频流,同时启用延迟报告功能支持音画同步。
3.3 AVDTP Set Configuration 响应逐字节解析
接收方收到配置命令并逐项校验通过后,会返回Response Accept响应。

响应的信令头格式与命令完全一致,我们快速解析:
第0字节: 0x62 → 二进制 0110 00 10
高4位
0110= 6:事务标签,与命令的标签完全一致,用于匹配对应的请求,上层可以通过该标签关联请求与结果。中间2位
00:包类型为单包,接受响应无额外载荷,无需分片。低2位
10:消息类型为Response Accept(接受响应),标识接收方同意本次配置,流状态从空闲切换为已配置。
第1字节: 0x03 → 二进制 00 000011
- 高2位保留,低6位为信号标识符0x03,与命令的信号ID一致,标识这是Set Configuration命令的响应。
Set Configuration的接受响应没有额外载荷,仅通过2字节的头部即可完成确认。如果配置失败,接收方会返回Response Reject(消息类型11),并在载荷中携带第一个失败的服务类别和对应的错误码,方便发起方定位问题并调整参数重试。
3.4 抓包解析常见错误与排障要点
在实际调试中,很多协议解析错误都源于对字段位宽、字节序的理解偏差,这里总结三个最常见的易错点:
-
SEID的位宽错误:很多初学者会把整个字节当作SEID值,忽略高2位的保留位。虽然SEID值较小时结果巧合正确,但遇到SEID大于63的情况就会出现解析错误,且不符合规范定义。排障时需要注意仅取低6位作为SEID值。
-
字节序混淆:HCI和L2CAP的多字节字段均为小端序(低字节在前,高字节在后),而AVDTP的字段多为大端序(按字节从左到右传输)。如果混淆字节序,会出现连接句柄、长度字段完全错误的情况,是新手最常踩的坑。
-
服务能力长度错位:解析服务能力列表时,如果错误计算某个条目的长度,会导致后续所有条目全部错位,出现"服务类别全是乱码"的情况。排障时可以先数清楚已知条目的长度,逐步对齐校验,优先定位第一个解析异常的条目。
掌握了逐字节解析的方法,就能独立定位绝大多数AVDTP协商失败的问题,无论是参数不匹配、服务不支持还是状态机错误,都能通过原始数据包快速定位根因。
四、测验
题目:请简述AVDTP中SEID和Stream Handle的区别,以及它们之间的映射关系。
参考答案:
(1)定义与作用不同:
-
SEID(流端点标识符)是空中传输的标识,用于两个设备在信令交互中指代某个流端点,是跨设备的标识;
-
Stream Handle(流句柄)是设备本地的标识,用于上层应用和协议栈之间交互时指代一条流,不会在空中传输。
(2)作用域与分配不同:
-
SEID的作用域是单条蓝牙连接,在同一条连接上的每个SEP有唯一的SEID,不同连接之间可以复用;SEID由各自设备的应用层分配,双方在Set Configuration时交换彼此的SEID;
-
Stream Handle的作用域是本地设备全局唯一,由本地AVDTP协议栈在流配置成功时分配并返回给上层应用。
(3)映射关系:
每个设备内部都会维护一张映射表,将对端的SEID、本地SEID和本地Stream Handle关联起来。上层应用调用接口时使用Stream Handle,协议栈发送信令时转换为对应的对端SEID;收到对端信令时,根据对端SEID找到对应的本地Stream Handle,再上报事件给应用层。
题目:AVDTP的服务能力协商分为哪几个步骤?常见的协商失败错误码有哪些,分别代表什么含义?
参考答案:
(1)协商步骤:
①发现阶段:INT发送Discovery命令,ACP返回自身所有SEP的列表,包含SEID、端点类型(SRC/SNK)、占用状态。INT从中选择一个目标SEP。
②能力查询阶段:INT发送Get All Capabilities(或兼容老设备的Get Capabilities)命令,查询目标SEP的全部服务能力,ACP返回完整的能力列表。
③配置选择阶段:INT根据自身能力和对端能力,选择一个双方都支持的最优配置组合。
④配置确认阶段:INT发送Set Configuration命令,将选定的配置发给ACP;ACP校验所有配置参数,全部支持则返回成功响应,双方进入Configured状态;不支持则返回拒绝响应,携带错误码。
(2)常见错误码及含义:
-
0x12 BAD_ACP_SEID:请求的SEID在对端不存在,通常是SEID传错或未做发现流程直接配置。 -
0x13 SEP_IN_USE:目标SEP已经被其他流占用,无法配置新的流。 -
0x17 BAD_SERV_CATEGORY:包含未定义的服务类别,对端无法识别该服务。 -
0x1A INVALID_CAPABILITIES:无效的能力配置,常见于Reconfigure时修改了不允许修改的传输层能力。 -
0x29 UNSUPPORTED_CONFIGURATION:配置参数格式正确,但对端不支持该配置,通常是编解码器参数不匹配导致。 -
0x31 BAD_STATE:当前状态不允许执行该命令,状态机不匹配,比如未配置就直接启动流。
题目:AVDTP的多路复用服务的作用是什么?它是如何实现多个会话共享同一个L2CAP通道的?
参考答案:
作用:
①减少L2CAP通道的数量,降低系统内存、调度等资源开销;
②将多个小包合并成一个L2CAP大包发送,降低协议头开销占比,提高带宽利用率;
③支持适配层分片,可以灵活拆分大数据包填充L2CAP载荷,进一步提升带宽使用效率。
实现原理:
①标识映射机制:引入TSID(传输会话ID,标识逻辑会话)和TCID(传输通道ID,标识物理L2CAP通道),通过建立TSID到TCID的映射,将多个逻辑会话映射到同一个物理通道。映射关系在流配置阶段通过Set Configuration命令协商确定。
②适配层包头:每个被复用的数据包前增加一个适配层包头,包含TSID、分片标记F、长度编码LCODE和长度字段。接收端根据包头中的TSID将数据包分发到对应的逻辑会话处理。
③分片机制:通过F位标记数据包的分段,允许将一个大的传输包拆分成多个分段,分别放在不同的L2CAP包中传输,充分利用L2CAP的载荷空间。接收端根据F位和长度信息将分段拼接成完整的数据包。