Miracast技术详解(二):RTSP协议
RTSP概述
在上一篇博客中我们已经通过Wi-Fi P2P建立好了Source和Sink端的TCP连接,在Miracast后续的音视频传输过程中,将采用RTSP协议来对流媒体进行控制。因此接下来的步骤就到了RTSP协商、会话建立及流媒体传输的阶段。首先,什么是RTSP协议呢?
实时流协议(Real Time Streaming Protocol,RTSP)是一种网络应用协议,专为娱乐和通信系统的使用,以控制流媒体服务器。该协议用于创建和控制终端之间的媒体会话。媒体服务器的客户端发布VCR命令,例如播放,录制和暂停,以便于实时控制从服务器到客户端(视频点播)或从客户端到服务器(语音录音)的媒体流。
流数据本身的传输不是RTSP的任务。大多数RTSP服务器使用实时传输协议(RTP)和实时传输控制协议(RTCP)结合媒体流传输。关于流媒体传输用到的协议与过程,我们将会在下一篇博客中进行详解。
抓包准备
要分析RTSP的指令协商过程,最好的方法就是抓取TCP的数据包,并进行分析。Android上可以采用tcpdump工具(tcpdump详解)抓取TCP的包,然后通过Wireshark工具(wireshark抓包新手使用教程)导入进行分析。
# 在Source端抓取tcpdump
adb shell
tcpdump -i any -p -s 0 -w /data/local/capture.pcap
# -i any 表示抓取所有接口(waln0、p2p0等)
最终抓取到的数据包如下图所示,通过Wireshark的过滤功能我们可以很方便的过滤RTSP协议的数据包:

WFD能力协商(Capability Negotiation)
在Source和Sink端的TCP连接成功建立之后,会马上进入到RTSP能力协商的阶段,主要涉及到RTSP M1-M4指令,双方将按照以下顺序发送与处理消息。

RTSP M1 Messages
由Source端发起一个RTSP OPTIONS M1请求,以确认Sink端所支持的RTSP方法请求。Request和Response如下所示:
Request(Source -> Sink)
OPTIONS * RTSP/1.0\r\n
Date: Wed, 11 Dec 2019 08:31:54 +0000\r\n
Server: stagefright/1.2 (Linux;Android 8.1.0)\r\n
CSeq: 1\r\n
Require: org.wfa.wfd1.0\r\n
\r\n
Response(Sink -> Source)
RTSP/1.0 200 OK\r\n
CSeq: 1\r\n
Public: org.wfa.wfd1.0, GET_PARAMETER, SET_PARAMETER\r\n
\r\n
RTSP M2 Messages
在Sink回复完M1指令后,会由Sink端发起一个RTSP OPTIONS M2请求,以确认Source端所支持的RTSP方法请求。Request和Response如下所示:
Request(Sink -> Source)
OPTIONS * RTSP/1.0\r\n
CSeq: 0\r\n
User-Agent: wfdsinkemu\r\n
Require: org.wfa.wfd1.0\r\n
\r\n
Response(Source -> Sink)
RTSP/1.0 200 OK\r\n
Date: Wed, 11 Dec 2019 08:31:54 +0000\r\n
Server: stagefright/1.2 (Linux;Android 8.1.0)\r\n
CSeq: 0\r\n
Public: org.wfa.wfd1.0, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER\r\n
\r\n
RTSP M3 Messages
在Source端回复完M2指令后,会由Source端发起GET_PARAMETER M3请求,以查询Sink端的属性以及能力,所查询的属性列表在请求最后。
Request(Source -> Sink)
GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n
Date: Wed, 11 Dec 2019 08:31:54 +0000\r\n
Server: stagefright/1.2 (Linux;Android 8.1.0)\r\n
CSeq: 2\r\n
Content-type: text/parameters
Content-length: 83
\r\n
Line-based text data: text/parameters (4 lines)
wfd_content_protection\r\n
wfd_video_formats\r\n
wfd_audio_codecs\r\n
wfd_client_rtp_ports\r\n
Sink端回复M3指令,告诉Source端自身支持的属性及能力,比较重要的几个属性:RTP端口号wfd_client_rtp_ports(传输流媒体用)、所支持的audio及video编解码格式wfd_audio_codecs,wfd_video_formats等…
Response(Sink -> Source)
RTSP/1.0 200 OK\r\n
CSeq: 2\r\n
Content-type: text/parameters
Content-length: 848
\r\n
Line-based text data: text/parameters (7 lines)
wfd_client_rtp_ports: RTP/AVP/UDP;unicast 20011 0 mode=play\r\n
wfd_audio_codecs: LPCM 00000002 00, AAC 00000001 00\r\n
wfd_video_formats: 78 00 01 01 00008400 00000000 00000000 00 0000 0000 00 none none\r\n
wfd_connector_type: 07\r\n
wfd_uibc_capability: none\r\n
wfd_content_protection: none\r\n
wfd_idr_request_capability: 1\r\n
RTSP M4 Messages
基于Sink端回复的M3指令,将由Source端发起SET_PARAMETER M4指令,以最终设置此次会话里的最佳参数集(收发双方都支持的编解码器类型等等)
Request(Source -> Sink)
SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n
Date: Wed, 11 Dec 2019 08:31:54 +0000\r\n
Server: stagefright/1.2 (Linux;Android 8.1.0)\r\n
CSeq: 3\r\n
Content-type: text/parameters
Content-length: 247
\r\n
Line-based text data: text/parameters (4 lines)
wfd_video_formats: 00 00 01 01 00000400 00000000 00000000 00 0000 0000 00 none none\r\n
wfd_audio_codecs: AAC 00000001 00\r\n
wfd_presentation_URL: rtsp://192.168.49.5/wfd1.0/streamid=0 none\r\n
wfd_client_rtp_ports: RTP/AVP/UDP;unicast 20011 0 mode=play\r\n
Response(Sink -> Source)
RTSP/1.0 200 OK\r\n
CSeq: 3\r\n
\r\n
wfd_video_formats格式解析
在官方WifiDisplaySource.cpp源码中,有对wfd_video_formats格式做了详细解释,各个字段的含义如下:
// wfd_video_formats:
// 1 byte "native"
// 1 byte "preferred-display-mode-supported" 0 or 1
// one or more avc codec structures
// 1 byte profile
// 1 byte level
// 4 byte CEA mask
// 4 byte VESA mask
// 4 byte HH mask
// 1 byte latency
// 2 byte min-slice-slice
// 2 byte slice-enc-params
// 1 byte framerate-control-support
// max-hres (none or 2 byte)
// max-vres (none or 2 byte)
其中比较重要的几个字段解释如下图所示:
| Field | Size (octets) | Description |
|---|---|---|
| Native Resolutions/Refresh Rates bitmap | 1 | Bitmap defined in Table 37 detailing native display resolutions and refresh rates supported by the WFD Sink. |
| Profiles bitmap | 1 | Bitmap defined in Table 38 detailing the H.264 profile indicated by this instance of the WFD Video Formats subelement. |
| Levels bitmap | 1 | Bitmap defined in Table 39 detailing the H.264 level indicated by this instance of the WFD Video Formats subelement. |
| CEA Resolutions/Refresh Rates bitmap | 4 | Bitmap defined in Table 34 detailing CEA resolutions and refresh rates supported by the CODEC. |
| VESA Resolutions/Refresh Rates bitmap | 4 | Bitmap defined in Table 35 detailing VESA resolutions and refresh rates supported by the CODEC. |
| HH Resolutions/Refresh Rates bitmap | 4 | Bitmap defined in Table 36 detailing HH resolutions and refresh rates supported by the CODEC. |
Native Resolutions/Refresh Rates Bitmap
该字段描述了Sink端设备当前的分辨率与刷新率,占1字节,其中低3位代表了分辨率模式选择位,高5位则代表当前分辨率与刷新率在表中的index。其中000代表选用CEA标准分辨率,而001则代表VESA标准分辨率,详见下表:
| Bits | Name | Interpretation |
|---|---|---|
| 2:0 | Table Selection bits | 0b000: Resolution/Refresh rate table selection: Index to CEA resolution/refresh rates (Table 34) 0b001: Resolution/Refresh rate table selection: Index VESA resolution/refresh rates (Table 35) 0b010: Resolution/Refresh rate table selection: Index HH resolutions/refresh rates (Table 36) 0b011~0b111: Reserved |
| 7:3 | Index bits | Index into resolution/refresh rate table selected by [B2:B0] |
CEA Resolutions/Refresh Rates Bitmap
该字段描述了当前Sink端设备所支持的分辨率与刷新率,占4个字节,总共32位,其中每一位都是一个flag,值为1代表支持该标志位对应的分辨率与刷新率。其中可以同时对多个标志位置1,代表同时支持这几种分辨率与刷新率。
| Bits | Index | Interpretation |
|---|---|---|
| 0 | 0 | 640x480 p60 |
| 1 | 1 | 720x480 p60 |
| 2 | 2 | 720x480 i60 |
| 3 | 3 | 720x576 p50 |
| 4 | 4 | 720x576 i50 |
| 5 | 5 | 1280x720 p30 |
| 6 | 6 | 1280x720 p60 |
| 7 | 7 | 1920x1080 p30 |
| 8 | 8 | 1920x1080 p60 |
| 9 | 9 | 1920x1080 i60 |
| 10 | 10 | 1280x720 p25 |
| 11 | 11 | 1280x720 p50 |
| 12 | 12 | 1920x1080 p25 |
| 13 | 13 | 1920x1080 p50 |
| 14 | 14 | 1920x1080 i50 |
| 15 | 15 | 1280x720 p24 |
| 16 | 16 | 1920x1080 p24 |
| 31:17 | - | Reserved |
Profiles Bitmap
该字段描述了当前Sink端设备所支持的H.264 profile配置,占1字节,低0位与低1位分别是CBP(Constrained Baseline Profile)与CHP(Constrained High Profile)标志位,置1时代表支持该配置,剩下的6位则为保留位。
| Bits | Name | Interpretation |
|---|---|---|
| 0 | CBP bit | 0b0: Constrained Baseline Profile (CBP) not supported 0b1: CBP supported |
| 1 | CHP bit | 0b0: Constrained High Profile (CHP) not supported 0b1: CHP supported |
| 7:2 | Reserved | Set to all zeros |
Levels Bitmap
该字段描述了当前Sink端设备所支持的H.264 level限制。其中level是一组特定的约束,表示一个profile所需的解码性能。占1字节,低5位代表了各个level的flag位,置1时表示支持该level。关于H.264中profile与level的介绍不在这里详细展开,有兴趣的可以自行了解。
| Bits | Name | Interpretation |
|---|---|---|
| 0 | H.264 Level 3.1 bit | 0b0: H.264 Level 3.1 not supported 0b1: H.264 Level 3.1 supported |
| 1 | H.264 Level 3.2 bit | 0b0: H.264 Level 3.2 not supported 0b1: H.264 Level 3.2 supported |
| 2 | H.264 Level 4 bit | 0b0: H.264 Level 4 not supported 0b1: H.264 Level 4 supported |
| 3 | H.264 Level 4.1 bit | 0b0: H.264 Level 4.1 not supported 0b1: H.264 Level 4.1 supported |
| 4 | H.264 Level 4.2 bit | 0b0: H.264 Level 4.2 not supported 0b1: H.264 Level 4.2 supported |
| 7:5 | Reserved | Set to all zeros |
例子
我们这里拿几个官方的例子来帮助大家理解,如第一组数据30 00 02 02 00000040 ...对应为720p/60帧,其中
Native 30二进制表示为0011 0000,低3位为0,代表CEA标准分辨率;高5位为分辨率index,00110换算为6,对应CEA分辨率表中的Index61280x720 p60Profile 02二进制表示为0000 0010,表示支持Constrained High ProfileLevels 02二进制表示为0000 0010,表示支持H.264 Level 3.2CEA 00000040二进制0000 0000 0000 0000 0000 0000 0100 0000,也就是第6位flag置1,对应CEA分辨率表中的Index 61280x720 p60。此外,CEA字段可以同时对多个标志位置1,代表同时支持这几种分辨率与刷新率。
其他的几个例子大家可以按照上面的思路一一分析,这里不再展开。
// For 720p60:
// use "30 00 02 02 00000040 00000000 00000000 00 0000 0000 00 none none\r\n"
// For 720p30:
// use "28 00 02 02 00000020 00000000 00000000 00 0000 0000 00 none none\r\n"
// For 720p24:
// use "78 00 02 02 00008000 00000000 00000000 00 0000 0000 00 none none\r\n"
// For 1080p30:
// use "38 00 02 02 00000080 00000000 00000000 00 0000 0000 00 none none\r\n"
WFD会话建立(Session Establishment)
在Sink回复完M4指令后,能力协商的过程就结束了,下一步则是WFD会话建立过程,主要涉及到RTSP M5-M7指令。双方将按照以下顺序发送与处理消息。

RTSP M5 Messages
由Source端发起SET_PARAMETER M5请求,通过wfd_trigger_method参数触发Sink端向Source端进行SETUP、PLAY、PAUSE、TEARDOWN等请求。如下M5 Request中设置了SETUP触发请求。
Request(Source -> Sink)
SET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n
Date: Wed, 11 Dec 2019 08:31:54 +0000\r\n
Server: stagefright/1.2 (Linux;Android 8.1.0)\r\n
CSeq: 4\r\n
Content-type: text/parameters
Content-length: 27
\r\n
Line-based text data: text/parameters (1 lines)
wfd_trigger_method: SETUP\r\n
Sink端则正常回复,表示自己已经收到SETUP触发请求即可。
Response(Sink -> Source)
RTSP/1.0 200 OK\r\n
CSeq: 4\r\n
\r\n
RTSP M6 Messages
上面说到了M5 Request中设置了SETUP触发请求,则此时应该由Sink端主动发送SETUP M6请求:
Request(Sink -> Source)
SETUP rtsp://192.168.49.5/wfd1.0/streamid=0 RTSP/1.0\r\n
CSeq: 1\r\n
Transport: RTP/AVP/UDP;unicast;client_port=20011
\r\n
此时Source端将完成RTSP会话的创建,并返回Session ID:
Response(Source -> Sink)
TSP/1.0 200 OK\r\n
Date: Wed, 11 Dec 2019 08:31:55 +0000\r\n
Server: stagefright/1.2 (Linux;Android 8.1.0)\r\n
CSeq: 1\r\n
Session: 1804289383;timeout=30
Transport: RTP/AVP/UDP;unicast;client_port=20011;server_port=26466
\r\n
RTSP M7 Messages
经过M6指令交互后,RTSP会话已经完成创建。此时将由Sink端发送PLAY M7请求,告诉发送端可以开始发送流媒体数据了:
Request(Sink -> Source)
PLAY rtsp://192.168.49.5/wfd1.0/streamid=0 RTSP/1.0\r\n
CSeq: 2\r\n
Session: 1804289383;timeout=30
\r\n
Source端回复M7指令,并且状态是200 OK时,WFD Session成功建立。
Response(Source -> Sink)
RTSP/1.0 200 OK\r\n
Date: Wed, 11 Dec 2019 08:31:55 +0000\r\n
Server: stagefright/1.2 (Linux;Android 8.1.0)\r\n
CSeq: 2\r\n
Session: 1804289383;timeout=30
Range: npt=now-\r\n
\r\n
经过以上M1-M7的指令交互,且成功创建WFD会话后,Source与Sink端的协商及会话过程已完成。这个时候Source端会按照Sink指定的UDP端口发送RTP数据包,包含音视频数据。
总结M1-M7过程:
| Message | Method | Direct | 简述 | 重点 |
|---|---|---|---|---|
| M1 | OPTIONS | Source -> Sink | 打招呼 | |
| M2 | OPTIONS | Source <- Sink | 打招呼 | |
| M3 | GET_PARAMETER | Source -> Sink | 你支持什么音视频格式 | wfd_video_formats/wfd_audio_codecs |
| M4 | SET_PARAMETER | Source -> Sink | 我们使用这个格式吧 | |
| M5 | SETUP | Source -> Sink | 建立连接吧 | |
| M6 | SETUP | Source <- Sink | 建立连接吧 | client_port/server_port/Session ID |
| M7 | PLAY | Source <- Sink | 开始发送数据吧 |
关闭会话
在RTSP M5 Messages这一节中,我们谈到会由Source端发起SET_PARAMETER M5请求,触发Sink端发送TEARDOWN请求。该请求可以使得Source与Sink端的RTSP及RTP连接断开。
RTSP M8 Messages
- 场景1:Source端主动关闭会话:
首先会由Source发送M5请求,并且wfd_trigger_method的值为TEARDOWN,触发Sink端发送TEARDOWN M8指令: - 场景2:Sink端主动关闭会话:
Sink端直接发送TEARDOWN M8指令
Request(Sink -> Source)
TEARDOWN rtsp://192.168.49.5/wfd1.0/streamid=0 RTSP/1.0\r\n
CSeq: 3\r\n
Session: 1804289383
\r\n
Response(Source -> Sink)
RTSP/1.0 200 OK\r\n
CSeq: 3\r\n
Date: Tue, Dec 17 2019 07:20:28 GMT\r\n
\r\n
总结
针对Miracast RTSP协商、会话建立及流媒体传输,我们来进行一下总结。
- 在Source和Sink端的TCP连接成功建立之后,会马上进入到RTSP能力协商的阶段,主要涉及到
M1-M4指令 - 能力协商的过程结束后,下一步则是WFD会话建立过程,主要涉及到
M5-M7指令 - WFD会话成功建立后,将由Source端通过UDP连接发送RTP音视频数据包
- 通过
PAUSE、PLAY、TEARDOWN等指令控制音视频流暂停、播放、关闭 - 通过
M16指令来维持WFD长连接,确保会话处于正常的状态
我们可以使用下图来对整个WFD会话的生命周期进行总结:

参考:
WFD_RTSP交互包分析
Miracast技术详解(二):RTSP协议
Miracast技术详解(二):RTSP协议的更多相关文章
- P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解
1.内容概述 P2P即点对点通信,或称为对等联网,与传统的服务器客户端模式(如下图"P2P结构模型"所示)有着明显的区别,在即时通讯方案中应用广泛(比如IM应用中的实时音视频通信. ...
- IPv6技术详解:基本概念、应用现状、技术实践(下篇)
本文来自微信技术架构部的原创技术分享. 1.前言 在上篇<IPv6技术详解:基本概念.应用现状.技术实践(上篇)>,我们讲解了IPV6的基本概念. 本篇将继续从以下方面展开对IPV6的讲解 ...
- IPv6技术详解:基本概念、应用现状、技术实践(上篇)
本文来自微信技术架构部的原创技术分享. 1.前言 普及IPV6喊了多少年了,连苹果的APP上架App Store也早已强制IPV6的支持,然并卵,因为历史遗留问题,即使在IPV4地址如果饥荒的情况下, ...
- CDN学习笔记二(技术详解)
一本好的入门书是带你进入陌生领域的明灯,<CDN技术详解>绝对是带你进入CDN行业的那盏最亮的明灯.因此,虽然只是纯粹的重点抄录,我也要把<CDN技术详解>的精华放上网.公诸同 ...
- Zookeeper系列二:分布式架构详解、分布式技术详解、分布式事务
一.分布式架构详解 1.分布式发展历程 1.1 单点集中式 特点:App.DB.FileServer都部署在一台机器上.并且访问请求量较少 1.2 应用服务和数据服务拆分 特点:App.DB.Fi ...
- CDN技术详解及实现原理
CDN技术详解 一本好的入门书是带你进入陌生领域的明灯,<CDN技术详解>绝对是带你进入CDN行业的那盏最亮的明灯.因此,虽然只是纯粹的重点抄录,我也要把<CDN技术详解>的精 ...
- 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)
一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...
- SSE技术详解:一种全新的HTML5服务器推送事件技术
前言 一般来说,Web端即时通讯技术因受限于浏览器的设计限制,一直以来实现起来并不容易,主流的Web端即时通讯方案大致有4种:传统Ajax短轮询.Comet技术.WebSocket技术.SSE(Ser ...
- Protocol Buffer技术详解(语言规范)
Protocol Buffer技术详解(语言规范) 该系列Blog的内容主体主要源自于Protocol Buffer的官方文档,而代码示例则抽取于当前正在开发的一个公司内部项目的Demo.这样做的目的 ...
- 腾讯技术分享:GIF动图技术详解及手机QQ动态表情压缩技术实践
本文来自腾讯前端开发工程师“ wendygogogo”的技术分享,作者自评:“在Web前端摸爬滚打的码农一枚,对技术充满热情的菜鸟,致力为手Q的建设添砖加瓦.” 1.GIF格式的历史 GIF ( Gr ...
随机推荐
- Linux中国开源社区停止运营
layout: post title: "Linux 中国开源社区停止运营" tags: - "Linux" 昨天看到老王发的公众号文章,Linux中国开源社区 ...
- AdoQuery 多列 查询 定位方法
frmClientDm.TopItemSkuShow_adoq.Locate('top_outer_iid;top_outer_sid', VarArrayOf([top_outer_iid,top_ ...
- .NET Core开发实战(第24课:文件提供程序:让你可以将文件放在任何地方)--学习笔记
24 | 文件提供程序:让你可以将文件放在任何地方 文件提供程序核心类型: 1.IFileProvider 2.IFileInfo 3.IDirectoryContents IFileProvider ...
- Pandas—to_csv()写入函数参数详解
1. to_csv函数的参数 DataFrame.to_csv(path_or_buf=None, sep=',', na_rep='', float_format=None, columns=Non ...
- 《Boosting Document-Level Relation Extraction by Mining and Injecting Logical Rules》论文阅读笔记
代码 原文地址 摘要 文档级关系抽取(DocRE)旨在从文档中抽取出所有实体对的关系.DocRE 面临的一个主要难题是实体对关系之间的复杂依赖性.与大部分隐式地学习强大表示的现有方法不同,最新的 Lo ...
- NC19995 [HAOI2015]树上操作
题目链接 题目 题目描述 有一棵点数为 N 的树,以点 1 为根,且树点有边权. 然后有 M 个 操作,分为三种: 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x 为根的子 ...
- Linux 中hdparm命令参数说明
hdparm命令提供了一个命令行的接口用于读取和设置IDE或SCSI硬盘参数. 语法 hdparm(选项)(参数) 选项 -a:设定读取文件时,预先存入块区的分区数,若不加上选项,则显示目前的设定: ...
- 轻松玩转Makefile | 基础用法
前言 本文通过几个简单的示例,可以快速了解Makefile的基本使用方法,适用于编译我们平时练习所编写的小量代码. 1. make命令 Makefile文件内容: all为目标,这里没有依赖的文件,这 ...
- 【framework】RootWindowContainer简介
1 前言 RootWindowContainer 是窗口容器的根容器,子容器是 DisplayContent.关于其父类及祖父类的介绍,见→WindowContainer简介.Configurat ...
- 使用 Visual Studio 断点调试 DLL
继上文说到使用 IDA 和 WinDbg 调试无 dmp 文件 那么在有源码的情况下可以直接断点调试 DLL,目的是查看 DLL 内部的函数调用 场景: 程序执行到某个 DLL 时突然崩溃,先确定 ...