diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index 3a97603f9..69273f8cf 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -948,8 +948,27 @@ vhost dash.srs.com { dash { # Whether DASH is enabled. # Transmux RTMP to DASH if on. - # default: off - enabled on; + # Default: off + enabled on; + # The duration of segment in seconds. + # Default: 10 + dash_fragment 10; + # The period to update the MPD in seconds. + # Default: 30 + dash_update_period 30; + # The depth of timeshift buffer in seconds. + # Default: 60 + dash_timeshift 60; + # The base/home dir/path for dash. + # All init and segment files will write under this dir. + dash_path ./objs/nginx/html; + # The DASH MPD file path. + # 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].mpd + dash_mpd_file [app]/[stream].mpd; } } diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index 60e2e2b09..9605c98e3 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -3917,7 +3917,8 @@ int SrsConfig::check_config() } else if (n == "dash") { for (int j = 0; j < (int)conf->directives.size(); j++) { string m = conf->at(j)->name; - if (m != "enabled") { + if (m != "enabled" && m != "dash_fragment" && m != "dash_update_period" && m != "dash_timeshift" && m != "dash_path" + && m != "dash_mpd_file") { ret = ERROR_SYSTEM_CONFIG_INVALID; srs_error("Illegal directive %s in vhost.dash, ret=%d", m.c_str(), ret); return ret; @@ -5988,6 +5989,91 @@ bool SrsConfig::get_dash_enabled(string vhost) return SRS_CONF_PERFER_FALSE(conf->arg0()); } +int SrsConfig::get_dash_fragment(string vhost) +{ + static int DEFAULT = 10 * 1000; + + SrsConfDirective* conf = get_dash(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("dash_fragment"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return (int)(1000 * ::atof(conf->arg0().c_str())); +} + +int SrsConfig::get_dash_update_period(string vhost) +{ + static int DEFAULT = 30 * 1000; + + SrsConfDirective* conf = get_dash(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("dash_update_period"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return (int)(1000 * ::atof(conf->arg0().c_str())); +} + +int SrsConfig::get_dash_timeshift(string vhost) +{ + static int DEFAULT = 60 * 1000; + + SrsConfDirective* conf = get_dash(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("dash_timeshift"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return (int)(1000 * ::atof(conf->arg0().c_str())); +} + +string SrsConfig::get_dash_path(string vhost) +{ + static string DEFAULT = "./objs/nginx/html"; + + SrsConfDirective* conf = get_dash(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("dash_path"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return conf->arg0(); +} + +string SrsConfig::get_dash_mpd_file(string vhost) +{ + static string DEFAULT = "[app]/[stream].mpd"; + + SrsConfDirective* conf = get_dash(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("dash_mpd_file"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return conf->arg0(); +} + SrsConfDirective* SrsConfig::get_hls(string vhost) { SrsConfDirective* conf = get_vhost(vhost); diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index fc9a7188e..4b99ec447 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -1140,6 +1140,16 @@ private: public: // Whether DASH is enabled. virtual bool get_dash_enabled(std::string vhost); + // Get the duration of segment in milliseconds. + virtual int get_dash_fragment(std::string vhost); + // Get the period to update MPD in milliseconds. + virtual int get_dash_update_period(std::string vhost); + // Get the depth of timeshift buffer in milliseconds. + virtual int get_dash_timeshift(std::string vhost); + // Get the base/home dir/path for dash, into which write files. + virtual std::string get_dash_path(std::string vhost); + // Get the path for DASH MPD, to generate the MPD file. + virtual std::string get_dash_mpd_file(std::string vhost); // hls section private: /** diff --git a/trunk/src/app/srs_app_dash.cpp b/trunk/src/app/srs_app_dash.cpp index 40e79b107..9b5a44a28 100644 --- a/trunk/src/app/srs_app_dash.cpp +++ b/trunk/src/app/srs_app_dash.cpp @@ -27,6 +27,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include +#include +#include +#include + +#include +using namespace std; SrsFragmentedMp4::SrsFragmentedMp4() { @@ -38,18 +45,150 @@ SrsFragmentedMp4::~SrsFragmentedMp4() SrsMpdWriter::SrsMpdWriter() { + req = NULL; + timeshit = update_period = fragment = 0; + last_update_mpd = -1; } SrsMpdWriter::~SrsMpdWriter() { } +int SrsMpdWriter::initialize(SrsRequest* r) +{ + int ret = ERROR_SUCCESS; + + req = r; + fragment = _srs_config->get_dash_fragment(r->vhost); + update_period = _srs_config->get_dash_update_period(r->vhost); + timeshit = _srs_config->get_dash_timeshift(r->vhost); + home = _srs_config->get_dash_path(r->vhost); + mpd_file = _srs_config->get_dash_mpd_file(r->vhost); + + return ret; +} + +int SrsMpdWriter::write(SrsFormat* format) +{ + int ret = ERROR_SUCCESS; + + // MPD is not expired? + if (last_update_mpd != -1 && srs_get_system_time_ms() - last_update_mpd < update_period) { + return ret; + } + last_update_mpd = srs_get_system_time_ms(); + + string mpd_path = srs_path_build_stream(mpd_file, req->vhost, req->app, req->stream); + string full_path = home + "/" + mpd_path; + string full_home = srs_path_dirname(full_path); + + if ((ret = srs_create_dir_recursively(full_home)) != ERROR_SUCCESS) { + srs_error("DASH: create MPD home failed, home=%s, ret=%d", full_home.c_str(), ret); + return ret; + } + + stringstream ss; + ss << "" << endl + << "" << endl + << " " << endl; + if (format->acodec) { + ss << " " << endl; + ss << " " << endl; + ss << " " << endl; + ss << " " << endl; + } + if (format->vcodec) { + int w = format->vcodec->width; + int h = format->vcodec->height; + ss << " " << endl; + ss << " " << endl; + ss << " " << endl; + ss << " " << endl; + } + ss << " " << endl + << "" << endl; + + SrsFileWriter fw; + if ((ret = fw.open(full_path)) != ERROR_SUCCESS) { + srs_error("DASH: open MPD file=%s failed, ret=%d", full_path.c_str(), ret); + return ret; + } + + string content = ss.str(); + if ((ret = fw.write((void*)content.data(), content.length(), NULL)) != ERROR_SUCCESS) { + srs_error("DASH: write MPD file=%s failed, ret=%d", full_path.c_str(), ret); + return ret; + } + + srs_trace("DASH: refresh MPD successed, size=%dB, file=%s", content.length(), full_path.c_str()); + + return ret; +} + SrsDashController::SrsDashController() { + mpd = new SrsMpdWriter(); } SrsDashController::~SrsDashController() { + srs_freep(mpd); +} + +int SrsDashController::initialize(SrsRequest* r) +{ + int ret = ERROR_SUCCESS; + + if ((ret = mpd->initialize(r)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsDashController::on_audio(SrsSharedPtrMessage* shared_audio, SrsFormat* format) +{ + int ret = ERROR_SUCCESS; + + if ((ret = refresh_mpd(format)) != ERROR_SUCCESS) { + srs_error("DASH: refresh the MPD failed. ret=%d", ret); + return ret; + } + + return ret; +} + +int SrsDashController::on_video(SrsSharedPtrMessage* shared_video, SrsFormat* format) +{ + int ret = ERROR_SUCCESS; + + if ((ret = refresh_mpd(format)) != ERROR_SUCCESS) { + srs_error("DASH: refresh the MPD failed. ret=%d", ret); + return ret; + } + + return ret; +} + +int SrsDashController::refresh_mpd(SrsFormat* format) +{ + int ret = ERROR_SUCCESS; + + // TODO: FIXME: Support pure audio streaming. + if (!format->acodec || !format->vcodec) { + return ret; + } + + if ((ret = mpd->write(format)) != ERROR_SUCCESS) { + return ret; + } + + return ret; } SrsDash::SrsDash() @@ -73,6 +212,11 @@ int SrsDash::initialize(SrsOriginHub* h, SrsRequest* r) hub = h; req = r; + if ((ret = controller->initialize(req)) != ERROR_SUCCESS) { + srs_error("DASH: initialize controller failed. ret=%d", ret); + return ret; + } + return ret; } @@ -88,7 +232,6 @@ int SrsDash::on_publish() if (!_srs_config->get_dash_enabled(req->vhost)) { return ret; } - enabled = true; return ret; @@ -102,6 +245,11 @@ int SrsDash::on_audio(SrsSharedPtrMessage* shared_audio, SrsFormat* format) return ret; } + if ((ret = controller->on_audio(shared_audio, format)) != ERROR_SUCCESS) { + srs_error("DASH: consume audio failed. ret=%d", ret); + return ret; + } + return ret; } @@ -113,6 +261,11 @@ int SrsDash::on_video(SrsSharedPtrMessage* shared_video, SrsFormat* format) return ret; } + if ((ret = controller->on_video(shared_video, format)) != ERROR_SUCCESS) { + srs_error("DASH: consume video failed. ret=%d", ret); + return ret; + } + return ret; } diff --git a/trunk/src/app/srs_app_dash.hpp b/trunk/src/app/srs_app_dash.hpp index 9b1456e90..1e87a8baf 100644 --- a/trunk/src/app/srs_app_dash.hpp +++ b/trunk/src/app/srs_app_dash.hpp @@ -29,6 +29,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include +#include + class SrsRequest; class SrsOriginHub; class SrsSharedPtrMessage; @@ -49,9 +51,27 @@ public: */ class SrsMpdWriter { +private: + SrsRequest* req; + int64_t last_update_mpd; +private: + // The duration of fragment in ms. + int fragment; + // The period to update the mpd in ms. + int update_period; + // The timeshift buffer depth. + int timeshit; + // The base or home dir for dash to write files. + std::string home; + // The MPD path template, from which to build the file path. + std::string mpd_file; public: SrsMpdWriter(); virtual ~SrsMpdWriter(); +public: + virtual int initialize(SrsRequest* r); + // Write MPD according to parsed format of stream. + virtual int write(SrsFormat* format); }; /** @@ -59,9 +79,17 @@ public: */ class SrsDashController { +private: + SrsMpdWriter* mpd; public: SrsDashController(); virtual ~SrsDashController(); +public: + virtual int initialize(SrsRequest* r); + virtual int on_audio(SrsSharedPtrMessage* shared_audio, SrsFormat* format); + virtual int on_video(SrsSharedPtrMessage* shared_video, SrsFormat* format); +private: + virtual int refresh_mpd(SrsFormat* format); }; /** diff --git a/trunk/src/kernel/srs_kernel_codec.cpp b/trunk/src/kernel/srs_kernel_codec.cpp index 187d61f60..f5aba41ff 100644 --- a/trunk/src/kernel/srs_kernel_codec.cpp +++ b/trunk/src/kernel/srs_kernel_codec.cpp @@ -652,13 +652,13 @@ int SrsFormat::on_aac_sequence_header(char* data, int size) bool SrsFormat::is_aac_sequence_header() { return acodec && acodec->id == SrsAudioCodecIdAAC - && audio && audio->aac_packet_type == SrsAudioAacFrameTraitSequenceHeader; + && audio && audio->aac_packet_type == SrsAudioAacFrameTraitSequenceHeader; } bool SrsFormat::is_avc_sequence_header() { return vcodec && vcodec->id == SrsVideoCodecIdAVC - && video && video->avc_packet_type == SrsVideoAvcFrameTraitSequenceHeader; + && video && video->avc_packet_type == SrsVideoAvcFrameTraitSequenceHeader; } int SrsFormat::video_avc_demux(SrsBuffer* stream, int64_t timestamp) diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index e8f0cde53..435c95b76 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -374,6 +374,11 @@ int SrsHttpFileServer::serve_file(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, _mime[".jpeg"] = "image/jpeg"; _mime[".jpg"] = "image/jpeg"; _mime[".gif"] = "image/gif"; + // For MPEG-DASH. + //_mime[".mpd"] = "application/dash+xml"; + _mime[".mpd"] = "text/xml"; + _mime[".m4s"] = "video/iso.segment"; + _mime[".mp4v"] = "video/mp4"; } if (true) {