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;
}
接着调用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 copy = tasks;
tasks.clear();
std::vector::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 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回调接口。