int SrsGopCache::cache(SrsSharedPtrMessage* msg) { int ret = ERROR_SUCCESS; if (!enable_gop_cache) { srs_verbose("gop cache is disabled."); return ret; } // got video, update the video count if acceptable if (msg->header.is_video()) { cached_video_count++; } // no acceptable video or pure audio, disable the cache. if (cached_video_count == 0) { srs_verbose("ignore any frame util got a h264 video frame."); return ret; } // clear gop cache when got key frame if (msg->header.is_video() && SrsCodec::video_is_keyframe(msg->payload, msg->size)) { srs_info("clear gop cache when got keyframe. vcount=%d, count=%d", cached_video_count, (int)gop_cache.size()); clear(); // curent msg is video frame, so we set to 1. cached_video_count = 1; } // cache the frame. gop_cache.push_back(msg->copy()); return ret; }
int SrsFFMPEG::initialize(std::string vhost, std::string port, std::string app, std::string stream, SrsConfDirective* engine) { int ret = ERROR_SUCCESS; config->get_engine_vfilter(engine, vfilter); vcodec = config->get_engine_vcodec(engine); vbitrate = config->get_engine_vbitrate(engine); vfps = config->get_engine_vfps(engine); vwidth = config->get_engine_vwidth(engine); vheight = config->get_engine_vheight(engine); vthreads = config->get_engine_vthreads(engine); vprofile = config->get_engine_vprofile(engine); vpreset = config->get_engine_vpreset(engine); config->get_engine_vparams(engine, vparams); acodec = config->get_engine_acodec(engine); abitrate = config->get_engine_abitrate(engine); asample_rate = config->get_engine_asample_rate(engine); achannels = config->get_engine_achannels(engine); config->get_engine_aparams(engine, aparams); output = config->get_engine_output(engine); // ensure the size is even. vwidth -= vwidth % 2; vheight -= vheight % 2; // input stream, from local. // ie. rtmp://127.0.0.1:1935/live/livestream input = "rtmp://127.0.0.1:"; input += port; input += "/"; input += app; input += "/"; input += stream; // output stream, to other/self server // ie. rtmp://127.0.0.1:1935/live/livestream_sd if (vhost == RTMP_VHOST_DEFAULT) { output = srs_replace(output, "[vhost]", "127.0.0.1"); } else { output = srs_replace(output, "[vhost]", vhost); } output = srs_replace(output, "[port]", port); output = srs_replace(output, "[app]", app); output = srs_replace(output, "[stream]", stream); // important: loop check, donot transcode again. // we think the following is loop circle: // input: rtmp://127.0.0.1:1935/live/livestream_sd // output: rtmp://127.0.0.1:1935/live/livestream_sd_sd std::string tail = ""; // tail="_sd" if (output.length() > input.length()) { tail = output.substr(input.length()); } // TODO: better dead loop check. // if input also endwiths the tail, loop detected. if (!tail.empty() && input.rfind(tail) == input.length() - tail.length()) { ret = ERROR_ENCODER_LOOP; srs_info("detect a loop cycle, input=%s, output=%s, ignore it. ret=%d", input.c_str(), output.c_str(), ret); return ret; } if (vcodec != SRS_ENCODER_VCODEC) { ret = ERROR_ENCODER_VCODEC; srs_error("invalid vcodec, must be %s, actual %s, ret=%d", SRS_ENCODER_VCODEC, vcodec.c_str(), ret); return ret; } if (vbitrate <= 0) { ret = ERROR_ENCODER_VBITRATE; srs_error("invalid vbitrate: %d, ret=%d", vbitrate, ret); return ret; } if (vfps <= 0) { ret = ERROR_ENCODER_VFPS; srs_error("invalid vfps: %.2f, ret=%d", vfps, ret); return ret; } if (vwidth <= 0) { ret = ERROR_ENCODER_VWIDTH; srs_error("invalid vwidth: %d, ret=%d", vwidth, ret); return ret; } if (vheight <= 0) { ret = ERROR_ENCODER_VHEIGHT; srs_error("invalid vheight: %d, ret=%d", vheight, ret); return ret; } if (vthreads < 0) { ret = ERROR_ENCODER_VTHREADS; srs_error("invalid vthreads: %d, ret=%d", vthreads, ret); return ret; } if (vprofile.empty()) { ret = ERROR_ENCODER_VPROFILE; srs_error("invalid vprofile: %s, ret=%d", vprofile.c_str(), ret); return ret; } if (vpreset.empty()) { ret = ERROR_ENCODER_VPRESET; srs_error("invalid vpreset: %s, ret=%d", vpreset.c_str(), ret); return ret; } if (acodec != SRS_ENCODER_ACODEC) { ret = ERROR_ENCODER_ACODEC; srs_error("invalid acodec, must be %s, actual %s, ret=%d", SRS_ENCODER_ACODEC, acodec.c_str(), ret); return ret; } if (abitrate <= 0) { ret = ERROR_ENCODER_ABITRATE; srs_error("invalid abitrate: %d, ret=%d", abitrate, ret); return ret; } if (asample_rate <= 0) { ret = ERROR_ENCODER_ASAMPLE_RATE; srs_error("invalid sample rate: %d, ret=%d", asample_rate, ret); return ret; } if (achannels != 1 && achannels != 2) { ret = ERROR_ENCODER_ACHANNELS; srs_error("invalid achannels, must be 1 or 2, actual %d, ret=%d", achannels, ret); return ret; } if (output.empty()) { ret = ERROR_ENCODER_OUTPUT; srs_error("invalid empty output, ret=%d", ret); return ret; } return ret; }
int SrsEdgeIngester::connect_server() { int ret = ERROR_SUCCESS; // reopen close_underlayer_socket(); SrsConfDirective* conf = _srs_config->get_vhost_edge_origin(_req->vhost); srs_assert(conf); // select the origin. std::string server = conf->args.at(origin_index % conf->args.size()); origin_index = (origin_index + 1) % conf->args.size(); std::string s_port = RTMP_DEFAULT_PORT; int port = ::atoi(RTMP_DEFAULT_PORT); size_t pos = server.find(":"); if (pos != std::string::npos) { s_port = server.substr(pos + 1); server = server.substr(0, pos); port = ::atoi(s_port.c_str()); } // open socket. // TODO: FIXME: extract utility method int sock = socket(AF_INET, SOCK_STREAM, 0); if(sock == -1){ ret = ERROR_SOCKET_CREATE; srs_error("create socket error. ret=%d", ret); return ret; } srs_assert(!stfd); stfd = st_netfd_open_socket(sock); if(stfd == NULL){ ret = ERROR_ST_OPEN_SOCKET; srs_error("st_netfd_open_socket failed. ret=%d", ret); return ret; } srs_freep(client); srs_freep(io); io = new SrsSocket(stfd); client = new SrsRtmpClient(io); kbps->set_io(io, io); // connect to server. std::string ip = srs_dns_resolve(server); if (ip.empty()) { ret = ERROR_SYSTEM_IP_INVALID; srs_error("dns resolve server error, ip empty. ret=%d", ret); return ret; } sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = inet_addr(ip.c_str()); if (st_connect(stfd, (const struct sockaddr*)&addr, sizeof(sockaddr_in), SRS_EDGE_INGESTER_TIMEOUT_US) == -1){ ret = ERROR_ST_CONNECT; srs_error("connect to server error. ip=%s, port=%d, ret=%d", ip.c_str(), port, ret); return ret; } srs_info("connect to server success. server=%s, ip=%s, port=%d", server.c_str(), ip.c_str(), port); srs_trace("edge connected, can_publish=%d, url=%s/%s, server=%s:%d", _source->can_publish(), _req->tcUrl.c_str(), _req->stream.c_str(), server.c_str(), port); return ret; }
int SrsSource::on_video(SrsCommonMessage* video) { int ret = ERROR_SUCCESS; SrsSharedPtrMessage* msg = new SrsSharedPtrMessage(); SrsAutoFree(SrsSharedPtrMessage, msg, false); if ((ret = msg->initialize(video)) != ERROR_SUCCESS) { srs_error("initialize the video failed. ret=%d", ret); return ret; } srs_verbose("initialize shared ptr video success."); #ifdef SRS_HLS if ((ret = hls->on_video(msg->copy())) != ERROR_SUCCESS) { srs_error("hls process video message failed. ret=%d", ret); return ret; } #endif // copy to all consumer if (true) { std::vector<SrsConsumer*>::iterator it; for (it = consumers.begin(); it != consumers.end(); ++it) { SrsConsumer* consumer = *it; if ((ret = consumer->enqueue(msg->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { srs_error("dispatch the video failed. ret=%d", ret); return ret; } } srs_info("dispatch video success."); } // copy to all forwarders. if (true) { std::vector<SrsForwarder*>::iterator it; for (it = forwarders.begin(); it != forwarders.end(); ++it) { SrsForwarder* forwarder = *it; if ((ret = forwarder->on_video(msg->copy())) != ERROR_SUCCESS) { srs_error("forwarder process video message failed. ret=%d", ret); return ret; } } } // cache the sequence header if h264 if (SrsCodec::video_is_sequence_header(msg->payload, msg->size)) { srs_freep(cache_sh_video); cache_sh_video = msg->copy(); srs_trace("update video sequence header success. size=%d", msg->header.payload_length); return ret; } // cache the last gop packets if ((ret = gop_cache->cache(msg)) != ERROR_SUCCESS) { srs_error("shrink gop cache failed. ret=%d", ret); return ret; } srs_verbose("cache gop success."); return ret; }
void SrsThread::thread_cycle() { int ret = ERROR_SUCCESS; _srs_context->generate_id(); srs_info("thread %s cycle start", _name); _cid = _srs_context->get_id(); srs_assert(handler); handler->on_thread_start(); // thread is running now. really_terminated = false; // wait for cid to ready, for parent thread to get the cid. while (!can_run && loop) { st_usleep(10 * 1000); } while (loop) { if ((ret = handler->on_before_cycle()) != ERROR_SUCCESS) { srs_warn("thread %s on before cycle failed, ignored and retry, ret=%d", _name, ret); goto failed; } srs_info("thread %s on before cycle success"); if ((ret = handler->cycle()) != ERROR_SUCCESS) { if (!srs_is_client_gracefully_close(ret) && !srs_is_system_control_error(ret)) { srs_warn("thread %s cycle failed, ignored and retry, ret=%d", _name, ret); } goto failed; } srs_info("thread %s cycle success", _name); if ((ret = handler->on_end_cycle()) != ERROR_SUCCESS) { srs_warn("thread %s on end cycle failed, ignored and retry, ret=%d", _name, ret); goto failed; } srs_info("thread %s on end cycle success", _name); failed: if (!loop) { break; } // to improve performance, donot sleep when interval is zero. // @see: https://github.com/ossrs/srs/issues/237 if (cycle_interval_us != 0) { st_usleep(cycle_interval_us); } } // readly terminated now. really_terminated = true; // when thread terminated normally, also disposed. // we must set to disposed before the on_thread_stop, which may free the thread. // @see https://github.com/ossrs/srs/issues/546 disposed = true; handler->on_thread_stop(); srs_info("thread %s cycle finished", _name); }
int SrsRtmp::start_fmle_publish(int stream_id) { int ret = ERROR_SUCCESS; // FCPublish double fc_publish_tid = 0; if (true) { SrsCommonMessage* msg = NULL; SrsFMLEStartPacket* pkt = NULL; if ((ret = srs_rtmp_expect_message<SrsFMLEStartPacket>(protocol, &msg, &pkt)) != ERROR_SUCCESS) { srs_error("recv FCPublish message failed. ret=%d", ret); return ret; } srs_info("recv FCPublish request message success."); SrsAutoFree(SrsCommonMessage, msg, false); fc_publish_tid = pkt->transaction_id; } // FCPublish response if (true) { SrsCommonMessage* msg = new SrsCommonMessage(); SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(fc_publish_tid); msg->set_packet(pkt, 0); if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { srs_error("send FCPublish response message failed. ret=%d", ret); return ret; } srs_info("send FCPublish response message success."); } // createStream double create_stream_tid = 0; if (true) { SrsCommonMessage* msg = NULL; SrsCreateStreamPacket* pkt = NULL; if ((ret = srs_rtmp_expect_message<SrsCreateStreamPacket>(protocol, &msg, &pkt)) != ERROR_SUCCESS) { srs_error("recv createStream message failed. ret=%d", ret); return ret; } srs_info("recv createStream request message success."); SrsAutoFree(SrsCommonMessage, msg, false); create_stream_tid = pkt->transaction_id; } // createStream response if (true) { SrsCommonMessage* msg = new SrsCommonMessage(); SrsCreateStreamResPacket* pkt = new SrsCreateStreamResPacket(create_stream_tid, stream_id); msg->set_packet(pkt, 0); if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { srs_error("send createStream response message failed. ret=%d", ret); return ret; } srs_info("send createStream response message success."); } // publish if (true) { SrsCommonMessage* msg = NULL; SrsPublishPacket* pkt = NULL; if ((ret = srs_rtmp_expect_message<SrsPublishPacket>(protocol, &msg, &pkt)) != ERROR_SUCCESS) { srs_error("recv publish message failed. ret=%d", ret); return ret; } srs_info("recv publish request message success."); SrsAutoFree(SrsCommonMessage, msg, false); } // publish response onFCPublish(NetStream.Publish.Start) if (true) { SrsCommonMessage* msg = new SrsCommonMessage(); SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); pkt->command_name = RTMP_AMF0_COMMAND_ON_FC_PUBLISH; pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart)); pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream.")); msg->set_packet(pkt, stream_id); if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { srs_error("send onFCPublish(NetStream.Publish.Start) message failed. ret=%d", ret); return ret; } srs_info("send onFCPublish(NetStream.Publish.Start) message success."); } // publish response onStatus(NetStream.Publish.Start) if (true) { SrsCommonMessage* msg = new SrsCommonMessage(); SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart)); pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream.")); pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); msg->set_packet(pkt, stream_id); if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { srs_error("send onStatus(NetStream.Publish.Start) message failed. ret=%d", ret); return ret; } srs_info("send onStatus(NetStream.Publish.Start) message success."); } srs_info("FMLE publish success."); return ret; }
int SrsRtmp::on_play_client_pause(int stream_id, bool is_pause) { int ret = ERROR_SUCCESS; if (is_pause) { // onStatus(NetStream.Pause.Notify) if (true) { SrsCommonMessage* msg = new SrsCommonMessage(); SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamPause)); pkt->data->set(StatusDescription, new SrsAmf0String("Paused stream.")); msg->set_packet(pkt, stream_id); if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { srs_error("send onStatus(NetStream.Pause.Notify) message failed. ret=%d", ret); return ret; } srs_info("send onStatus(NetStream.Pause.Notify) message success."); } // StreamEOF if (true) { SrsCommonMessage* msg = new SrsCommonMessage(); SrsUserControlPacket* pkt = new SrsUserControlPacket(); pkt->event_type = SrcPCUCStreamEOF; pkt->event_data = stream_id; msg->set_packet(pkt, 0); if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { srs_error("send PCUC(StreamEOF) message failed. ret=%d", ret); return ret; } srs_info("send PCUC(StreamEOF) message success."); } } else { // onStatus(NetStream.Unpause.Notify) if (true) { SrsCommonMessage* msg = new SrsCommonMessage(); SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamUnpause)); pkt->data->set(StatusDescription, new SrsAmf0String("Unpaused stream.")); msg->set_packet(pkt, stream_id); if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { srs_error("send onStatus(NetStream.Unpause.Notify) message failed. ret=%d", ret); return ret; } srs_info("send onStatus(NetStream.Unpause.Notify) message success."); } // StreanBegin if (true) { SrsCommonMessage* msg = new SrsCommonMessage(); SrsUserControlPacket* pkt = new SrsUserControlPacket(); pkt->event_type = SrcPCUCStreamBegin; pkt->event_data = stream_id; msg->set_packet(pkt, 0); if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { srs_error("send PCUC(StreanBegin) message failed. ret=%d", ret); return ret; } srs_info("send PCUC(StreanBegin) message success."); } } return ret; }
int SrsRtmp::start_play(int stream_id) { int ret = ERROR_SUCCESS; // StreamBegin if (true) { SrsCommonMessage* msg = new SrsCommonMessage(); SrsUserControlPacket* pkt = new SrsUserControlPacket(); pkt->event_type = SrcPCUCStreamBegin; pkt->event_data = stream_id; msg->set_packet(pkt, 0); if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { srs_error("send PCUC(StreamBegin) message failed. ret=%d", ret); return ret; } srs_info("send PCUC(StreamBegin) message success."); } // onStatus(NetStream.Play.Reset) if (true) { SrsCommonMessage* msg = new SrsCommonMessage(); SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamReset)); pkt->data->set(StatusDescription, new SrsAmf0String("Playing and resetting stream.")); pkt->data->set(StatusDetails, new SrsAmf0String("stream")); pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); msg->set_packet(pkt, stream_id); if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { srs_error("send onStatus(NetStream.Play.Reset) message failed. ret=%d", ret); return ret; } srs_info("send onStatus(NetStream.Play.Reset) message success."); } // onStatus(NetStream.Play.Start) if (true) { SrsCommonMessage* msg = new SrsCommonMessage(); SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamStart)); pkt->data->set(StatusDescription, new SrsAmf0String("Started playing stream.")); pkt->data->set(StatusDetails, new SrsAmf0String("stream")); pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); msg->set_packet(pkt, stream_id); if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { srs_error("send onStatus(NetStream.Play.Reset) message failed. ret=%d", ret); return ret; } srs_info("send onStatus(NetStream.Play.Reset) message success."); } // |RtmpSampleAccess(false, false) if (true) { SrsCommonMessage* msg = new SrsCommonMessage(); SrsSampleAccessPacket* pkt = new SrsSampleAccessPacket(); msg->set_packet(pkt, stream_id); if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { srs_error("send |RtmpSampleAccess(false, false) message failed. ret=%d", ret); return ret; } srs_info("send |RtmpSampleAccess(false, false) message success."); } // onStatus(NetStream.Data.Start) if (true) { SrsCommonMessage* msg = new SrsCommonMessage(); SrsOnStatusDataPacket* pkt = new SrsOnStatusDataPacket(); pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeDataStart)); msg->set_packet(pkt, stream_id); if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { srs_error("send onStatus(NetStream.Data.Start) message failed. ret=%d", ret); return ret; } srs_info("send onStatus(NetStream.Data.Start) message success."); } srs_info("start play success."); return ret; }
int SrsServer::do_cycle() { int ret = ERROR_SUCCESS; // find the max loop int max = srs_max(0, SRS_SYS_TIME_RESOLUTION_MS_TIMES); max = srs_max(max, SRS_SYS_RUSAGE_RESOLUTION_TIMES); max = srs_max(max, SRS_SYS_CPU_STAT_RESOLUTION_TIMES); max = srs_max(max, SRS_SYS_DISK_STAT_RESOLUTION_TIMES); max = srs_max(max, SRS_SYS_MEMINFO_RESOLUTION_TIMES); max = srs_max(max, SRS_SYS_PLATFORM_INFO_RESOLUTION_TIMES); max = srs_max(max, SRS_SYS_NETWORK_DEVICE_RESOLUTION_TIMES); max = srs_max(max, SRS_SYS_NETWORK_RTMP_SERVER_RESOLUTION_TIMES); // the deamon thread, update the time cache while (true) { // the interval in config. int heartbeat_max_resolution = (int)(_srs_config->get_heartbeat_interval() / 100); // dynamic fetch the max. int __max = max; __max = srs_max(__max, heartbeat_max_resolution); for (int i = 0; i < __max; i++) { st_usleep(SRS_SYS_CYCLE_INTERVAL * 1000); // for gperf heap checker, // @see: research/gperftools/heap-checker/heap_checker.cc // if user interrupt the program, exit to check mem leak. // but, if gperf, use reload to ensure main return normally, // because directly exit will cause core-dump. #ifdef SRS_AUTO_GPERF_MC if (signal_gmc_stop) { srs_warn("gmc got singal to stop server."); return ret; } #endif if (signal_reload) { signal_reload = false; srs_info("get signal reload, to reload the config."); if ((ret = _srs_config->reload()) != ERROR_SUCCESS) { srs_error("reload config failed. ret=%d", ret); return ret; } srs_trace("reload config success."); } // update the cache time or rusage. if ((i % SRS_SYS_TIME_RESOLUTION_MS_TIMES) == 0) { srs_info("update current time cache."); srs_update_system_time_ms(); } if ((i % SRS_SYS_RUSAGE_RESOLUTION_TIMES) == 0) { srs_info("update resource info, rss."); srs_update_system_rusage(); } if ((i % SRS_SYS_CPU_STAT_RESOLUTION_TIMES) == 0) { srs_info("update cpu info, cpu usage."); srs_update_proc_stat(); } if ((i % SRS_SYS_DISK_STAT_RESOLUTION_TIMES) == 0) { srs_info("update disk info, disk iops."); srs_update_disk_stat(); } if ((i % SRS_SYS_MEMINFO_RESOLUTION_TIMES) == 0) { srs_info("update memory info, usage/free."); srs_update_meminfo(); } if ((i % SRS_SYS_PLATFORM_INFO_RESOLUTION_TIMES) == 0) { srs_info("update platform info, uptime/load."); srs_update_platform_info(); } if ((i % SRS_SYS_NETWORK_DEVICE_RESOLUTION_TIMES) == 0) { srs_info("update network devices info."); srs_update_network_devices(); } if ((i % SRS_SYS_NETWORK_RTMP_SERVER_RESOLUTION_TIMES) == 0) { srs_info("update network rtmp server info."); resample_kbps(NULL); srs_update_rtmp_server((int)conns.size(), kbps); } #ifdef SRS_AUTO_HTTP_PARSER if (_srs_config->get_heartbeat_enabled()) { if ((i % heartbeat_max_resolution) == 0) { srs_info("do http heartbeat, for internal server to report."); http_heartbeat->heartbeat(); } } #endif srs_info("server main thread loop"); } } return ret; }