diff --git a/README.md b/README.md index 1f5f030d3..a4f3af865 100755 --- a/README.md +++ b/README.md @@ -566,6 +566,7 @@ Supported operating systems and hardware: ### SRS 2.0 history +* v2.0, 2015-03-30, for [#351](https://github.com/winlinvip/simple-rtmp-server/issues/351), support config the m3u8/ts path for hls. 2.0.149. * v2.0, 2015-03-17, for [#155](https://github.com/winlinvip/simple-rtmp-server/issues/155), osx(darwin) support demo with nginx and ffmpeg. 2.0.143. * v2.0, 2015-03-15, start [2.0release branch](https://github.com/winlinvip/simple-rtmp-server/tree/2.0release), 80773 lines. * v2.0, 2015-03-14, fix [#324](https://github.com/winlinvip/simple-rtmp-server/issues/324), support hstrs(http stream trigger rtmp source) edge mode. 2.0.140. diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index 46eb83b8f..7441cad6f 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -528,17 +528,38 @@ vhost with-hls.srs.com { # default: disk hls_storage disk; # the hls output path. - # the app dir is auto created under the hls_path. - # for example, for rtmp stream: - # rtmp://127.0.0.1/live/livestream - # http://127.0.0.1/live/livestream.m3u8 - # where hls_path is /hls, srs will create the following files: - # /hls/live the app dir for all streams. - # /hls/live/livestream.m3u8 the HLS m3u8 file. - # /hls/live/livestream-1.ts the HLS media/ts file. - # in a word, the hls_path is for vhost. + # the m3u8 file is configed by hls_path/hls_m3u8_file, the default is: + # ./objs/nginx/html/[app]/[stream].m3u8 + # the ts file is configed by hls_path/hls_ts_file, the default is: + # ./objs/nginx/html/[app]/[stream]-[seq].ts + # @remark the hls_path is compatible with srs v1 config. # default: ./objs/nginx/html hls_path ./objs/nginx/html; + # the hls m3u8 file name. + # we supports some variables to generate the filename. + # [vhost], the vhost of stream. + # [app], the app of stream. + # [stream], the stream name of stream. + # default: [app]/[stream].m3u8 + hls_m3u8_file [app]/[stream].m3u8; + # the hls ts file name. + # we supports some variables to generate the filename. + # [vhost], the vhost of stream. + # [app], the app of stream. + # [stream], the stream name of stream. + # [2006], replace this const to current year. + # [01], replace this const to current month. + # [02], replace this const to current date. + # [15], replace this const to current hour. + # [04], repleace this const to current minute. + # [05], repleace this const to current second. + # [999], repleace this const to current millisecond. + # [timestamp],replace this const to current UNIX timestamp in ms. + # [seq], the sequence number of ts. + # @see https://github.com/winlinvip/simple-rtmp-server/wiki/v2_CN_DVR#custom-path + # @see https://github.com/winlinvip/simple-rtmp-server/wiki/v2_CN_DeliveryHLS#hls-config + # default: [app]/[stream]-[seq].ts + hls_ts_file [app]/[stream]-[seq].ts; # the hls entry prefix, which is base url of ts url. # if specified, the ts path in m3u8 will be like: # http://your-server/live/livestream-0.ts diff --git a/trunk/conf/hls.conf b/trunk/conf/hls.conf index 0263b7ea7..698a9e577 100644 --- a/trunk/conf/hls.conf +++ b/trunk/conf/hls.conf @@ -10,5 +10,7 @@ vhost __defaultVhost__ { hls_fragment 10; hls_window 60; hls_path ./objs/nginx/html; + hls_m3u8_file [app]/[stream].m3u8; + hls_ts_file [app]/[stream]-[seq].ts; } } diff --git a/trunk/conf/http.hls.conf b/trunk/conf/http.hls.conf index 74b2a219e..19d3622cc 100644 --- a/trunk/conf/http.hls.conf +++ b/trunk/conf/http.hls.conf @@ -12,8 +12,10 @@ http_server { vhost __defaultVhost__ { hls { enabled on; - hls_path ./objs/nginx/html; hls_fragment 10; hls_window 60; + hls_path ./objs/nginx/html; + hls_m3u8_file [app]/[stream].m3u8; + hls_ts_file [app]/[stream]-[seq].ts; } } diff --git a/trunk/conf/transcode2hls.audio.only.conf b/trunk/conf/transcode2hls.audio.only.conf index addd00e43..8b0fab0de 100644 --- a/trunk/conf/transcode2hls.audio.only.conf +++ b/trunk/conf/transcode2hls.audio.only.conf @@ -7,9 +7,11 @@ max_connections 1000; vhost __defaultVhost__ { hls { enabled on; - hls_path ./objs/nginx/html; hls_fragment 10; hls_window 60; + hls_path ./objs/nginx/html; + hls_m3u8_file [app]/[stream].m3u8; + hls_ts_file [app]/[stream]-[seq].ts; } transcode { enabled on; diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index 1af1d03d0..9f3c9d4a0 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -1482,6 +1482,7 @@ int SrsConfig::check_config() string m = conf->at(j)->name.c_str(); if (m != "enabled" && m != "hls_entry_prefix" && m != "hls_path" && m != "hls_fragment" && m != "hls_window" && m != "hls_on_error" && m != "hls_storage" && m != "hls_mount" && m != "hls_td_ratio" && m != "hls_aof_ratio" && m != "hls_acodec" && m != "hls_vcodec" + && m != "hls_m3u8_file" && m != "hls_ts_file" ) { ret = ERROR_SYSTEM_CONFIG_INVALID; srs_error("unsupported vhost hls directive %s, ret=%d", m.c_str(), ret); @@ -3168,7 +3169,41 @@ string SrsConfig::get_hls_path(string vhost) if (!conf) { return SRS_CONF_DEFAULT_HLS_PATH; } + + return conf->arg0(); +} + +string SrsConfig::get_hls_m3u8_file(string vhost) +{ + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_M3U8_FILE; + } + + SrsConfDirective* conf = hls->get("hls_m3u8_file"); + + if (!conf) { + return SRS_CONF_DEFAULT_HLS_M3U8_FILE; + } + + return conf->arg0(); +} +string SrsConfig::get_hls_ts_file(string vhost) +{ + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_TS_FILE; + } + + SrsConfDirective* conf = hls->get("hls_ts_file"); + + if (!conf) { + return SRS_CONF_DEFAULT_HLS_TS_FILE; + } + return conf->arg0(); } diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 6692c8fd6..631498539 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -46,6 +46,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_CONF_DEFAULT_MAX_CONNECTIONS 1000 #define SRS_CONF_DEFAULT_HLS_PATH "./objs/nginx/html" +#define SRS_CONF_DEFAULT_HLS_M3U8_FILE "[app]/[stream].m3u8" +#define SRS_CONF_DEFAULT_HLS_TS_FILE "[app]/[stream]-[seq].ts" #define SRS_CONF_DEFAULT_HLS_FRAGMENT 10 #define SRS_CONF_DEFAULT_HLS_TD_RATIO 1.5 #define SRS_CONF_DEFAULT_HLS_AOF_RATIO 2.0 @@ -871,9 +873,17 @@ public: */ virtual std::string get_hls_entry_prefix(std::string vhost); /** - * get the HLS ts/m3u8 file store path. - */ + * get the HLS ts/m3u8 file store path. + */ virtual std::string get_hls_path(std::string vhost); + /** + * get the HLS m3u8 file path template. + */ + virtual std::string get_hls_m3u8_file(std::string vhost); + /** + * get the HLS ts file path template. + */ + virtual std::string get_hls_ts_file(std::string vhost); /** * get the hls fragment time, in seconds. */ diff --git a/trunk/src/app/srs_app_dvr.cpp b/trunk/src/app/srs_app_dvr.cpp index 77a230841..ee0411f80 100644 --- a/trunk/src/app/srs_app_dvr.cpp +++ b/trunk/src/app/srs_app_dvr.cpp @@ -27,7 +27,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -#include #include using namespace std; @@ -42,6 +41,7 @@ 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 @@ -422,76 +422,8 @@ string SrsFlvSegment::generate_path() // the flv file path std::string flv_path = path_config; - - // variable [vhost] - flv_path = srs_string_replace(flv_path, "[vhost]", req->vhost); - // variable [app] - flv_path = srs_string_replace(flv_path, "[app]", req->app); - // variable [stream] - flv_path = srs_string_replace(flv_path, "[stream]", req->stream); - - // date and time substitude - // clock time - timeval tv; - if (gettimeofday(&tv, NULL) == -1) { - return flv_path; - } - - // to calendar time - struct tm* tm; - if ((tm = localtime(&tv.tv_sec)) == NULL) { - return flv_path; - } - - // the buffer to format the date and time. - char buf[64]; - - // [2006], replace with current year. - if (true) { - snprintf(buf, sizeof(buf), "%d", 1900 + tm->tm_year); - flv_path = srs_string_replace(flv_path, "[2006]", buf); - } - // [2006], replace with current year. - if (true) { - snprintf(buf, sizeof(buf), "%d", 1900 + tm->tm_year); - flv_path = srs_string_replace(flv_path, "[2006]", buf); - } - // [01], replace this const to current month. - if (true) { - snprintf(buf, sizeof(buf), "%d", 1 + tm->tm_mon); - flv_path = srs_string_replace(flv_path, "[01]", buf); - } - // [02], replace this const to current date. - if (true) { - snprintf(buf, sizeof(buf), "%d", tm->tm_mday); - flv_path = srs_string_replace(flv_path, "[02]", buf); - } - // [15], replace this const to current hour. - if (true) { - snprintf(buf, sizeof(buf), "%d", tm->tm_hour); - flv_path = srs_string_replace(flv_path, "[15]", buf); - } - // [04], repleace this const to current minute. - if (true) { - snprintf(buf, sizeof(buf), "%d", tm->tm_min); - flv_path = srs_string_replace(flv_path, "[04]", buf); - } - // [05], repleace this const to current second. - if (true) { - snprintf(buf, sizeof(buf), "%d", tm->tm_sec); - flv_path = srs_string_replace(flv_path, "[05]", buf); - } - // [999], repleace this const to current millisecond. - if (true) { - snprintf(buf, sizeof(buf), "%03d", (int)(tv.tv_usec / 1000)); - flv_path = srs_string_replace(flv_path, "[999]", buf); - } - // [timestamp],replace this const to current UNIX timestamp in ms. - if (true) { - int64_t now_us = ((int64_t)tv.tv_sec) * 1000 * 1000 + (int64_t)tv.tv_usec; - snprintf(buf, sizeof(buf), "%"PRId64, now_us / 1000); - flv_path = srs_string_replace(flv_path, "[timestamp]", buf); - } + 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; } diff --git a/trunk/src/app/srs_app_hls.cpp b/trunk/src/app/srs_app_hls.cpp index 446946ba9..c166f1540 100644 --- a/trunk/src/app/srs_app_hls.cpp +++ b/trunk/src/app/srs_app_hls.cpp @@ -53,6 +53,7 @@ using namespace std; #include #include #include +#include // drop the segment when duration of ts too small. #define SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS 100 @@ -204,8 +205,9 @@ int SrsHlsMuxer::sequence_no() return _sequence_no; } -int SrsHlsMuxer::update_config(SrsRequest* r, string entry_prefix, string path, int fragment, int window, double aof_ratio) -{ +int SrsHlsMuxer::update_config(SrsRequest* r, string entry_prefix, + string path, string m3u8_file, string ts_file, int fragment, int window, double aof_ratio +) { int ret = ERROR_SUCCESS; srs_freep(req); @@ -213,6 +215,8 @@ int SrsHlsMuxer::update_config(SrsRequest* r, string entry_prefix, string path, hls_entry_prefix = entry_prefix; hls_path = path; + hls_m3u8_file = m3u8_file; + hls_ts_file = ts_file; hls_fragment = fragment; hls_aof_ratio = aof_ratio; hls_window = window; @@ -249,7 +253,7 @@ int SrsHlsMuxer::segment_open(int64_t segment_start_dts) // TODO: create all parents dirs. // create dir for app. - if (should_write_file && (ret = create_dir()) != ERROR_SUCCESS) { + if (should_write_file && (ret = create_dir(current->full_path)) != ERROR_SUCCESS) { return ret; } @@ -292,22 +296,23 @@ int SrsHlsMuxer::segment_open(int64_t segment_start_dts) current->segment_start_dts = segment_start_dts; // generate filename. - char filename[128]; - snprintf(filename, sizeof(filename), - "%s-%d.ts", req->stream.c_str(), current->sequence_no); + std::string ts_file = hls_ts_file; + ts_file = srs_path_build_stream(ts_file, req->vhost, req->app, req->stream); + ts_file = srs_path_build_timestamp(ts_file); + if (true) { + std::stringstream ss; + ss << current->sequence_no; + ts_file = srs_string_replace(ts_file, "[seq]", ss.str()); + } - // TODO: use temp file and rename it. - current->full_path = hls_path; - current->full_path += "/"; - current->full_path += req->app; - current->full_path += "/"; - current->full_path += filename; + // replace variables + current->full_path = hls_path + "/" + ts_file; current->uri += hls_entry_prefix; if (!hls_entry_prefix.empty() && !srs_string_ends_with(hls_entry_prefix, "/")) { current->uri += "/"; } - current->uri += filename; + current->uri += ts_file; std::string tmp_file = current->full_path + ".tmp"; if ((ret = current->muxer->open(tmp_file.c_str())) != ERROR_SUCCESS) { @@ -524,10 +529,8 @@ int SrsHlsMuxer::refresh_m3u8() std::string m3u8_file = hls_path; m3u8_file += "/"; - m3u8_file += req->app; - m3u8_file += "/"; - m3u8_file += req->stream; - m3u8_file += ".m3u8"; + m3u8_file += hls_m3u8_file; + m3u8_file = srs_path_build_stream(m3u8_file, req->vhost, req->app, req->stream); m3u8 = m3u8_file; m3u8_file += ".temp"; @@ -631,7 +634,7 @@ int SrsHlsMuxer::_refresh_m3u8(string m3u8_file) return ret; } -int SrsHlsMuxer::create_dir() +int SrsHlsMuxer::create_dir(string filepath) { int ret = ERROR_SUCCESS; @@ -639,9 +642,11 @@ int SrsHlsMuxer::create_dir() return ret; } - std::string app_dir = hls_path; - app_dir += "/"; - app_dir += req->app; + std::string app_dir = filepath; + size_t pos = string::npos; + if ((pos = app_dir.rfind("/")) != string::npos) { + app_dir = app_dir.substr(0, pos); + } // TODO: cleanup the dir when startup. @@ -678,7 +683,9 @@ int SrsHlsCache::on_publish(SrsHlsMuxer* muxer, SrsRequest* req, int64_t segment // get the hls m3u8 ts list entry prefix config std::string entry_prefix = _srs_config->get_hls_entry_prefix(vhost); // get the hls path config - std::string hls_path = _srs_config->get_hls_path(vhost); + std::string path = _srs_config->get_hls_path(vhost); + std::string m3u8_file = _srs_config->get_hls_m3u8_file(vhost); + std::string ts_file = _srs_config->get_hls_ts_file(vhost); // the audio overflow, for pure audio to reap segment. double hls_aof_ratio = _srs_config->get_hls_aof_ratio(vhost); @@ -686,7 +693,7 @@ int SrsHlsCache::on_publish(SrsHlsMuxer* muxer, SrsRequest* req, int64_t segment // for the HLS donot requires the EXT-X-MEDIA-SEQUENCE be monotonically increase. // open muxer - if ((ret = muxer->update_config(req, entry_prefix, hls_path, hls_fragment, hls_window, hls_aof_ratio)) != ERROR_SUCCESS) { + if ((ret = muxer->update_config(req, entry_prefix, path, m3u8_file, ts_file, hls_fragment, hls_window, hls_aof_ratio)) != ERROR_SUCCESS) { srs_error("m3u8 muxer update config failed. ret=%d", ret); return ret; } diff --git a/trunk/src/app/srs_app_hls.hpp b/trunk/src/app/srs_app_hls.hpp index ed90261da..1b590d184 100644 --- a/trunk/src/app/srs_app_hls.hpp +++ b/trunk/src/app/srs_app_hls.hpp @@ -169,6 +169,8 @@ private: private: std::string hls_entry_prefix; std::string hls_path; + std::string hls_m3u8_file; + std::string hls_ts_file; double hls_aof_ratio; int hls_fragment; int hls_window; @@ -209,7 +211,9 @@ public: /** * when publish, update the config for muxer. */ - virtual int update_config(SrsRequest* r, std::string entry_prefix, std::string path, int fragment, int window, double aof_ratio); + virtual int update_config(SrsRequest* r, std::string entry_prefix, + std::string path, std::string m3u8_file, std::string ts_file, + int fragment, int window, double aof_ratio); /** * open a new segment(a new ts file), * @param segment_start_dts use to calc the segment duration, @@ -240,7 +244,7 @@ public: private: virtual int refresh_m3u8(); virtual int _refresh_m3u8(std::string m3u8_file); - virtual int create_dir(); + virtual int create_dir(std::string filepath); }; /** diff --git a/trunk/src/app/srs_app_utility.cpp b/trunk/src/app/srs_app_utility.cpp index 2b0ad02ad..9e93a4601 100644 --- a/trunk/src/app/srs_app_utility.cpp +++ b/trunk/src/app/srs_app_utility.cpp @@ -32,6 +32,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #endif #include +#include using namespace std; #include @@ -111,6 +112,91 @@ int srs_get_log_level(string level) } } +string srs_path_build_stream(string template_path, string vhost, string app, string stream) +{ + std::string path = template_path; + + // variable [vhost] + path = srs_string_replace(path, "[vhost]", vhost); + // variable [app] + path = srs_string_replace(path, "[app]", app); + // variable [stream] + path = srs_string_replace(path, "[stream]", stream); + + return path; +} + +string srs_path_build_timestamp(string template_path) +{ + std::string path = template_path; + + + // date and time substitude + // clock time + timeval tv; + if (gettimeofday(&tv, NULL) == -1) { + return path; + } + + // to calendar time + struct tm* tm; + if ((tm = localtime(&tv.tv_sec)) == NULL) { + return path; + } + + // the buffer to format the date and time. + char buf[64]; + + // [2006], replace with current year. + if (true) { + snprintf(buf, sizeof(buf), "%d", 1900 + tm->tm_year); + path = srs_string_replace(path, "[2006]", buf); + } + // [2006], replace with current year. + if (true) { + snprintf(buf, sizeof(buf), "%d", 1900 + tm->tm_year); + path = srs_string_replace(path, "[2006]", buf); + } + // [01], replace this const to current month. + if (true) { + snprintf(buf, sizeof(buf), "%d", 1 + tm->tm_mon); + path = srs_string_replace(path, "[01]", buf); + } + // [02], replace this const to current date. + if (true) { + snprintf(buf, sizeof(buf), "%d", tm->tm_mday); + path = srs_string_replace(path, "[02]", buf); + } + // [15], replace this const to current hour. + if (true) { + snprintf(buf, sizeof(buf), "%d", tm->tm_hour); + path = srs_string_replace(path, "[15]", buf); + } + // [04], repleace this const to current minute. + if (true) { + snprintf(buf, sizeof(buf), "%d", tm->tm_min); + path = srs_string_replace(path, "[04]", buf); + } + // [05], repleace this const to current second. + if (true) { + snprintf(buf, sizeof(buf), "%d", tm->tm_sec); + path = srs_string_replace(path, "[05]", buf); + } + // [999], repleace this const to current millisecond. + if (true) { + snprintf(buf, sizeof(buf), "%03d", (int)(tv.tv_usec / 1000)); + path = srs_string_replace(path, "[999]", buf); + } + // [timestamp],replace this const to current UNIX timestamp in ms. + if (true) { + int64_t now_us = ((int64_t)tv.tv_sec) * 1000 * 1000 + (int64_t)tv.tv_usec; + snprintf(buf, sizeof(buf), "%"PRId64, now_us / 1000); + path = srs_string_replace(path, "[timestamp]", buf); + } + + return path; +} + void srs_parse_endpoint(string ip_port, string& ip, string& port) { ip = "0.0.0.0"; diff --git a/trunk/src/app/srs_app_utility.hpp b/trunk/src/app/srs_app_utility.hpp index 150ce71ca..11991bfd2 100644 --- a/trunk/src/app/srs_app_utility.hpp +++ b/trunk/src/app/srs_app_utility.hpp @@ -50,6 +50,29 @@ extern int srs_socket_connect(std::string server, int port, int64_t timeout, st_ */ extern int srs_get_log_level(std::string level); +/** +* build the path according to vhost/app/stream, where replace variables: +* [vhost], the vhost of stream. +* [app], the app of stream. +* [stream], the stream name of stream. +* @return the replaced path. +*/ +extern std::string srs_path_build_stream(std::string template_path, std::string vhost, std::string app, std::string stream); + +/** +* build the path according to timestamp, where replace variables: +* [2006], replace this const to current year. +* [01], replace this const to current month. +* [02], replace this const to current date. +* [15], replace this const to current hour. +* [04], repleace this const to current minute. +* [05], repleace this const to current second. +* [999], repleace this const to current millisecond. +* [timestamp],replace this const to current UNIX timestamp in ms. +* @return the replaced path. +*/ +extern std::string srs_path_build_timestamp(std::string template_path); + /** * parse the endpoint to ip and port. * @param ip_port the ip and port which formats in <[ip:]port>