在本章将浏览ffserver的源代码,理解其设计的思路。重点研究ffserver对rtp rtcp的支持,研究ffserver管理多个连接的方法。
为使用rtsp管理多播,进行rtp rtcp的流媒体传输做准备。
在研究ffserver源码之前,我们需要理解ffserver的配置文件ffserver.conf。在ffserver.conf中透露了管理ffserver的蛛丝马迹。
ffmpeg\tests\目录下的ffserver.conf
MaxBandwidth指每个连接的最大带宽
Feed和Stream配置了该ffserver的输入和ffserver的输出。Feed是一个ffserver获得流的地方。可以是从一个ffmpeg的encoder或者另一个ffserver或
者是一个编码好的文件。每个Feed中包含一个video和/或一个audio。
定义每个输出的流。流的格式 帧率 来源 GOP 等。
现在分析ffserver.c
1. main()
首先解析了配置文件,打开指定的文件流
然后创建子进程,并在子进程中执行http_server
2. http_server
a.打开ffserver的监听端口
b.打开rtsp的监听端口
c start_multicast
.为各个需要多播的流创建相应的multicast ip 和 multicast port。
并初始到各个multicast组的rtp连接的context。
所谓初始相应的rtp连接的Context是指 分配HttpContext
在HttpContext中指明相应连接的protocol state sessionId。
//这里注意当HttpContext中使用的是本地avi文件,它的域是怎样初始化的
C10. av_open_input_file 打开相应的输入文件
在av_open_input_file中,对相应的输入文件的格式进行Probe。并将Probe的结果,用以初始化HttpContext中的AVInputFormat。
当打开一个本地文件时,常常是根据后缀名进行Probe的。可以看到,在av_open_input_file中将AVProbeData中的buf域初始化为空。
在进行Probe时,先通过执行各个AVInputFormat中的read_probe函数,若没法进行read_probe,则采用后缀名的比较方法。
AVProbeData中的buf域何时会被初始化呢?
在跟踪时发现,av_read_packet中有对AVProbeData中的buf进行初始化的代码。
在av_open_input_file中,先没有对AVProbeData做任何初始化就开始Probe。意味着仅仅通过filename的后缀名进行Probe。如果通过后缀名Probe失败,在av_open_input_file中先url_fopen然后再次Probe。
Probe完成后调用av_open_input_stream(),使用Probe到的AVInputFormat打开相应的文件流
在av_open_input_stream中调用相应的AVInputFormat的read_header()获得特定文件的基础信息,放在AVFormatContext的pri_data中,给后面解码时使用。
也就是说,在av_open_input_file中完成了对AVFormatContext的初始化和AVInputFormat的初始化。AVFormatContext中已经包括了打开文件的头部信息,供解码时使用。而AVInputFormat则在Probe时完成了初始化。
rtp_new_av_stream()
在其中分配一个rtp连接所需的上下文。分配一个RtpContext并赋给URLContext。其中包括该rtp连接使用的两个端口 ttl等信息。HttpContext中的rtp_handles[]即在此初始。每个rtp stream对应一个rtp_handle。该rtp_handle也就是URLContext中。
对HttpContext中的每个Stream,每个Stream中的更小的stream分配管理该小stream的rtp_handle。
并加入到first_http_ctx中。设置HttpContext时,如果是rtsp端口的连接,设为RTSPSTATE_WAIT_REQUEST;如果是serverfd的连接,设为HTTPSTATE_WAIT_REQUEST
e.处于WAIT_REQUEST状态下的HttpContext使用相应的poll_entry来进行管理。
然后依次对每个HttpContext进行handle_connection。
handle_connection()根据每个HttpContext的State以及相应的poll_entry进行处理。
注意:实际的状态变迁数据读取都是在handle_connection中完成的。
在进行rtsp的SETUP时,调用rtp_new_connection创建了rtp连接的HttpContext。且注意rtp连接的HttpContext的初始状态为HTTPSTATE_READY。并将该HttpContext加入到first_http_ctx的链表当中。
重点理解rtsp是怎样管理多个rtp的connection。
最不能理解的是在rtsp的wiki上,While HTTP is stateless, RTSP is a stateful protocol. A session identifier is used to keep track of sessions when needed; thus, no permanent TCP connection is required。是不是意味着rtsp的tcp连接能随时断开?断开后再通过服务端的session_id进行恢复?
g.在执行完rtsp_parse_request之后,该HttpContext状态变成了RTSPSTATE_SEND_REPLY。
这里可能需要注意,在rtsp的监听端口上,当来了一个新的客户端连接时,则新建一个针对该连接的HTTPContext。这样每个客户端由一个RTSP的HTTPContext控制状态。它控制多个RTP流?还是说只控制一个RTP流?像AVI文件的传输,有video流audio流和text流。它们是使用一个RTSP控制还是三个RTSP控制?显然一个AVI文件三个流是相关的,如果要快进,三个流都要快进;如果要暂停,三个流都要暂停。所以使用一个RTSP连接管理三个RTP流。只进行一次SETUP,但建立三个RTP connection?????
这个问题现在相当不清楚。。。。。。
好像是这样的,在ffmpeg的rtsp_cmd_setup中,貌似是
每个流进行了一次SETUP。也就是说,如果是个avi文件,需要进行三次SETUP,分别建立音频流视频流和字幕流。再第一次SETUP时分配相应的RTP的HTTPContext,以后两个SETUP,共用已经建立的该HTTPContext。在rtsp_cmd_setup中,在第一次SETUP命令时,执行rtp_new_connection(),以后两次执行rtp_new_av_stream()。
执行完相应的rtsp_cmd_xxx之后,通过url_close_dyn_buf完成了解析后产生的应答的传送。
在某个rtsp连接,遇到第一个SETUP命令时,执行了rtp_new_connection(),然后再rtp_new_av_stream。以后的几次SETUP,执行的都是rtp_new_av_stream。也就是说,假如有一个avi文件,涉及audio video text三个流,那么仅通过一个HttpContext来处理。虽然有三个rtp传
输(audio video text使用三个不同的rtp流进行传输,同时还要有三个不同的rtcp流)。
在rtp_new_connection中,将该HttpContext的session_id设置成由rtsp的第一次SETUP时产生的session_id。状态设置成HTTPSTATE_READY,并加入由first_http_ctx引导
的链表中。
在rtp_new_av_stream中,为相应的流建立了URLContext,并将该URLContext赋给该connection(对应一个session)的rtp_handles(参见ffserver 3215行)。同时为相应的rtp流建立一个AVFormatContext,同时将该AVFormatContext赋给该HttpContext的rtp_ctx数组
这里提一下两个函数:url_open_dyn_packet_buf()和url_close_dyn_buf()
url_open_dyn_packet_buf()用来创建相应的ByteIOContext,并初始化其中的read write函数,和缓冲区。ByteIOContext的write,不过是将一个缓冲区中的内容写到自己的缓冲区中。在url_close_dyn_buf中,执行了put_flush_packet,在put_flush_packet中完成了从ByteIOContext的buffer将数据copy到ByteIOContext的opaque中。
一个rtsp连接可以管理多个session。每个session包括一组相关的流的传输。同时每个session有一个自己的HttpContext(除了被这个大的rtsp的HttpContext管理外),在该HttpContext中包含了自己的session_id。
对于每个play的命令,rtsp连接先在所有的HttpContext中查找到相应session的HttpContext,设置该HttpContext的状态为HTTPSTATE_SEND_DATA。然后返回确认信息。
在http_server()检查是否有新的连接,并更新连接的状态。同时,对每个连接进行处理,调用handle_connection()。当某个连接的状态是HTTPSTATE_SEND_DATA时,在handle_connection()函数中,通过执行http_send_data(),完成数据的发送
http_send_data()中先调用http_prepare_data,从文件中准备数据。这里牵涉到从一个文件中读出三个流的问题。
从文件中将流读出来,并赋给了一个AVPacket。现在重点研究该AVPacket的结构,用它来理解发送三个流(video audio text)并同步的方法。
在http_prepare_data中,先通过av_read_frame()从该session HttpContext对应的输入中读取一帧,见Ffserver.c 2136行。根据该帧对应的流,将该帧写到相应的流的AVFormatContext中。相应的流的AVFormatContext调用该流对应的AVOutputFormat的write_packet方法。这里是rtp_muxer的rtp_write_packet方法。将数据从一个缓冲区写到另一个缓冲区。
通过http_prepare_data()将要发送的数据放到该session的HttpContext的缓冲区中。涉及到从相应的文件中读取一帧并将该帧放到正确的流的缓冲区中。
然后根据发送时间确认是否要发送。get_packet_send_cloc
k()-get_server_clock()。如果时间未到,不发送
最后通过url_write()使用相应的流的URLContext将该session的HTTPContext的缓冲区中的数据发送出去。
一个需要注意的地方,在rtp_protocol的rtp_write中,先分析了存储在缓冲区中的数据,确认类型是rtp的还是rtcp的,从URLContext中的priv_data获得RTPContext。获得相应的rtp_hd或rtcp_hd。再通过url_write()操作缓冲区的内容。由于rtp_hd和rtcp_hd这两个URLContext的URLProtocol都被初始化为UDPProtocol,这里即采用UDPProtocol发送缓冲区中的内容。
j.现在再去看http_prepare_data部分。研究管理该session的HttpContext怎样读取文件中的多个流,怎样与rtcp的sr和rr交互,怎样控制传输速率,怎样用rtp重新打包读出的文件中的帧。
在http_prepare_data中,先通过av_read_frame()读取相应session的HttpContext对应的输入文件,读入一个AVPacket,即av_read_frame(c->fmt_in,&pkt)。
而实际的控制操作,与rtp rtcp交互,是通过av_write_frame()将读取到的AVPacket写入对应的stream的AVFormatContext的缓冲区中。
在av_write_frame中调用相应AVFormatContext的AVOutputFormat的write_packet方法。这里每个流的AVFormatContext的AVOutputFormat在rtsp_cmd_setup时调用new_av_stream()中被初始化为rtp_muxer,所以在执行rtp_muxer的write_packet即rtp_write_packet()。
rtp_write_packet中,将一个AVPacket写到相应的AVFormatContext的缓冲区中。
在这里使用到了AVFormatContext中的priv_data。将该值赋给了RTPMuxContext。
而AVFormatContext中的priv_data是在rtp_new_av_stream中,见ffserver.c的3236,通过av_set_parameters分配的。在av_set_parameters中通过检查该AVFormatContext的AVOutputContext的priv_data_size来决定是否需要在AVFormatContext中分配priv_data部分。也就是说,AVOutputContext的MuxContext是存放在相应的AVFormatContext的priv_data部分的。
rtp_write_packet中可以发现,rtp数据包的统计由RTPMuxContext完成。
在这里同时发送了发送报告,通过rtcp_send_sr()。(并非立即发送,而是将sr写入到相应的AVFormatContext的缓冲区中)
同时,rtp流每个数据包的包装,加入ssrc的头部和时间 载荷标志都是通过RTPMuxContext完成的。因为这些信息都存储在RTPMuxContext中。
k.上面有一点需要注意,因为是将该写入相应的AVFormatContext的缓冲区中,数据信息和sr都放在一个缓冲区中,在发送时根据载荷类型,使用该AVFormatContext的URLContext中的URLProtocol中的priv_data。该priv_data中有两个URLContext,分别是hd和hdc,选择相应的hd hdc发送。
L.再次考虑是怎样读取client端的rr,并通过该rr来控制发送包的速度。
ff
mpeg是一个开
源
的音视频处理的开发套件,它包括几个非常实用的命令行工具,
ff
mpeg、
ff
probe、
ff
server
和
ff
play。本文实现的是
ff
mpeg +
ff
server
来搭建基于 http 的视频点播系统。C/C++程序员进阶方向,音视频流媒体高级开发学习路线:音视频基础-
FF
mpeg实战-流媒体客户端-流媒体服务器-WebRTC开发-Android NDK开发_哔哩哔哩_bilibiliwww.bilibili.com/video/BV1ka411P7HS正在上传…重新上传取消。
《1》、输入
源
能将音/视频传给
ff
server
的外部应用,通常是
ff
mpeg应用。输入
源
会和
ff
server
连接然后将自己绑定到一个或多个feed上。(同一时刻一个feed只能绑定一个输入
源
)。一个输入
源
能绑到多个f...
搭建流媒体服务器的方法有多种,前面介绍过在windows环境下使用VLC搭建流媒体服务(参见:Win10环境:使用VLC搭建
RTSP
服务器)。今天是第二篇,使用
ff
server
,下面是
ff
server
官网关于
ff
server
的介绍,详细见:
ff
server
document
ff
server
is a
stream
ing
server
for both
audio
and video. I...