diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index fed04b8ab..4f61eaa77 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -108,10 +108,19 @@ vhost dvr.srs.com { # http://127.0.0.1/live/livestream.m3u8 # where dvr_path is /dvr, srs will create the following files: # /dvr/live the app dir for all streams. - # /dvr/live/livestream.flv the dvr flv file. + # /dvr/live/livestream.{time}.flv the dvr flv file. + # @remark, the time use system timestamp in ms, user can use http callback to rename it. # in a word, the dvr_path is for vhost. # default: ./objs/nginx/html dvr_path ./objs/nginx/html; + # the dvr plan. canbe: + # session reap flv when session end(unpublish). + # segment reap flv when flv duration exceed the specified duration. + # default: session + dvr_plan session; + # the param for plan(segment), in seconds. + # default: 30 + dvr_duration 30; } } diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index 9a5d91854..5a7c3957e 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -2354,6 +2354,40 @@ string SrsConfig::get_dvr_path(string vhost) return conf->arg0(); } +string SrsConfig::get_dvr_plan(string vhost) +{ + SrsConfDirective* dvr = get_dvr(vhost); + + if (!dvr) { + return SRS_CONF_DEFAULT_DVR_PLAN; + } + + SrsConfDirective* conf = dvr->get("dvr_plan"); + + if (!conf) { + return SRS_CONF_DEFAULT_DVR_PLAN; + } + + return conf->arg0(); +} + +int SrsConfig::get_dvr_duration(string vhost) +{ + SrsConfDirective* dvr = get_dvr(vhost); + + if (!dvr) { + return SRS_CONF_DEFAULT_DVR_DURATION; + } + + SrsConfDirective* conf = dvr->get("dvr_duration"); + + if (!conf) { + return SRS_CONF_DEFAULT_DVR_DURATION; + } + + return ::atoi(conf->arg0().c_str()); +} + SrsConfDirective* SrsConfig::get_http_api() { return root->get("http_api"); diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index a4a46c0ca..c7d3e8e2b 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -45,6 +45,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_CONF_DEFAULT_HLS_FRAGMENT 10 #define SRS_CONF_DEFAULT_HLS_WINDOW 60 #define SRS_CONF_DEFAULT_DVR_PATH "./objs/nginx/html" +#define SRS_CONF_DEFAULT_DVR_PLAN_SESSION "session" +#define SRS_CONF_DEFAULT_DVR_PLAN_SEGMENT "segment" +#define SRS_CONF_DEFAULT_DVR_PLAN SRS_CONF_DEFAULT_DVR_PLAN_SESSION +#define SRS_CONF_DEFAULT_DVR_DURATION 30 // in ms, for HLS aac sync time. #define SRS_CONF_DEFAULT_AAC_SYNC 100 // in ms, for HLS aac flush the audio @@ -229,6 +233,8 @@ private: public: virtual bool get_dvr_enabled(std::string vhost); virtual std::string get_dvr_path(std::string vhost); + virtual std::string get_dvr_plan(std::string vhost); + virtual int get_dvr_duration(std::string vhost); // http api section private: virtual SrsConfDirective* get_http_api(); diff --git a/trunk/src/app/srs_app_dvr.cpp b/trunk/src/app/srs_app_dvr.cpp index 2cb77fab5..45c745531 100644 --- a/trunk/src/app/srs_app_dvr.cpp +++ b/trunk/src/app/srs_app_dvr.cpp @@ -26,6 +26,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifdef SRS_AUTO_DVR #include +#include using namespace std; #include @@ -35,6 +36,7 @@ using namespace std; #include #include #include +#include SrsFileStream::SrsFileStream() { @@ -302,7 +304,7 @@ SrsDvrPlan::~SrsDvrPlan() srs_freep(enc); } -int SrsDvrPlan::initialize(SrsSource* source) +int SrsDvrPlan::initialize(SrsSource* source, SrsRequest* /*req*/) { int ret = ERROR_SUCCESS; @@ -401,9 +403,16 @@ int SrsDvrPlan::on_video(SrsSharedPtrMessage* video) return ret; } -SrsDvrPlan* SrsDvrPlan::create_plan() +SrsDvrPlan* SrsDvrPlan::create_plan(string vhost) { - return new SrsDvrSessionPlan(); + std::string plan = _srs_config->get_dvr_plan(vhost); + if (plan == SRS_CONF_DEFAULT_DVR_PLAN_SEGMENT) { + return new SrsDvrSegmentPlan(); + } else if (plan == SRS_CONF_DEFAULT_DVR_PLAN_SESSION) { + return new SrsDvrSessionPlan(); + } else { + return new SrsDvrSessionPlan(); + } } SrsDvrSessionPlan::SrsDvrSessionPlan() @@ -427,14 +436,13 @@ int SrsDvrSessionPlan::on_publish(SrsRequest* req) return ret; } - std::string path = _srs_config->get_dvr_path(req->vhost); - path += "/"; - path += req->app; - path += "/"; - path += req->stream; - path += ".flv"; + std::stringstream path; - if ((ret = flv_open(req->get_stream_url(), path)) != ERROR_SUCCESS) { + path << _srs_config->get_dvr_path(req->vhost) + << "/" << req->app << "/" + << req->stream << "." << srs_get_system_time_ms() << ".flv"; + + if ((ret = flv_open(req->get_stream_url(), path.str())) != ERROR_SUCCESS) { return ret; } dvr_enabled = true; @@ -458,6 +466,132 @@ void SrsDvrSessionPlan::on_unpublish() dvr_enabled = false; } +SrsDvrSegmentPlan::SrsDvrSegmentPlan() +{ + starttime = -1; + duration = 0; + segment_duration = -1; +} + +SrsDvrSegmentPlan::~SrsDvrSegmentPlan() +{ +} + +int SrsDvrSegmentPlan::initialize(SrsSource* source, SrsRequest* req) +{ + int ret = ERROR_SUCCESS; + + if ((ret = SrsDvrPlan::initialize(source, req)) != ERROR_SUCCESS) { + return ret; + } + + segment_duration = _srs_config->get_dvr_duration(req->vhost); + // to ms + segment_duration *= 1000; + + return ret; +} + +int SrsDvrSegmentPlan::on_publish(SrsRequest* req) +{ + int ret = ERROR_SUCCESS; + + // support multiple publish. + if (dvr_enabled) { + return ret; + } + + if (!_srs_config->get_dvr_enabled(req->vhost)) { + return ret; + } + + std::stringstream path; + + path << _srs_config->get_dvr_path(req->vhost) + << "/" << req->app << "/" + << req->stream << "." << srs_get_system_time_ms() << ".flv"; + + if ((ret = flv_open(req->get_stream_url(), path.str())) != ERROR_SUCCESS) { + return ret; + } + dvr_enabled = true; + + return ret; +} + +void SrsDvrSegmentPlan::on_unpublish() +{ + // support multiple publish. + if (!dvr_enabled) { + return; + } + + // ignore error. + int ret = flv_close(); + if (ret != ERROR_SUCCESS) { + srs_warn("ignore flv close error. ret=%d", ret); + } + + dvr_enabled = false; +} + +int SrsDvrSegmentPlan::on_audio(SrsSharedPtrMessage* audio) +{ + int ret = ERROR_SUCCESS; + + if (!dvr_enabled) { + return ret; + } + + if ((ret = update_duration(audio)) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = SrsDvrPlan::on_audio(audio)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsDvrSegmentPlan::on_video(SrsSharedPtrMessage* video) +{ + int ret = ERROR_SUCCESS; + + if (!dvr_enabled) { + return ret; + } + + if ((ret = update_duration(video)) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = SrsDvrPlan::on_video(video)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsDvrSegmentPlan::update_duration(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + // foreach msg, collect the duration. + if (starttime < 0 || starttime > msg->header.timestamp) { + starttime = msg->header.timestamp; + } + duration += msg->header.timestamp - starttime; + starttime = msg->header.timestamp; + + // reap if exceed duration. + if (duration > 0 && segment_duration > 0 && duration > segment_duration) { + on_unpublish(); + } + + return ret; +} + SrsDvr::SrsDvr(SrsSource* source) { _source = source; @@ -469,14 +603,14 @@ SrsDvr::~SrsDvr() srs_freep(plan); } -int SrsDvr::initialize() +int SrsDvr::initialize(SrsRequest* req) { int ret = ERROR_SUCCESS; srs_freep(plan); - plan = SrsDvrPlan::create_plan(); + plan = SrsDvrPlan::create_plan(req->vhost); - if ((ret = plan->initialize(_source)) != ERROR_SUCCESS) { + if ((ret = plan->initialize(_source, req)) != ERROR_SUCCESS) { return ret; } diff --git a/trunk/src/app/srs_app_dvr.hpp b/trunk/src/app/srs_app_dvr.hpp index f4506a8a1..6de317e36 100644 --- a/trunk/src/app/srs_app_dvr.hpp +++ b/trunk/src/app/srs_app_dvr.hpp @@ -127,7 +127,7 @@ public: SrsDvrPlan(); virtual ~SrsDvrPlan(); public: - virtual int initialize(SrsSource* source); + virtual int initialize(SrsSource* source, SrsRequest* req); virtual int on_publish(SrsRequest* req) = 0; virtual void on_unpublish() = 0; virtual int on_meta_data(SrsOnMetaDataPacket* metadata); @@ -137,14 +137,11 @@ protected: virtual int flv_open(std::string stream, std::string path); virtual int flv_close(); public: - static SrsDvrPlan* create_plan(); + static SrsDvrPlan* create_plan(std::string vhost); }; /** -* default session plan: -* 1. start dvr when session start(publish). -* 2. stop dvr when session stop(unpublish). -* 3. always dvr to file: dvr_path/app/stream.flv +* session plan: reap flv when session complete(unpublish) */ class SrsDvrSessionPlan : public SrsDvrPlan { @@ -156,6 +153,29 @@ public: virtual void on_unpublish(); }; +/** +* segment plan: reap flv when duration exceed. +*/ +class SrsDvrSegmentPlan : public SrsDvrPlan +{ +private: + int64_t duration; + int64_t starttime; + // in config, in ms + int segment_duration; +public: + SrsDvrSegmentPlan(); + virtual ~SrsDvrSegmentPlan(); +public: + virtual int initialize(SrsSource* source, SrsRequest* req); + virtual int on_publish(SrsRequest* req); + virtual void on_unpublish(); + virtual int on_audio(SrsSharedPtrMessage* audio); + virtual int on_video(SrsSharedPtrMessage* video); +private: + virtual int update_duration(SrsSharedPtrMessage* msg); +}; + /** * dvr(digital video recorder) to record RTMP stream to flv file. * TODO: FIXME: add utest for it. @@ -175,7 +195,7 @@ public: * when system initialize(encoder publish at first time, or reload), * initialize the dvr will reinitialize the plan, the whole dvr framework. */ - virtual int initialize(); + virtual int initialize(SrsRequest* req); /** * publish stream event, * when encoder start to publish RTMP stream. diff --git a/trunk/src/app/srs_app_source.cpp b/trunk/src/app/srs_app_source.cpp index bb7910867..b798e51b4 100644 --- a/trunk/src/app/srs_app_source.cpp +++ b/trunk/src/app/srs_app_source.cpp @@ -507,7 +507,7 @@ int SrsSource::initialize() int ret = ERROR_SUCCESS; #ifdef SRS_AUTO_DVR - if ((ret = dvr->initialize()) != ERROR_SUCCESS) { + if ((ret = dvr->initialize(req)) != ERROR_SUCCESS) { return ret; } #endif @@ -641,7 +641,7 @@ int SrsSource::on_reload_vhost_dvr(string vhost) dvr->on_unpublish(); // reinitialize the dvr, update plan. - if ((ret = dvr->initialize()) != ERROR_SUCCESS) { + if ((ret = dvr->initialize(req)) != ERROR_SUCCESS) { return ret; } diff --git a/trunk/src/kernel/srs_kernel_utility.cpp b/trunk/src/kernel/srs_kernel_utility.cpp index d50c5cc2b..4fa4ac573 100644 --- a/trunk/src/kernel/srs_kernel_utility.cpp +++ b/trunk/src/kernel/srs_kernel_utility.cpp @@ -31,6 +31,10 @@ static int64_t _srs_system_time_us_cache = 0; int64_t srs_get_system_time_ms() { + if (_srs_system_time_us_cache <= 0) { + srs_update_system_time_ms(); + } + return _srs_system_time_us_cache / 1000; }