SRS 代码分析【DVR录像实现】
来源:互联网 发布:linux 访问smb 编辑:程序博客网 时间:2024/06/05 02:26
DVR配置详解:https://github.com/ossrs/srs/wiki/v3_CN_DVR
推流到源站时会调用SrsOrginHub对象提供的initialize, on_publish, on_metadata, on_video, on_audio,on_unpublish函数。
1.SrsOriginHub::initialize初始化时会调用SrsDvr::initialize
srs_error_t SrsDvr::initialize(SrsOriginHub* h, SrsRequest* r){ srs_error_t err = srs_success; req = r; hub = h; SrsConfDirective* conf = _srs_config->get_dvr_apply(r->vhost); actived = srs_config_apply_filter(conf, r); srs_freep(plan); if ((err = SrsDvrPlan::create_plan(r->vhost, &plan)) != srs_success) { return srs_error_wrap(err, "create plan"); } std::string path = _srs_config->get_dvr_path(r->vhost); SrsDvrSegmenter* segmenter = NULL; if (srs_string_ends_with(path, ".mp4")) { segmenter = new SrsDvrMp4Segmenter(); } else { segmenter = new SrsDvrFlvSegmenter(); } if ((err = plan->initialize(hub, segmenter, r)) != srs_success) { return srs_error_wrap(err, "plan initialize"); } return err;}
首先调用SrvDvrPlan::create_plan根据配置文件来确定使用session(整个流录制成一个文件SrsDvrSessionPlan)还是segment(按照时长分段录制成多个文件SrsDvrSegmentPlan )的计划,这里只分析SrsDvrSessionPlan。
接着创建SrsDvrSegmenter,这里只分析flv文件录像SrsDvrFlvSegementer。
最后调用plan->initialize。SrsDvrSessionPlan直接使用基类的SrsDvrPlan::initialize;SrsDvrSegmentPlan重写了initialize方法在调用完SrsDvrPlan::initialize后接着从config文件中读取wait_keyframe, cduration配置参数。
SrsDvrPlan::initialize实现如下
srs_error_t SrsDvrPlan::initialize(SrsOriginHub* h, SrsDvrSegmenter* s, SrsRequest* r){ srs_error_t err = srs_success; hub = h; req = r; segment = s; if ((err = segment->initialize(this, r)) != srs_success) { return srs_error_wrap(err, "segmenter"); } if ((err = async->start()) != srs_success) { return srs_error_wrap(err, "async"); } return err;}
SrsDvrPlan::initialize首先调用SrsDvrSegmenter::initialize获取配置文件中的jitter_algorithm, wait_keyframe两个参数
接着调用async->start()开启协程,async是一个任务处理的协程。最终进入协程的循环处理中。
srs_error_t SrsAsyncCallWorker::cycle(){ srs_error_t err = srs_success; while (true) { if ((err = trd->pull()) != srs_success) { return srs_error_wrap(err, "async call worker"); } if (tasks.empty()) { srs_cond_wait(wait); } std::vector<ISrsAsyncCallTask*> copy = tasks; tasks.clear(); std::vector<ISrsAsyncCallTask*>::iterator it; for (it = copy.begin(); it != copy.end(); ++it) { ISrsAsyncCallTask* task = *it; int ret = ERROR_SUCCESS; if ((ret = task->call()) != ERROR_SUCCESS) { srs_warn("ignore async callback %s, ret=%d", task->to_string().c_str(), ret); } srs_freep(task); } } return err;}
2.SrsOriginHub::on_publish发布流时会调用SrsDvr::on_publish()
int SrsDvr::on_publish(){ int ret = ERROR_SUCCESS; // the dvr for this stream is not actived. if (!actived) { return ret; } if ((ret = plan->on_publish()) != ERROR_SUCCESS) { return ret; } return ret;}这里只分析SrsDvrSessionPlan的on_publish,代码如下
int SrsDvrSegmentPlan::on_publish(){ int ret = ERROR_SUCCESS; // support multiple publish. if (dvr_enabled) { return ret; } if (!_srs_config->get_dvr_enabled(req->vhost)) { return ret; } if ((ret = segment->close()) != ERROR_SUCCESS) { return ret; } if ((ret = segment->open()) != ERROR_SUCCESS) { return ret; } dvr_enabled = true; return ret;}调用close关闭已经打开的文件,接着生成文件路径,调用fragment->create_dir创建路径,最后调用fs->open打开一个包含临时的flv tmp文件。
int SrsDvrSegmenter::open(){ int ret = ERROR_SUCCESS; // ignore when already open. if (fs->is_open()) { return ret; } string path = generate_path(); if (srs_path_exists(path)) { ret = ERROR_DVR_CANNOT_APPEND; srs_error("DVR can't append to exists path=%s. ret=%d", path.c_str(), ret); return ret; } fragment->set_path(path); // create dir first. if ((ret = fragment->create_dir()) != ERROR_SUCCESS) { return ret; } // create jitter. srs_freep(jitter); jitter = new SrsRtmpJitter(); // open file writer, in append or create mode. string tmp_dvr_file = fragment->tmppath(); if ((ret = fs->open(tmp_dvr_file)) != ERROR_SUCCESS) { srs_error("open file stream for file %s failed. ret=%d", path.c_str(), ret); return ret; } // initialize the encoder. if ((ret = open_encoder()) != ERROR_SUCCESS) { srs_error("initialize enc by fs for file %s failed. ret=%d", path.c_str(), ret); return ret; } srs_trace("dvr stream %s to file %s", req->stream.c_str(), path.c_str()); return ret;}该函数调用open_encoder实际调用的是SrsDvrFlvSegementer::open_encoder
int SrsDvrFlvSegmenter::open_encoder(){ int ret = ERROR_SUCCESS; has_keyframe = false; // update the duration and filesize offset. duration_offset = 0; filesize_offset = 0; srs_freep(enc); enc = new SrsFlvTransmuxer(); if ((ret = enc->initialize(fs)) != ERROR_SUCCESS) { return ret; } // write the flv header to writer. if ((ret = enc->write_header()) != ERROR_SUCCESS) { srs_error("write flv header failed. ret=%d", ret); return ret; } return ret;}该函数首先创建flv 编码器对象SrsFlvTransmuxer,通过调用enc->initialize方法将SrsFileWritter(打开的flv文件生成的)与编码器关联,最后调用enc->write_header将flv文件头部写入。
3.SrsOriginHub::on_meta_data接收到metadata时会调用SrsDvrPlan::on_meta_data
int SrsDvrPlan::on_meta_data(SrsSharedPtrMessage* shared_metadata){ int ret = ERROR_SUCCESS; if (!dvr_enabled) { return ret; } return segment->write_metadata(shared_metadata);}
int SrsDvrSegmenter::write_metadata(SrsSharedPtrMessage* metadata){ return encode_metadata(metadata);}该函数调用encode_metadata实际调用的是SrsDvrFlvSegementer::encode_metadata
int SrsDvrFlvSegmenter::encode_metadata(SrsSharedPtrMessage* metadata){ int ret = ERROR_SUCCESS; // Ignore when metadata already written. if (duration_offset || filesize_offset) { return ret; } SrsBuffer stream; if ((ret = stream.initialize(metadata->payload, metadata->size)) != ERROR_SUCCESS) { return ret; } SrsAmf0Any* name = SrsAmf0Any::str(); SrsAutoFree(SrsAmf0Any, name); if ((ret = name->read(&stream)) != ERROR_SUCCESS) { return ret; } SrsAmf0Object* obj = SrsAmf0Any::object(); SrsAutoFree(SrsAmf0Object, obj); if ((ret = obj->read(&stream)) != ERROR_SUCCESS) { return ret; } // remove duration and filesize. obj->set("filesize", NULL); obj->set("duration", NULL); // add properties. obj->set("service", SrsAmf0Any::str(RTMP_SIG_SRS_SERVER)); obj->set("filesize", SrsAmf0Any::number(0)); obj->set("duration", SrsAmf0Any::number(0)); int size = name->total_size() + obj->total_size(); char* payload = new char[size]; SrsAutoFreeA(char, payload); // 11B flv header, 3B object EOF, 8B number value, 1B number flag. duration_offset = fs->tellg() + size + 11 - SrsAmf0Size::object_eof() - SrsAmf0Size::number(); // 2B string flag, 8B number value, 8B string 'duration', 1B number flag filesize_offset = duration_offset - SrsAmf0Size::utf8("duration") - SrsAmf0Size::number(); // convert metadata to bytes. if ((ret = stream.initialize(payload, size)) != ERROR_SUCCESS) { return ret; } if ((ret = name->write(&stream)) != ERROR_SUCCESS) { return ret; } if ((ret = obj->write(&stream)) != ERROR_SUCCESS) { return ret; } // to flv file. if ((ret = enc->write_metadata(18, payload, size)) != ERROR_SUCCESS) { return ret; } return ret;}该函数首先解析接收到的metadata,然后将filesize和duration置空,增加一些属性,接着调用enc->write_metadata将数据写入到流中。
该函数还会记录下duration_offset, filesize_offset的位置,在录像结束获取到视频的大小和时长时能够再次进行更新。
4.SrsOriginHub::on_video接收到视频数据时会调用SrsDvrPlan::on_video
int SrsDvrPlan::on_video(SrsSharedPtrMessage* shared_video, SrsFormat* format){ int ret = ERROR_SUCCESS; if (!dvr_enabled) { return ret; } if ((ret = segment->write_video(shared_video, format)) != ERROR_SUCCESS) { return ret; } return ret;}
int SrsDvrSegmenter::write_video(SrsSharedPtrMessage* shared_video, SrsFormat* format){ int ret = ERROR_SUCCESS; SrsSharedPtrMessage* video = shared_video->copy(); SrsAutoFree(SrsSharedPtrMessage, video); if ((jitter->correct(video, jitter_algorithm)) != ERROR_SUCCESS) { return ret; } if ((ret = encode_video(video, format)) != ERROR_SUCCESS) { return ret; } if ((ret = on_update_duration(video)) != ERROR_SUCCESS) { return ret; } return ret;}该函数调用encode_video实际调用的是SrsDvrFlvSegementer::encode_video
int SrsDvrFlvSegmenter::encode_video(SrsSharedPtrMessage* video, SrsFormat* format){ int ret = ERROR_SUCCESS; char* payload = video->payload; int size = video->size; bool sh = (format->video->avc_packet_type == SrsVideoAvcFrameTraitSequenceHeader); bool keyframe = (!sh && format->video->frame_type == SrsVideoAvcFrameTypeKeyFrame); if (keyframe) { has_keyframe = true; } // accept the sequence header here. // when got no keyframe, ignore when should wait keyframe. if (!has_keyframe && !sh) { if (wait_keyframe) { srs_info("dvr: ignore when wait keyframe."); return ret; } } if ((ret = enc->write_video(video->timestamp, payload, size)) != ERROR_SUCCESS) { return ret; } return ret;}该函数将调用enc->write_metadata将数据写入。
4.SrsOriginHub::on_audio接收到音频数据时会调用SrsDvrPlan::on_audio,on_audio的处理过程与on_video类似这里不做分析了。
5.SrsOriginHub::on_unpublish取消发布时会调用SrsDvrPlan::on_unpublish,实际调用的是SrsDvrSessionPlan::on_unpublish
void SrsDvrSessionPlan::on_unpublish(){ // support multiple publish. if (!dvr_enabled) { return; } // ignore error. int ret = segment->close(); if (ret != ERROR_SUCCESS) { srs_warn("ignore flv close error. ret=%d", ret); } dvr_enabled = false;}
int SrsDvrSegmenter::close(){ int ret = ERROR_SUCCESS; // ignore when already closed. if (!fs->is_open()) { return ret; } // Close the encoder, then close the fs object. if ((ret = close_encoder()) != ERROR_SUCCESS) { return ret; } fs->close(); // when tmp flv file exists, reap it. if ((ret = fragment->rename()) != ERROR_SUCCESS) { return ret; } // TODO: FIXME: the http callback is async, which will trigger thread switch, // so the on_video maybe invoked during the http callback, and error. if ((ret = plan->on_reap_segment()) != ERROR_SUCCESS) { srs_error("dvr: notify plan to reap segment failed. ret=%d", ret); return ret; } return ret;}该函数会调用close_encoder关闭编码器,调用fs->close关闭打开的文件,接着调用fragment->rename将tmp flv 文件重命名为不带tmp的文件。
close_encoder()会调用refresh_metadata()完成metadata中duration,filesize属性的更新。
int SrsDvrFlvSegmenter::refresh_metadata(){ int ret = ERROR_SUCCESS; // no duration or filesize specified. if (!duration_offset || !filesize_offset) { return ret; } int64_t cur = fs->tellg(); // buffer to write the size. char* buf = new char[SrsAmf0Size::number()]; SrsAutoFreeA(char, buf); SrsBuffer stream; if ((ret = stream.initialize(buf, SrsAmf0Size::number())) != ERROR_SUCCESS) { return ret; } // filesize to buf. SrsAmf0Any* size = SrsAmf0Any::number((double)cur); SrsAutoFree(SrsAmf0Any, size); stream.skip(-1 * stream.pos()); if ((ret = size->write(&stream)) != ERROR_SUCCESS) { return ret; } // update the flesize. fs->seek2(filesize_offset); if ((ret = fs->write(buf, SrsAmf0Size::number(), NULL)) != ERROR_SUCCESS) { return ret; } // duration to buf SrsAmf0Any* dur = SrsAmf0Any::number((double)fragment->duration() / 1000.0); SrsAutoFree(SrsAmf0Any, dur); stream.skip(-1 * stream.pos()); if ((ret = dur->write(&stream)) != ERROR_SUCCESS) { return ret; } // update the duration fs->seek2(duration_offset); if ((ret = fs->write(buf, SrsAmf0Size::number(), NULL)) != ERROR_SUCCESS) { return ret; } // reset the offset. fs->seek2(cur); return ret;}
生成完录像对象后,调用plan->on_reap_segment通过http回调将录像的文件路径通知出去。
int SrsDvrPlan::on_reap_segment(){ int ret = ERROR_SUCCESS; int cid = _srs_context->get_id(); SrsFragment* fragment = segment->current(); string fullpath = fragment->fullpath(); if ((ret = async->execute(new SrsDvrAsyncCallOnDvr(cid, req, fullpath))) != ERROR_SUCCESS) { return ret; } return ret;}该函数实际将SrsDvrAsyncCallOnDvr做为task投递到SrsAsyncCallWorker::cycle()中去执行,最终调用SrsDvrAsyncCallOnDvr::call
int SrsDvrAsyncCallOnDvr::call(){ int ret = ERROR_SUCCESS; if (!_srs_config->get_vhost_http_hooks_enabled(req->vhost)) { return ret; } // the http hooks will cause context switch, // so we must copy all hooks for the on_connect may freed. // @see https://github.com/ossrs/srs/issues/475 vector<string> hooks; if (true) { SrsConfDirective* conf = _srs_config->get_vhost_on_dvr(req->vhost); if (!conf) { srs_info("ignore the empty http callback: on_dvr"); return ret; } hooks = conf->args; } for (int i = 0; i < (int)hooks.size(); i++) { std::string url = hooks.at(i); if ((ret = SrsHttpHooks::on_dvr(cid, url, req, path)) != ERROR_SUCCESS) { srs_error("hook client on_dvr failed. url=%s, ret=%d", url.c_str(), ret); return ret; } } return ret;}该函数调用Http回调接口。
- SRS 代码分析【DVR录像实现】
- SRS 代码分析【HTTP-FLV传输实现】
- SRS 代码分析【转发流实现】
- SRS 代码分析【RTMP握手实现】
- SRS 代码分析
- srs之dvr
- SRS 代码分析【服务器启动】
- SRS 代码分析【HLS切片】
- SRS 代码分析【RTMP连接请求响应】
- SRS 代码分析【FLV文件解析】
- SRS 代码分析【mpeg-ts解析】
- SRS 代码分析【RTMP信息play/publish】
- SRS 代码分析【RTMP Chunck数据读取】
- SRS 代码分析【RTMP Chunck数据发送】
- SRS 代码分析【保存AAC音频文件】
- SRS 代码分析【保存MP3音频文件】
- srs代码学习(6)--如何实现edge
- srs 日志分析---spark
- Java参数传递类型有几种
- Linux 网络编程
- 滑动条缩放图片
- Java Web中的Action、Dao、Service、Model
- 封装OKHttp,工具类
- SRS 代码分析【DVR录像实现】
- 为什么要用枚举实现Singleton--java
- linux_安装CDN
- 浅谈C/C++排序函数中cmp()比较函数的写法
- from表单+地区
- eclipse打点没提示原因
- 菜鸟备忘录[CSS3]——媒体查询使用方法@media
- CodeForces 830C Bamboo Partition
- SpringAOP整合Hibernate并使用事务(模拟买书的过程)