Kevin Wen's Blog


  • 首页

  • 归档

  • 标签

Try vulkan on windows

发表于 2017-07-01 | 阅读次数

0x1 Vulkan简介

Vulkan是轻量级、更贴近底层硬件的接口,可使GPU驱动程序充分利用多核CPU性能.

对于CPU处理是瓶颈的3D场景,采用Vulkan能提升性能.

Vulkan可以节省shader编译的时间,因为Vulkan只需要处理SPIR-V中间代码.

Vulkan不负责内存的管理,多线程的管理和并发访问保护,由应用程序去负责这些工作.

下面介绍一下如何在Windows下编译和测试Vulkan,显卡是Intel HD Graphics 520.

0x2 安装支持Vulkan的graphics驱动

下载并安装最新的graphics驱动

https://communities.intel.com/external-link.jspa?url=https%3A%2F%2Fdownloadmirror.intel.com%2F26563%2Feng%2Fwin64_154514.4590.zip

0x3 安装Lunarg Vulkan SDK

下载地址如下

https://vulkan.lunarg.com/sdk/home#sdk/downloadConfirm/1.0.51.0/windows/VulkanSDK-1.0.51.0-Installer.exe

0x4 安装Vulkan Hardware Capability Viewer

用于查看系统中vulkan支持信息

可执行程序如下

http://vulkan.gpuinfo.org/downloads/vulkancapsviewer_1_4_win64.zip

代码如下

https://github.com/SaschaWillems/VulkanCapsViewer

运行效果如下

vulkan1

0x5 编译运行Vulkan demo

代码如下

https://github.com/GameTechDev/stardust_vulkan

运行效果如下

vulkan2

下载gpu-z,查看gpu使用情况

https://www.techpowerup.com/download/techpowerup-gpu-z/

从gpu-z的使用情况可知,gpu一直很忙,说明vulkan已经工作

vulkan3

0x6 编译运行Vulkan example

测试代码如下

https://github.com/SaschaWillems/Vulkan

运行效果如下

vulkan4

0x7 Vulkan Architecture on Windows

vulkan5

vulkan-1.dll, The Vulkan loader.

ig9icd64.dll/igc64.dll/igvk64.dll, Intel vulkan driver.

0x8 Vulkan Architecture on Android

vulkan6

libvulkan.so

Android vulkan HAL library, this library will load IHV’s vulkan library in /system/lib/hw automatically, application will load this library then map vulkan api call to IHV’s vulkan driver library.

vulkan.xxx.so

IHV’s vulkan driver library, it supports SPIR-V shader input, and implements vulkan API, will trigger GPU HW to do acutal graphics processing.

P2P直播客户端RsPlayer的设计

发表于 2017-06-17 | 阅读次数

0x1 客户端总体框架

传统的直播客户端只能从流媒体服务器下载流媒体数据,RsPlayer作为一个P2P(peer to peer)的直播客户端,可以同时从流媒体服务器和其他客户端请求媒体数据,比传统的直播客户端相比可以节省流媒体服务器的带宽.

下图显示了客户端的框架图.

最上层是UI层,提供用户的交互操作,用来显示播放视频窗口.

然后是P2PCore层,该层采用P2P方式接收和发送媒体数据.

P2PCore的下面是network层,用来处理socket接收和发送.

ffmpeg层用来处理媒体数据,音视频分离,音视频解码,音视频输出等.

architecture

0x2 P2PCore模块

如下图所示,p2pcore模块需要和三类服务器进行通讯,先从web server得到需要播放的流媒体链接,然后从tracker得到super peer的相关信息,然后再连接到super peer下载所需要的直播流数据,同时也会从其他p2pcore处下载直播流数据.

下面会详细介绍一下和这三类服务器的交互,也会介绍一下和其他p2pcore的交互过程.
p2pcore

与web服务器交互

RsStreamer把p2p直播流链接存储在web服务器上,RsPlayer读取存储在web服务器上的p2p直播流链接,然后解析该链接,从中读取tracker服务器的地址,然后建立和tracker服务器的tcp连接.

与tracker服务器交互

p2pcore发送登录消息到tracker服务器.

p2pcore把p2p直播流链接(从web服务器下载得到)解析得到的直播流信息发送给tracker服务器.

tracker服务器返回消息给p2pcore,告诉将要播放的直播流对应的super peer地址信息,同时也告诉播放同一直播流的其他p2pcore的地址信息.

p2pcore得到super peer地址以后,请求与其建立连接,并请求其发送相应的媒体数据.

p2pcore得到其他p2pcore地址以后也会请求与其建立相应的连接.

p2pcore还会定期向tracker服务器请求直播流的相关信息, 如当前播放的数据包的编号.

tracker服务器发送信息告诉p2pcore当前直播播放的数据包的编号.

与super peer交互

p2pcore建立和super peer的连接以后,把需要播放的直播流资源的信息发送给super peer, 并把直播流的数据包编号发送给super peer.

super peer然后把需要播放的直播流的头文件信息发送给p2p client, 然后根据其请求的资源编号发送直播流数据包.

上面提到了直播流的数据包编号,这个编号用来标记直播数据包,可以在p2p服务器和客户端之间统一管理直播数据. 如客户端需要哪个数据包,只需要把编号发送给服务器或其他客户端.

与其他p2pcore交互

这部分是体现RsPlayer直播客户端中p2p的部分,RsPlayer的p2pcore模块需要分析哪些数据需要从super peer得到,哪些数据需要从其他p2pcore处得到。

p2pcore建立和其他p2pcore的连接以后,需要查询其他p2pcore上有哪些数据包,其他p2pcore把其拥有的数据包信息发送过来以后,RsPlayer经过分析,决定需要从其接收哪些数据包,然后把需要的数据包信息发送给其他p2pcore, 其他p2pcore根据其请求的数据包编号发送相应的媒体数据包.

0x3 多媒体解码模块

p2pcore模块中的接收线程接收网络码流放到buffer中,ffmpeg中的解码线程从buffer中取出码流并解码输出.
buffer_manager

ffmpeg介绍

ffmpeg是一套开源的音视频处理库,包括文件格式处理库libavformat, 音视频编解码库libavcodec等,,目前各大视频网站提供的应用程序中都集成了ffmpeg.

p2pcore模块与ffmpeg模块的交互

修改ffmpeg中libavformat的代码,在ffmpeg中添加buffer protocol的输入接口,通过调用buffer protocol的输入接口,把外部读取buffer的函数指针作为参数传入到ffmpeg中,ffmpeg的解码线程需要数据的时候就调用这个函数指针,从而读取到需要的码流数据.

ffmpeg中添加buffer protocol的输入接口代码如下.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* buffer protocol */
static int buff_open(URLContext *h, const char *filename, int flags)
{
int fd;
char *context = strstr(filename,"context");
av_strstart(filename, "buff:", &filename);
fd = atoi(filename);
h->priv_data = (void *) (intptr_t) fd;
av_strstart(context, "context:", &context);
h->priv_data2 = (void *) (atoi(context));
h->is_streamed = 1;
return 0;
}
typedef int (*read_callback_fcn)(void* param, unsigned char *buf, int size);
static int buff_read(URLContext *h, unsigned char *buf, int size)
{
read_callback_fcn read_callback = (read_callback_fcn ) h->priv_data;
return read_callback(h->priv_data2, buf, size);
}
static int buff_write(URLContext *h, unsigned char *buf, int size)
{
return 0;
}
URLProtocol buff_protocol = {
"buff",
buff_open,
buff_read,
buff_write,
NULL,
NULL,
NULL,
NULL,
NULL,
file_get_handle
};

如何调用这个buffer protocol呢,我们的播放采用ffplay的流程,把按如下所示生成的mediafeeder_str作为ffplay的参数即可,mediafeeder_context是下面提到的类mediadatafeeder的对象指针,mediafeeder_handle是下面提到的类mediadatafeeder成员函数readdata()指针. ffplay先解析mediafeeder_str的内容,根据buff关键字匹配到buffer protocol的输入接口, buffer protocol的输入接口然后来调用mediadatafeeder::readdata().

1
sprintf(mediafeeder_str,"buff:%dcontext:%d\0",mediafeeder_handle, mediafeeder_context);

前面提到ffmpeg解码的时候会调用buffer protocol中的buff_read()函数,然后再调用p2pcore中的mediadatafeeder::readdata ()函数. 然后再调用livebuffermgr::getblock()读取到码流.

下面是在p2pcore中实现buffer读取的函数.

1
2
3
4
int mediadatafeeder::readdata(void* param, unsigned char* buffer, int size)
{
}

p2pcore中的buffer管理如下.
getblock()接口供上面的readdata函数调用, 相当于ffmpeg线程中调用该函数.
putblock()会被p2pcore中的网络接收线程调用.

由于livebuffermgr的getblock()和putblock()会被不同的线程调用,所以里面的buffer数据访问需要加锁以避免多线程之间的数据访问冲突.

1
2
3
4
5
6
void livebuffermgr::getblock(int blockID, int blockSize, unsigned char* data)
{
}
void livebuffermgr::putblock(int blockID, int blockSize, unsigned char* data)
{
}

0x4 在Android平台上的实现

在Android平台上实现RsPlayer客户端的话,p2pcore和ffmpeg都封装成相应的so库, 需要设置Android NDK开发环境, 采用Android Studio来开发.

optimize jpeg decoder on multicore cpu

发表于 2017-06-10 | 阅读次数

0x1 jpeg解码流程

JPEG是Joint Photographic Expert Group的缩写,按照解码算法的基本结构进行分类的话,可以分为两种方式,

有失真的非可逆的DCT方式.

无失真的可逆的Spatial方式.

DCT方式的解码流程

dct_jpeg

Spatial方式的解码流程

spatial_jpeg

0x2 多核优化介绍

我们对应用程序采用多线程化来充分利用多核的性能,下面介绍一下多线程框架.

多线程框架介绍

典型的多线程框架包括一个主线程,多个工作线程,一个工作队列.

主线程负责接收外部分配过来的任务,把任务拆分成子任务并保存到工作队列中,然后调度工作线程进行具体的操作.

工作线程执行具体的操作,工作线程完成一个操作以后通知主线程,并更新当前子任务的状态到工作队列中,工作线程进入空闲状态.

主线程根据工作队列中子任务的状态,分配子任务到刚才空闲的工作线程中,工作线程继续执行分配到的子任务.

多线程框架可以表示如下图所示

multithread_framework

多线程并行执行和线程间同步

多个工作线程之间是并行执行的,这样可以充分利用多核的优势.

但是工作线程执行完成以后,需要和其他工作线程进行同步,因为工作线程执行的子任务之间可能有依赖关系,也就是说某一个子任务可能需要等其他子任务执行完成以后才能继续执行.

多线程程序框架

下面列出各个模块的代码.

工作线程

1
2
3
4
5
6
7
8
9
class workthread
{
public:
virtual void threadproc();
bool start();
void stop();
private:
workerqueue* worker_queue;
};

工作队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class task
{
private:
int start;
int end;
};
class workerqueue
{
public:
void puttask();
void gettask();
void updatetask();
private:
vector<task*> task_vector;
};

主线程

1
2
3
4
5
6
7
8
class mainthread
{
public:
void schedule_workerthread();
private:
vector<workthread*> thread_vector;
workerqueue* worker_queue;
};

0x3 jpeg解码多核优化分析

解码模块分解

从上面的解码流程图可知,一个jpeg解码包括熵解码, 反量化,idct,加上后处理如颜色空间转换csc等.

先分析各个模块在整个解码过程中占用多少时间,这个过程可以通过运行典型的测试来得到,通过计算典型jpeg文件的解码时间在各个模块中的分布,可以知道这几个子模块在jpeg解码器中占用多少时间。通过分析各个模块的时间,我们可以把这个解码流程分成几个时间差不多的子模块,如可以分解成这三个子模块,huffman decoder, iq和idct, csc. 进行这样的分析是为了使各个模块的执行时间差不多,这样采用多核执行的时候能使各个核的运行更balance.

另外我们可以把整个图像分解成几部分,对这几部分分别进行解码。可以按照16/32/64/128的高度进行分解,这样可以把图像分解成很多份,如1920*1080的jpeg图像,分解成1920/16 = 120份,这120份的图像分别进行解码,可以提高解码效率,当然这个120份之间还有一些前后依赖关系,所以不能完全并行,需要进行同步和调度,下面会提到如何进行同步和调度. 另外具体是按16的高度,还是按照其他高度进行分解,需要进行tuning,需要参考图像的大小和工作线程的个数(一般等同于cpu的核数),进行各个高度分解下的性能比较,得出一个性能最好的分解方式.

解码模块实现

把jpeg解码器的模块独立成子模块来实现. 下面简单介绍一下这些模块.

1.熵解码器,记为huffman decoder

扩展系统中也包括算术解码器,这里仅以huffman decoder来代替.

分别对DC和AC系数进行解码,根据DC/AC的解码表来解码.

DC系数的解码还需要参考前一个像素块的DC系数,因为huffman decoder解码出来的只是和前一个像素块的DC系数的查分值,把查分值加上前一个像素块的DC系数就得到了当前像素块的DC值.

AC系数的解码是根据解码表计算出非零AC系数和该AC系数前面的零的个数,就是通常所说的run和level, 然后根据zigzag的扫描方式还原成8*8的系数.

2.反量化器和反DCT,记为iq/idct

对前面得到的8*8的系数,查找量化表,然后进行IDCT计算,得到解码以后的像素值.

3.颜色空间转换,记为csc

执行yuv到rgb的转换.

解码模块之间的同步

前面提到把整个解码流程分解成huffman decoder, inverse quantizer/idct, csc.

对于某一个8*8的像素块来说,只能是按照前面的解码流程来进行解码,不能有跳跃

我们整个图像分解成几部分以后,只有前面一部分的huffman decoder解码完成以后,后面一部分的huffman decoder才能开始,因为前后部分的码流只有在解码以后才能分清楚从具体什么位置开始是后面一部分的码流,也就是说不经过解码,前后部分的码流没办法分割,另外一个原因是DC值得解码需要参考前面一个像素块的DC.

前面一部分完成了huffman解码以后,可以开始inverse quantizer和idct,在这个时候后面一部分可以开始huffman解码,以此类推.

解码模块的执行过程演示

下图演示了多线程解码的过程.

我们把一个解码图像分成多块block1,block2,block3,block4,… 对每块图像分别执行上面介绍的解码子模块.

T0时间点,只有block1进行huffman解码. block1的解码工作由线程1完成.

T1时间点,block1进行iq/idct, block2进行huffman解码. block1的解码工作由线程1完成,block2的解码工作由线程2完成.

T2时间点,block1进行csc, block2进行iq/idct解码, block3进行huffman解码. block1的解码工作由线程1完成,block2的解码工作由线程2完成,block3的解码工作由线程3完成

T3时间点,block2进行csc解码, block3进行iq/idct解码, block4进行huffman解码. block2的解码工作由线程2完成,block3的解码工作由线程3完成,block4的解码工作由线程1完成

如上分析所示,这个多线程解码的过程只能保证3个线程被利用到,如果cpu的核比较多的话,并不能很好地利用cpu资源. 这个时候可以考虑多个jpeg同时解码来充分利用cpu资源.

multithread_framework

RsStreamer监控模块设计

发表于 2017-06-04 | 阅读次数

0x1 需求分析

0x10 HTTP监控

RsStreamer作为P2P流媒体服务器,需要和很多并发连接的P2P播放器进行通讯,把媒体数据发送给这些P2P播放器进行播放,另外还需要和其他如认证服务器进行通讯,进行用户验证等操作.

RsStreamer在运行的过程中,很多内部状态需要及时地提供给用户,如统计有多少个用户观看了某部影片,提供某部影片流媒体服务的总带宽是多少,通过http服务的形式提供这些内部状态的监控信息,用户可以通过浏览器来直观地访问这些数据,从而了解系统运行的相关信息.

另外用户还可以通过浏览器来配置RsStreamer,如限制某个用户的带宽,对某个特定的媒体频道限制用户数目.

其他流媒体平台如helix server也提供网页形式的界面供用户查看运行状态和进行相关配置管理.

其他开源软件如nginx也有类似的功能.

0x11 数据分析

通过RsStreamer提供的http服务,把系统运行的相关信息通过RESTful接口的形式提供给数据分析系统(如elasticsearch),进行分析以后,可以对用户播放流媒体影片的行为进行数据挖掘,训练影片推荐系统,从而提供更好的用户体验.

下面先分析一下haproxy系统中监控模块是如何实现的,然后再提出RsStreamer中监控模块的设计方案.

0x2 HAProxy的实现

0x20 总体结构

haproxy中有关监控模块的总体结构图如下所示.
haproxy stats architecture

http service基于epoll对外提供http服务,接受浏览器的请求,建立和浏览器之间的tcp连接,根据运行状态生成对应的html格式数据,通过tcp把html数据发送给浏览器,浏览器接收到数据以后显示haproxy的运行状态.

0x21 代码流程

程序执行流程图如下

haproxy sequence

程序的执行从main函数开始,然后调用poller函数等待事件的触发.
有stats的请求到来,poller函数触发.
然后根据运行状态生成html格式数据,通过tcp发生给浏览器.
下面对这个流程图的执行过程进行进一步的分析.

0x22 相关代码模块分析

处理网络连接的主循环

该循环调用poller的poll函数,检测是否有socket事件发生,用户在浏览器中输入http://127.0.0.1:1080/stats, haproxy收到请求,run_poll_loop()检测到有client来连接,建立tcp连接,然后调用applet_run_active()来处理这些事件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* Runs the polling loop */
static void run_poll_loop()
{
while (1) {
/* Process a few tasks */
process_runnable_tasks();
/* check if we caught some signals and process them */
signal_process_queue();
/* Check if we can expire some tasks */
next = wake_expired_tasks();
/* stop when there's nothing left to do */
if (jobs == 0)
break;
/* expire immediately if events are pending */
if (fd_cache_num || tasks_run_queue || signal_queue_len || applets_active_queue)
next = now_ms;
/* The poller will ensure it returns around <next> */
cur_poller.poll(&cur_poller, next);
fd_process_cached_events();
applet_run_active();
}
}

poller的poll()函数实现

1.首先扫描fd更新列表fd_updt,找到需要处理的fd,再调用epoll_ctl()来往内核来注册epoll事件.
2.然后调用epoll_wait()等待事件的发生,事件触发以后,epoll_wait()函数返回,返回值是触发的事件数目.
3.再遍历已经触发的事件,根据是要读还是写分别调用fd_may_recv()和fd_may_send().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
REGPRM2 static void _do_poll(struct poller *p, int exp)
{
... ...
/* first, scan the update list to find polling changes */
for (updt_idx = 0; updt_idx < fd_nbupdt; updt_idx++) {
fd = fd_updt[updt_idx];
if ((eo ^ en) & FD_EV_POLLED_RW) {
/* poll status changed */
... ...
epoll_ctl(epoll_fd, opcode, fd, &ev);
}
}
... ...
/* now let's wait for polled events */
status = epoll_wait(epoll_fd, epoll_events, global.tune.maxpollevents, wait_time);
/* process polled events */
for (count = 0; count < status; count++) {
... ...
fd = epoll_events[count].data.fd;
... ...
if (n & (FD_POLL_IN | FD_POLL_HUP | FD_POLL_ERR))
fd_may_recv(fd);
if (n & (FD_POLL_OUT | FD_POLL_ERR))
fd_may_send(fd);
}
}

applet_run_active()函数实现

从applet队列中找到需要执行的任务,调用相应的handler函数,对stats模块,其handler函数为http_stats_io_handler().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void applet_run_active()
{
... ...
while (!LIST_ISEMPTY(&applet_cur_queue)) {
curr = LIST_ELEM(applet_cur_queue.n, typeof(curr), runq);
... ...
si_applet_cant_get(si);
si_applet_stop_put(si);
curr->applet->fct(curr);
si_applet_wake_cb(si);
channel_release_buffer(si_ic(si), &curr->buffer_wait);
... ...
}
}

stats模块在applet中的注册

把stats的处理函数http_stats_io_handler注册到applet中.
请注意关键字attribute((constructor))是gcc的关键字,表明这个函数是在main()函数执行之前被调用.

1
2
3
4
5
6
7
8
9
10
11
12
truct applet http_stats_applet = {
.obj_type = OBJ_TYPE_APPLET,
.name = "<STATS>", /* used for logging */
.fct = http_stats_io_handler,
.release = NULL,
};
__attribute__((constructor))
static void __stat_init(void)
{
cli_register_kw(&cli_kws);
}

生成html格式监控数据

生成html格式监控数据的调用堆栈如下.

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) bt
#0 stats_fill_be_stats (px=px@entry=0x74fc30, flags=flags@entry=17, stats=stats@entry=0x7328e0 <stats>, len=len@entry=83)
at src/stats.c:1725
#1 0x000000000046d238 in stats_dump_be_stats (si=si@entry=0x7a1a50, px=px@entry=0x74fc30, flags=flags@entry=17)
at src/stats.c:1814
#2 0x000000000046edff in stats_dump_proxy_to_buffer (si=si@entry=0x7a1a50, px=px@entry=0x74fc30, uri=uri@entry=0x751a60)
at src/stats.c:2112
#3 0x000000000046f967 in stats_dump_stat_to_buffer (si=si@entry=0x7a1a50, uri=0x751a60) at src/stats.c:2570
#4 0x000000000046ffe7 in http_stats_io_handler (appctx=0x7a1e00) at src/stats.c:3072
#5 0x00000000004dc208 in applet_run_active () at src/applet.c:66
#6 0x000000000040a3d8 in run_poll_loop () at src/haproxy.c:2194
#7 main (argc=<optimized out>, argv=0x7fffffffddb8) at src/haproxy.c:2700

0x23 监控相关配置

在配置文件中加入下面的内容,然后启动haproxy,这样haproxy就支持状态监控了.

1
2
3
4
5
6
7
8
listen admin_stats
bind 0.0.0.0:1080
mode http #http的7层模式
option httplog #http日志格式
maxconn 10 #最大连接数
stats refresh 30s #自动刷新时间
stats uri /stats #页面url
stats auth admin:admin #设置监控页面的用户和密码:admin

0x24 浏览器运行效果如下

从图中可以看到系统运行的状态.
haproxy stats

0x3 系统设计

参考haproxy的监控模块,设计RssStreamer的监控模块如下图所示.
http service基于st对外提供http服务,st是基于epoll来实现的, 接受浏览器的请求,建立和浏览器之间的tcp连接,state monitor负责收集系统各个模块的运行状态,html generator根据运行状态生成对应的html格式数据,通过tcp把html数据发送给浏览器,浏览器接收到数据以后显示RsStreamer的运行状态.
rsstreamer stats architecture

analysis of rtsp

发表于 2016-07-16 | 阅读次数

0x01 RTSP和RTMP简介

RTSP协议是流媒体协议,通过在播放器和服务端交互建立连接以后,服务器在不同的通道通过UDP把音频/视频数据发送给播放器。
RTSP协议传输的数据格式一般为TS,MP4。因为音视频是分别通过不同的UDP通道传输的,所以在服务端需要把ts/mp4文件做demux,然后再传输。我们可以采用live555,vlc,ffmpeg等作为rtsp服务器。

而RTMP协议则是通过一个TCP通道把音视频数据从服务端发送到播放器,RTMP协议传输的一般为flv格式数据。开源的RTMP服务器有red5,nginx-rtmp-module,srs。red5是java实现的服务器,nginx-rtmp-module是基于web服务器nginx开发的rtmp流媒体服务器,而srs的实现基于st(state-threads)的单线程多协程服务器。

0x02 RTSP交互过程

RTSP服务端和RTSP客户端的交互过程如下。
C表示RTSP客户端,S表示RTSP服务端

0x21 OPTION

C->S: OPTION request //C询问S有哪些方法可用
S->C: OPTION response //S回应提供的所有可用方法

0x22 DESCRIBE

C->S: DESCRIBE request //C要求得到S提供的媒体初始化描述信息
S->C: DESCRIBE response //S回应媒体初始化描述信息(SDP)

0x23 SETUP

C->S: SETUP request //C请求S建立会话
S->C: SETUP response //S建立会话,返回会话相关信息

0x24 PLAY

C->S: PLAY request //C请求播放
S->C: PLAY response //S回应请求
S->C: //RTP发送流媒体数据

0x25 TEARDOWN

C->S: TEARDOWN request //C请求关闭会话
S->C: TEARDOWN response //S回应请求

0x03 抓包分析

RTSP服务端和客户端的整体交互过程如下。
rtsp_sequence

0x31 OPTION

客户端询问服务器有哪些方法可用。
options_c_s
服务器返回可以使用的方法。
DESCRIBE,SETUP,TEARDOWN,PLAY,PAUSE,GET_PARAMETER\r\n
options_s_c

0x32 DESCRIBE

客户端请求服务端发送SDP,服务端发送SDP给客户端。
如下所示,SDP中包括H264的sps和pps信息。
describe

0x33 SETUP

客户端请求建立Audio和Video的UDP连接。

下面的Request请求建立Video的UDP连接。
Request: SETUP rtsp://192.168.40.1:8554/1/trackID=0 RTSP/1.0\r\n

下面的Request请求建立Audio的UDP连接。
Request: SETUP rtsp://192.168.40.1:8554/1/trackID=1 RTSP/1.0\r\n

setup

0x34 PLAY

客户端请求服务端开始提供流媒体服务,开始发送音视频数据到客户端。
如下所示,Payload type为DynamicRTP-Type-96 (96)的RTP包发送的是Video数据,Payload type为MPEG-I/II Audio (14)的RTP包发送的是Audio数据。
play

0x35 RTCP包

在开始传输音视频RTP包以后,RTCP用来提供客户端和服务端之间的传输反馈。服务端发送给客户端的RTCP包是Sender Report,客户端发送给服务端的RTCP包是Receiver Report。
rtcp

1…45
Kevin Wen

Kevin Wen

45 日志
21 标签
© 2022 Kevin Wen
访问量 访问人数