diff --git a/trunk/conf/dvr.mp4.conf b/trunk/conf/dvr.mp4.conf new file mode 100644 index 0000000000..dccb8c3d64 --- /dev/null +++ b/trunk/conf/dvr.mp4.conf @@ -0,0 +1,13 @@ +# the config for srs to dvr in session mode +# @see https://github.com/ossrs/srs/wiki/v3_CN_DVR +# @see full.conf for detail config. + +listen 1935; +max_connections 1000; +vhost __defaultVhost__ { + dvr { + enabled on; + dvr_path ./objs/nginx/html/[app]/[stream].[timestamp].mp4; + dvr_plan session; + } +} diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index c43f6bd08e..800722e72b 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -1103,9 +1103,9 @@ vhost hds.srs.com { # vhost for dvr vhost dvr.srs.com { - # dvr RTMP stream to file, + # DVR RTMP stream to file, # start to record to file when encoder publish, - # reap flv according by specified dvr_plan. + # reap flv/mp4 according by specified dvr_plan. dvr { # whether enabled dvr features # default: off @@ -1118,12 +1118,13 @@ vhost dvr.srs.com { # default: all dvr_apply all; # the dvr plan. canbe: - # session reap flv when session end(unpublish). - # segment reap flv when flv duration exceed the specified dvr_duration. + # session reap flv/mp4 when session end(unpublish). + # segment reap flv/mp4 when flv duration exceed the specified dvr_duration. # append always append to flv file, never reap it. + # @remark MP4 only support session or segment plan, not suport append plan. # default: session dvr_plan session; - # the dvr output path. + # the dvr output path, *.flv or *.mp4. # we supports some variables to generate the filename. # [vhost], the vhost of stream. # [app], the app of stream. @@ -1154,6 +1155,10 @@ vhost dvr.srs.com { # dvr_path /data/[vhost]/[app]/[2006]/[01]/[stream]-[02]-[15].[04].[05].[999].flv; # => # dvr_path /data/ossrs.net/live/2015/01/livestream-03-10.57.30.776.flv; + # 5. DVR to mp4: + # dvr_path ./objs/nginx/html/[app]/[stream].[timestamp].mp4; + # => + # dvr_path ./objs/nginx/html/live/livestream.1420254068776.mp4; # @see https://github.com/ossrs/srs/wiki/v2_CN_DVR#custom-path # @see https://github.com/ossrs/srs/wiki/v2_EN_DVR#custom-path # segment,session apply it. diff --git a/trunk/src/app/srs_app_dvr.cpp b/trunk/src/app/srs_app_dvr.cpp index 77f7a8bf54..ccc0ef362c 100644 --- a/trunk/src/app/srs_app_dvr.cpp +++ b/trunk/src/app/srs_app_dvr.cpp @@ -42,59 +42,57 @@ using namespace std; #include #include #include +#include // update the flv duration and filesize every this interval in ms. #define SRS_DVR_UPDATE_DURATION_INTERVAL 60000 -SrsFlvSegment::SrsFlvSegment(SrsDvrPlan* p) +SrsDvrSegmenter::SrsDvrSegmenter() { req = NULL; jitter = NULL; - plan = p; - + plan = NULL; + duration = 0; + + path = ""; fs = new SrsFileWriter(); - enc = new SrsFlvEncoder(); jitter_algorithm = SrsRtmpJitterAlgorithmOFF; - path = ""; - has_keyframe = false; - duration = 0; - starttime = -1; - stream_starttime = 0; - stream_previous_pkt_time = -1; - stream_duration = 0; - - duration_offset = 0; - filesize_offset = 0; - _srs_config->subscribe(this); } -SrsFlvSegment::~SrsFlvSegment() +SrsDvrSegmenter::~SrsDvrSegmenter() { _srs_config->unsubscribe(this); srs_freep(jitter); srs_freep(fs); - srs_freep(enc); } -int SrsFlvSegment::initialize(SrsRequest* r) +int SrsDvrSegmenter::initialize(SrsDvrPlan* p, SrsRequest* r) { int ret = ERROR_SUCCESS; - + req = r; - jitter_algorithm = (SrsRtmpJitterAlgorithm)_srs_config->get_dvr_time_jitter(req->vhost); + plan = p; + + int jitter = _srs_config->get_dvr_time_jitter(req->vhost); + jitter_algorithm = (SrsRtmpJitterAlgorithm)jitter; return ret; } -bool SrsFlvSegment::is_overflow(int64_t max_duration) +string SrsDvrSegmenter::get_path() +{ + return path; +} + +bool SrsDvrSegmenter::is_overflow(int64_t max_duration) { return duration >= max_duration; } -int SrsFlvSegment::open(bool use_tmp_file) +int SrsDvrSegmenter::open(bool use_tmp_file) { int ret = ERROR_SUCCESS; @@ -104,7 +102,14 @@ int SrsFlvSegment::open(bool use_tmp_file) } path = generate_path(); - bool fresh_flv_file = !srs_path_exists(path); + bool can_append = srs_string_ends_with(path, ".flv"); + + bool target_exists = srs_path_exists(path); + if (!can_append && target_exists) { + ret = ERROR_DVR_CANNOT_APPEND; + srs_error("DVR can't append to exists path=%s. ret=%d", path.c_str(), ret); + return ret; + } // create dir first. std::string dir = srs_path_dirname(path); @@ -115,60 +120,46 @@ int SrsFlvSegment::open(bool use_tmp_file) srs_info("create dir=%s ok", dir.c_str()); // create jitter. - if ((ret = create_jitter(!fresh_flv_file)) != ERROR_SUCCESS) { - srs_error("create jitter failed, path=%s, fresh=%d. ret=%d", path.c_str(), fresh_flv_file, ret); + if ((ret = create_jitter(target_exists)) != ERROR_SUCCESS) { + srs_error("create jitter failed, path=%s, exists=%d. ret=%d", path.c_str(), target_exists, ret); return ret; } // generate the tmp flv path. - if (!fresh_flv_file || !use_tmp_file) { + if (target_exists || !use_tmp_file) { // when path exists, always append to it. // so we must use the target flv path as output flv. - tmp_flv_file = path; + tmp_dvr_file = path; } else { // when path not exists, dvr to tmp file. - tmp_flv_file = path + ".tmp"; + tmp_dvr_file = path + ".tmp"; } // open file writer, in append or create mode. - if (!fresh_flv_file) { - if ((ret = fs->open_append(tmp_flv_file)) != ERROR_SUCCESS) { + if (target_exists) { + if ((ret = fs->open_append(tmp_dvr_file)) != ERROR_SUCCESS) { srs_error("append file stream for file %s failed. ret=%d", path.c_str(), ret); return ret; } srs_trace("dvr: always append to when exists, file=%s.", path.c_str()); } else { - if ((ret = fs->open(tmp_flv_file)) != ERROR_SUCCESS) { + 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 = enc->initialize(fs)) != ERROR_SUCCESS) { - srs_error("initialize enc by fs for file %s failed. ret=%d", path.c_str(), ret); + if ((ret = open_encoder()) != ERROR_SUCCESS) { return ret; } - // when exists, donot write flv header. - if (fresh_flv_file) { - // write the flv header to writer. - if ((ret = enc->write_header()) != ERROR_SUCCESS) { - srs_error("write flv header failed. ret=%d", ret); - return ret; - } - } - - // update the duration and filesize offset. - duration_offset = 0; - filesize_offset = 0; - srs_trace("dvr stream %s to file %s", req->stream.c_str(), path.c_str()); return ret; } -int SrsFlvSegment::close() +int SrsDvrSegmenter::close() { int ret = ERROR_SUCCESS; @@ -177,19 +168,19 @@ int SrsFlvSegment::close() return ret; } - // update duration and filesize. - if ((ret = update_flv_metadata()) != ERROR_SUCCESS) { + // 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 (tmp_flv_file != path) { - if (rename(tmp_flv_file.c_str(), path.c_str()) < 0) { + if (tmp_dvr_file != path) { + if (rename(tmp_dvr_file.c_str(), path.c_str()) < 0) { ret = ERROR_SYSTEM_FILE_RENAME; srs_error("rename flv file failed, %s => %s. ret=%d", - tmp_flv_file.c_str(), path.c_str(), ret); + tmp_dvr_file.c_str(), path.c_str(), ret); return ret; } } @@ -204,35 +195,179 @@ int SrsFlvSegment::close() return ret; } -int SrsFlvSegment::write_metadata(SrsSharedPtrMessage* metadata) +string SrsDvrSegmenter::generate_path() +{ + // the path in config, for example, + // /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv + std::string path_config = _srs_config->get_dvr_path(req->vhost); + + // add [stream].[timestamp].flv as filename for dir + if (!srs_string_ends_with(path_config, ".flv")) { + path_config += "/[stream].[timestamp].flv"; + } + + // the flv file path + std::string flv_path = path_config; + flv_path = srs_path_build_stream(flv_path, req->vhost, req->app, req->stream); + flv_path = srs_path_build_timestamp(flv_path); + + return flv_path; +} + +int SrsDvrSegmenter::create_jitter(bool target_exists) { int ret = ERROR_SUCCESS; + + // When DVR target file not exists, create new jitter. + if (!target_exists) { + // jitter when publish, ensure whole stream start from 0. + srs_freep(jitter); + jitter = new SrsRtmpJitter(); + + duration = 0; - if (duration_offset || filesize_offset) { return ret; } + // when jitter ok, do nothing. + if (jitter) { + return ret; + } + + // always ensure the jitter crote. + // for the first time, initialize jitter from exists file. + jitter = new SrsRtmpJitter(); + + // TODO: FIXME: implements it. + + return ret; +} + +int SrsDvrSegmenter::on_reload_vhost_dvr(std::string /*vhost*/) +{ + int ret = ERROR_SUCCESS; + + jitter_algorithm = (SrsRtmpJitterAlgorithm)_srs_config->get_dvr_time_jitter(req->vhost); + + return ret; +} + +SrsDvrFlvSegmenter::SrsDvrFlvSegmenter() +{ + enc = new SrsFlvEncoder(); + + duration_offset = 0; + filesize_offset = 0; + + has_keyframe = false; + + starttime = -1; + stream_starttime = 0; + stream_previous_pkt_time = -1; + stream_duration = 0; +} + +SrsDvrFlvSegmenter::~SrsDvrFlvSegmenter() +{ + srs_freep(enc); +} + +int SrsDvrFlvSegmenter::open_encoder() +{ + int ret = ERROR_SUCCESS; + + if ((ret = enc->initialize(fs)) != ERROR_SUCCESS) { + srs_error("initialize enc by fs for file %s failed. ret=%d", path.c_str(), ret); + return ret; + } + + bool target_exists = srs_path_exists(path); + if (target_exists){ + has_keyframe = false; + + // fresh stream starting. + starttime = -1; + stream_previous_pkt_time = -1; + stream_starttime = srs_update_system_time_ms(); + stream_duration = 0; + + // write the flv header to writer. + if ((ret = enc->write_header()) != ERROR_SUCCESS) { + srs_error("write flv header failed. ret=%d", ret); + return ret; + } + } + + // update the duration and filesize offset. + duration_offset = 0; + filesize_offset = 0; + + return ret; +} + +int SrsDvrFlvSegmenter::close_encoder() +{ + return refresh_metadata(); +} + +int SrsDvrFlvSegmenter::on_update_duration(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + // we must assumpt that the stream timestamp is monotonically increase, + // that is, always use time jitter to correct the timestamp. + // except the time jitter is disabled in config. + + // set the segment starttime at first time + if (starttime < 0) { + starttime = msg->timestamp; + } + + // no previous packet or timestamp overflow. + if (stream_previous_pkt_time < 0 || stream_previous_pkt_time > msg->timestamp) { + stream_previous_pkt_time = msg->timestamp; + } + + // collect segment and stream duration, timestamp overflow is ok. + duration += msg->timestamp - stream_previous_pkt_time; + stream_duration += msg->timestamp - stream_previous_pkt_time; + + // update previous packet time + stream_previous_pkt_time = msg->timestamp; + + return ret; +} + +int SrsDvrFlvSegmenter::write_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)); @@ -241,12 +376,12 @@ int SrsFlvSegment::write_metadata(SrsSharedPtrMessage* metadata) 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; @@ -266,10 +401,10 @@ int SrsFlvSegment::write_metadata(SrsSharedPtrMessage* metadata) return ret; } -int SrsFlvSegment::write_audio(SrsSharedPtrMessage* shared_audio) +int SrsDvrFlvSegmenter::write_audio(SrsSharedPtrMessage* shared_audio) { int ret = ERROR_SUCCESS; - + SrsSharedPtrMessage* audio = shared_audio->copy(); SrsAutoFree(SrsSharedPtrMessage, audio); @@ -283,7 +418,7 @@ int SrsFlvSegment::write_audio(SrsSharedPtrMessage* shared_audio) if ((ret = enc->write_audio(timestamp, payload, size)) != ERROR_SUCCESS) { return ret; } - + if ((ret = on_update_duration(audio)) != ERROR_SUCCESS) { return ret; } @@ -291,10 +426,10 @@ int SrsFlvSegment::write_audio(SrsSharedPtrMessage* shared_audio) return ret; } -int SrsFlvSegment::write_video(SrsSharedPtrMessage* shared_video) +int SrsDvrFlvSegmenter::write_video(SrsSharedPtrMessage* shared_video) { int ret = ERROR_SUCCESS; - + SrsSharedPtrMessage* video = shared_video->copy(); SrsAutoFree(SrsSharedPtrMessage, video); @@ -302,7 +437,7 @@ int SrsFlvSegment::write_video(SrsSharedPtrMessage* shared_video) int size = video->size; bool is_sequence_header = SrsFlvCodec::video_is_sequence_header(payload, size); - bool is_key_frame = SrsFlvCodec::video_is_h264(payload, size) + bool is_key_frame = SrsFlvCodec::video_is_h264(payload, size) && SrsFlvCodec::video_is_keyframe(payload, size) && !is_sequence_header; if (is_key_frame) { has_keyframe = true; @@ -311,7 +446,7 @@ int SrsFlvSegment::write_video(SrsSharedPtrMessage* shared_video) } } srs_verbose("dvr video is key: %d", is_key_frame); - + // accept the sequence header here. // when got no keyframe, ignore when should wait keyframe. if (!has_keyframe && !is_sequence_header) { @@ -340,35 +475,35 @@ int SrsFlvSegment::write_video(SrsSharedPtrMessage* shared_video) return ret; } -int SrsFlvSegment::update_flv_metadata() +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) { @@ -383,114 +518,62 @@ int SrsFlvSegment::update_flv_metadata() 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; } -string SrsFlvSegment::get_path() +SrsDvrMp4Segmenter::SrsDvrMp4Segmenter() { - return path; + enc = new SrsMp4Encoder(); } -string SrsFlvSegment::generate_path() +SrsDvrMp4Segmenter::~SrsDvrMp4Segmenter() { - // the path in config, for example, - // /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv - std::string path_config = _srs_config->get_dvr_path(req->vhost); - - // add [stream].[timestamp].flv as filename for dir - if (!srs_string_ends_with(path_config, ".flv")) { - path_config += "/[stream].[timestamp].flv"; - } - - // the flv file path - std::string flv_path = path_config; - flv_path = srs_path_build_stream(flv_path, req->vhost, req->app, req->stream); - flv_path = srs_path_build_timestamp(flv_path); - - return flv_path; + srs_freep(enc); } -int SrsFlvSegment::create_jitter(bool loads_from_flv) +int SrsDvrMp4Segmenter::open_encoder() { int ret = ERROR_SUCCESS; - - // when path exists, use exists jitter. - if (!loads_from_flv) { - // jitter when publish, ensure whole stream start from 0. - srs_freep(jitter); - jitter = new SrsRtmpJitter(); - - // fresh stream starting. - starttime = -1; - stream_previous_pkt_time = -1; - stream_starttime = srs_update_system_time_ms(); - stream_duration = 0; - - // fresh segment starting. - has_keyframe = false; - duration = 0; - - return ret; - } - - // when jitter ok, do nothing. - if (jitter) { - return ret; - } + return ret; +} - // always ensure the jitter crote. - // for the first time, initialize jitter from exists file. - jitter = new SrsRtmpJitter(); +int SrsDvrMp4Segmenter::close_encoder() +{ + int ret = ERROR_SUCCESS; + return ret; +} - // TODO: FIXME: implements it. +int SrsDvrMp4Segmenter::write_metadata(SrsSharedPtrMessage* metadata) +{ + int ret = ERROR_SUCCESS; + return ret; +} +int SrsDvrMp4Segmenter::write_audio(SrsSharedPtrMessage* shared_audio) +{ + int ret = ERROR_SUCCESS; return ret; } -int SrsFlvSegment::on_update_duration(SrsSharedPtrMessage* msg) +int SrsDvrMp4Segmenter::write_video(SrsSharedPtrMessage* shared_video) { int ret = ERROR_SUCCESS; - - // we must assumpt that the stream timestamp is monotonically increase, - // that is, always use time jitter to correct the timestamp. - // except the time jitter is disabled in config. - - // set the segment starttime at first time - if (starttime < 0) { - starttime = msg->timestamp; - } - - // no previous packet or timestamp overflow. - if (stream_previous_pkt_time < 0 || stream_previous_pkt_time > msg->timestamp) { - stream_previous_pkt_time = msg->timestamp; - } - - // collect segment and stream duration, timestamp overflow is ok. - duration += msg->timestamp - stream_previous_pkt_time; - stream_duration += msg->timestamp - stream_previous_pkt_time; - - // update previous packet time - stream_previous_pkt_time = msg->timestamp; - return ret; } -int SrsFlvSegment::on_reload_vhost_dvr(std::string /*vhost*/) +int SrsDvrMp4Segmenter::refresh_metadata() { int ret = ERROR_SUCCESS; - - jitter_algorithm = (SrsRtmpJitterAlgorithm)_srs_config->get_dvr_time_jitter(req->vhost); - return ret; } @@ -553,7 +636,7 @@ SrsDvrPlan::SrsDvrPlan() req = NULL; dvr_enabled = false; - segment = new SrsFlvSegment(this); + segment = NULL; async = new SrsAsyncCallWorker(); } @@ -563,13 +646,14 @@ SrsDvrPlan::~SrsDvrPlan() srs_freep(async); } -int SrsDvrPlan::initialize(SrsRequest* r) +int SrsDvrPlan::initialize(SrsDvrSegmenter* s, SrsRequest* r) { int ret = ERROR_SUCCESS; req = r; + segment = s; - if ((ret = segment->initialize(r)) != ERROR_SUCCESS) { + if ((ret = segment->initialize(this, r)) != ERROR_SUCCESS) { return ret; } @@ -643,19 +727,31 @@ int SrsDvrPlan::on_reap_segment() return ret; } -SrsDvrPlan* SrsDvrPlan::create_plan(string vhost) +int SrsDvrPlan::create_plan(string vhost, SrsDvrPlan** pplan) { + int ret = ERROR_SUCCESS; + std::string plan = _srs_config->get_dvr_plan(vhost); + std::string path = _srs_config->get_dvr_path(vhost); + bool is_mp4 = srs_string_ends_with(path, ".mp4"); + if (srs_config_dvr_is_plan_segment(plan)) { - return new SrsDvrSegmentPlan(); + *pplan = new SrsDvrSegmentPlan(); } else if (srs_config_dvr_is_plan_session(plan)) { - return new SrsDvrSessionPlan(); + *pplan = new SrsDvrSessionPlan(); } else if (srs_config_dvr_is_plan_append(plan)) { - return new SrsDvrAppendPlan(); + if (is_mp4) { + ret = ERROR_DVR_ILLEGAL_PLAN; + srs_error("DVR plan append not support MP4. ret=%d", ret); + return ret; + } + *pplan = new SrsDvrAppendPlan(); } else { srs_error("invalid dvr plan=%s, vhost=%s", plan.c_str(), vhost.c_str()); srs_assert(false); } + + return ret; } SrsDvrSessionPlan::SrsDvrSessionPlan() @@ -683,7 +779,7 @@ int SrsDvrSessionPlan::on_publish() return ret; } - if ((ret = segment->open()) != ERROR_SUCCESS) { + if ((ret = segment->open(true)) != ERROR_SUCCESS) { return ret; } @@ -793,7 +889,7 @@ int SrsDvrAppendPlan::update_duration(SrsSharedPtrMessage* msg) last_update_time = msg->timestamp; srs_assert(segment); - if (!segment->update_flv_metadata()) { + if ((ret = segment->refresh_metadata()) != ERROR_SUCCESS) { return ret; } @@ -813,11 +909,11 @@ SrsDvrSegmentPlan::~SrsDvrSegmentPlan() srs_freep(metadata); } -int SrsDvrSegmentPlan::initialize(SrsRequest* req) +int SrsDvrSegmentPlan::initialize(SrsDvrSegmenter* s, SrsRequest* req) { int ret = ERROR_SUCCESS; - if ((ret = SrsDvrPlan::initialize(req)) != ERROR_SUCCESS) { + if ((ret = SrsDvrPlan::initialize(s, req)) != ERROR_SUCCESS) { return ret; } @@ -845,7 +941,7 @@ int SrsDvrSegmentPlan::on_publish() return ret; } - if ((ret = segment->open()) != ERROR_SUCCESS) { + if ((ret = segment->open(true)) != ERROR_SUCCESS) { return ret; } @@ -946,7 +1042,7 @@ int SrsDvrSegmentPlan::update_duration(SrsSharedPtrMessage* msg) } // open new flv file - if ((ret = segment->open()) != ERROR_SUCCESS) { + if ((ret = segment->open(true)) != ERROR_SUCCESS) { return ret; } @@ -992,9 +1088,19 @@ int SrsDvr::initialize(SrsOriginHub* h, SrsRequest* r) actived = srs_config_apply_filter(conf, r); srs_freep(plan); - plan = SrsDvrPlan::create_plan(r->vhost); - - if ((ret = plan->initialize(r)) != ERROR_SUCCESS) { + if ((ret = SrsDvrPlan::create_plan(r->vhost, &plan)) != ERROR_SUCCESS) { + return ret; + } + + 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 ((ret = plan->initialize(segmenter, r)) != ERROR_SUCCESS) { return ret; } diff --git a/trunk/src/app/srs_app_dvr.hpp b/trunk/src/app/srs_app_dvr.hpp index 38f6632de0..da61dd732f 100644 --- a/trunk/src/app/srs_app_dvr.hpp +++ b/trunk/src/app/srs_app_dvr.hpp @@ -47,81 +47,43 @@ class SrsDvrPlan; class SrsJsonAny; class SrsJsonObject; class SrsThread; +class SrsMp4Encoder; #include #include #include /** -* a piece of flv segment. -* when open segment, support start at 0 or not. -*/ -class SrsFlvSegment : public ISrsReloadHandler + * The segmenter for DVR, to write a segment file in flv/mp4. + */ +class SrsDvrSegmenter : public ISrsReloadHandler { -private: - SrsRequest* req; - SrsDvrPlan* plan; -private: - /** - * the underlayer dvr stream. - * if close, the flv is reap and closed. - * if open, new flv file is crote. - */ - SrsFlvEncoder* enc; - SrsRtmpJitter* jitter; - SrsRtmpJitterAlgorithm jitter_algorithm; +protected: SrsFileWriter* fs; -private: - /** - * the offset of file for duration value. - * the next 8 bytes is the double value. - */ - int64_t duration_offset; - /** - * the offset of file for filesize value. - * the next 8 bytes is the double value. - */ - int64_t filesize_offset; -private: - std::string tmp_flv_file; -private: - /** - * current segment flv file path. - */ + // The path of current segment flv file path. std::string path; - /** - * whether current segment has keyframe. - */ - bool has_keyframe; - /** - * current segment starttime, RTMP pkt time. - */ - int64_t starttime; - /** - * current segment duration - */ +protected: + SrsRtmpJitter* jitter; + SrsRtmpJitterAlgorithm jitter_algorithm; + // The duration in ms of current segment. int64_t duration; - /** - * stream start time, to generate atc pts. abs time. - */ - int64_t stream_starttime; - /** - * stream duration, to generate atc segment. - */ - int64_t stream_duration; - /** - * previous stream RTMP pkt time, used to calc the duration. - * for the RTMP timestamp will overflow. - */ - int64_t stream_previous_pkt_time; +protected: + SrsRequest* req; + SrsDvrPlan* plan; +private: + std::string tmp_dvr_file; public: - SrsFlvSegment(SrsDvrPlan* p); - virtual ~SrsFlvSegment(); + SrsDvrSegmenter(); + virtual ~SrsDvrSegmenter(); public: /** * initialize the segment. */ - virtual int initialize(SrsRequest* r); + virtual int initialize(SrsDvrPlan* p, SrsRequest* r); + /** + * get the current dvr path. + */ + virtual std::string get_path(); /** * whether segment is overflow. */ @@ -131,32 +93,29 @@ class SrsFlvSegment : public ISrsReloadHandler * @remark ignore when already open. * @param use_tmp_file whether use tmp file if possible. */ - virtual int open(bool use_tmp_file = true); + virtual int open(bool use_tmp_file); /** * close current segment. * @remark ignore when already closed. */ virtual int close(); - /** - * write the metadata to segment. - */ - virtual int write_metadata(SrsSharedPtrMessage* metadata); - /** - * @param shared_audio, directly ptr, copy it if need to save it. - */ - virtual int write_audio(SrsSharedPtrMessage* shared_audio); - /** - * @param shared_video, directly ptr, copy it if need to save it. - */ - virtual int write_video(SrsSharedPtrMessage* shared_video); - /** - * update the flv metadata. - */ - virtual int update_flv_metadata(); - /** - * get the current dvr path. - */ - virtual std::string get_path(); +protected: + virtual int open_encoder() = 0; +public: + // Write the metadata. + virtual int write_metadata(SrsSharedPtrMessage* metadata) = 0; + // Write audio packet. + // @param shared_audio, directly ptr, copy it if need to save it. + virtual int write_audio(SrsSharedPtrMessage* shared_audio) = 0; + // Write video packet. + // @param shared_video, directly ptr, copy it if need to save it. + virtual int write_video(SrsSharedPtrMessage* shared_video) = 0; + // Refresh the metadata. For example, there is duration in flv metadata, + // when DVR in append mode, the duration must be update every some seconds. + // @remark Maybe ignored by concreate segmenter. + virtual int refresh_metadata() = 0; +protected: + virtual int close_encoder() = 0; private: /** * generate the flv segment path. @@ -164,18 +123,81 @@ class SrsFlvSegment : public ISrsReloadHandler virtual std::string generate_path(); /** * create flv jitter. load jitter when flv exists. - * @param loads_from_flv whether loads the jitter from exists flv file. - */ - virtual int create_jitter(bool loads_from_flv); - /** - * when update the duration of segment by rtmp msg. + * @param target_exists whether loads the jitter from exists flv file. */ - virtual int on_update_duration(SrsSharedPtrMessage* msg); + virtual int create_jitter(bool target_exists); // interface ISrsReloadHandler public: virtual int on_reload_vhost_dvr(std::string vhost); }; +/** + * The FLV segmenter to use FLV encoder to write file. + */ +class SrsDvrFlvSegmenter : public SrsDvrSegmenter +{ +private: + // The FLV encoder, for FLV target. + SrsFlvEncoder* enc; +private: + // The offset of file for duration value. + // The next 8 bytes is the double value. + int64_t duration_offset; + // The offset of file for filesize value. + // The next 8 bytes is the double value. + int64_t filesize_offset; + // Whether current segment has keyframe. + bool has_keyframe; +private: + // The current segment starttime in ms, RTMP pkt time. + int64_t starttime; + // The stream start time in ms, to generate atc pts. abs time. + int64_t stream_starttime; + // The stream duration in ms, to generate atc segment. + int64_t stream_duration; + /** + * The previous stream RTMP pkt time in ms, used to calc the duration. + * for the RTMP timestamp will overflow. + */ + // TODO: FIXME: Use utility object to calc it. + int64_t stream_previous_pkt_time; +public: + SrsDvrFlvSegmenter(); + virtual ~SrsDvrFlvSegmenter(); +protected: + virtual int open_encoder(); + virtual int close_encoder(); +private: + // When update the duration of segment by rtmp msg. + virtual int on_update_duration(SrsSharedPtrMessage* msg); +public: + virtual int write_metadata(SrsSharedPtrMessage* metadata); + virtual int write_audio(SrsSharedPtrMessage* shared_audio); + virtual int write_video(SrsSharedPtrMessage* shared_video); + virtual int refresh_metadata(); +}; + +/** + * The MP4 segmenter to use MP4 encoder to write file. + */ +class SrsDvrMp4Segmenter : public SrsDvrSegmenter +{ +private: + // The MP4 encoder, for MP4 target. + SrsMp4Encoder* enc; +public: + SrsDvrMp4Segmenter(); + virtual ~SrsDvrMp4Segmenter(); +protected: + virtual int open_encoder(); + virtual int close_encoder(); +public: + virtual int write_metadata(SrsSharedPtrMessage* metadata); + virtual int write_audio(SrsSharedPtrMessage* shared_audio); + virtual int write_video(SrsSharedPtrMessage* shared_video); + virtual int refresh_metadata(); +}; + /** * the dvr async call. */ @@ -202,19 +224,17 @@ class SrsDvrAsyncCallOnDvr : public ISrsAsyncCallTask // TODO: FIXME: the plan is too fat, refine me. class SrsDvrPlan { -public: - friend class SrsFlvSegment; public: SrsRequest* req; protected: - SrsFlvSegment* segment; + SrsDvrSegmenter* segment; SrsAsyncCallWorker* async; bool dvr_enabled; public: SrsDvrPlan(); virtual ~SrsDvrPlan(); public: - virtual int initialize(SrsRequest* r); + virtual int initialize(SrsDvrSegmenter* s, SrsRequest* r); virtual int on_publish() = 0; virtual void on_unpublish() = 0; /** @@ -229,12 +249,16 @@ class SrsDvrPlan * @param shared_video, directly ptr, copy it if need to save it. */ virtual int on_video(SrsSharedPtrMessage* shared_video); -protected: +// Internal interface for segmenter. +public: + // When segmenter close a segment. virtual int on_reap_segment(); + // When segmenter got a keyframe. virtual int on_video_keyframe(); + // The plan may need to process the timestamp. virtual int64_t filter_timestamp(int64_t timestamp); public: - static SrsDvrPlan* create_plan(std::string vhost); + static int create_plan(std::string vhost, SrsDvrPlan** pplan); }; /** @@ -284,7 +308,7 @@ class SrsDvrSegmentPlan : public SrsDvrPlan SrsDvrSegmentPlan(); virtual ~SrsDvrSegmentPlan(); public: - virtual int initialize(SrsRequest* req); + virtual int initialize(SrsDvrSegmenter* s, SrsRequest* req); virtual int on_publish(); virtual void on_unpublish(); virtual int on_meta_data(SrsSharedPtrMessage* shared_metadata); @@ -295,9 +319,9 @@ class SrsDvrSegmentPlan : public SrsDvrPlan }; /** -* dvr(digital video recorder) to record RTMP stream to flv file. -* TODO: FIXME: add utest for it. -*/ + * DVR(Digital Video Recorder) to record RTMP stream to flv/mp4 file. + * TODO: FIXME: add utest for it. + */ class SrsDvr : public ISrsReloadHandler { private: diff --git a/trunk/src/app/srs_app_edge.cpp b/trunk/src/app/srs_app_edge.cpp index 8de0c05e22..1d82a27786 100644 --- a/trunk/src/app/srs_app_edge.cpp +++ b/trunk/src/app/srs_app_edge.cpp @@ -213,7 +213,9 @@ void SrsEdgeIngester::stop() upstream->close(); // notice to unpublish. - source->on_unpublish(); + if (source) { + source->on_unpublish(); + } } string SrsEdgeIngester::get_curr_origin() diff --git a/trunk/src/app/srs_app_http_hooks.hpp b/trunk/src/app/srs_app_http_hooks.hpp index d0a0d27fd6..b8aae97d99 100644 --- a/trunk/src/app/srs_app_http_hooks.hpp +++ b/trunk/src/app/srs_app_http_hooks.hpp @@ -35,7 +35,6 @@ class SrsHttpUri; class SrsStSocket; class SrsRequest; class SrsHttpParser; -class SrsFlvSegment; class SrsHttpClient; /** diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index b7b4fcb32c..c26a5c2567 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -255,6 +255,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define ERROR_MP4_MOOV_OVERFLOW 3079 #define ERROR_MP4_ILLEGAL_SAMPLES 3080 #define ERROR_MP4_ILLEGAL_TIMESTAMP 3081 +#define ERROR_DVR_CANNOT_APPEND 3082 +#define ERROR_DVR_ILLEGAL_PLAN 3083 /////////////////////////////////////////////////////// // HTTP/StreamCaster/KAFKA protocol error. diff --git a/trunk/src/kernel/srs_kernel_mp4.cpp b/trunk/src/kernel/srs_kernel_mp4.cpp index f14fa00c78..ea4c5cd7f6 100644 --- a/trunk/src/kernel/srs_kernel_mp4.cpp +++ b/trunk/src/kernel/srs_kernel_mp4.cpp @@ -3783,3 +3783,18 @@ int SrsMp4Decoder::do_load_next_box(SrsMp4Box** ppbox, uint32_t required_box_typ return ret; } +SrsMp4Encoder::SrsMp4Encoder() +{ + writer = NULL; +} + +SrsMp4Encoder::~SrsMp4Encoder() +{ +} + +int SrsMp4Encoder::initialize(ISrsWriter* w) +{ + writer = w; + return ERROR_SUCCESS; +} + diff --git a/trunk/src/kernel/srs_kernel_mp4.hpp b/trunk/src/kernel/srs_kernel_mp4.hpp index 2c15e18fa7..a61dabe267 100644 --- a/trunk/src/kernel/srs_kernel_mp4.hpp +++ b/trunk/src/kernel/srs_kernel_mp4.hpp @@ -36,6 +36,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +class ISrsWriter; class ISrsReadSeeker; class SrsMp4TrackBox; class SrsMp4MediaBox; @@ -1538,8 +1539,7 @@ class SrsMp4Decoder public: /** * Initialize the decoder with a reader r. - * @param r The underlayer io reader, user must manage it for decoder never open/free it, - * the decoder just read data from the reader. + * @param r The underlayer io reader, user must manage it. */ virtual int initialize(ISrsReadSeeker* rs); /** @@ -1567,5 +1567,23 @@ class SrsMp4Decoder virtual int do_load_next_box(SrsMp4Box** ppbox, uint32_t required_box_type); }; +/** + * The MP4 muxer. + */ +class SrsMp4Encoder +{ +private: + ISrsWriter* writer; +public: + SrsMp4Encoder(); + virtual ~SrsMp4Encoder(); +public: + /** + * Initialize the encoder with a writer w. + * @param w The underlayer io writer, user must manage it. + */ + virtual int initialize(ISrsWriter* w); +}; + #endif