srs源码解析3【rtmp播放】

目录

前言:

一、主要流程

二、详细介绍

三、总结


前言:

目前直播领域业界流行的协议主要有三个:rtmp、http-flv、hls。其中,http-flv、hls是终端播放(称为下行)经常使用的协议,rtmp通常作为推流协议(称为上行)使用。srs主要有两种模式:回源模式 和 推流模式,笔者由于项目中主要涉及到的是回源模式,所以本文重点分析一下srs中从收到rtmp播放请求到被动去使用rtmp协议向上回源的整个流程。

一、主要流程

建立rtmp连接---获取或创建SrsSource---创建consumer-------循环获取consumer中的msg并按rtmp协议发送(同时处理客户端信令)
                                                                                           |                                |---独立的微线程接收客户端发送的rtmp信令

                                                                                           |---触发SrsSource中的ingester回源

二、详细介绍

      从rtmp建连说起,首先来看SrsConnection和她的子类。srs中每个客户端的连接都会对应一个SrsConnection对象,但是SrsConnection是一个虚类,类似于一个接口。具体的连接对象实际上是SrsConnection的子类对象:

     SrsRtmpConn(rtmp请求)

     SrsHttpApi(api请求)

     SrsResponseOnlyHttpConn(http-flv和hls请求)

    srs收到rtmp播放请求,会生成一个SrsRtmpConn对象,并创建一个微线程去独立处理这个请求,微线程会走到SrsConnection::cycle():

int SrsConnection::cycle()
{
    ...
    
    ret = do_cycle();
    
    ...
    
}

          cycle()主要调用了SrsRtmpConn的 do_cycle(),这个函数主要完成rtmp连接和connect命令的处理,然后把将后续处理交给SrsRtmpConn::service_cycle()去处理。另外,当service_cycle()返回,也就是播放停止时,会根据配置需要进行停播回调通知。

int SrsRtmpConn::do_cycle()
{
    int ret = ERROR_SUCCESS;
    
    srs_trace("RTMP client ip=%s", ip.c_str());
    
    //设置tcp连接的发送和接收超时
    rtmp->set_recv_timeout(SRS_CONSTS_RTMP_RECV_TIMEOUT_US);
    rtmp->set_send_timeout(SRS_CONSTS_RTMP_SEND_TIMEOUT_US);
    
    // 建立rtmp连接,支持简单连接和复杂连接,一般使用的是复杂连接
    if ((ret = rtmp->handshake()) != ERROR_SUCCESS) {
        srs_error("rtmp handshake failed. ret=%d", ret);
        return ret;
    }
    srs_verbose("rtmp handshake success");
    
    // 接收客户端的connect命令,主要获取tcUrl
    if ((ret = rtmp->connect_app(req)) != ERROR_SUCCESS) {
        srs_error("rtmp connect vhost/app failed. ret=%d", ret);
        return ret;
    }
    srs_verbose("rtmp connect app success");
    
    // set client ip to request.
    req->ip = ip;
    
    srs_trace("connect app, "
        "tcUrl=%s, pageUrl=%s, swfUrl=%s, schema=%s, vhost=%s, port=%s, app=%s, args=%s", 
        req->tcUrl.c_str(), req->pageUrl.c_str(), req->swfUrl.c_str(), 
        req->schema.c_str(), req->vhost.c_str(), req->port.c_str(),
        req->app.c_str(), (req->args? "(obj)":"null"));
    
    // 获取connect命令带的参数
    if(req->args) {
        std::string srs_version;
        std::string srs_server_ip;
        int srs_pid = 0;
        int srs_id = 0;
        
        SrsAmf0Any* prop = NULL;
        if ((prop = req->args->ensure_property_string("srs_version")) != NULL) {
            srs_version = prop->to_str();
        }
        if ((prop = req->args->ensure_property_string("srs_server_ip")) != NULL) {
            srs_server_ip = prop->to_str();
        }
        if ((prop = req->args->ensure_property_number("srs_pid")) != NULL) {
            srs_pid = (int)prop->to_number();
        }
        if ((prop = req->args->ensure_property_number("srs_id")) != NULL) {
            srs_id = (int)prop->to_number();
        }
        
        srs_info("edge-srs ip=%s, version=%s, pid=%d, id=%d", 
            srs_server_ip.c_str(), srs_version.c_str(), srs_pid, srs_id);
        if (srs_pid > 0) {
            srs_trace("edge-srs ip=%s, version=%s, pid=%d, id=%d", 
                srs_server_ip.c_str(), srs_version.c_str(), srs_pid, srs_id);
        }
    }
    // 后续操作
    ret = service_cycle();
    // 如果配置文件中有回调配置,这里会通过post方式对给配置中的每个url发送播放停止消息
    http_hooks_on_close();

    return ret;
}

接下来SrsRtmpConn::service_cycle(),主要完成rtmp连接阶段的命令交互:1、发送确认窗口大小 2、发送设置带宽 3、接收客户端的确认窗口 4、根据配置设置chunk大小 5、发送命令消息中的“结果”(_result),通知客户端连接的状态

int SrsRtmpConn::service_cycle()
{    
    ...
    rtmp->set_window_ack_size((int)(2.5 * 1000 * 1000)))
    ...
    rtmp->set_peer_bandwidth((int)(2.5 * 1000 * 1000), 2))
    ...
    bandwidth->bandwidth_check(rtmp, skt, req, local_ip);
    ...
    rtmp->set_chunk_size(chunk_size))
    ...
    rtmp->response_connect_app(req, local_ip.c_str()))
    ...
    rtmp->on_bw_done()
    ...
    while (!disposed) {
        ret = stream_service_cycle();
        ...
    }
    return ret;
}

         接下来建立网络流和播放就都交给stream_service_cycle()来处理了,主要做的事情包括:

        1)建立网络流

        2)从tcUrl中解析出req的各个属性,比如host、app、stream等 

        3)获取SrsSource。在srs中,每个流都对应一个SrsSource,她是这条流相关的所有角色的纽带,比如(回源模式):回源服务play_edge、缓存gop_cache、消费者consumers(包换SrsConnection对应客户播放连接)等,都可以通过SrsSource来获取

        4)服务器发送命令消息中的“响应状态”,告知客户端“播放”命令执行成功

        5)调用playing(source)触发回源

int SrsRtmpConn::stream_service_cycle()
{
    ...
    rtmp->identify_client(res->stream_id, type, req->stream, req->duration))
    srs_discovery_tc_url(req->tcUrl, req->schema, req->host, req->vhost, req->app, req->stream, req->port, req->param);
    ...    
    SrsSource* source = NULL;
    if ((ret = SrsSource::fetch_or_create(req, server, &source)) != ERROR_SUCCESS) {
        return ret;
    }
    ...
    bool vhost_is_edge = _srs_config->get_vhost_is_edge(req->vhost);
    bool enabled_cache = _srs_config->get_gop_cache(req->vhost);
    ...
    source->set_cache(enabled_cache);
    ...
    switch (type) {
        case SrsRtmpConnPlay: {
            ...
            rtmp->start_play(res->stream_id))
            http_hooks_on_play()
            ...
            ret = playing(source);
            http_hooks_on_stop();
            ...
        }
        case SrsRtmpConnFMLEPublish: {
            ...
            rtmp->start_fmle_publish(res->stream_id))
            ...            
            return publishing(source);
        }
        case SrsRtmpConnHaivisionPublish: {
            ...
            rtmp->start_haivision_publish(res->stream_id))
            ...
            return publishing(source);
        }
        case SrsRtmpConnFlashPublish: {
            rtmp->start_flash_publish(res->stream_id))
            ...
            return publishing(source);
        }
        default: {
            ret = ERROR_SYSTEM_CLIENT_INVALID;
            ...
            return ret;
        }
    }
    return ret;
}

      接着分析playing(source),看看对于rtmp播放请求,srs是怎么继续处理的。

int SrsRtmpConn::playing(SrsSource* source)
{
    ...
    SrsConsumer* consumer = NULL;
    source->create_consumer(this, consumer))
    ...
    SrsQueueRecvThread trd(consumer, rtmp, SRS_PERF_MW_SLEEP);
    
    // start isolate recv thread.
    if ((ret = trd.start()) != ERROR_SUCCESS) {
        return ret;
    }
    
    wakable = consumer;
    ret = do_playing(source, consumer, &trd);
    wakable = NULL;  
    trd.stop();
    ...
}

      可以看到,这个函数主要做了三件事:

     1)创建consumer,创建的同时,如果这条流对应的SrsSource已经在回源了,她会有meatadata、音频元信息、视频头元信息,可能还有有缓存的gop数据,所以在创建consumer的时候,会将SrsSource中的这些数据直接发送给客户端

     2)创建一个接收微线程,我们知道客户端在播放rtmp的过程中是可以给服务端发送信令的,所以我们需要一个接受微线程去接受这些信令

     3)最后一件事是playing(source)最重要的任务,就是触发回源。调用过程如下:

     source->create_consumer(this, consumer)

                   |----play_edge->on_client_play()

                                             |----ingester->start() // 当回源器是就绪状态的时候会调用

                                                               |----SrsEdgeIngester::cycle()

     从上面的调用可以看到,创建consumer的时候,如果SrsSource对应的ingester的状态是就绪态,就会触发ingester启动独立的微线程去回源。独立的危险程究竟是如何回源的呢?让我们继续看SrsEdgeIngester::cycle(),这里实际上主要是作为一个rtmp客户端,去向上回源请求播放rtmp,这里的client实际上是一个rtmp客户端(注:如果我们想改造为支持多种协议回源,可以根据不同的req请求去初始化不同的类型的client),SrsEdgeIngester::cycle()只完成了作为rtmp客户端去向上回源时的连接和前期的信令交互,然后真正的回源交给了SrsEdgeIngester::ingest()去完成

int SrsEdgeIngester::cycle()
{
    ...
    _source->on_source_id_changed(_srs_context->get_id());  
    ...
    connect_server(ep_server, ep_port))
    ...
    client->set_recv_timeout(SRS_CONSTS_RTMP_RECV_TIMEOUT_US);
    client->set_send_timeout(SRS_CONSTS_RTMP_SEND_TIMEOUT_US);

    SrsRequest* req = _req;
    
    client->handshake()
    connect_app(ep_server, ep_port))
    client->create_stream(stream_id))
    client->play(req->stream, stream_id))
    _edge->on_ingest_play()
    
    ret = ingest();
    ...
}

      下面来看看真正持续回源的循环体SrsEdgeIngester::ingest(),他是真正接收rtmp的msg的地方。前面说过回源是在一个独立的微线程里面来完成的,该微线程会循环向上读取msg,读取到msg就会通过process_publish_message(msg)交给SrsSource来分发到所有对应的consumer的发送队列中去。另外,client->recv_msg返回的是按rtmp协议从chunks中解析出来的msg,具体解析需要到SrsRtmpClient中去看

int SrsEdgeIngester::ingest()
{
    ...
    client->set_recv_timeout(SRS_EDGE_INGESTER_TIMEOUT_US);
    ...
    while (!pthread->interrupted()) {
        ...
        SrsCommonMessage* msg = NULL;
        client->recv_message(&msg))
        ...
        SrsAutoFree(SrsCommonMessage, msg);
        process_publish_message(msg))
    }
    ...
}

      刚才分析的playing(source),重点分析了里面会通过source->create_consumer(this, consumer)去触发一个独立的微线程去向上回源。但是播放连接的处理线程会继续调用do_playing(source, consumer, &trd)去将音视频数据下发给客户端,下面是do_playing的代码解析,可以看到,默认情况下,播放连接的处理微线程默认情况下会每隔mw_sleep毫秒从其对应的consumer的发送队列中读取新的msg(这些msg是回源微线程经过SrsSource写入到该队列中的),并发送给客户端。另外,在每次循环中也会查看有没有收到客户端发送的信令,主要会处理close和pause命令。

int SrsRtmpConn::do_playing(SrsSource* source, SrsConsumer* consumer, SrsQueueRecvThread* trd)
{
    ...
    SrsMessageArray msgs(SRS_PERF_MW_MSGS);
    ...    
    while (!disposed) {
        if (expired) {
            return ret;
        }
        while (!trd->empty()) {
            SrsCommonMessage* msg = trd->pump();
            if ((ret = process_play_control_msg(consumer, msg)) != ERROR_SUCCESS) {
                return ret;
            }
        }        
        if ((ret = trd->error_code()) != ERROR_SUCCESS) {
            return ret;
        }
        mer->wait(SRS_PERF_MW_MIN_MSGS, mw_sleep);
        }
        ...
        consumer->dump_packets(&msgs, count))
        ...        
        if (count <= 0) {
            st_usleep(mw_sleep * 1000);
            continue;
        }
        ...
        rtmp->send_and_free_messages(msgs.msgs, count, res->stream_id))
        ...
        if (send_min_interval > 0) {
            st_usleep((int64_t)(send_min_interval * 1000));
        }
    }    
    return ret;
}

        到这里,rtmp播放的整条链路就已经串联起来了!!!

三、总结

       目前原生srs的回源模式,只支持rtmp协议的回源方式。如果需要支持多种协议,可以通过提供不同的ingester子类型来实现。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值