1. SrsRtmpConn::publishing

int SrsRtmpConn::publishing(SrsSource* source)
{
int ret = ERROR_SUCCESS; /* 在配置文件中配置了 refer_publish 配置项才会进行 refer check */
if ((ret = refer->check(req->pageUrl, _srs_config->get_refer_pubish(req->vhost)))
!= ERROR_SUCCESS) {
srs_error("check publish_refer failed. ret=%d", ret);
return ret;
}
srs_verbose("check publish_refer success."); /* 若配置文件中使能了 http_hooks 配置项,则调用该函数,否则忽略 */
if ((ret = http_hooks_on_publish()) != ERROR_SUCCESS) {
srs_error("http hook on_publish failed. ret=%d", ret);
return ret;
} /* 若配置文件中没有配置 mode 配置项,则返回 false */
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());
/* 该函数会启动接收推流数据的线程 recv,然后当前线程会循环进入休眠,并在醒来
* 后更新状态信息 */
ret = do_publishing(source, &trd); // stop isolate recv thread
trd.stop();
} if (ret != ERROR_SYSTEM_STREAM_BUSY) {
release_publish(source, vhost_is_edge);
} /* whatever the acquire publish, always release publish.
* when the acquire error in the middle-way, the publish state changed,
* but failed, so we must cleanup it.
* @see https://github.com/ossrs/srs/issues/474: srs_librtmp推送后返回错误码1009
* @remark when stream is busy, should never release it. */
http_hooks_on_unpublish(); return ret;
}

2. SrsRtmpConn::acquire_publish

int SrsRtmpConn::acquire_publish(SrsSource* source, bool is_edge)
{
int ret = ERROR_SUCCESS; /* 检测是否可以进行 publish */
if (!source->can_publish(is_edge)) {
ret = ERROR_SYSTEM_STREAM_BUSY;
srs_warn("stream %s is already publishing. ret=%d",
req->get_stream_url().c_str(), ret);
return ret;
} // when edge, ignore the publish event, directly proxy it.
if (is_edge) {
if ((ret = source->on_edge_start_publish()) != ERROR_SUCCESS) {
srs_error("notice edge start publish stream failed. ret=%d", ret);
return ret;
}
} else {
/* 通知可以进行 publish */
if ((ret = source->on_publish()) != ERROR_SUCCESS) {
srs_error("notify publish failed. ret=%d", ret);
return ret;
}
} return ret;
}

2.1 SrsSource::can_publish

bool SrsSource::can_publish(bool is_edge)
{
/* 当为 edge 时,检测当前 edge 的状态,只要不为 SrsEdgeStatePublish,即可进行 publish */
if (is_edge) {
return publish_edge->can_publish();
} /* 默认初始化时为 true */
return _can_publish;
}

2.2 SrsSource::on_publish

/**
* publish stream event notify.
* @param _req the request from client, the source will deep copy it,
* for when reload the request of client maybe invalid.
*/
int SrsSource::on_publish()
{
int ret = ERROR_SUCCESS; // update the request object.
srs_assert(_req); /* 若当前线程上下文的id与_source_id不一致,表明id改变了,需要更新_source_id,
* 并通知所有的 consumers */
/* whatever, the publish thread is the source or edge source,
* save its id to source id */
on_source_id_changed(_srs_context->get_id); /* the mix queue to correct the timestamp for mix_correct algorithm */
/* reset the mix queue. */
mix_queue->clear(); /* detect the monotonically again. */
is_monotonically_increase = true;
last_packet_time = 0; /* create forwarders: 需要配置文件中配置 forward 配置项,否则忽略该函数 */
if ((ret = create_forwarders()) != ERROR_SUCCESS) {
srs_error("create forwarders failed. ret=%d", ret);
return ret;
} // TODO: FIXME: use initialize to set req.
#ifdef SRS_AUTO_TRANSCODE
if ((ret = encoder->on_publish(_req)) != ERROR_SUCCESS) {
srs_error("start encoder failed. ret=%d", ret);
return ret;
}
#endif // TODO: FIXME: use initialize to set req.
#ifdef SRS_AUTO_HLS
if ((ret = hls->on_publish(_req, false)) != ERROR_SUCCESS) {
srs_error("start hls failed. ret=%d", ret);
return ret;
}
#endif // TODO: FIXME: use initialize to set req.
#ifdef SRS_AUTO_DVR
if ((ret = dvr->on_publish(_req)) != ERROR_SUCCESS) {
srs_error("start dvr failed. ret=%d", ret);
return ret;
}
#endif #ifdef SRS_AUTO_HDS
if ((ret = hds->on_publish(_req)) != ERROR_SUCCESS) {
srs_error("start hds failed. ret=%d", ret);
return ret;
}
#endif // notify the handler.
srs_assert(handler);
/* handler 指向子类 SrsServer,因此调用子类实现的 on_publish */
if ((ret = handler->on_publish(this, _req)) != ERROR_SUCCESS) {
srs_error("handle on publish failed. ret=%d", ret);
return ret;
}
/* 更新 stat 中的值 */
SrsStatistic* stat = SrsStatistic::instance();
stat->on_stream_publish(_req, _source_id); return ret;
}

2.2.1 SrsSource::on_source_id_changed

/**
* source id changed
* @id: 当前线程的 id
*/
int SrsSource::on_source_id_changed(int id)
{
int ret = ERROR_SUCCESS; if (_source_id == id) {
return ret;
} if (_pre_source_id == -1) {
_pre_source_id = id;
} else if (_pre_source_id != _source_id) {
_pre_source_id = _source_id;
} /* source id,
* for publish, it's the publish client id.
* for edge, it's the edge ingest id.
* when source id changed, for example, the edge reconnect,
* invoke the on_source_id_changed() to let all clients know.
*/
_source_id = id; /* SrsConsumer: the consumer for SrsSource, that is a play client. */
/* notice all consumer */
std::vector<SrsConsumer*>::iterator it;
for (it = consumers.begin(); it != consumers.end(); ++it) {
SrsConsumer* consumer = *it;
consumer->update_source_id();
} retrn ret;
}

2.2.2 SrsServer::on_publish

int SrsServer::on_publish(SrsSource* s, SrsRequest* r)
{
int ret = ERROR_SUCCESS; /* 暂时忽略,没有启用 */
#ifdef SRS_AUTO_HTTP_SERVER
if ((ret = http_server->http_mount(s, r)) != ERROR_SUCCESS) {
return ret;
}
#endif return ret;
}

3. SrsPublishRecvThread 构造

/**
* the publish recv thread got message and callback the souorce method
* to process message.
*/
SrsPublishRecvThread::SrsPublishRecvThread(
SrsRtmpServer* rtmp_sdk,
SrsRequest* _req, int mr_sock_fd, int timeout_ms,
SrsRtmpConn* conn, SrsSource* source, bool is_fmle, bool is_edge
): trd(this, rtmp_sdk, timeout_ms)
{
rtmp = rtmp_sdk; _conn = conn;
/* the params for conn callback. */
_source = source;
_is_fmle = is_fmle;
_is_edge = is_edge; /* the recv thread error code. */
recv_error_code = ERROR_SUCCESS;
/* the msgs already got. */
_nb_msgs = 0;
/* The video frames we got. */
video_frames = 0;
/* the error timeout cond
* @see https://github.com/ossrs/srs/issues/244 */
error = st_cond_new();
/* merged context id. */
ncid = cid = 0; req = _req;
mr_fd = mr_sock_fd; // the mr settings,
// @see https://github.com/ossrs/srs/issues/241
mr = _srs_config->get_mr_enabled(req->vhost);
mr_sleep = _srs_config->get_mr_sleep_ms(req->vhost); realtime = _srs_config->get_realtime_enabled(req->vhost); /* for reload handler to register itself,
* when config service do the reload, callback the handler. */
_srs_config->subscribe(this);
}

该 SrsPublishRecvThread 类中同时有一个 SrsRecvThread 类的成员 trd,因此会同时构造 SrsRecvThread 类。

3.1 SrsRecvThread 构造函数

/**
* the recv thread, use message handler to handle each received message.
*/
SrsRecvThread::SrsRecvThread(ISrsMessageHandler* msg_handler,
SrsRtmpServer* rtmp_sdk, int timeout_ms)
{ timeout = timeout_ms;
/* handler 指向子类 SrsPublishRecvThread 的指针对象 */
handler = msg_handler;
rtmp = rtmp_sdk;
/* 构造一个可重复使用的线程:recv */
trd = new SrsReusableThread2("recv", this);
}

3.2 SrsReusableThread2 构造函数

SrsReusableThread2::SrsReusableThread2(const char* n, ISrsReusableThread2Handler* h,
int64_t interval_us)
{
/* 指向子类 SrsRecvThread 的指针对象 */
handler = h;
/* 构造一个 SrsThread 类,该类代表一个线程 */
pthread = new internal::SrsThread(n, this, interval_us, true);
}

4. SrsRtmpConn::do_publishing

int SrsRtmpConn::do_publishing(SrsSource* source, SrsPublishRecvThread* trd)
{
int ret = ERROR_SUCCESS; /**
* SrsPithyPrint:
* the stage is used for a collection of object to do print,
* the print time in a stage is constant and not changed,
* that is, we always got one message to print every specified time.
*
* for example, stage #1 for all play clients, print time is 3s,
* if there is 1client, it will print every 3s.
* if there is 10clients, random select one to print every 3s.
* Usage:
* SrsPithyPrint* pprint = SrsPithyPrint::create_rtmp_play();
* SrsAutoFree(SrsPithyPrint, pprint);
* while (true) {
* pprint->elapse();
* if (pprint->can_print()) {
* // print pithy message.
* // user can get the elapse time by: pprint->age()
* }
* // read and write RTMP messages.
* }
*/
SrsPithyPrint* pprint = SrsPithyPrint::create_rtmp_publish();
SrsAutoFree(SrsPithyPrint, pprint); /* 构建并启动一个专门用于接收客户端推流数据的 recv 线程 */
// 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_cid()); // initialize the publish timeout.
/* 若没有配置 publish_1stpkt_timeout,则当没有收到消息时使用默认超时时间 20000ms */
publish_1stpkt_timeout = _srs_config->get_publish_1stpkt_timeout(req->vhost);
/* 若没有配置 publish_normal_timeout,则当已经收到过消息时使用默认超时时间 5000ms */
publish_normal_timeout = _srs_config->get_publish_normal_timeout(req->vhost); // set the sock options.
set_sock_options(); if (true) {
/* vhost{} 中是否启用了 mr 功能,否返回 false */
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_msgd = 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_error("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;
} // when not got any messages, timeout.
if (trd->nb_msgs() <= nb_msgs) {
ret = ERROR_SOCKET_TIMEOUT;
srs_warn("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;
}

4.1 SrsPithyPrint::create_rtmp_publish

为 rtmp publish 构造一个 SrsPithyPrint 类。

SrsPithyPrint* SrsPithyPrint::create_rtmp_publish()
{
return new SrsPithyPrint(SRS_CONSTS_STAGE_PUBLISH_USER);
}

4.1.1 SrsPithyPrint 构造函数

SrsPithyPrint::SrsPithyPrint(int _stage_id)
{
stage_id = _stage_id;
client_id = enter_stage();
previous_tick = srs_get_system_time_ms();
_age = 0;
}

4.1.2 SrsPithyPrint::enter_stage

int SrsPithyPrint::enter_stage()
{
SrsStageInfo* stage = NULL; std::map<int, SrsStageInfo*>::iterator it = _srs_stages.find(stage_id);
if (it == _srs_stages.end()) {
stage = new SrsStageInfo(stage_id);
_srs_stages[stage_id] = stage;
} else {
stage = it->second;
} srs_assert(stage != NULL);
client_id = stage->nb_clients++; srs_verbose("enter stage, stage_id=%d, client_id=%d, nb_clients=%d, time_ms=%d",
stage->stage_id, client_id, stage->nb_clients, stage->pithy_print_time_ms); return client_id;
}

4.2 SrsPublishRecvThread::start: 启动线程

int SrsPublishRecvThread::start()
{
/* 调用 SrsRecvThread::start() */
int ret = trd.start();
ncid = cid = trd.cid();
return ret;
}

4.2.1 SrsRecvThread::start

int SrsRecvThread::start()
{
/* 接着调用 SrsReusableThread2::start() */
return trd->start();
}

4.2.2 SrsReusableThread2::start

/* for the reusable thread, start and stop by user. */
int SrsReusableThread2::start()
{
/* 调用 SrsThread::start() */
return pthread->start();
}

4.2.3 SrsThread::start

int SrsThread::start()
{
int ret = ERROR_SUCCESS; if (tid) {
srs_info("thread %s already running.", _name);
return ret;
} if ((tid = st_thread_create(thread_fun, this, (joinable? 1:0), 0)) == NULL) {
ret = ERROR_ST_CREATE_CYCLE_THREAD;
srs_error("st_thread_create failed. ret=%d", ret);
return ret;
} disposed = false;
// we set to loop to true for thread to run.
loop = true; // wait for cid to ready, for parent thread to get the cid.
while (_cid < 0) {
st_usleep(10 * 1000);
} // now, cycle thread can run.
can_run = true; return ret;
}

以上 Thread 类之间的关系图

4.3 SrsRtmpConn::set_sock_options

void SrsRtmpConn::set_sock_options()
{
/* 若配置文件中的 vhost{} 没有配置 tcp_nodelay 配置项,默认返回 false */
bool nvalue = _srs_config->get_tcp_nodelay(req->vhost);
if (nvalue != tcp_nodelay) {
tcp_nodelay = nvalue;
#ifdef SRS_PERF_TCP_NODELAY
int fd = st_netfd_fileno(stfd); socklen_t nb_v = sizeof(int); int ov = 0;
getsocket(fd, IPPROTO_TCP, TCP_NODELAY, &ov, &nb_v); int v = tcp_nodelay;
// set the socket send buffer when required larger buffer
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &v, nb_v) < 0) {
srs_warn("set sock TCP_NODELAY=%d failed.", v);
}
getsocket(fd, IPPROTO_TCP, TCP_NODELAY, &v, &nb_v); srs_trace("set TCP_NODELAY %d=>%d", ov, v);
#else
srs_warn("SRS_PERF_TCP_NODELAY is disabled but tcp_nodelay configed.");
#endif
}
}

4.4 SrsPithyPrint::elapse

/* auto calc the elapse time */
void SrsPithyPrint::elapse()
{
SrsStageInfo* stage = _srs_stages[stage_id];
srs_assert(stage != NULL); int64_t diff = srs_get_system_time_ms() - previous_tick;
diff = srs_max(0, diff); stage->elapse(diff);
_age += diff;
previous_tick = srs_get_system_time_ms();
} void SrsStageInfo::elapse(int64_t diff)
{
age += diff;
}

4.5 SrsPublishRecvThread::wait

int SrsPublishRecvThread::wait(int timeout_ms)
{
/* 若已经发生错误,则直接返回错误码 */
if (recv_error_code != ERROR_SUCCESS) {
return recv_error_code;
} // ignore any return of cond wait.
st_cond_timedwait(error, timeout_ms * 1000); return ERROR_SUCCESS;
}

4.5.1 st_cond_timedwait

int st_cond_timedwait(_st_cond_t* cvar, st_utime_t timeout)
{
_st_thread_t *me = _ST_CURRENT_THREAD();
int rv; if (me->flags & _ST_FL_INTERRUPT) {
me->flags &= ~_ST_FL_INTERRUPT;
errno = EINTR;
return -1;
} /* Put caller thread on the condition variable's wait queue */
me->state = _ST_ST_COND_WAIT;
ST_APPEND_LINK(&me->wait_links, &cvar->wait_q); /* 当超时时间不为 -1 时,将当前线程添加到 sleep 队列中 */
if (timeout != ST_UTIME_NO_TIMEOUT)
_ST_ADD_SLEEPQ(me, timeout); /* 然后切换上下文环境,直到被条件变量唤醒或是超时事件到了而醒来 */
_ST_SWITCH_CONTEXT(me); ST_REMOVE_LINK(&me->wait_links);
rv = 0; if (me->flags & _ST_FL_TIMEDOUT) {
me->flags &= ~_ST_FL_TIMEDOUT;
errno = ETIME;
rv = -1;
}
if (me->flags & _ST_FL_INTERRUPT) {
me->flags &= ~_ST_FL_INTERRUPT;
errno = EINTR;
rv = -1;
} return rv;
}

4.5.2 ST_APPEND_LINK

/* Insert an element "_e" at the end of the list "_l" */
#define ST_APPEND_LINK(_e, _l) ST_INSERT_BEFORE(_e, _l) /* Insert element "_e" into the list, before "_l" */
#define ST_INSERT_BEFORE(_e, _l) \
ST_BEGIN_MACRO \
(_e)->next = (_l); \
(_e)->prev = (_l)->prev; \
(-l)->prev->next = (_e); \
(-l)->prev = (_e); \
ST_END_MACRO

4.6 SrsStatistic::on_video_frames

/* When got videos, update the frames.
* We only stat the total number of video frames. */
int SrsStatistic::on_video_frames(SrsRequest* req, int nb_frames)
{
int ret = ERROR_SUCCESS; SrsStatisticVhost* vhost = create_vhost(req);
SrsStatisticStream* stream = create_stream(vhost, req); stream->nb_frames += nb_frames; return ret;
}

5. SrsPublishRecvThread::stop

当从 do_publishing 函数返回时,终止接收推流数据的 recv 线程。

void SrsPublishRecvThread::stop()
{
trd.stop();
}

5.1 SrsRecvThread::stop

void SrsRecvThread::stop()
{
trd->stop();
}

5.2 SrsReusableThread2::stop

void SrsReusableThread2::stop()
{
pthread->stop();
}

5.3 SrsThread::stop

void SrsThread::stop()
{
if (!tid) {
return;
} /* 设置线程循环标志,退出循环 */
loop = false; dispose(); _cid = -1;
can_run = false;
tid = NULL;
}

5.4 SrsThread::dispose

void SrsThread::dispose()
{
if (disposed) {
return;
} // the interrupt will cause the socket to read/write error,
// which will terminate the cycle thread.
st_thread_interrupt(tid); // when joinable, wait util quit.
if (_joinable) {
// wait the thread to exit.
int ret = st_thread_join(tid, NULL);
if (ret) {
srs_warn("core: ignore join thread failed.");
}
} // wait the thread actually terminated.
// sometimes the thread join return -1, for example,
// when thread use st_recvfrom, the thread join return -1.
// so here, we use a variable to ensure the thread stopped.
// @remark event the thread not joinable, we must ensure the thread stopped when stop.
while (!really_terminated) {
st_usleep(10 * 1000); if (really_terminated) {
break;
}
srs_warn("core: wait thread to actually terminated");
} disposed = true;
}

5.4.1 st_thread_interrupt

void st_thread_interrupt(_st_thread_t *thread)
{
/* If thread is already dead */
if (thread->state == _ST_ST_ZOMEBIE)
return; /* 设置线程标志为 interrupt */
thread->flags |= _ST_FL_INTERRUPT; /* 若线程当前正在运行或已在 run 队列中,则直接返回,在线程函数会检测
* 线程的 flags,若发现是 interrput,则会终止线程函数的执行 */
if (thread->state == _ST_ST_RUNNING || thread->state == _ST_ST_RUNNABLE)
return; /* 否则,若线程当前处于 sleep 队列中,即休眠状态,则将该线程从
* 休眠队列中移除 */
if (thread->flags & _ST_FL_ON_SLEEPQ)
_ST_DEL_SLEEPQ(thread); /* Make thread runnable */
thread->state = _ST_ST_RUNNABLE;
_ST_ADD_RUNQ(thread);
}

5.4.2 st_thread_join

int st_thread_join(_st_thread_t *thread, void **retvalp)
{
_st_cond_t *term = thread_term; /* Can't join a non-joinable thread */
if (term == NULL) {
errno = EINVAL;
return -1;
}
if (_ST_CURRENT_THREAD() == thread) {
errno = EDEADLK;
return -1;
} /* Multiple threads can't wait on the same joinable thread */
if (term->wait_q.next != &term->wait_q) {
errno = EINVAL;
return -1;
} while (thread->state != _ST_ST_ZOMBIE) {
if (st_cond_timedwait(term, ST_UTIME_NO_TIMEOUT) != 0)
return -1;
} if (retvalp)
*retvalp = thread->retval; /*
* Remove target thread from the zombie queue and make it runnable.
* When it gets scheduled later, it will do the clean up.
*/
thread->state = _ST_ST_RUNNABLE;
_ST_DEL_ZOMBIEQ(thread);
_ST_ADD_RUNQ(thread); return 0;
}

SRS之SrsRtmpConn::publishing详解的更多相关文章

  1. SRS之SrsRtmpConn::stream_service_cycle详解

    首先使用 obs 推流符合如下流程:参考自 Hanvision Makito X cann't publish to SRS.. FFMPEG: C/S: Handshake C: ConnectAp ...

  2. SRS之SrsRtmpConn::service_cycle详解

    1. SrsRtmpConn::service_cycle 当服务器在 conn 线程的开始调用 connect_app 函数接收并解析客户端发送的 connect 消息后,调用该 service_c ...

  3. SRS之SrsHls::on_audio详解

    1. SrsHls::on_audio 将音频数据封装到 ts 文件中. /* * mux the audio packet to ts. * @param shared_audio, directl ...

  4. SRS之SrsHlsCache::reap_segment详解

    1. 是否可切片的检测 首先在调用 SrsHlsCache::reap_segment 函数进行切片时,针对音频或视频,都会有一个函数来进行检测当前片的时长是否符合所要求的时长. 对于音频,会调用 S ...

  5. SRS之SrsHls::on_video详解

    1. SrsHls::on_video /* * mux the video packets to ts. * @param shared_video, directly ptr, copy it i ...

  6. SRS之SrsTsContext::encode_pes详解

    1. SrsTsContext::encode_pes 该函数位于 srs_kernel_ts.cpp 中.下面的分析基于假设当前要封装的消息是视频. /* * @msg: 要写入到 ts 文件中的音 ...

  7. SRS之SrsRtmpServer::connect_app详解

    1. connect('live') 2. SrsRtmpServer::connect_app 位于 srs_rtmp_stack.cpp.在 SRS 的 RTMP 连接处理线程 conn 中,当与 ...

  8. linux查看端口及端口详解

    今天现场查看了TCP端口的占用情况,如下图   红色部分是IP,现场那边问我是不是我的程序占用了tcp的链接,,我远程登陆现场查看了一下,这种类型的tcp链接占用了400多个,,后边查了一下资料,说E ...

  9. 网络-02-端口号-linux端口详解大全

    端口详解 1 tcpmux TCP Port Service Multiplexer 传输控制协议端口服务多路开关选择器  2 compressnet Management Utility compr ...

随机推荐

  1. 【持续集成工具】 Jenkins

    一.什么是持续集成 持续集成(CI):简单来说就是指将开发者的工作内容频繁地集成到主干中. 而持续集成工具可以将开发者频繁需要构建,编译,测试,部署等操作自动进行,为开发提供了非常大便利. 二.持续集 ...

  2. mint-ui下拉加载min和上拉刷新(demo实例)

    <template> <div class="share"> <div class="header"> <div cl ...

  3. PHP删除字符串中的空格和换行符 将字符串中的连续多个空格转换为一个空格

    //删除空格和回车 function trimall($str){ $qian=array(" "," ","\t","\n&qu ...

  4. fragment事务 的基本处理

    处理fragment事务 动态加载fragmentMyFragment2 fragment2=new MyFragment2();//new出一个fragment对象FragmentManager f ...

  5. three.js之创建一个几何体

    <html> <head> <title>My first three.js app</title> <style> body { marg ...

  6. linux基础—课堂随笔05_文本三剑客之SED

    1.简介 sed是非交互式的编辑器,它不会修改文件,除非使用shell重定向来保存结果.默认情况下,所有的输出行都被打印到屏幕上. sed编辑器逐行处理文件(或输入),并将结果发送到屏幕.具体过程如下 ...

  7. c++中cin和cout的用法

    cin和cout是c++中的标准输入输出流. 一.cin cin的一般用法: cin>>变量a>>变量b>>变量c; cin会自动辨别变量的类型,如a可以是char ...

  8. IntelliJ IDEA安装后几个重要的目录及配置文件讲解

    本文大概记录了IntelliJ IDEA安装之后比较重要的目录和几个核心文件 重要的安装目录 安装完之后的bin目录大致如下 IntelliJ IDEA 的安装目录并不复杂,上图为最常改动的 bin ...

  9. Invalid character found in the request target. The valid characters are defined in RFC 7230 and RF

    SpringBoot 请求参数包含 [] 特殊符号 返回400状态 //springBoot 启动类 添加 bean @Bean public TomcatServletWebServerFactor ...

  10. 第八章 watch监听 83 名称案例-使用watch监听文本框数据的变化

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8&quo ...