目标:
本章我们将分析SRS4.0 RTMP服务模块与推流相关的代码处理逻辑。

内容:
根据上节内容可知,SRS4.0针对RTMP推流客户端的处理逻辑,主要在协程SrsRtmpConn::stream_service_cycle()中通过调用SrsRtmpConn::publishing()函数进行处理。(为了方便理解,下面函数使用了简化后的伪代码,但不影响理解函数的主流程)
1、检测是否可以向当前的source对象推流,如果source的状态是已经有人在推流了,则acquire_publish函数返回失败。
2、创建推流端接收协程SrsPublishRecvThread,这个协程用于实际接收客户端向服务器发送的推流数据。
3、SrsRtmpConn协程将在do_publishing函数内部循环处理,主要工作是统计接收的数据,监控上述接收协程。
srs_error_t SrsRtmpConn::publishing(SrsLiveSource* source)
{
http_hooks_on_publish(); // 此回调函数向外通知客户端推流开始
// 检测是否可以向当前的source对象推流,如果source的状态是已经有人在推流了,则acquire_publish函数返回失败
if ((err = acquire_publish(source)) == srs_success)
{
// 创建推流端接收协程,这个协程用于实际接收客户端向服务器发送的推流数据
SrsPublishRecvThread rtrd(rtmp, req, srs_netfd_fileno(stfd), 0, this, source, );
// SrsRtmpConn协程将在do_publishing函数内部循环处理,
// 同时do_publishing函数内部还会启动SrsPublishRecvThread协程
err = do_publishing(source, &rtrd);
rtrd.stop(); // 执行到这里,表示SrsRtmpConn协程退出循环,这里是主动结束之前创建的接收协程
}
release_publish(source); // 推流结束,释放source中的推流信息
http_hooks_on_unpublish(); // 此回调函数向外通知客户端推流结束
}
SrsRtmpConn协程在推流端的处理,主要工作是统计接收的数据,并检测如果没有接收到新数据,则结束协程
srs_error_t SrsRtmpConn::do_publishing(SrsLiveSource* source, SrsPublishRecvThread* rtrd)
{
rtrd->start(); // 启动接收协程
while (true) {
// conn协程好像主要做统计工作,以及判断接收数据超时后退出do_publishing
if ((err = trd->pull()) != srs_success) // 协程运行出错,则结束协程
return srs_error_wrap();
if (nb_msgs == 0) {
rtrd->wait(20秒); // 如果是第一次接收报文,conn协程在此阻塞20秒
}
else {
rtrd->wait(5秒); // 如果是普通接收报文,conn协程在此阻塞5秒
}
if ((rtrd->error_code()) != srs_success)
return srs_error_wrap(); // 如果接收协程错误则退出循环,结束协程
if (rtrd->nb_msgs() <= nb_msgs)
return srs_error_wrap(); // 超时检测发现没有接收到新数据则退出循环,结束协程
nb_msgs = rtrd->nb_msgs(); // 更新数据,此变量是int64,不用担心溢出翻转
stat->on_video_frames(); // 通过单件类SrsStatistic,刷新每条流的接收视频帧计数
}
}
推流端接收协程的创建和处理逻辑:
在创建SrsPublishRecvThread对象时,它的构造函数会同时创建一个SrsRecvThread协程对象,所以,真正的推流端接收协程是SrsRecvThread::do_cycle()
SrsPublishRecvThread::SrsPublishRecvThread()
: trd(this, rtmp_sdk, tm, parent_cid)
{
}
srs_error_t SrsRecvThread::cycle() {
rtmp->set_recv_timeout(SRS_UTIME_NO_TIMEOUT); // 设置为阻塞式读取
do_cycle(); //
rtmp->set_recv_timeout(timeout); // 读取连接结束,恢复默认timeout
}
srs_error_t SrsRecvThread::do_cycle()
{
rtmp->recv_message(&msg); // 此函数用于得到一个完整RTMP Message消息,即一个完整的音频帧或视频帧
// 到此我们大概知道,具体的RTMP协议都封装在SrsRtmpServer类中
// 对于初学者,可以先不关心SrsRtmpServer类的实现细节
pumper->consume(msg); // 对于推流端,这里的pumper对象类型为SrsPublishRecvThread
// 对于拉流端,这里的pumper对象类型为SrsQueueRecvThread
}
// 接收推流端数据并处理
srs_error_t SrsPublishRecvThread::consume(SrsCommonMessage* msg)
{
_conn->handle_publish_message(_source, msg); // 最终,SrsRtmpConn接收推流报文
if (++nn_msgs_for_yield_ >= 15) {
nn_msgs_for_yield_ = 0;
srs_thread_yield(); // 连续接收15帧数据后,当前协程主动让出CPU
}
}
处理一个完整的RTMP报文
srs_error_t SrsRtmpConn::handle_publish_message()
{
// 这里是接收AMF编码的RTMP控制命令,实际上好像意义不大,更多可能是防错处理
if (msg->header.is_amf0_command() || msg->header.is_amf3_command())
{
rtmp->decode_message(msg, &pkt);
if (dynamic_cast<SrsFMLEStartPacket*>(pkt)) {
SrsFMLEStartPacket* unpublish = dynamic_cast<SrsFMLEStartPacket*>(pkt);
rtmp->fmle_unpublish(); // for fmle, drop others except the fmle start packet.
}
return err;
}
// 处理RTMP相关的video, audio, MetaData(元数据)报文,
// 首先处理的是MetaData报文
// 其次是视频第一帧AVC sequence header和音频第一帧AAC sequence header
// 最后是普通的音视频数据帧
process_publish_message(source, msg); // video, audio, data message
}
按照RTMP协议,推拉流过程中:
- 首先,发送的总是是MetaData报文
- 其次,是视频第一帧AVC sequence header和音频第一帧AAC sequence header
- 最后,是普通的音视频数据帧
srs_error_t SrsRtmpConn::process_publish_message()
{
if (info->edge) {
// 如果sever工作在edge模式,则调用此接口向源站转发报文,然后直接返回
// 此时可以看到,edge模式下,SRS属于纯转发,所以不会在本地做HLS(ts、m3u8文件)处理
// 同样,对于edge模式下的,推流处理逻辑主要在SrsPublishEdge和SrsEdgeForwarder类实现
// 对于初学者,可暂时不关注这两个类,只要知道SRS对edge模式的设计规格是:
// 推流到edge上时,edge会直接将流转发给源站;播放edge上的流时,edge会回源拉流。
return source->on_edge_proxy_publish(msg);
}
// 非edge模式,报文分类传入source对象
if (msg->header.is_audio()) {
// 音频报文处理分支
return source->on_audio(msg);
}
if (msg->header.is_video()) {
// 视频报文处理分支
return source->on_video(msg);
}
if

本文详细解析了SRS4.0中RTMP服务模块的推流代码,涉及检测流状态、协程调度、数据接收与处理、关键帧缓存、边缘处理与源站逻辑。通过关键帧处理、多媒体数据流转及队列管理,展示了服务端如何确保流畅的推拉流体验。
最低0.47元/天 解锁文章
1万+

被折叠的 条评论
为什么被折叠?



