4、SRS4.0源代码分析之RTMP推流处理

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

目标:

    本章我们将分析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协议,推拉流过程中:

  1. 首先,发送的总是是MetaData报文
  2. 其次,是视频第一帧AVC sequence header和音频第一帧AAC sequence header
  3. 最后,是普通的音视频数据帧
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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值