From a3648063d5d5ec0f4be5689b2a3e2bfb4e8158f6 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 24 Feb 2015 17:29:30 +0800 Subject: [PATCH] fix #179, support dvr http api. 2.0.123. --- README.md | 1 + trunk/conf/full.conf | 4 + trunk/src/app/srs_app_dvr.cpp | 128 +++++++++++++++++++++++++++++ trunk/src/app/srs_app_dvr.hpp | 9 ++ trunk/src/app/srs_app_http_api.cpp | 15 ++++ 5 files changed, 157 insertions(+) diff --git a/README.md b/README.md index f7eb64efb5..8e0b31a931 100755 --- a/README.md +++ b/README.md @@ -532,6 +532,7 @@ Supported operating systems and hardware: ### SRS 2.0 history +* v2.0, 2015-02-24, fix [#179](https://github.com/winlinvip/simple-rtmp-server/issues/179), support dvr http api. 2.0.123. * v2.0, 2015-02-19, refine dvr, append file when dvr file exists. 2.0.122. * v2.0, 2015-02-19, refine pithy print to more easyer to use. 2.0.121. * v2.0, 2015-02-18, fix [#133](https://github.com/winlinvip/simple-rtmp-server/issues/133), support push rtsp to srs. 2.0.120. diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index dce1f1813f..2d875e0ee8 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -314,6 +314,9 @@ vhost dvr.srs.com { # vhost, stop all dvr of this vhost. # response in json, where: # {code:0} + # method=PUT, use as RPC(remote process call). + # reap_segment, the request params in json, where: + # {action:"reap_segment", vhost:"__defaultVhost", path_tmpl:"./[15].[04].[05].[999].flv"} # when reap segment, the callback POST request in json: # {action:"on_dvr_reap_segment", client_id:100, vhost:"__defaultVhost__", # app:"live", stream:"livestream", cwd:"/home/winlin/srs", file:"./dvr.flv" @@ -322,6 +325,7 @@ vhost dvr.srs.com { # @read https://github.com/winlinvip/simple-rtmp-server/wiki/v2_CN_DVR#http-callback # @read https://github.com/winlinvip/simple-rtmp-server/wiki/v2_EN_DVR#http-callback # default: session + # TODO: FIXME: update wiki for the api plan. dvr_plan session; # the dvr output path. # we supports some variables to generate the filename. diff --git a/trunk/src/app/srs_app_dvr.cpp b/trunk/src/app/srs_app_dvr.cpp index 75248be5d2..6ed944b6ef 100644 --- a/trunk/src/app/srs_app_dvr.cpp +++ b/trunk/src/app/srs_app_dvr.cpp @@ -48,6 +48,9 @@ using namespace std; // the sleep interval for http async callback. #define SRS_AUTO_ASYNC_CALLBACL_SLEEP_US 300000 +// the use raction for dvr rpc. +#define SRS_DVR_USER_ACTION_REAP_SEGMENT "reap_segment" + SrsFlvSegment::SrsFlvSegment(SrsDvrPlan* p) { req = NULL; @@ -1003,6 +1006,10 @@ int SrsDvrApiPlan::on_video(SrsSharedPtrMessage* __video) sh_video = __video->copy(); } + if ((ret = check_user_actions(__video)) != ERROR_SUCCESS) { + return ret; + } + if ((ret = SrsDvrPlan::on_video(__video)) != ERROR_SUCCESS) { return ret; } @@ -1101,6 +1108,29 @@ int SrsDvrApiPlan::stop() return ret; } +int SrsDvrApiPlan::rpc(SrsJsonObject* obj) +{ + int ret = ERROR_SUCCESS; + + SrsJsonAny* prop = NULL; + if ((prop = obj->ensure_property_string("action")) == NULL) { + ret = ERROR_HTTP_DVR_REQUEST; + srs_error("dvr: rpc required action request. ret=%d", ret); + return ret; + } + + action = prop->to_str(); + if (action == SRS_DVR_USER_ACTION_REAP_SEGMENT) { + if ((prop = obj->ensure_property_string("path_tmpl")) != NULL) { + path_template = prop->to_str(); + } + } else { + ret = ERROR_HTTP_DVR_REQUEST; + } + + return ret; +} + int SrsDvrApiPlan::on_reap_segment() { int ret = ERROR_SUCCESS; @@ -1116,6 +1146,64 @@ int SrsDvrApiPlan::on_reap_segment() return ret; } +int SrsDvrApiPlan::check_user_actions(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + srs_assert(segment); + + if (action == SRS_DVR_USER_ACTION_REAP_SEGMENT) { + // when wait keyframe, ignore if no frame arrived. + // @see https://github.com/winlinvip/simple-rtmp-server/issues/177 + if (_srs_config->get_dvr_wait_keyframe(req->vhost)) { + if (!msg->is_video()) { + return ret; + } + + char* payload = msg->payload; + int size = msg->size; + bool is_key_frame = SrsFlvCodec::video_is_h264(payload, size) + && SrsFlvCodec::video_is_keyframe(payload, size) + && !SrsFlvCodec::video_is_sequence_header(payload, size); + if (!is_key_frame) { + return ret; + } + } + + // reap segment + if ((ret = segment->close()) != ERROR_SUCCESS) { + return ret; + } + + // use new path template if user specified. + if (!path_template.empty() && (ret = set_path_tmpl(path_template)) != ERROR_SUCCESS) { + return ret; + } + + // open new flv file + if ((ret = segment->open()) != ERROR_SUCCESS) { + return ret; + } + + // update sequence header + if (metadata && (ret = SrsDvrPlan::on_meta_data(metadata)) != ERROR_SUCCESS) { + return ret; + } + if (sh_video && (ret = SrsDvrPlan::on_video(sh_video)) != ERROR_SUCCESS) { + return ret; + } + if (sh_audio && (ret = SrsDvrPlan::on_audio(sh_audio)) != ERROR_SUCCESS) { + return ret; + } + } + + // reset rcp params. + action = ""; + path_template = ""; + + return ret; +} + SrsDvrAppendPlan::SrsDvrAppendPlan() { last_update_time = 0; @@ -1506,6 +1594,46 @@ int SrsApiDvrPool::stop(string vhost) return ret; } +int SrsApiDvrPool::rpc(SrsJsonAny* json) +{ + int ret = ERROR_SUCCESS; + + if (!json->is_object()) { + ret = ERROR_HTTP_DVR_REQUEST; + srs_error("dvr: rpc required object request. ret=%d", ret); + return ret; + } + + SrsJsonObject* obj = json->to_object(); + + SrsJsonAny* prop = NULL; + if ((prop = obj->ensure_property_string("vhost")) == NULL) { + ret = ERROR_HTTP_DVR_REQUEST; + srs_error("dvr: rpc required vhost request. ret=%d", ret); + return ret; + } + std::string vhost = prop->to_str(); + + std::vector plans; + for (int i = 0; i < (int)dvrs.size(); i++) { + SrsDvrApiPlan* plan = dvrs.at(i); + if (!vhost.empty() && plan->req->vhost != vhost) { + continue; + } + plans.push_back(plan); + } + + for (int i = 0; i < (int)plans.size(); i++) { + SrsDvrApiPlan* plan = plans.at(i); + + if ((ret = plan->rpc(obj)) != ERROR_SUCCESS) { + return ret; + } + } + + return ret; +} + SrsDvr::SrsDvr(SrsSource* s) { source = s; diff --git a/trunk/src/app/srs_app_dvr.hpp b/trunk/src/app/srs_app_dvr.hpp index 45865b22b6..c1619c7677 100644 --- a/trunk/src/app/srs_app_dvr.hpp +++ b/trunk/src/app/srs_app_dvr.hpp @@ -44,6 +44,7 @@ class SrsFileWriter; class SrsFlvEncoder; class SrsDvrPlan; class SrsJsonAny; +class SrsJsonObject; class SrsThread; #include @@ -305,6 +306,10 @@ class SrsDvrApiPlan : public SrsDvrPlan std::string callback; bool autostart; bool started; +private: + // user action, reap_segment. + std::string action; + std::string path_template; public: SrsDvrApiPlan(); virtual ~SrsDvrApiPlan(); @@ -322,8 +327,11 @@ class SrsDvrApiPlan : public SrsDvrPlan virtual int start(); virtual int dumps(std::stringstream& ss); virtual int stop(); + virtual int rpc(SrsJsonObject* obj); protected: virtual int on_reap_segment(); +private: + virtual int check_user_actions(SrsSharedPtrMessage* msg); }; /** @@ -389,6 +397,7 @@ class SrsApiDvrPool virtual int dumps(std::string vhost, std::stringstream& ss); virtual int create(SrsJsonAny* json); virtual int stop(std::string vhost); + virtual int rpc(SrsJsonAny* json); }; /** diff --git a/trunk/src/app/srs_app_http_api.cpp b/trunk/src/app/srs_app_http_api.cpp index 69e62e2cfe..f8236567b1 100644 --- a/trunk/src/app/srs_app_http_api.cpp +++ b/trunk/src/app/srs_app_http_api.cpp @@ -516,6 +516,21 @@ int SrsGoApiDvrs::serve_http(ISrsGoHttpResponseWriter* w, SrsHttpMessage* r) } else if (r->is_http_delete()) { int ret = pool->stop(r->query_get("vhost")); + ss << __SRS_JOBJECT_START + << __SRS_JFIELD_ERROR(ret) + << __SRS_JOBJECT_END; + } else if (r->is_http_put()) { + int ret = ERROR_SUCCESS; + + std::string body = r->body(); + SrsJsonAny* json = SrsJsonAny::loads((char*)body.c_str()); + if (!json) { + ret = ERROR_HTTP_JSON_REQUIRED; + } else { + SrsAutoFree(SrsJsonAny, json); + ret = pool->rpc(json); + } + ss << __SRS_JOBJECT_START << __SRS_JFIELD_ERROR(ret) << __SRS_JOBJECT_END;