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回调接口。