音视频分布传输协议(AVDTP)

音视频分布传输协议(AVDTP)

本笔记为作者在学习蓝牙Host协议栈的一些心得体会,如有不对的地方,请包涵与谅解!

​ ------------by wsoz

音视频分布传输协议(AVDTP)

下面就是蓝牙的重点音视频传输相关的协议了,AVDTP、A2DP、AVCTP、AVRCP都与音视频相关,再这个之前我们还是先去了解一下SCO/eSCO链路,便于我们后续理解。

音频链路

通话音频链路

SCO/eSCO链路是经典蓝牙里的同步语音链路 ,主要给通话音频用(不是媒体音频);ACL是异步链路是并列关系同一层级,ACL链路主要由L2CAP协议进行服务而SCO/eSCO则不由L2CAP进行服务。

音频链路路径

  • Controller→HCI→Host→PCM/I2S→外部Codec音频解码器进行播放
  • Controller→内部PCM/I2S→ 外部Codec音频解码器进行播放

首先第一条路径就是通过HCI传输到Host进行解析,中间会利用HCI进行传输,而第二条路径则直接通过Controller内部的PCM/I2S直接传输到外部音频解码器上进行播放即可。

相对于第一种由Host参与音频处理,在像蓝牙耳机、蓝牙音箱这类设备中通常采用第二条路径目的就是为了节省功耗

同时要注意区分控制通道数据通道

  • 控制通道就是在建立 SCO/eSCO 连接的指令走的HCI命令,要通过HCI进行传输
  • 数据通道就是音频数据的传输通道,就是上面的两条路径
媒体音频链路

媒体音频链路对应的就是音乐播放、视频声音播放等场景。它和通话音频不一样,媒体音频不走SCO/eSCO,而是走 ACL/L2CAP 以及上层媒体协议。

如果本机是 Source(音源端),发送路径大致可以理解为:

  • 上层音频数据/编码帧(如 SBC) → A2DP/AVDTP → L2CAP → HCI → Controller → 空口发给对端

如果本机是 Sink(接收端),接收路径大致可以理解为:

  • 对端空口数据 → Controller → HCI → L2CAP → AVDTP/A2DP → 解码播放(通过I2S进行外部播放)

对应的控制主要由 AVDTP 负责,用来做音频流的发现、协商、配置和状态控制


协议介绍

AVDTP(Audio/Video Distribution Transport Protocol)是经典蓝牙里专门用于音视频分发的传输控制协议。

它本身不定义"播放/暂停"这类用户按键语义,也不直接规定具体业务界面,而是负责把音频流"怎么建起来、怎么协商、怎么控制状态"这件事做好。

你可以把 AVDTP 理解成:媒体音频链路的控制与管理层

它主要做这些事:

  • 发现对端可用的流端点(SEP)
  • 交换并协商能力(比如支持哪些编解码能力)
  • 配置流参数并建立媒体通道
  • 控制流状态(打开、开始、暂停、关闭、释放)
  • 异常时中止流(Abort)并回到可恢复状态

在分层上,AVDTP 位于 L2CAP 之上,负责媒体流的信令控制。

后续像 A2DP 这类媒体 Profile,会基于 AVDTP 来完成实际的音频分发流程。

AVDTP构成与服务

构成

AVDTP的组成由以下的几部分构成:

  • Signalling(信令)

    控制面。负责发现端点、能力协商、配置、Open/Start/Suspend/Close/Abort 等命令交互

  • Adaptation Layer(适配层)

    把上层编码后的音频帧适配到 AVDTP/L2CAP 传输格式(封包、解包、边界处理等),让数据能正确下发到传输层。

  • Stream Manager(流管理)

    状态机与资源管理。维护每条流的状态(Idle/Configured/Open/Streaming 等),决定何时建流、停流、释放。

  • Recovery(恢复)

    可选功能。用于丢包恢复相关能力(如恢复窗口等)协商与处理,提升链路较差时的稳健性。

服务

下面这里按照规范中的服务划分继续看:

  1. Basic Service(Media Transport)

    AVDTP 基本服务确保通过单个传输通道传输每个 session 的媒体包

    当 Basic Service 开启时,活跃的实体主要只有 SignallingStream Manager

    这里才是真正传输媒体数据的基础服务,也就是最基础的媒体包传输机制

    这个服务是最基础的服务,同时也是必选服务

  2. Recovery Service(可选)

    该服务是在媒体包传输过程中增加"恢复相关能力"的一种可选服务,本质上是在 Basic Service 的基础上增加抗丢包能力。

    当该服务启用时,除了 SignallingStream Manager 之外,还会用到 Recovery 组件。

    常见会协商这些参数:

    • Recovery Type
    • MRWS(Maximum Recovery Window Size)
    • MNMP(Maximum Number of Media Packets in Parity Code)
  3. Reporting Service(可选)

    该服务打开时,会向本地应用提供到远程设备媒体流的统计信息,例如时间对齐包丢失情况等。

    这些报告主要用于做媒体流同步,或者让应用根据当前链路情况去调整错误隐藏策略。

    Reporting Service 可以配置为:

    • 单向(从 SNK 到 SRC,或从 SRC 到 SNK)
    • 双向

    具体采用哪种方式,取决于应用需求。

    同时应用还可以根据上下文去调整报告周期,以及控制该服务的激活和停用。

    报告服务可以通过独立的传输通道,把 report packet 发送到远端设备。

  4. Adaptation Service - Multiplexing (可选)

    在 Multiplexing 模式下,多个 transport session 可以共享同一个公共的传输(L2CAP)通道。

    这几个 transport session:

    • 可以属于同一个流
    • 也可以属于不同的流

    此外,一个 L2CAP 数据包中还可以包含多个数据包,这些数据包可以来自相同 transport session,也可以来自不同 transport session。

    正因为如此,每个封装头里都需要带上对应信息来标识媒体包、报告包或恢复包;其中头里的 TSID 用于让 SNK 端正确地把数据路由到对应的 transport session。

    在流配置过程中,INT 会分配 TSIDTCID 并通知 ACP

    建立流程也不一定每次都新开一个 L2CAP 通道,而是可以通过引用已有 TCID 的方式,把新的 transport session 映射到现有 L2CAP 通道上。

  5. Adaptation Service - Robust Header Compression(可选)

    健壮报头压缩是一种传输增强服务,用来减少媒体包和恢复包头部引入的额外开销。

    这个机制是可选的,但如果要建立该能力,那么流两端必须使用相同配置。

    该机制还允许使用反馈通道,这个反馈通道会在媒体流配置协商时一起确定。

传输和信令通道的建立

在参与 stream connection 的两个设备之间,最多可能需要 4 个 L2CAP 通道

这一部分主要关注的就是:

  • Signaling Channel 怎么建立
  • Transport Channel 怎么建立
  • 哪些服务可能共用通道
  • 建立顺序和优先级是什么

后面继续看通道建立流程时,再结合图去理解会更直观。


AVDTP术语

下面对AVDTP中常用的术语进行介绍方便熟悉:

Stream :两个点对点设备之间的流媒体数据

Source (SRC) and Sink (SNK) :SRC 是音视频的发送方 ,SNK 是音视频的接收方

Initiator (INT) and Acceptor (ACP) :INT和ACP是通过谁发送命令谁就是INT,然后回复response的为ACP,是在动态切换的。

Application and Transport Service Capabilities :这个说的就是应用服务传输服务的功能。应用服务功能比如协商、配置音源设备的 codec,内容保护系统等;传输服务能力比如数据报文的分割和重组,数据包的防丢检测等等。

Services, Service Categories, and Service Parameters :AVDTP 的特性集合 。在启动时,INT会询问ACP的特性能力,并对需要的特性能力进行配置。具体就可以包括上面的 Application CapabilitiesTransport Service Capabilities 的能力。

Media Packets, Recovery Packets, and Reporting Packets :流媒体数据包用来封装流媒体数据,方向从 SRC→SNK ;恢复数据包主要包含了恢复数据,方向 SRC→SNK ;报告数据包含的时 Qos 的报告包,方向是双向的

Stream End Point (SEP) :流端点,提供一个协议中"可被发现可被配置可用于收发媒体流的音频端口/能力实体"。

Stream Context (SC) :流上下文,指在流设置过程中,两个对等设备达到一个公共的了解流的配置,包括选择的服务,参数,以及传输通道分配。简而言之就是流通道的公共配置上下文

Stream Handle (SH):流句柄。在 SRC 和 SNK 建立了连接之后分配的一个独立的标识符,代表了上层对流的引用。

**Stream End Point Identifier (SEID):**流端点标识,对特定设备的跨设备引用,该引用用于信令事物。

**Stream End Point State:**流端点状态。

**Transport Session:**传输会话。在 A/V 传输层的内部,在配对的 AVDTP 实体之间,流可以分解为一个、两个或多个三个传输会话。

**Transport Session Identifier (TSID):**传输会话标识。代表对一个传输会话的引用。

**Transport Channel:**传输通道。传输通道指的是对 A/V 传输层下层承载程序的抽象,始终对应 L2CAP 的通道

**Transport Channel Identifier (TCID):**传输通道标识。代表对一个传输通道的引用。

整体概念框架

上面这些术语单独看都不难,但真正容易混的是:角色、端点、流、会话、通道分别在说哪一层东西。

可以先按下面这个顺序去理解:

第一层:角色

  • SRC / SNK 解决的是"谁发音频,谁收音频"
  • INT / ACP 解决的是"这次命令事务里谁发命令,谁回响应"

也就是说:

  • SRC / SNK 看的是媒体流方向
  • INT / ACP 看的是信令交互方向

这两个角色不是一回事。

第二层:端点

  • SEP 是逻辑上的流端点
  • 一个设备上可以有一个或多个 SEP
  • 每个 SEP 都会声明自己的能力,例如它是 Source 还是 Sink,支持哪些 Service Capabilities

所以 SEP 可以理解成:一条媒体流最终要挂接到的逻辑能力端口

第三层:流

  • Stream 是 SRC 和 SNK 之间建立起来的一条逻辑媒体流
  • 它不是物理通道,而是一条已经协商好的媒体会话
  • SC 表示这条流协商完成后的公共配置上下文
  • SH 是上层对这条流进行引用时使用的本地句柄

所以:

  • Stream 更像是一条"业务流"
  • SH 只是这条流的"编号/引用"

第四层:传输

  • 一条流在 AVDTP 内部还会进一步映射到一个或多个 Transport Session
  • TSID 用来标识某个 Transport Session
  • Transport Channel 则是更底层的承载抽象,它最终对应到 L2CAP Channel
  • TCID 用来标识某个 Transport Channel

可以简单理解成:

  • Stream 是逻辑业务流
  • Transport Session 是这条流在传输层里的具体会话
  • Transport Channel 是这些会话真正落到下面时所使用的传输通道

第五层:真正怎么串起来

一条典型媒体流大致是这样建立起来的:

  1. 设备之间先通过 Signaling Channel 做发现和协商
  2. INT 发现对端的 SEP,读取它的 capabilities
  3. 双方对某个 SRC SEP 和某个 SNK SEP 达成配置一致
  4. 形成这条流对应的 SC
  5. 为这条流分配或映射 Transport Session / Transport Channel
  6. 后续媒体包、恢复包、报告包就通过对应的 transport path 传输

如果只压成一句话去记,可以这样记:

  • SRC / SNK 是角色
  • SEP 是端点
  • Stream 是逻辑流
  • SH 是流句柄
  • Transport Session 是流在传输层里的会话
  • Transport Channel 是最终承载这些会话的 L2CAP 通道

所以你后面再看图时,就不要把这些概念都看成"同一个东西":

  • SEP 不是 Stream
  • Stream 不是 Channel
  • Channel 也不是 Handle

它们只是从能力端点 -> 逻辑流 -> 传输会话 -> 承载通道这样一层层落下去的关系。


AVDTP封包及格式

AVDTP的封包主要有两种封包:一种是控制连接建立等的signaling包;一种是传输媒体流的media transport包。

AVDTP Signal 封包

该封包的主要用于专门传 AVDTP 的控制命令和响应,比如:Discover、Set Configuration、Open、Start。

AVDTP 的 Signaling message 根据是否分片,可以分为:

  • Single Packet:单一封包,不分片
  • Start Packet:分片起始封包
  • Continue Packet:分片中间封包
  • End Packet:分片结束封包
Single Packet 格式

当一条 signaling message 没有超过当前 L2CAP MTU 时,就可以直接用单一封包发送。

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | Signal Identifier(6)     |
+-----------+--------------------------+

Octet 2 ~ n:
+--------------------------------------+
| Signal Parameters                    |
+--------------------------------------+
Start Packet 格式

当 signaling message 过长时,需要分片发送。第一片就是 Start Packet。

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+--------------------------------------+
| NOSP (Number Of Signal Packets)      |
+--------------------------------------+

Octet 2:
+-----------+--------------------------+
| RFA(2)    | Signal Identifier(6)     |
+-----------+--------------------------+

Octet 3 ~ n:
+--------------------------------------+
| First Fragment of Signal Parameters  |
+--------------------------------------+
Continue Packet 格式

Start Packet 后面的中间分片就是 Continue Packet。

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1 ~ n:
+--------------------------------------+
| Continued Signal Parameters          |
+--------------------------------------+
End Packet 格式

最后一片就是 End Packet,表示这一条 signaling message 的分片发送结束。

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1 ~ n:
+--------------------------------------+
| Last Fragment of Signal Parameters   |
+--------------------------------------+
头字段含义
  • Transaction Label

    事务标签,用来标识一次 signaling 事务。

    这个值由 INT 发起命令时给出,ACP 在返回对应 response 时要带回同样的值。

  • Packet Type

    表示当前信令消息是否分片,以及当前这一片属于哪种类型:

    • 00:Single Packet
    • 01:Start Packet
    • 10:Continue Packet
    • 11:End Packet
  • Message Type

    表示当前 signaling packet 是命令还是响应,以及响应结果类型:

    • 00:Command
    • 01:General Reject
    • 10:Response Accept
    • 11:Response Reject
  • RFA

    保留字段(Reserved for Future Addition),当前一般置 0 即可。

  • Signal Identifier

    表示具体是哪一种 AVDTP 信令命令,例如 DiscoverGet CapabilitiesSet ConfigurationOpenStart 等。

    在 response 中,也会带回同样的 Signal Identifier

  • NOSP

    Number Of Signal Packets,表示当前这条分片 signaling message 一共会被拆成多少个 packet。

    接收端可以根据这个值做重组与完整性判断。

  • Signal Parameters

    该信令命令/响应实际携带的参数内容,例如 SEID、capabilities、error code 等。

补充理解
  • Single PacketStart Packet 都带有 Signal Identifier,因为接收端要先知道这是哪种命令
  • Continue PacketEnd Packet 只负责继续承载剩余参数数据,因此不再重复带 Signal Identifier
  • 一条被分片的 signaling message,在同一方向上要按顺序连续发送,不能和下一条普通 signaling message 交错发送
AVDTP media transport 封包

这里先看最常见的 Basic Service 下的媒体传输封包。

它和前面的 signaling 封包不一样,不是拿来传控制命令的,而是拿来真正承载媒体数据的。

从格式上看,这里的媒体头部是一个RTP 风格的头部,后面再跟真正的媒体负载。

基本格式
text 复制代码
Octet 0:
+-------------+-----+-----+----------------+
| Version(2)  | P(1)| X(1)| CSRC Count(4)  |
+-------------+-----+-----+----------------+

Octet 1:
+-----------+-------------------------------+
| Marker(1) | Payload Type(7)               |
+-----------+-------------------------------+

Octet 2 ~ 3:
+-------------------------------------------+
| Sequence Number                           |
+-------------------------------------------+

Octet 4 ~ 7:
+-------------------------------------------+
| Timestamp                                 |
+-------------------------------------------+

Octet 8 ~ 11:
+-------------------------------------------+
| SSRC                                      |
+-------------------------------------------+

Octet 12 ~ m:
+-------------------------------------------+
| CSRC List(可选)                         |
+-------------------------------------------+

Octet m+1 ~ n:
+-------------------------------------------+
| Media Payload                             |
+-------------------------------------------+
头字段含义
  • Version

    版本字段。这里通常就是 2

  • P(Padding)

    填充标志。

    如果置位,说明这个 packet 的末尾带有 padding 字节。常见音频场景里一般为 0

  • X(Extension)

    扩展头标志。

    如果置位,说明在基本头后面还跟着扩展头。常见 A2DP 音频里一般也很少用,通常为 0

  • CSRC Count

    后面跟随的 CSRC 个数。

    一般蓝牙音频里这个值通常是 0,也就是后面没有额外的 CSRC List

  • Marker

    标记位。

    这个位的具体语义通常由上层 profile / 应用去定义。实际学习 A2DP 时,这个位一般不是最核心的关注点。

  • Payload Type

    负载类型。

    用来标识当前媒体负载的类型。抓包里经常能看到一个固定值,但真正的 codec 类型并不是只靠它判断,更关键的还是前面协商好的 codec capability。

  • Sequence Number

    序列号。

    每发送一个媒体 packet 一般递增 1,接收端可以用它判断:

    • 包是否乱序
    • 是否丢包
  • Timestamp

    时间戳。

    这个时间戳反映的是媒体采样时间基准 ,不是简单的"当前系统时间"。

    接收端可以用它做同步、抖动缓冲和播放时序控制。

  • SSRC

    Synchronization Source,同步源标识。

    用来标识这一条媒体流的发送源。

  • CSRC List

    Contributing Source 列表。

    用来表示有哪些贡献源参与了当前媒体内容。蓝牙音频里通常基本不会重点碰到,很多时候就是没有。

  • Media Payload

    真正的媒体数据负载。

    这里面放的就是编码后的音频内容,比如 SBC、AAC 之类的数据。

    再往里面看时,还可能会有codec 自己的 payload header,这个就属于更上面的 A2DP/codec 具体定义了。

补充理解
  • AVDTP Signal 封包 走的是信令通道 ,用来传 Discover / Set Configuration / Open / Start 这些控制命令
  • AVDTP media transport 封包 走的是传输通道,用来真正传媒体数据
  • media transport 封包没有 前面 signaling 里的 Transaction Label / Message Type / Signal Identifier
  • Sequence Number 是按媒体包递增,不是按里面的音频帧递增
  • 一个 media payload 里可以装一个或多个 codec frame,这取决于具体 codec 封装方式
  • 如果底层因为 MTU/HCI 需要做分段,那是更下面链路层或 L2CAP/HCI 的事情,不是 signaling 那种 Start / Continue / End 分片头

这里先把它记成一句话就行:

  • Signal packet 管控制
  • Media transport packet 管真正的音频数据
AVDTP Error Code

AVDTP 里的 Error Code 本质上就是:当对端拒绝某个 signaling 命令时,用来说明拒绝原因的错误码

它一般出现在 Response Reject 的参数里,用来告诉对端到底是:

  • 封包格式错了
  • SEID 不对
  • capability 不合法
  • 当前状态不允许执行该命令

这里要注意一个点:

  • General Reject 主要是通用拒绝,不等同于下面这些普通 Error Code
  • 后面我们学习具体 signaling 命令时,看到 Reject 响应再去对应这些错误码就行

下面先把常见的 Error Code 记住:

Error Code 名称 含义
0x01 BAD_HEADER_FORMAT 信令包头格式错误
0x11 BAD_LENGTH 参数长度不对
0x12 BAD_SEID SEID 不合法
0x13 SEP_IN_USE 该 SEP 已经在使用中
0x14 SEP_NOT_IN_USE 该 SEP 当前并未处于使用中
0x17 BAD_SERV_CATEGORY Service Category 不合法
0x18 BAD_PAYLOAD_FORMAT payload 格式错误
0x19 NOT_SUPPORTED_COMMAND 对端不支持该命令
0x1A INVALID_CAPABILITIES capability 内容不合法,常见于 Reconfigure
0x22 BAD_RECOVERY_TYPE Recovery Type 不合法或未定义
0x23 BAD_MEDIA_TRANSPORT_FORMAT Media Transport 相关 capability 格式错误
0x25 BAD_RECOVERY_FORMAT Recovery capability 格式错误
0x26 BAD_ROHC_FORMAT Header Compression capability 格式错误
0x27 BAD_CP_FORMAT Content Protection capability 格式错误
0x28 BAD_MULTIPLEXING_FORMAT Multiplexing capability 格式错误
0x29 UNSUPPORTED_CONFIGURATION 配置本身格式没错,但对端不支持
0x31 BAD_STATE 当前状态下不能处理该命令
0x65 BAD_REPORT_FORMAT Reporting Service capability 格式错误
0x80 INVALID_SERVICE_CATEGORY 无效的 service category
0x81 INSUFFICIENT_RESOURCE 资源不足,无法完成本次请求

如果只先记最重要的几个,优先记这几个就够了:

  • 0x12 BAD_SEID
  • 0x13 SEP_IN_USE
  • 0x14 SEP_NOT_IN_USE
  • 0x29 UNSUPPORTED_CONFIGURATION
  • 0x31 BAD_STATE

因为后面在 Set Configuration / Open / Start / Suspend / Close 这些命令里,经常会碰到它们。


AVDTP Signal 常用命令

signal 命令主要是按照建立流程如下:Discover → Capabilities → Set Configuration → Open → Start → Suspend/Close/Abort 。

下面我们依次来进行学习。

AVDTP_DISCOVER

每个 AVDTP 端都会注册一个或者多个 SEP,通过 SEID(SEP ID) 来标示,这个命令就是获取对端的 SEP 信息,包括 SEID(SEP 的 ID),In Use(是否被使用),Media Type(Audio,Media,MultiMedia),TSEP(角色是 Sink 还是 Source)。

命令格式

AVDTP_DISCOVERSignal Identifier = 0x01,该命令本身没有额外参数

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_DISCOVER(0x01)     |
+-----------+--------------------------+

也就是说本质上就是:INT 发一个 Discover 命令,请 ACP 把当前有哪些 SEP 告诉我。

Response Accept 格式

如果 Discover 成功,对端会返回一个或多个 SEP 信息项,每个 SEP2 字节

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_DISCOVER(0x01)     |
+-----------+--------------------------+

Octet 2:
+--------------------------+----------+------+
| ACP SEID(6)              | In Use(1)| RFA  |
+--------------------------+----------+------+

Octet 3:
+----------------------+---------+---------+
| Media Type(4)        | TSEP(1) | RFA(3)  |
+----------------------+---------+---------+

Octet 4 ~ ...:
+-----------------------------------------+
| Other SEP Information (每个 SEP 2 字节) |
+-----------------------------------------+

返回参数说明

  • ACP SEID

    对端 SEP 的编号。

  • In Use

    表示该 SEP 当前是否已经在使用:

    • 0:Not In Use
    • 1:In Use
  • Media Type

    表示媒体类型,常见有:

    • 0x0:Audio
    • 0x1:Video
    • 0x2:Multimedia
  • TSEP

    表示该 SEP 的方向角色:

    • 0:SRC
    • 1:SNK

Response Reject 格式

如果 Discover 失败,则返回 Reject,后面带 Error Code

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_DISCOVER(0x01)     |
+-----------+--------------------------+

Octet 2:
+--------------------------------------+
| ERROR_CODE                           |
+--------------------------------------+

补充理解

  • Discover 只负责告诉你:对端有哪些 SEP
  • 它不会返回这个 SEP 具体支持哪些 codec
  • 更详细的能力信息要靠后面的 Get Capabilities / Get All Capabilities
AVDTP_GET_ALL_CAPABILTIES

该命令会直接获取到SEP的整个CAPABILTIES集合,相对于AVDTP_GET_CAPABILTIES则只是获取到某个特定的CAPABILTIES,效率太低了已经被替代了。

补充说明

更直接一点理解就是:

  • AVDTP_GET_CAPABILITIES:查询该 SEP 的能力
  • AVDTP_GET_ALL_CAPABILTIES:查询该 SEP完整能力集合

现在实际使用时一般更偏向直接用 GET_ALL_CAPABILITIES,因为它返回得更全。

尤其像一些后续扩展能力,例如 Delay Reporting,通常就更适合通过这个命令一起拿回来。

命令格式

AVDTP_GET_ALL_CAPABILTIESSignal Identifier = 0x0C

该命令需要指定要查询哪个对端 SEP ,所以请求里要带 ACP SEID

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------------+
| RFA(2)    | AVDTP_GET_ALL_CAPABILITIES(0x0C)|
+-----------+--------------------------------+

Octet 2:
+--------------------------+------------------+
| ACP SEID(6)              | RFA(2)           |
+--------------------------+------------------+

也就是说:

  • INT 先指定一个对端的 SEP
  • 然后要求 ACP 把这个 SEP 的完整能力集合返回回来

请求参数说明

  • ACP SEID

    表示要查询的对端 SEP 编号。

    也就是:我要查的是哪一个 SEP 的能力

Response Accept 格式

如果成功,对端会返回这个 SEP 的一组 Service Capabilities

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------------+
| RFA(2)    | AVDTP_GET_ALL_CAPABILITIES(0x0C)|
+-----------+--------------------------------+

Octet 2 ~ ...:
+------------------------------------------------------+
| Service Capabilities                                 |
+------------------------------------------------------+

Response Reject 格式

如果查询失败,则返回 Reject,后面带 Error Code

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------------+
| RFA(2)    | AVDTP_GET_ALL_CAPABILITIES(0x0C)|
+-----------+--------------------------------+

Octet 2:
+--------------------------------------+
| ERROR_CODE                           |
+--------------------------------------+

补充理解

  • Discover 是先拿到有哪些 SEP
  • Get All Capabilities 才是继续查看某个 SEP 具体支持什么
  • 后面像 Set Configuration,本质上就是从这里返回的能力里选一套双方都支持的配置
  • 如果你只是想快速理解流程,就把它记成:Discover 找端点,Get All Capabilities 查端点能力
Service Capabilities介绍

Service Capabilities 可以直接理解成:某个 SEP 对外声明的能力项集合

前面的 Discover 只是告诉我们"有哪些 SEP",而真正要知道这个 SEP 支持什么能力、能不能拿来建流、支持什么 codec、有没有内容保护/恢复/延迟上报 ,就要看这里的 Service Capabilities

它一般会出现在这些命令里:

  • AVDTP_GET_CAPABILITIES
  • AVDTP_GET_ALL_CAPABILITIES
  • AVDTP_SET_CONFIGURATION
  • AVDTP_RECONFIGURE

你可以把它简单理解成:

  • 查询阶段 :看对端这个 SEP 有哪些能力
  • 配置阶段:从这些能力里选出一套双方都接受的配置

基本格式

每一个 Service Capability 都是同样的基本结构:

text 复制代码
Octet 0:
+--------------------------------------+
| Service Category                     |
+--------------------------------------+

Octet 1:
+--------------------------------------+
| LOSC                                 |
+--------------------------------------+

Octet 2 ~ n:
+--------------------------------------+
| Service Capability Information       |
+--------------------------------------+

字段说明

  • Service Category

    表示当前这一项 capability 属于哪一类能力。

    比如是 Media TransportMedia CodecContent Protection 还是 Delay Reporting

  • LOSC

    Length Of Service Capabilities,表示后面这项 capability 参数区的长度

    也就是说接收端就是靠这个字段知道:这一项 capability 后面还有多少字节。

  • Service Capability Information

    这一项 capability 的具体内容。

    如果是 Media Codec,这里面放的就是 codec 类型和 codec 参数;如果是 Content Protection,这里面放的就是保护方式相关内容。

常见的 Service Category

Service Category 名称 作用
0x01 Media Transport 最基础的媒体传输能力,表示该 SEP 可用于基本媒体数据传输
0x02 Reporting 支持报告服务,可用于上报统计/同步相关信息
0x03 Recovery 支持恢复服务,用于增强抗丢包能力
0x04 Content Protection 内容保护能力
0x05 Header Compression 报头压缩能力
0x06 Multiplexing 多路复用能力
0x07 Media Codec 编解码能力,后面最重要的一项
0x08 Delay Reporting 支持延迟上报
Media Transport Capabilities

这是最基础的一项 capability,没有额外参数

text 复制代码
Octet 0:
+--------------------------------------+
| Service Category = Media Transport   |
+--------------------------------------+

Octet 1:
+--------------------------------------+
| LOSC = 0x00                          |
+--------------------------------------+

也就是说:

  • LOSC = 0
  • 后面没有额外的 capability information

这里其实很好理解:

  • Service Category:声明这是 Media Transport
  • LOSC = 0:表示后面没有参数

也就是单纯告诉对端:我支持最基础的媒体传输能力

Reporting Capabilities

这个格式和 Media Transport 很像,也没有额外参数

text 复制代码
Octet 0:
+--------------------------------------+
| Service Category = Reporting         |
+--------------------------------------+

Octet 1:
+--------------------------------------+
| LOSC = 0x00                          |
+--------------------------------------+

也就是只声明:该 SEP 支持 Reporting Service

这里同样很简单:

  • Service Category:声明这是 Reporting
  • LOSC = 0:表示后面没有额外参数

也就是单纯说明:我支持 Reporting 功能

Recovery Capabilities

Recovery 的参数区固定是 3 字节

text 复制代码
Octet 0:
+--------------------------------------+
| Service Category = Recovery          |
+--------------------------------------+

Octet 1:
+--------------------------------------+
| LOSC = 0x03                          |
+--------------------------------------+

Octet 2:
+--------------------------------------+
| Recovery Type                        |
+--------------------------------------+

Octet 3:
+--------------------------------------+
| MRWS                                 |
+--------------------------------------+

Octet 4:
+--------------------------------------+
| MNMP                                 |
+--------------------------------------+

其中:

  • Recovery Type:恢复类型,常见 0x01 = RFC2733
  • MRWSMaximum Recovery Window Size
  • MNMPMaximum Number of Media Packets in Parity Code

简单理解这 3 个参数:

  • Recovery Type:采用哪一种恢复方式
  • MRWS:恢复窗口大小,可以理解成"一次恢复机制最多覆盖多大的窗口"
  • MNMP:一次恢复编码里最多涉及多少个媒体包
Media Codec Capabilities

这个是后面最关键的一项,因为真正的 codec 信息就在这里。

text 复制代码
Octet 0:
+--------------------------------------+
| Service Category = Media Codec       |
+--------------------------------------+

Octet 1:
+--------------------------------------+
| LOSC                                 |
+--------------------------------------+

Octet 2:
+----------------------+---------------+
| Media Type(4)        | RFA(4)        |
+----------------------+---------------+

Octet 3:
+--------------------------------------+
| Media Codec Type                     |
+--------------------------------------+

Octet 4 ~ n:
+--------------------------------------+
| Media Codec Specific Information     |
+--------------------------------------+

其中:

  • Media Type:媒体类型,比如 Audio / Video / Multimedia
  • Media Codec Type:具体 codec 类型
  • Media Codec Specific Information:codec 自己的参数内容

这里你重点先看这几个:

  • Media Type:这是音频、视频还是多媒体
  • Media Codec Type:到底是哪种 codec,例如后面常见的 SBC
  • Media Codec Specific Information:这个 codec 的具体参数,比如采样率、声道、bitpool 这类内容

所以这块本质上就是:声明这个 SEP 支持什么编码格式,以及这个编码格式的参数范围

Content Protection Capabilities

这个 capability 用来描述内容保护类型,以及可选的保护相关参数。

text 复制代码
Octet 0:
+--------------------------------------+
| Service Category = Content Protection|
+--------------------------------------+

Octet 1:
+--------------------------------------+
| LOSC                                 |
+--------------------------------------+

Octet 2:
+--------------------------------------+
| CP_TYPE_LSB                          |
+--------------------------------------+

Octet 3:
+--------------------------------------+
| CP_TYPE_MSB                          |
+--------------------------------------+

Octet 4 ~ n:
+--------------------------------------+
| CP Type Specific Value               |
+--------------------------------------+

也就是说:

  • CP_TYPE 是一个 16 bit 的内容保护类型
  • 后面还可以跟该保护类型自己定义的附加数据
  • 所以这里通常可以理解成:LOSC = 2 + N

简单理解:

  • CP_TYPE_LSB / CP_TYPE_MSB:这两个字节拼起来就是内容保护类型
  • CP Type Specific Value:该保护方案自己的额外参数

也就是:先说明保护类型,再带这个保护类型对应的附加数据

Header Compression Capabilities

这个 capability 的参数区是 1 字节标志位

text 复制代码
Octet 0:
+--------------------------------------+
| Service Category = Header Compression|
+--------------------------------------+

Octet 1:
+--------------------------------------+
| LOSC = 0x01                          |
+--------------------------------------+

Octet 2:
+-----------+------------+----------+----------+
| BackCh(1) | Recovery(1)| Media(1) | RFA(5)   |
+-----------+------------+----------+----------+

这里本质上就是:

  • BackCh:back channel 相关
  • Recovery:是否用于 recovery packet
  • Media:是否用于 media packet

简单理解这 3 个 bit:

  • Media = 1:表示支持对媒体包做头压缩
  • Recovery = 1:表示支持对恢复包做头压缩
  • BackCh = 1:表示支持对回传通道相关内容做头压缩

剩下的 RFA(5) 就是保留位,先不用管。

Multiplexing Capabilities

这个 capability 的格式相对复杂一些,因为它要把 Transport SessionTransport Channel 的映射带出来。

text 复制代码
Octet 0:
+--------------------------------------+
| Service Category = Multiplexing      |
+--------------------------------------+

Octet 1:
+--------------------------------------+
| LOSC                                 |
+--------------------------------------+

Octet 2:
+-----------+--------------------------+
| FRAG(1)   | RFA(7)                   |
+-----------+--------------------------+

Octet 3:
+----------------------+---------------+
| TSID(Media)(5)       | RFA(3)        |
+----------------------+---------------+

Octet 4:
+----------------------+---------------+
| TCID(Media)(5)       | RFA(3)        |
+----------------------+---------------+

Octet 5 ~ 6(可选):
+----------------------+---------------+
| TSID/TCID(Reporting) | RFA           |
+----------------------+---------------+

Octet 7 ~ 8(可选):
+----------------------+---------------+
| TSID/TCID(Recovery)  | RFA           |
+----------------------+---------------+

这里要注意:

  • LOSC 是可变的
  • 如果只有 Media,那后面字段更短
  • 如果还带 Reporting / Recovery,那对应的 TSID / TCID 也会继续跟在后面

其中:

  • FRAG:是否允许 Adaptation Layer Fragmentation
  • TSID:Transport Session Identifier
  • TCID:Transport Channel Identifier

这里简单理解:

  • FRAG:是否允许适配层分片
  • TSID:某个传输会话的编号
  • TCID:某个传输通道的编号

所以 Multiplexing 这块本质上是在说:
哪些 transport session 要映射到哪些 transport channel 上。

Delay Reporting Capabilities

这个 capability 也没有额外参数,格式和 Media Transport / Reporting 类似。

text 复制代码
Octet 0:
+--------------------------------------+
| Service Category = Delay Reporting   |
+--------------------------------------+

Octet 1:
+--------------------------------------+
| LOSC = 0x00                          |
+--------------------------------------+

也就是只声明:该 SEP 支持 Delay Reporting

这里也很好理解:

  • Service Category:声明这是 Delay Reporting
  • LOSC = 0:没有额外参数

也就是单纯说明:我支持延迟上报能力

AVDTP_SET_CONFIGURATION

该命令就是在前面 DiscoverGet All Capabilities 之后,真正去配置一条流 的关键命令。

也就是从已经查到的那些 Service Capabilities 里,选出一套双方都能接受的配置,告诉对端:后面这条流就按这套配置来建立

这里要注意一点:

  • 前面的 Get All Capabilities 是"看对端支持什么"
  • Set Configuration 则是"我现在决定用哪一套"

所以这个命令是正式建流前最关键的一步。

命令格式

AVDTP_SET_CONFIGURATIONSignal Identifier = 0x03

该命令需要同时带:

  • ACP SEID:对端要被配置的 SEP
  • INT SEID:本地参与这条流的 SEP
  • Service Capabilities:本次选定的配置内容
text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------------+
| RFA(2)    | AVDTP_SET_CONFIGURATION(0x03)  |
+-----------+--------------------------------+

Octet 2:
+--------------------------+------------------+
| ACP SEID(6)              | RFA(2)           |
+--------------------------+------------------+

Octet 3:
+--------------------------+------------------+
| INT SEID(6)              | RFA(2)           |
+--------------------------+------------------+

Octet 4 ~ n:
+--------------------------------------------+
| Service Capabilities                       |
+--------------------------------------------+

请求参数说明

  • ACP SEID

    对端的 SEP 编号。

    也就是:我要去配置对端哪一个 SEP

  • INT SEID

    本地的 SEP 编号。

    也就是:本地是哪一个 SEP 要和对端这个 SEP 建流

  • Service Capabilities

    本次真正选中的配置内容。

    这里带的已经不是"对端全部支持项",而是本次决定采用的那一套参数

举个最直接的理解:

  • 前面 Get All Capabilities 返回的是"候选集合"
  • 这里 Set Configuration 带的是"最终选项"

Response Accept 格式

如果对端接受这次配置,就返回 Response Accept,它本身没有额外参数

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------------+
| RFA(2)    | AVDTP_SET_CONFIGURATION(0x03)  |
+-----------+--------------------------------+

也就是说:

对端同意后,这条流的配置就基本确定下来了。

Response Reject 格式

如果对端拒绝,则返回 Reject

这个命令的 Reject 比前面普通的 Error Code 多一个字段:Service Category

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------------+
| RFA(2)    | AVDTP_SET_CONFIGURATION(0x03)  |
+-----------+--------------------------------+

Octet 2:
+--------------------------------------+
| Service Category                     |
+--------------------------------------+

Octet 3:
+--------------------------------------+
| ERROR_CODE                           |
+--------------------------------------+

这里多带一个 Service Category 的原因很好理解:

  • 因为 Set Configuration 一次可能会带很多 capability
  • 对端如果拒绝,需要告诉你:到底是哪一项 capability 配错了

Reject 参数说明

  • Service Category

    表示出错的是哪一类 capability。

  • ERROR_CODE

    表示具体的失败原因。

补充理解

Set Configuration 这一步本质上就是:给一对 SEP 指定最终要使用的流配置
没有 Set Configuration,后面的流就还没有真正被配置完成。

AVDTP_GET_CONFIGURATION

这个命令就是用于读取某个已经配置过的 SEP 当前配置内容

也就是说,在 Set Configuration 成功之后,如果想再确认一下当前这条流到底配置成了什么样,就可以使用 Get Configuration

所以它和前面的区别可以直接这样记:

  • Get All Capabilities:看这个 SEP 支持什么
  • Set Configuration:设置这条流 决定用什么
  • Get Configuration:读取这条流 当前实际用了什么

命令格式

AVDTP_GET_CONFIGURATIONSignal Identifier = 0x04

这个命令需要指定要读取哪个对端 SEP 的当前配置 ,所以请求里要带 ACP SEID

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------------+
| RFA(2)    | AVDTP_GET_CONFIGURATION(0x04)  |
+-----------+--------------------------------+

Octet 2:
+--------------------------+------------------+
| ACP SEID(6)              | RFA(2)           |
+--------------------------+------------------+

请求参数说明

  • ACP SEID

    对端的 SEP 编号。

    也就是:我要读取对端哪一个 SEP 当前已经生效的配置

Response Accept 格式

如果成功,对端会返回这个 SEP 当前已经设置好的 Service Capabilities

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------------+
| RFA(2)    | AVDTP_GET_CONFIGURATION(0x04)  |
+-----------+--------------------------------+

Octet 2 ~ n:
+--------------------------------------------+
| Service Capabilities                       |
+--------------------------------------------+

这里返回的就不是"所有支持项",而是:
当前这条流已经选定并生效的那一组配置项

返回参数说明

  • Service Capabilities

    返回当前这个 SEP 已经配置好的 capability 集合。

    常见里面会包含:

    • Media Transport
    • Media Codec
    • 以及其他当前实际启用的可选 capability

也就是说:

  • Get All Capabilities 返回的是"可选范围"
  • Get Configuration 返回的是"当前结果"

Response Reject 格式

如果读取失败,则返回 Reject,后面带 Error Code

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------------+
| RFA(2)    | AVDTP_GET_CONFIGURATION(0x04)  |
+-----------+--------------------------------+

Octet 2:
+--------------------------------------+
| ERROR_CODE                           |
+--------------------------------------+

补充理解

这个命令本质上更像一个"回读确认 "命令。

它不是用来协商能力的,也不是用来下发配置的,而是用来读取当前已经生效的配置结果。

所以你后面看到它时,可以直接这样理解:

  • Get All Capabilities:候选列表
  • Set Configuration:下发选择
  • Get Configuration:回读结果

在主流程里它没有 Discover / Set Configuration / Open / Start 那么核心,

但它的作用很清楚:确认当前这条流到底配置成了什么。

AVDTP_OPEN

这个命令就是在 Set Configuration 完成之后,用来打开这条流对应的传输通道

也就是说,到了这一步,双方已经不是"只是把参数谈好了",而是开始真正去把后面传媒体数据要用的 transport path 准备好。

这里要注意:

  • Set Configuration:只是把流配置定下来
  • Open:把这条流真正打开
  • Start:才是开始真正传媒体数据

所以 OpenStart 不是一回事。

命令格式

AVDTP_OPENSignal Identifier = 0x06

这个命令只需要带一个 ACP SEID,表示要打开哪一个已经配置好的对端 SEP

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_OPEN(0x06)         |
+-----------+--------------------------+

Octet 2:
+--------------------------+------------------+
| ACP SEID(6)              | RFA(2)           |
+--------------------------+------------------+

请求参数说明

  • ACP SEID

    对端已经配置好的 SEP 编号。

    也就是:我要打开的是哪一条已经配置好的流端点

Response Accept 格式

如果对端接受 Open,则返回 Response Accept,并且这个响应没有额外参数

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_OPEN(0x06)         |
+-----------+--------------------------+

在最常见的 Basic Service 场景下,这一步之后就会把后续媒体传输要用的 L2CAP transport channel 建起来。

也就是说,流进入了"已经打开,但还没开始送媒体数据"的状态。

Response Reject 格式

如果对端拒绝 Open,则返回 Reject,后面只带 Error Code

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_OPEN(0x06)         |
+-----------+--------------------------+

Octet 2:
+--------------------------------------+
| ERROR_CODE                           |
+--------------------------------------+

Set Configuration 不一样,这里 Reject 没有 Service Category

因为到 Open 这一步时,前面的配置已经确定了,现在失败更多是:

  • 这个 SEP 当前状态不允许打开
  • 或者这条流本身还没有正确配置好

补充理解

你可以直接把 Open 理解成:
把"已经协商好的流"切换到"可开始传输"的准备状态

如果只压成一句话去记:

  • Open = 链路/传输通道准备好
  • Start = 媒体数据真正开始发送
AVDTP_START

这个命令就是在 Open 之后,真正通知对端:这条流现在开始进入媒体传输状态

也就是说,从这一步开始,双方不再只是"链路准备好了",而是正式进入 Streaming 阶段。

所以这三个命令的关系可以直接这样记:

  • Set Configuration:把配置定下来
  • Open:把传输通道准备好
  • Start:开始真正传媒体数据

命令格式

AVDTP_STARTSignal Identifier = 0x07

这个命令请求里带的是一个或多个 ACP SEID,表示要启动哪些已经打开的流。

在最常见的场景里,一般就是只带 1 个 SEID

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_START(0x07)        |
+-----------+--------------------------+

Octet 2 ~ n:
+--------------------------+------------------+
| ACP SEID(6)              | RFA(2)           |
+--------------------------+------------------+
| ACP SEID(6)              | RFA(2)           |   (可继续重复)
+--------------------------+------------------+

也就是说:

  • 请求里没有单独的 NSEP 字段
  • 带了几个 SEID,就表示要启动几个流
  • 实际常见抓包里往往只有一个 SEID

请求参数说明

  • ACP SEID

    表示要启动的对端 SEP

    这些 SEP 必须已经:

    • 配置完成
    • 已经 Open

否则通常就会被拒绝。

Response Accept 格式

如果对端接受 Start,则返回 Response Accept,它没有额外参数

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_START(0x07)        |
+-----------+--------------------------+

这个响应一旦成功,就表示这条流已经进入可以收发媒体数据的状态。

后面就会真正开始走 AVDTP media transport packet

Response Reject 格式

如果对端拒绝 Start,则返回 Reject。

这里会带:

  • 出错的 ACP SEID
  • ERROR_CODE
text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_START(0x07)        |
+-----------+--------------------------+

Octet 2:
+--------------------------+------------------+
| ACP SEID(6)              | RFA(2)           |
+--------------------------+------------------+

Octet 3:
+--------------------------------------+
| ERROR_CODE                           |
+--------------------------------------+

这里多带一个 ACP SEID 很重要,因为 Start 可以一次启动多个流。

如果对端拒绝,它就要明确告诉你:到底是哪一个 SEID 启动失败了

补充理解

Start 才是真正意义上的"开始送媒体数据"。

如果只压成一句话去记:

  • Open:把路打通
  • Start:真正开始跑数据
AVDTP_SUSPEND

这个命令就是在流已经 Start 之后,临时把媒体流暂停下来

也就是说,媒体数据先不继续传了,但这条流本身并没有被彻底关掉。

所以它和 Close 的区别可以先这样记:

  • Suspend:暂停流,后面还可以再 Start
  • Close:关闭流,要把传输通道收掉

命令格式

AVDTP_SUSPENDSignal Identifier = 0x09

这个命令和 Start 很像,也可以带一个或多个 ACP SEID

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_SUSPEND(0x09)      |
+-----------+--------------------------+

Octet 2 ~ n:
+--------------------------+------------------+
| ACP SEID(6)              | RFA(2)           |
+--------------------------+------------------+
| ACP SEID(6)              | RFA(2)           |   (可继续重复)
+--------------------------+------------------+

也就是说:

  • 带几个 SEID,就表示要暂停几个流
  • 最常见场景还是只带一个 SEID

请求参数说明

  • ACP SEID

    表示要暂停的对端 SEP

    这些 SEP 一般都应该已经处于 Streaming 状态。

Response Accept 格式

如果对端接受 Suspend,则返回 Response Accept,它没有额外参数

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_SUSPEND(0x09)      |
+-----------+--------------------------+

成功之后,可以直接理解成:

  • 这条流从 Streaming 回到 Open
  • 媒体数据先停下来
  • 但流本身还在

Response Reject 格式

如果对端拒绝 Suspend,则返回 Reject。

因为 Suspend 可以一次带多个 SEID,所以 Reject 里会带:

  • 出错的 ACP SEID
  • ERROR_CODE
text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_SUSPEND(0x09)      |
+-----------+--------------------------+

Octet 2:
+--------------------------+------------------+
| ACP SEID(6)              | RFA(2)           |
+--------------------------+------------------+

Octet 3:
+--------------------------------------+
| ERROR_CODE                           |
+--------------------------------------+

补充理解

你可以把 Suspend 理解成:
媒体流暂停,但传输通道和流配置都还保留着。

所以后面如果再想恢复,通常就直接重新发:

  • Start
AVDTP_CLOSE

这个命令就是把这条已经打开的流关闭掉

Suspend 不一样,Close 不是"先停一下",而是要把这条流当前的传输连接收掉。

命令格式

AVDTP_CLOSESignal Identifier = 0x08

这个命令只带一个 ACP SEID

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_CLOSE(0x08)        |
+-----------+--------------------------+

Octet 2:
+--------------------------+------------------+
| ACP SEID(6)              | RFA(2)           |
+--------------------------+------------------+

请求参数说明

  • ACP SEID

    表示要关闭的对端 SEP

    一般来说这个 SEP 当前应该已经处于:

    • Open
    • 或者 Streaming

Response Accept 格式

如果对端接受 Close,则返回 Response Accept,它没有额外参数

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_CLOSE(0x08)        |
+-----------+--------------------------+

成功之后可以直接理解成:

  • 这条流不再处于 Open / Streaming
  • transport channel 会被关闭
  • 如果后面还想重新传,通常要重新走 Open -> Start

Response Reject 格式

如果对端拒绝 Close,则返回 Reject。

这里 Reject 只带 ERROR_CODE

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_CLOSE(0x08)        |
+-----------+--------------------------+

Octet 2:
+--------------------------------------+
| ERROR_CODE                           |
+--------------------------------------+

补充理解

如果只看流程关系:

  • Suspend:从 Streaming 回到 Open
  • Close:把 Open / Streaming 这条流真正关掉

所以 Close 的动作明显比 Suspend 更重。

AVDTP_ABORT

这个命令就是异常中止当前流

它不是正常的"暂停"或"关闭"流程,而更像是:当前这条流出问题了,直接强制终止,尽快回收状态

所以它和 Close 的区别可以这样记:

  • Close:正常关闭
  • Abort:异常中止

命令格式

AVDTP_ABORTSignal Identifier = 0x0A

这个命令同样只带一个 ACP SEID

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_ABORT(0x0A)        |
+-----------+--------------------------+

Octet 2:
+--------------------------+------------------+
| ACP SEID(6)              | RFA(2)           |
+--------------------------+------------------+

请求参数说明

  • ACP SEID

    表示要被中止的对端 SEP

Response Accept 格式

如果对端接受 Abort,则返回 Response Accept,它没有额外参数

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_ABORT(0x0A)        |
+-----------+--------------------------+

成功之后,可以理解成:

  • 当前这条流被强制终止
  • 状态机会尽快回到可重新开始的初始状态

Response Reject 格式

如果对端拒绝 Abort,则返回 Reject。

这里和 Close 一样,Reject 只带 ERROR_CODE

text 复制代码
Octet 0:
+----------------------+---------------+---------------+
| Transaction Label(4) | Packet Type(2)| Message Type(2)|
+----------------------+---------------+---------------+

Octet 1:
+-----------+--------------------------+
| RFA(2)    | AVDTP_ABORT(0x0A)        |
+-----------+--------------------------+

Octet 2:
+--------------------------------------+
| ERROR_CODE                           |
+--------------------------------------+

总结

AVDTP我的理解就是管理媒体音频流传输通道的协议,它主要负责的就是对于媒体流通道的发现、能力配置协商、连接断开控制等。

然后对于封包的话就是:1. 信令包就是该层的一个控制包,直接传递给L2CAP进行再一次包的构建。 2.媒体流包则是先要接收到上层的payload进行封装后再向L2CAP层进行发送完成再一次封包。

AVDTP控制流通道建立:

  • 首先先进行SEP的发现
  • 询问SEP的能力特性
  • 综合本端支持的能力特性,进行配置SEP(之后可选择再次确认)
  • 然后就进入到OPEN→START就可以开启流通道
  • 最后如果结束就进行CLOSE(中间如果异常会出现SUSPEND或者ABORT)
相关推荐
华清远见成都中心2 小时前
嵌入式就业岗位有哪些?
嵌入式·华清远见成都中心·成都嵌入式培训机构
starvapour14 小时前
Ubuntu系统下基于终端的音频相关命令
linux·ubuntu·音视频
高山流水&上善1 天前
基于BERT情感分析与多维度可视化的B站热门视频评论分析系统
人工智能·bert·音视频
阿酷tony1 天前
如何做视频课程的报名观看?实现报名后,才能观看视频?
音视频
福大大架构师每日一题1 天前
ollama v0.20.0 更新:Gemma 4 全家桶发布,音频、视觉、MoE、BPE 支持全面升级
音视频·ollama
无垠的广袤2 天前
【Titan RA8P1 Board】J-Link 调试
单片机·嵌入式·开发板·调试器·jlink
Flamingˢ2 天前
ZYNQ + OV5640 + HDMI 视频系统调试记录:一次 RGB888 与 RGB565 引发的黑屏问题
arm开发·嵌入式硬件·fpga开发·vim·音视频
小白zlm2 天前
连续系统-离散系统的转换
算法·嵌入式·电机控制·pmsm
凉、介2 天前
Flash 块设备驱动开发
c语言·驱动开发·笔记·学习·操作系统·嵌入式