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/237SrsPublishRecvThread 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 threadtrd.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_TRANSCODEif ((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_HLSif ((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_DVRif ((ret = dvr->on_publish(_req)) != ERROR_SUCCESS) {srs_error("start dvr failed. ret=%d", ret);return ret;}
#endif#ifdef SRS_AUTO_HDSif ((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_SERVERif ((ret = http_server->http_mount(s, r)) != ERROR_SUCCESS) {return ret;}
#endifreturn 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/241mr = _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/441trd->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/851SrsStatistic* 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();// reportableif (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_NODELAYint 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 bufferif (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);
#elsesrs_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;
}

转载于:https://www.cnblogs.com/jimodetiantang/p/9087775.html

SRS之SrsRtmpConn::publishing详解相关推荐

  1. SRS之SrsHlsCache::reap_segment详解

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

  2. LTE参考信号CRS、DRS、SRS、DMRS详解

    LTE参考信号CRS.DRS.SRS.DMRS详解 SRS:Sounding Reference Signal(上行探测参考信号) 作用:上行信道估计,选择MCS和上行频率选择性调度,TDD系统中,估 ...

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

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

  4. Apache2 httpd.conf配置文件中文版详解

    Apache2 httpd.conf配置文件中文版详解 # # 基于 NCSA 服务的配置文件. # #这是Apache服务器主要配置文件. #它包含服务器的影响服务器运行的配置指令. #参见< ...

  5. 通用线程:POSIX 线程详解,第 2部分——称作互斥对象的小玩意

    通用线程:POSIX 线程详解,第 2部分--称作互斥对象的小玩意 Daniel Robbins (drobbins@gentoo.org), 总裁/CEO, Gentoo Technologies, ...

  6. 详解MariaDB数据库的事务

    1.什么是事务 数据库事务:(database transaction): 事务是由一组SQL语句组成的逻辑处理单元,一组事务中的SQL语句要不全部执行成功功:如果其中某一条执行失败,则这组SQL语句 ...

  7. NodeJs学习笔记002--npm常用命令详解

    npm 常用命令详解 npm是什么 npm install 安装模块 npm uninstall 卸载模块 npm update 更新模块 npm outdated 检查模块是否已经过时 npm ls ...

  8. FFmpeg入门详解之83:流媒体与直播技术

    流媒体 流媒体又叫流式媒体,它是指商家用一个视频传送服务器(比如:vlc)把节目(比如:ande10.mp4)当成数据包发出,传送到网络上.用户通过解压设备对这些数据进行解压后,节目就会像发送前那样显 ...

  9. FFmpeg入门详解之117:视频监控的架构和流程

    几张架构图带您快速了解视频监控 图一 图二 图三 图四 视频监控系统的简介 视频监控 视频监控是安全防范系统的重要组成部分,英文Cameras and Surveillance.传统的监控系统包括前端 ...

  10. R统计绘图-PCA详解1(princomp/principal/prcomp/rda等)

    此文为<精通机器学习:基于R>的学习笔记,书中第九章详细介绍了无监督学习-主成分分析(PCA)的分析过程和结果解读. PCA可以对相关变量进行归类,从而降低数据维度,提高对数据的理解.分析 ...

最新文章

  1. 【C++】二叉树的先序、中序、后序遍历序列
  2. docker build 指定dockerfile
  3. 请允许我悄悄的爱你一次好吗 zz
  4. 启动tomcat时,一直卡在Deploying web application directory这块的解决方案
  5. python文件分发_python 写一个文件分发小程序
  6. 【STM32】跑马灯实验主要程序代码分析
  7. 《ANSYS 14热力学/电磁学/耦合场分析自学手册》——2.8 工具条
  8. 关于axios+spring boot无法进行重定向的问题
  9. 区块链研习 | 区块链的能力很大又很小
  10. row_number() over()排序功能
  11. 11.05T2 线段树+卡特兰数
  12. and/or(||)的理解
  13. php 数组任意位置插入值
  14. C++day02 学习笔记
  15. 微信开发-点击链接自己主动加入关注
  16. python 数学公式显示_python 数学公式·
  17. 企业招聘软件测试笔试题,奇虎软件测试工程师招聘面试笔试题题
  18. staircase nim 经典组合游戏
  19. Affine-Transformation Parameters Regression for Face Alignment
  20. excel查找并返回多行数据

热门文章

  1. java当前类路径_Java取得当前类的路径
  2. 区块链 solana TPS吞吐率
  3. zotero mac 插入BibTeX条目 快捷键
  4. 【Django 2021年最新版教程22】数据库model 批量插入创建
  5. 云南民大java期中考试_中南民族大学Java语言程序设计期末试卷A卷
  6. jQuery基础(jQuery概念,jQuery与js入口函数的区别及其入口函数的其他写法和冲突问题)
  7. CentOS 6.5 端口转发
  8. php5.3 PHP5.4 PHP5.5 php5.6 新特性/使用PHP5.5/PHP5.6要注意的
  9. Mysql 基础操作:DDL、DML、CRUD 与 常用命令
  10. Linux 命令之 yum 软件仓库