SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP信息Publish

来源:互联网 发布:数据营销 编辑:程序博客网 时间:2024/04/29 04:46

前言

       上一节中已经介绍了rtmp流监听,一旦有rtmp请求之后处理,单独为该链接开启一个线程等等,这一节我们来分析一下一个rtmp请求到来之后,除了创建线程之外,还要做哪些工作。同样,只是代码框架分析。

首先对Publish做的工作大概描述一下:
      1、Rtmp协议几乎是从推送端过来的唯一协议(仅限于srs2.0及以下版本),因为Rtmp协议时效性。
      2、建立Rtmp协议链接:握手、建立网络连接、建立流通道、Publish/Play。
      3、接受Rtmp信息:Rtmp协议解析。
      4、成功接受到Rtmp信息处理:缓存、抖动矫正、分发到消费者堆栈(Play消费者,Forward消费者)、进行HLS切片等等。
这一讲内容只讲述RTMP协议Publish过程,SRS源码相关其他总结:
      SRS(simple-rtmp-server)流媒体服务器源码分析--系统启动
      SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP消息play
      SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP信息Publish
      SRS(simple-rtmp-server)流媒体服务器源码分析--HLS切片
Rtmp信息接受框架如下:


 一、进入循环


       接着上一节rtmp链接分析       
int SrsConnection::cycle(){    int ret = ERROR_SUCCESS;        _srs_context->generate_id();    id = _srs_context->get_id();        ip = srs_get_peer_ip(st_netfd_fileno(stfd));srs_trace("ip:%s", ip);        ret = do_cycle();        // if socket io error, set to closed.    if (srs_is_client_gracefully_close(ret)) {        ret = ERROR_SOCKET_CLOSED;    }        // success.    if (ret == ERROR_SUCCESS) {        srs_trace("client finished.");    }        // client close peer.    if (ret == ERROR_SOCKET_CLOSED) {        srs_warn("client disconnect peer. ret=%d", ret);    }    return ERROR_SUCCESS;}
进来之后获取客户端ip,进入do_cycle()函数,这里也要同样注意,在理解纯虚函数上是我的缺点。

二、rtmp协议链接


    RTMP协议是应用层协议,是要靠底层可靠的传输层协议(通常是TCP)来保证信息传输的可靠性的。在基于传输层协议的链接建立完成后,RTMP协议也要客户端和服务器通过“握手”来建立基于传输层链接之上的RTMP Connection链接,在Connection链接上会传输一些控制信息,如SetChunkSize,SetACKWindowSize。其中CreateStream命令会创建一个Stream链接,用于传输具体的音视频数据和控制这些信息传输的命令信息。RTMP协议传输时会对数据做自己的格式化,这种格式的消息我们称之为RTMP Message,而实际传输的时候为了更好地实现多路复用、分包和信息的公平性,发送端会把Message划分为带有Message ID的Chunk,每个Chunk可能是一个单独的Message,也可能是Message的一部分,在接受端会根据chunk中包含的data的长度,messageid和message的长度把chunk还原成完整的Message,从而实现信息的收发。

Rtmp协议规定:

(1)、 握手:固定步骤,RTMP连接都是以握手作为开始。

(2)、 建立连接:用于建立客户端和服务端之间的“网络连接”

(3)、 建立流:用于建立客户端和服务端之间的“网络流”,即多媒体通道。

(4)、 Publish/play:推流或者播放


1、握手 建立网络连接


   有关rtmp协议握手内容,可以参考rtmp协议博客:http://blog.csdn.net/ManagerUser/article/category/6996313

   直接来看建立网络连接:

// TODO: return detail message when error for client.int SrsRtmpConn::do_cycle(){    int ret = ERROR_SUCCESS;        srs_trace("RTMP client ip=%s", ip.c_str());    rtmp->set_recv_timeout(SRS_CONSTS_RTMP_RECV_TIMEOUT_US);    rtmp->set_send_timeout(SRS_CONSTS_RTMP_SEND_TIMEOUT_US);    //1、rtmp握手    if ((ret = rtmp->handshake()) != ERROR_SUCCESS) {        srs_error("rtmp handshake failed. ret=%d", ret);        return ret;    }    srs_verbose("rtmp handshake success");    //2、rtmp建立网络连接    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;        // discovery vhost, resolve the vhost from config    SrsConfDirective* parsed_vhost = _srs_config->get_vhost(req->vhost);    if (parsed_vhost) {        req->vhost = parsed_vhost->arg0();    }        srs_info("discovery app success. schema=%s, vhost=%s, port=%s, app=%s",        req->schema.c_str(), req->vhost.c_str(), req->port.c_str(), req->app.c_str());        if (req->schema.empty() || req->vhost.empty() || req->port.empty() || req->app.empty()) {        ret = ERROR_RTMP_REQ_TCURL;        srs_error("discovery tcUrl failed. "            "tcUrl=%s, schema=%s, vhost=%s, port=%s, app=%s, ret=%d",            req->tcUrl.c_str(), req->schema.c_str(), req->vhost.c_str(), req->port.c_str(), req->app.c_str(), ret);        return ret;    }        // check vhost    if ((ret = check_vhost()) != ERROR_SUCCESS) {        srs_error("check vhost failed. ret=%d", ret);        return ret;    }    srs_verbose("check vhost success.");        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"));        // show client identity    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();        http_hooks_on_close();    return ret;}
 在以上函数中,进行Rtmp握手,rtmp网络连接。建立网络连接之后要做一些确认工作。内容如下:
 进入service_cycle()
int SrsRtmpConn::service_cycle(){        int ret = ERROR_SUCCESS;    //1、服务器接收到连接命令消息后,发送确认窗口大小(Window Acknowledgement Size)协议消息到客户端,同时连接到连接命令中提到的应用程序。    if ((ret = rtmp->set_window_ack_size((int)(2.5 * 1000 * 1000))) != ERROR_SUCCESS) {        srs_error("set window acknowledgement size failed. ret=%d", ret);        return ret;    }    srs_verbose("set window acknowledgement size success");    //2、服务器发送设置带宽()协议消息到客户端。    if ((ret = rtmp->set_peer_bandwidth((int)(2.5 * 1000 * 1000), 2)) != ERROR_SUCCESS) {        srs_error("set peer bandwidth failed. ret=%d", ret);        return ret;    }    srs_verbose("set peer bandwidth success");    // get the ip which client connected.    std::string local_ip = srs_get_local_ip(st_netfd_fileno(stfd));        // do bandwidth test if connect to the vhost which is for bandwidth check.    if (_srs_config->get_bw_check_enabled(req->vhost)) {        return bandwidth->bandwidth_check(rtmp, skt, req, local_ip);    }        // do token traverse before serve it.    // @see https://github.com/ossrs/srs/pull/239    if (true) {        bool vhost_is_edge = _srs_config->get_vhost_is_edge(req->vhost);        bool edge_traverse = _srs_config->get_vhost_edge_token_traverse(req->vhost);        if (vhost_is_edge && edge_traverse) {            if ((ret = check_edge_token_traverse_auth()) != ERROR_SUCCESS) {                srs_warn("token auth failed, ret=%d", ret);                return ret;            }        }    }        // set chunk size to larger.    // set the chunk size before any larger response greater than 128,    // to make OBS happy, @see https://github.com/ossrs/srs/issues/454    int chunk_size = _srs_config->get_chunk_size(req->vhost);    if ((ret = rtmp->set_chunk_size(chunk_size)) != ERROR_SUCCESS) {        srs_error("set chunk_size=%d failed. ret=%d", chunk_size, ret);        return ret;    }    srs_info("set chunk_size=%d success", chunk_size);        // response the client connect ok.    if ((ret = rtmp->response_connect_app(req, local_ip.c_str())) != ERROR_SUCCESS) {        srs_error("response connect app failed. ret=%d", ret);        return ret;    }    srs_verbose("response connect app success");            if ((ret = rtmp->on_bw_done()) != ERROR_SUCCESS) {        srs_error("on_bw_done failed. ret=%d", ret);        return ret;    }    srs_verbose("on_bw_done success");        while (!disposed) {        ret = stream_service_cycle();                // stream service must terminated with error, never success.        // when terminated with success, it's user required to stop.        if (ret == ERROR_SUCCESS) {            continue;        }                // when not system control error, fatal error, return.        if (!srs_is_system_control_error(ret)) {            if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) {                srs_error("stream service cycle failed. ret=%d", ret);            }            return ret;        }                // for republish, continue service        if (ret == ERROR_CONTROL_REPUBLISH) {            // set timeout to a larger value, wait for encoder to republish.            rtmp->set_send_timeout(SRS_REPUBLISH_RECV_TIMEOUT_US);            rtmp->set_recv_timeout(SRS_REPUBLISH_SEND_TIMEOUT_US);                        srs_trace("control message(unpublish) accept, retry stream service.");            continue;        }                // for "some" system control error,         // logical accept and retry stream service.        if (ret == ERROR_CONTROL_RTMP_CLOSE) {            // TODO: FIXME: use ping message to anti-death of socket.            // @see: https://github.com/ossrs/srs/issues/39            // set timeout to a larger value, for user paused.            rtmp->set_recv_timeout(SRS_PAUSED_RECV_TIMEOUT_US);            rtmp->set_send_timeout(SRS_PAUSED_SEND_TIMEOUT_US);                        srs_trace("control message(close) accept, retry stream service.");            continue;        }                // for other system control message, fatal error.        srs_error("control message(%d) reject as error. ret=%d", ret, ret);        return ret;    }        return ret;}
网络连接确认工作完成之后,进入客户身份识别代码中

2、client identify


进入stream_service_cycle()
int SrsRtmpConn::stream_service_cycle(){    int ret = ERROR_SUCCESS;            SrsRtmpConnType type;    if ((ret = rtmp->identify_client(res->stream_id, type, req->stream, req->duration)) != ERROR_SUCCESS) {        if (!srs_is_client_gracefully_close(ret)) {            srs_error("identify client failed. ret=%d", ret);        }        return ret;    }    req->strip();    srs_trace("client identified, type=%s, stream_name=%s, duration=%.2f",         srs_client_type_string(type).c_str(), req->stream.c_str(), req->duration);        // security check    if ((ret = security->check(type, ip, req)) != ERROR_SUCCESS) {        srs_error("security check failed. ret=%d", ret);        return ret;    }    srs_info("security check ok");        // Never allow the empty stream name, for HLS may write to a file with empty name.    // @see https://github.com/ossrs/srs/issues/834    if (req->stream.empty()) {        ret = ERROR_RTMP_STREAM_NAME_EMPTY;        srs_error("RTMP: Empty stream name not allowed, ret=%d", ret);        return ret;    }    // client is identified, set the timeout to service timeout.    rtmp->set_recv_timeout(SRS_CONSTS_RTMP_RECV_TIMEOUT_US);    rtmp->set_send_timeout(SRS_CONSTS_RTMP_SEND_TIMEOUT_US);        // find a source to serve.    SrsSource* source = NULL;    if ((ret = SrsSource::fetch_or_create(req, server, &source)) != ERROR_SUCCESS) {        return ret;    }    srs_assert(source != NULL);        // update the statistic when source disconveried.    SrsStatistic* stat = SrsStatistic::instance();    if ((ret = stat->on_client(_srs_context->get_id(), req, this, type)) != ERROR_SUCCESS) {        srs_error("stat client failed. ret=%d", ret);        return ret;    }    bool vhost_is_edge = _srs_config->get_vhost_is_edge(req->vhost);    bool enabled_cache = _srs_config->get_gop_cache(req->vhost);    srs_trace("source url=%s, ip=%s, cache=%d, is_edge=%d, source_id=%d[%d]",        req->get_stream_url().c_str(), ip.c_str(), enabled_cache, vhost_is_edge,         source->source_id(), source->source_id());    source->set_cache(enabled_cache);        client_type = type;    switch (type) {        case SrsRtmpConnPlay: {            srs_verbose("start to play stream %s.", req->stream.c_str());                        // response connection start play            if ((ret = rtmp->start_play(res->stream_id)) != ERROR_SUCCESS) {                srs_error("start to play stream failed. ret=%d", ret);                return ret;            }            if ((ret = http_hooks_on_play()) != ERROR_SUCCESS) {                srs_error("http hook on_play failed. ret=%d", ret);                return ret;            }                        srs_info("start to play stream %s success", req->stream.c_str());            ret = playing(source);            http_hooks_on_stop();                        return ret;        }        case SrsRtmpConnFMLEPublish: {            srs_verbose("FMLE start to publish stream %s.", req->stream.c_str());                        if ((ret = rtmp->start_fmle_publish(res->stream_id)) != ERROR_SUCCESS) {                srs_error("start to publish stream failed. ret=%d", ret);                return ret;            }                        return publishing(source);        }        case SrsRtmpConnHaivisionPublish: {            srs_verbose("Haivision start to publish stream %s.", req->stream.c_str());                        if ((ret = rtmp->start_haivision_publish(res->stream_id)) != ERROR_SUCCESS) {                srs_error("start to publish stream failed. ret=%d", ret);                return ret;            }                        return publishing(source);        }        case SrsRtmpConnFlashPublish: {            srs_verbose("flash start to publish stream %s.", req->stream.c_str());                        if ((ret = rtmp->start_flash_publish(res->stream_id)) != ERROR_SUCCESS) {                srs_error("flash start to publish stream failed. ret=%d", ret);                return ret;            }                        return publishing(source);        }        default: {            ret = ERROR_SYSTEM_CLIENT_INVALID;            srs_info("invalid client type=%d. ret=%d", type, ret);            return ret;        }    }    return ret;}

       通过客户身份识别identify_client()函数后,Srs就知道是publish还是play,进入相应的分支。首先这里分支有点多,SrsRtmpConnPlay播放rtmp流,SrsRtmpConnFMLEPublish,fmle发布rtmp流,我们接受rtmp流,就是从SrsRtmpConnFMLEPublish分支里面进去。

注意:使用vlc播放器播放从SrsRtmpConnPlay分支进入。


3、进入publish分支


进入publishing();
int SrsRtmpConn::publishing(SrsSource* source){    int ret = ERROR_SUCCESS;    if ((ret = refer->check(req->pageUrl, _srs_config->get_refer_publish(req->vhost))) != ERROR_SUCCESS) {        srs_error("check publish_refer failed. ret=%d", ret);        return ret;    }    srs_verbose("check publish_refer success.");    // http回调,通知vhost SRS发生了publish事件。    if ((ret = http_hooks_on_publish()) != ERROR_SUCCESS) {        srs_error("http hook on_publish failed. ret=%d", ret);        return ret;    }    bool vhost_is_edge = _srs_config->get_vhost_is_edge(req->vhost);    if ((ret = acquire_publish(source, vhost_is_edge)) == ERROR_SUCCESS) {        // use isolate thread to recv,        // @see: https://github.com/ossrs/srs/issues/237        // 创建了一个接受线程对象,这个在后面用,        SrsPublishRecvThread trd(rtmp, req,             st_netfd_fileno(stfd), 0, this, source,            client_type != SrsRtmpConnFlashPublish,            vhost_is_edge);        srs_info("start to publish stream %s success", req->stream.c_str());        ret = do_publishing(source, &trd);        // stop isolate recv thread        trd.stop();    }        // whatever the acquire publish, always release publish.    // when the acquire error in the midlle-way, the publish state changed,    // but failed, so we must cleanup it.    // @see https://github.com/ossrs/srs/issues/474    // @remark when stream is busy, should never release it.    if (ret != ERROR_SYSTEM_STREAM_BUSY) {        release_publish(source, vhost_is_edge);    }    http_hooks_on_unpublish();    return ret;}


三、单独创建rtmp接收线程


进入do_publishing(),等待接受rtmp信息。
int SrsRtmpConn::do_publishing(SrsSource* source, SrsPublishRecvThread* trd){    int ret = ERROR_SUCCESS;        SrsPithyPrint* pprint = SrsPithyPrint::create_rtmp_publish();    SrsAutoFree(SrsPithyPrint, pprint);    // 创建一个独立的接收线程,专门用于Rtmp协议的接受,因此,下一步进入rtmp接受线程至关重要。    // start isolate recv thread.    if ((ret = trd->start()) != ERROR_SUCCESS) {        srs_error("start isolate recv thread failed. ret=%d", ret);        return ret;    }        // change the isolate recv thread context id,    // merge its log to current thread.    int receive_thread_cid = trd->get_cid();    trd->set_cid(_srs_context->get_id());        // initialize the publish timeout.    publish_1stpkt_timeout = _srs_config->get_publish_1stpkt_timeout(req->vhost);    publish_normal_timeout = _srs_config->get_publish_normal_timeout(req->vhost);        // set the sock options.    set_sock_options();        if (true) {        bool mr = _srs_config->get_mr_enabled(req->vhost);        int mr_sleep = _srs_config->get_mr_sleep_ms(req->vhost);        srs_trace("start publish mr=%d/%d, p1stpt=%d, pnt=%d, tcp_nodelay=%d, rtcid=%d",                  mr, mr_sleep, publish_1stpkt_timeout, publish_normal_timeout, tcp_nodelay, receive_thread_cid);    }    int64_t nb_msgs = 0;    uint64_t nb_frames = 0;    while (!disposed) {// 计算时间        pprint->elapse();        // 资源过期        // when source is set to expired, disconnect it.        if (expired) {            ret = ERROR_USER_DISCONNECT;            srs_trace("connection expired. ret=%d", ret);            return ret;        }        // 等待        // cond wait for timeout.        if (nb_msgs == 0) {            // when not got msgs, wait for a larger timeout.            // @see https://github.com/ossrs/srs/issues/441            trd->wait(publish_1stpkt_timeout);        } else {            trd->wait(publish_normal_timeout);        }// 检测该线程出现问题        // check the thread error code.        if ((ret = trd->error_code()) != ERROR_SUCCESS) {            if (!srs_is_system_control_error(ret) && !srs_is_client_gracefully_close(ret)) {                srs_error("recv thread failed. ret=%d", ret);            }            return ret;        }        //上一次接受RTMP包数量如果等于现在接受的,说明本次没有接收到,publish超时        // when not got any messages, timeout.        if (trd->nb_msgs() <= nb_msgs) {            ret = ERROR_SOCKET_TIMEOUT;            srs_trace("publish timeout %dms, nb_msgs=%"PRId64", ret=%d",                nb_msgs? publish_normal_timeout : publish_1stpkt_timeout, nb_msgs, ret);            break;        }        nb_msgs = trd->nb_msgs();                // Update the stat for video fps.        // @remark https://github.com/ossrs/srs/issues/851        SrsStatistic* stat = SrsStatistic::instance();        if ((ret = stat->on_video_frames(req, (int)(trd->nb_video_frames() - nb_frames))) != ERROR_SUCCESS) {            return ret;        }// 获取视频帧数量        nb_frames = trd->nb_video_frames();        // reportable        if (pprint->can_print()) {            kbps->sample();            bool mr = _srs_config->get_mr_enabled(req->vhost);            int mr_sleep = _srs_config->get_mr_sleep_ms(req->vhost);            srs_trace("<- "SRS_CONSTS_LOG_CLIENT_PUBLISH                " time=%"PRId64", okbps=%d,%d,%d, ikbps=%d,%d,%d, mr=%d/%d, p1stpt=%d, pnt=%d", pprint->age(),                kbps->get_send_kbps(), kbps->get_send_kbps_30s(), kbps->get_send_kbps_5m(),                kbps->get_recv_kbps(), kbps->get_recv_kbps_30s(), kbps->get_recv_kbps_5m(),                mr, mr_sleep, publish_1stpkt_timeout, publish_normal_timeout            );        }    }    return ret;}

该while(!disposed)是一个检测rtmp连接循环,不是真正Rtmp接受线程。


四、rtmp接受


int SrsRecvThread::cycle(){    int ret = ERROR_SUCCESS;    while (!trd->interrupted()) {        if (!handler->can_handle()) {            st_usleep(timeout * 1000);            continue;        }            SrsCommonMessage* msg = NULL;srs_trace("come in isloate recv thread\n");        // recv and handle message        ret = rtmp->recv_message(&msg);        if (ret == ERROR_SUCCESS) {            ret = handler->handle(msg);        }            if (ret != ERROR_SUCCESS) {            if (!srs_is_client_gracefully_close(ret) && !srs_is_system_control_error(ret)) {                srs_error("thread process message failed. ret=%d", ret);            }                // we use no timeout to recv, should never got any error.            trd->interrupt();                        // notice the handler got a recv error.            handler->on_recv_error(ret);                return ret;        }        srs_verbose("thread loop recv message. ret=%d", ret);    }    return ret;
ok,进入真正Rtmp接受线程,当然第一步是rtmp接受了,第二步就是rtmp信息处理了。我们来看rtmp接受。

1、rtmp接受

 
rtmp接受还是挺复杂的,绕了这么大的弯子,终于来到rtmp接受了,具体代码框架流程请看框架图。
int SrsProtocol::recv_message(SrsCommonMessage** pmsg){    *pmsg = NULL;        int ret = ERROR_SUCCESS;        while (true) {        SrsCommonMessage* msg = NULL;//接受RTMP块流        if ((ret = recv_interlaced_message(&msg)) != ERROR_SUCCESS) {            if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) {                srs_error("recv interlaced message failed. ret=%d", ret);            }            srs_freep(msg);            return ret;        }        srs_trace("entire msg received");                if (!msg) {            srs_info("got empty message without error.");            continue;        }                if (msg->size <= 0 || msg->header.payload_length <= 0) {            srs_trace("ignore empty message(type=%d, size=%d, time=%"PRId64", sid=%d).",                msg->header.message_type, msg->header.payload_length,                msg->header.timestamp, msg->header.stream_id);            srs_freep(msg);            continue;        }                if ((ret = on_recv_message(msg)) != ERROR_SUCCESS) {            srs_error("hook the received msg failed. ret=%d", ret);            srs_freep(msg);            return ret;        }                srs_trace("got a msg, cid=%d, type=%d, size=%d, time=%"PRId64,             msg->header.perfer_cid, msg->header.message_type, msg->header.payload_length,             msg->header.timestamp);        *pmsg = msg;        break;    }        return ret;}

进入接受recv_interlaced_message()函数,

int SrsProtocol::recv_interlaced_message(SrsCommonMessage** pmsg){    int ret = ERROR_SUCCESS;        // chunk stream basic header.    char fmt = 0;    int cid = 0;    // 检查基本信息   1、CSID  2、Chunk type:0,1,2,3    if ((ret = read_basic_header(fmt, cid)) != ERROR_SUCCESS) {        if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) {            srs_error("read basic header failed. ret=%d", ret);        }        return ret;    }    srs_verbose("read basic header success. fmt=%d, cid=%d", fmt, cid);        // the cid must not negative.    srs_assert(cid >= 0);        // get the cached chunk stream.    SrsChunkStream* chunk = NULL;        // use chunk stream cache to get the chunk info.    // @see https://github.com/ossrs/srs/issues/249    if (cid < SRS_PERF_CHUNK_STREAM_CACHE) {        // chunk stream cache hit.        srs_verbose("cs-cache hit, cid=%d", cid);        // already init, use it direclty        chunk = cs_cache[cid];        srs_verbose("cached chunk stream: fmt=%d, cid=%d, size=%d, message(type=%d, size=%d, time=%"PRId64", sid=%d)",            chunk->fmt, chunk->cid, (chunk->msg? chunk->msg->size : 0), chunk->header.message_type, chunk->header.payload_length,            chunk->header.timestamp, chunk->header.stream_id);    } else {        // chunk stream cache miss, use map.        if (chunk_streams.find(cid) == chunk_streams.end()) {            chunk = chunk_streams[cid] = new SrsChunkStream(cid);            // set the perfer cid of chunk,            // which will copy to the message received.            chunk->header.perfer_cid = cid;            srs_verbose("cache new chunk stream: fmt=%d, cid=%d", fmt, cid);        } else {            chunk = chunk_streams[cid];            srs_verbose("cached chunk stream: fmt=%d, cid=%d, size=%d, message(type=%d, size=%d, time=%"PRId64", sid=%d)",                chunk->fmt, chunk->cid, (chunk->msg? chunk->msg->size : 0), chunk->header.message_type, chunk->header.payload_length,                chunk->header.timestamp, chunk->header.stream_id);        }    }    // 检查信息头信息    // chunk stream message header    if ((ret = read_message_header(chunk, fmt)) != ERROR_SUCCESS) {        if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) {            srs_error("read message header failed. ret=%d", ret);        }        return ret;    }    srs_verbose("read message header success. "            "fmt=%d, ext_time=%d, size=%d, message(type=%d, size=%d, time=%"PRId64", sid=%d)",             fmt, chunk->extended_timestamp, (chunk->msg? chunk->msg->size : 0), chunk->header.message_type,             chunk->header.payload_length, chunk->header.timestamp, chunk->header.stream_id);//获取rtmp负载信息    // read msg payload from chunk stream.    SrsCommonMessage* msg = NULL;    if ((ret = read_message_payload(chunk, &msg)) != ERROR_SUCCESS) {        if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) {            srs_error("read message payload failed. ret=%d", ret);        }        return ret;    }    // 没有获得一个完整的RTMP信息,试着获取下一个块流    // not got an entire RTMP message, try next chunk.    if (!msg) {        srs_verbose("get partial message success. size=%d, message(type=%d, size=%d, time=%"PRId64", sid=%d)",                (msg? msg->size : (chunk->msg? chunk->msg->size : 0)), chunk->header.message_type, chunk->header.payload_length,                chunk->header.timestamp, chunk->header.stream_id);        return ret;    }    // 获取一个完整的RTMP信息    *pmsg = msg;    srs_info("get entire message success. size=%d, message(type=%d, size=%d, time=%"PRId64", sid=%d)",            (msg? msg->size : (chunk->msg? chunk->msg->size : 0)), chunk->header.message_type, chunk->header.payload_length,            chunk->header.timestamp, chunk->header.stream_id);                return ret;}
这里面就有一些相关Rtmp协议的东西,检车Basic Header、Message Header获取payload信息,ok,rtmp信息接收到了。

2、Rtmp信息处理


int SrsPublishRecvThread::handle(SrsCommonMessage* msg){    int ret = ERROR_SUCCESS;        // when cid changed, change it.    if (ncid != cid) {        _srs_context->set_id(ncid);        cid = ncid;    }    _nb_msgs++;    if (msg->header.is_video()) {// 接受到的视频帧数量        video_frames++;srs_trace("message:%d, video:%d\n", _nb_msgs, video_frames);    }        // log to show the time of recv thread.    srs_trace("recv thread now=%"PRId64"us, got msg time=%"PRId64"ms, size=%d",        srs_update_system_time_ms(), msg->header.timestamp, msg->size);    // the rtmp connection will handle this message    ret = _conn->handle_publish_message(_source, msg, _is_fmle, _is_edge);    // must always free it,    // the source will copy it if need to use.    srs_freep(msg);        return ret;}
首先做了一个统计,统计所有接受到的数据块和视频数据块。

进入process_publish_message()函数

int SrsRtmpConn::process_publish_message(SrsSource* source, SrsCommonMessage* msg, bool vhost_is_edge){    int ret = ERROR_SUCCESS;    //发布到源站服务器    // for edge, directly proxy message to origin.    if (vhost_is_edge) {        if ((ret = source->on_edge_proxy_publish(msg)) != ERROR_SUCCESS) {            srs_error("edge publish proxy msg failed. ret=%d", ret);            return ret;        }        return ret;    }        // process audio packet    if (msg->header.is_audio()) {        if ((ret = source->on_audio(msg)) != ERROR_SUCCESS) {            srs_error("source process audio message failed. ret=%d", ret);            return ret;        }        return ret;    }    // process video packet    if (msg->header.is_video()) {        if ((ret = source->on_video(msg)) != ERROR_SUCCESS) {            srs_error("source process video message failed. ret=%d", ret);            return ret;        }        return ret;    }        // process aggregate packet    if (msg->header.is_aggregate()) {        if ((ret = source->on_aggregate(msg)) != ERROR_SUCCESS) {            srs_error("source process aggregate message failed. ret=%d", ret);            return ret;        }        return ret;    }        // process onMetaData    if (msg->header.is_amf0_data() || msg->header.is_amf3_data()) {        SrsPacket* pkt = NULL;        if ((ret = rtmp->decode_message(msg, &pkt)) != ERROR_SUCCESS) {            srs_error("decode onMetaData message failed. ret=%d", ret);            return ret;        }        SrsAutoFree(SrsPacket, pkt);        if (dynamic_cast<SrsOnMetaDataPacket*>(pkt)) {            SrsOnMetaDataPacket* metadata = dynamic_cast<SrsOnMetaDataPacket*>(pkt);            if ((ret = source->on_meta_data(msg, metadata)) != ERROR_SUCCESS) {                srs_error("source process onMetaData message failed. ret=%d", ret);                return ret;            }            srs_info("process onMetaData message success.");            return ret;        }                srs_info("ignore AMF0/AMF3 data message.");        return ret;    }        return ret;}
该函数做了一下几点处理:

(1)发布到源站服务器(相关CDN知识请参考:http://blog.csdn.net/ManagerUser/article/details/73909997)

(2)处理音频包

(3)处理视频包

注意:这里对音视频数据包的处理差不多,所有只看一下视频数据包的处理即可。

进入on_video()函数

int SrsSource::on_video(SrsCommonMessage* shared_video){    int ret = ERROR_SUCCESS;        // monotically increase detect.    if (!mix_correct && is_monotonically_increase) {        if (last_packet_time > 0 && shared_video->header.timestamp < last_packet_time) {            is_monotonically_increase = false;            srs_warn("VIDEO: stream not monotonically increase, please open mix_correct.");        }    }    last_packet_time = shared_video->header.timestamp;        // drop any unknown header video.    // @see https://github.com/ossrs/srs/issues/421    if (!SrsFlvCodec::video_is_acceptable(shared_video->payload, shared_video->size)) {        char b0 = 0x00;        if (shared_video->size > 0) {            b0 = shared_video->payload[0];        }                srs_warn("drop unknown header video, size=%d, bytes[0]=%#x", shared_video->size, b0);        return ret;    }        // convert shared_video to msg, user should not use shared_video again.    // the payload is transfer to msg, and set to NULL in shared_video.    //将SrsCommonMessage类的所有数据完全拷贝给SrsSharedPtrMessage    SrsSharedPtrMessage msg;    if ((ret = msg.create(shared_video)) != ERROR_SUCCESS) {        srs_error("initialize the video failed. ret=%d", ret);        return ret;    }    srs_info("Video dts=%"PRId64", size=%d", msg.timestamp, msg.size);        // directly process the audio message.    if (!mix_correct) {        return on_video_imp(&msg);    }    // msg.copy()返回msg自己指针    // insert msg to the queue.    mix_queue->push(msg.copy());        // fetch someone from mix queue.    SrsSharedPtrMessage* m = mix_queue->pop();    if (!m) {        return ret;    }    //消费单调递增消息    // consume the monotonically increase message.    if (m->is_audio()) {        ret = on_audio_imp(m);    } else {        ret = on_video_imp(m);    }    srs_freep(m);        return ret;}
该函数做了rtmp块数据的拷贝,不用太在意,只关系接受到的rtmp信息在SrsCommonMessage类中即可。

进入on_video_imp()

int SrsSource::on_video_imp(SrsSharedPtrMessage* msg){    int ret = ERROR_SUCCESS;        srs_info("Video dts=%"PRId64", size=%d", msg->timestamp, msg->size);        bool is_sequence_header = SrsFlvCodec::video_is_sequence_header(msg->payload, msg->size);        // whether consumer should drop for the duplicated sequence header.    bool drop_for_reduce = false;    if (is_sequence_header && cache_sh_video && _srs_config->get_reduce_sequence_header(_req->vhost)) {        if (cache_sh_video->size == msg->size) {            drop_for_reduce = srs_bytes_equals(cache_sh_video->payload, msg->payload, msg->size);            srs_warn("drop for reduce sh video, size=%d", msg->size);        }    }    // cache the sequence header if h264    // donot cache the sequence header to gop_cache, return here.    if (is_sequence_header) {        srs_freep(cache_sh_video);// 缓存到shareMessage        cache_sh_video = msg->copy();                // parse detail audio codec        SrsAvcAacCodec codec;                // user can disable the sps parse to workaround when parse sps failed.        // @see https://github.com/ossrs/srs/issues/474        // 是否解析SPS        codec.avc_parse_sps = _srs_config->get_parse_sps(_req->vhost);// AAC 高级音频编码// AVC 高级视频编码        SrsCodecSample sample;        if ((ret = codec.video_avc_demux(msg->payload, msg->size, &sample)) != ERROR_SUCCESS) {            srs_error("source codec demux video failed. ret=%d", ret);            return ret;        }                // when got video stream info.        SrsStatistic* stat = SrsStatistic::instance();        if ((ret = stat->on_video_info(_req, SrsCodecVideoAVC, codec.avc_profile, codec.avc_level)) != ERROR_SUCCESS) {            return ret;        }                srs_trace("%dB video sh,  codec(%d, profile=%s, level=%s, %dx%d, %dkbps, %dfps, %ds)",            msg->size, codec.video_codec_id,            srs_codec_avc_profile2str(codec.avc_profile).c_str(),            srs_codec_avc_level2str(codec.avc_level).c_str(), codec.width, codec.height,            codec.video_data_rate / 1000, codec.frame_rate, codec.duration);    }    #ifdef SRS_AUTO_HLS    if ((ret = hls->on_video(msg, is_sequence_header)) != ERROR_SUCCESS) {        // apply the error strategy for hls.        // @see https://github.com/ossrs/srs/issues/264        std::string hls_error_strategy = _srs_config->get_hls_on_error(_req->vhost);        if (srs_config_hls_is_on_error_ignore(hls_error_strategy)) {            srs_warn("hls process video message failed, ignore and disable hls. ret=%d", ret);                        // unpublish, ignore ret.            hls->on_unpublish();                        // ignore.            ret = ERROR_SUCCESS;        } else if (srs_config_hls_is_on_error_continue(hls_error_strategy)) {            if (srs_hls_can_continue(ret, cache_sh_video, msg)) {                ret = ERROR_SUCCESS;            } else {                srs_warn("hls continue video failed. ret=%d", ret);                return ret;            }        } else {            srs_warn("hls disconnect publisher for video error. ret=%d", ret);            return ret;        }    }#endif    #ifdef SRS_AUTO_DVR    if ((ret = dvr->on_video(msg)) != ERROR_SUCCESS) {        srs_warn("dvr process video message failed, ignore and disable dvr. ret=%d", ret);                // unpublish, ignore ret.        dvr->on_unpublish();                // ignore.        ret = ERROR_SUCCESS;    }#endif#ifdef SRS_AUTO_HDS    if ((ret = hds->on_video(msg)) != ERROR_SUCCESS) {        srs_warn("hds process video message failed, ignore and disable dvr. ret=%d", ret);                // unpublish, ignore ret.        hds->on_unpublish();        // ignore.        ret = ERROR_SUCCESS;    }#endif    // 将信息入到消费者堆栈里面    // copy to all consumer    if (!drop_for_reduce) {;        for (int i = 0; i < (int)consumers.size(); i++) {            SrsConsumer* consumer = consumers.at(i);            if ((ret = consumer->enqueue(msg, atc, jitter_algorithm)) != ERROR_SUCCESS) {                srs_error("dispatch the video failed. ret=%d", ret);                return ret;            }        }        srs_info("dispatch video success.");    }    // copy to all forwarders.    if (!forwarders.empty()) {printf("forward to other server");        std::vector<SrsForwarder*>::iterator it;        for (it = forwarders.begin(); it != forwarders.end(); ++it) {            SrsForwarder* forwarder = *it;            if ((ret = forwarder->on_video(msg)) != ERROR_SUCCESS) {                srs_error("forwarder process video message failed. ret=%d", ret);                return ret;            }        }    }        // when sequence header, donot push to gop cache and adjust the timestamp.    if (is_sequence_header) {        return ret;    }    // cache the last gop packets    if ((ret = gop_cache->cache(msg)) != ERROR_SUCCESS) {        srs_error("gop cache msg failed. ret=%d", ret);        return ret;    }    srs_verbose("cache gop success.");        // if atc, update the sequence header to abs time.    if (atc) {        if (cache_sh_video) {            cache_sh_video->timestamp = msg->timestamp;        }        if (cache_metadata) {            cache_metadata->timestamp = msg->timestamp;        }    }        return ret;}
该函数有点长,也非常重要,分为段来介绍;
前半段代码通过SrsAvcAacCodec类来检测视频编码,这里RTMP协议支持视频编码格式H264,音频AAC。
后半部分代码主要做了转发入栈操作:
(1)HLS转发(该部分内容在后面文章中介绍)
(2)将rtmp信息转发给消费者列表(用户列表,用户在play的时候直接到该列表中去数据并进行play)
(3)将rtmp信息forward到其他服务器(该部分内容在后面章节中介绍)

五、Publish总结


至此,rtmp信息publish过程就结束了,总结一下:

    (1)rtmp协议链接。包括握手,创建网络连接,控制信息交互。

    (2)创建单独的接受线程去处理Rtmp信息接受。

    (3)缓存数据到SrsCommonMessage类。

    (4)数据转发,转发给消费者,forward给其他服务器(边缘服务器或者源站服务器)


原创粉丝点击