winlin

for #351, support config the m3u8/ts path for hls. 2.0.149.

... ... @@ -562,6 +562,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.
... ...
... ... @@ -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
... ...
... ... @@ -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;
}
}
... ...
... ... @@ -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;
}
}
... ...
... ... @@ -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;
... ...
... ... @@ -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();
}
... ...
... ... @@ -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,10 +873,18 @@ 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.
*/
virtual double get_hls_fragment(std::string vhost);
... ...
... ... @@ -27,7 +27,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <fcntl.h>
#include <sstream>
#include <sys/time.h>
#include <algorithm>
using namespace std;
... ... @@ -42,6 +41,7 @@ using namespace std;
#include <srs_rtmp_amf0.hpp>
#include <srs_kernel_stream.hpp>
#include <srs_app_json.hpp>
#include <srs_app_utility.hpp>
// 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;
}
... ...
... ... @@ -53,6 +53,7 @@ using namespace std;
#include <srs_kernel_file.hpp>
#include <srs_rtmp_buffer.hpp>
#include <srs_kernel_ts.hpp>
#include <srs_app_utility.hpp>
// 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;
}
... ...
... ... @@ -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);
};
/**
... ...
... ... @@ -32,6 +32,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <sys/sysctl.h>
#endif
#include <stdlib.h>
#include <sys/time.h>
using namespace std;
#include <srs_kernel_log.hpp>
... ... @@ -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";
... ...
... ... @@ -51,6 +51,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>
*/
... ...
... ... @@ -31,7 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// current release version
#define VERSION_MAJOR 2
#define VERSION_MINOR 0
#define VERSION_REVISION 148
#define VERSION_REVISION 149
// server info.
#define RTMP_SIG_SRS_KEY "SRS"
... ...