SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP信息Publish
来源:互联网 发布:数据营销 编辑:程序博客网 时间:2024/04/29 04:46
前言
上一节中已经介绍了rtmp流监听,一旦有rtmp请求之后处理,单独为该链接开启一个线程等等,这一节我们来分析一下一个rtmp请求到来之后,除了创建线程之外,还要做哪些工作。同样,只是代码框架分析。
SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP消息play
一、进入循环
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网络连接。建立网络连接之后要做一些确认工作。内容如下:
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
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分支
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接收线程
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接受
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;}该函数有点长,也非常重要,分为段来介绍;
五、Publish总结
至此,rtmp信息publish过程就结束了,总结一下:
(1)rtmp协议链接。包括握手,创建网络连接,控制信息交互。
(2)创建单独的接受线程去处理Rtmp信息接受。
(3)缓存数据到SrsCommonMessage类。
(4)数据转发,转发给消费者,forward给其他服务器(边缘服务器或者源站服务器)
- SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP信息Publish
- SRS(simple-rtmp-server)流媒体服务器源码分析--启动
- SRS(simple-rtmp-server)流媒体服务器源码分析--HLS切片
- SRS(simple-rtmp-server)流媒体服务器源码分析--启动
- SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP消息play
- SRS 代码分析【RTMP信息play/publish】
- SRS流媒体服务器压测RTMP&HLS
- srs (simple rtmp server)编译
- Simple-RTMP-Server 服务器搭建
- Simple-RTMP-Server 服务器测试
- Ubuntu 14.04 64bit上编译安装simple-rtmp-server(srs)服务器
- Ubuntu 14.04 64bit上编译安装simple-rtmp-server(srs)服务器
- Ubuntu 14.04 64bit上编译安装simple-rtmp-server(srs)服务器
- srs rtmp
- RTMP流媒体服务器 crtmpserver
- simple-rtmp-server安装
- 开源rtmp服务器srs主要类图
- Nginx搭建rtmp流媒体服务器
- UVA 455
- shader内置光
- Unity UGUI中按钮自动触发UI事件
- Cannot checkout from svn: Cannot run program "svn"和 Can't use Subversion command line client:svn
- Oracle 笔记(一)、安装及体系结构
- SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP信息Publish
- vector的用法
- php中_initialize的返回
- gulp项目打包
- 文章标题
- gitlab API使用
- License解释
- 使用cardview 属性找不到解决办法
- Centos7安装Hadoop2.8步骤