winlin

Merge pull request #327 from wenjiegit/develop

for #328, add hds supported.
  1 +# the config for srs to delivery hds
  2 +# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleHDS
  3 +# @see full.conf for detail config.
  4 +
  5 +listen 1935;
  6 +max_connections 1000;
  7 +
  8 +daemon off;
  9 +srs_log_tank console;
  10 +srs_log_level trace;
  11 +
  12 +vhost __defaultVhost__ {
  13 + hds {
  14 + enabled on;
  15 + hds_fragment 10;
  16 + hds_window 60;
  17 + hds_path ./objs/nginx/html;
  18 + }
  19 +}
@@ -1407,6 +1407,7 @@ int SrsConfig::check_config() @@ -1407,6 +1407,7 @@ int SrsConfig::check_config()
1407 && n != "mr" && n != "mw_latency" && n != "min_latency" 1407 && n != "mr" && n != "mw_latency" && n != "min_latency"
1408 && n != "security" && n != "http_remux" 1408 && n != "security" && n != "http_remux"
1409 && n != "http" && n != "http_static" 1409 && n != "http" && n != "http_static"
  1410 + && n != "hds"
1410 ) { 1411 ) {
1411 ret = ERROR_SYSTEM_CONFIG_INVALID; 1412 ret = ERROR_SYSTEM_CONFIG_INVALID;
1412 srs_error("unsupported vhost directive %s, ret=%d", n.c_str(), ret); 1413 srs_error("unsupported vhost directive %s, ret=%d", n.c_str(), ret);
@@ -3277,6 +3278,85 @@ string SrsConfig::get_hls_vcodec(string vhost) @@ -3277,6 +3278,85 @@ string SrsConfig::get_hls_vcodec(string vhost)
3277 return conf->arg0(); 3278 return conf->arg0();
3278 } 3279 }
3279 3280
  3281 +SrsConfDirective *SrsConfig::get_hds(const string &vhost)
  3282 +{
  3283 + SrsConfDirective* conf = get_vhost(vhost);
  3284 +
  3285 + if (!conf) {
  3286 + return NULL;
  3287 + }
  3288 +
  3289 + return conf->get("hds");
  3290 +}
  3291 +
  3292 +bool SrsConfig::get_hds_enabled(const string &vhost)
  3293 +{
  3294 + SrsConfDirective* hds = get_hds(vhost);
  3295 +
  3296 + if (!hds) {
  3297 + return false;
  3298 + }
  3299 +
  3300 + SrsConfDirective* conf = hds->get("enabled");
  3301 +
  3302 + if (!conf) {
  3303 + return false;
  3304 + }
  3305 +
  3306 + return conf->arg0() == "on";
  3307 +}
  3308 +
  3309 +string SrsConfig::get_hds_path(const string &vhost)
  3310 +{
  3311 + SrsConfDirective* hds = get_hds(vhost);
  3312 +
  3313 + if (!hds) {
  3314 + return SRS_CONF_DEFAULT_HDS_PATH;
  3315 + }
  3316 +
  3317 + SrsConfDirective* conf = hds->get("hds_path");
  3318 +
  3319 + if (!conf) {
  3320 + return SRS_CONF_DEFAULT_HDS_PATH;
  3321 + }
  3322 +
  3323 + return conf->arg0();
  3324 +}
  3325 +
  3326 +double SrsConfig::get_hds_fragment(const string &vhost)
  3327 +{
  3328 + SrsConfDirective* hds = get_hds(vhost);
  3329 +
  3330 + if (!hds) {
  3331 + return SRS_CONF_DEFAULT_HDS_FRAGMENT;
  3332 + }
  3333 +
  3334 + SrsConfDirective* conf = hds->get("hds_fragment");
  3335 +
  3336 + if (!conf) {
  3337 + return SRS_CONF_DEFAULT_HDS_FRAGMENT;
  3338 + }
  3339 +
  3340 + return ::atof(conf->arg0().c_str());
  3341 +}
  3342 +
  3343 +double SrsConfig::get_hds_window(const string &vhost)
  3344 +{
  3345 + SrsConfDirective* hds = get_hds(vhost);
  3346 +
  3347 + if (!hds) {
  3348 + return SRS_CONF_DEFAULT_HDS_WINDOW;
  3349 + }
  3350 +
  3351 + SrsConfDirective* conf = hds->get("hds_window");
  3352 +
  3353 + if (!conf) {
  3354 + return SRS_CONF_DEFAULT_HDS_WINDOW;
  3355 + }
  3356 +
  3357 + return ::atof(conf->arg0().c_str());
  3358 +}
  3359 +
3280 SrsConfDirective* SrsConfig::get_dvr(string vhost) 3360 SrsConfDirective* SrsConfig::get_dvr(string vhost)
3281 { 3361 {
3282 SrsConfDirective* conf = get_vhost(vhost); 3362 SrsConfDirective* conf = get_vhost(vhost);
@@ -101,10 +101,15 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @@ -101,10 +101,15 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
101 #define SRS_CONF_DEFAULT_TRANSCODE_IFORMAT "flv" 101 #define SRS_CONF_DEFAULT_TRANSCODE_IFORMAT "flv"
102 #define SRS_CONF_DEFAULT_TRANSCODE_OFORMAT "flv" 102 #define SRS_CONF_DEFAULT_TRANSCODE_OFORMAT "flv"
103 103
  104 +// hds default value
  105 +#define SRS_CONF_DEFAULT_HDS_PATH "./objs/nginx/html"
  106 +#define SRS_CONF_DEFAULT_HDS_WINDOW (60)
  107 +#define SRS_CONF_DEFAULT_HDS_FRAGMENT (10)
  108 +
104 namespace _srs_internal 109 namespace _srs_internal
105 { 110 {
106 class SrsConfigBuffer; 111 class SrsConfigBuffer;
107 -}; 112 +}
108 113
109 /** 114 /**
110 * the config directive. 115 * the config directive.
@@ -903,6 +908,32 @@ public: @@ -903,6 +908,32 @@ public:
903 * get the HLS default video codec. 908 * get the HLS default video codec.
904 */ 909 */
905 virtual std::string get_hls_vcodec(std::string vhost); 910 virtual std::string get_hls_vcodec(std::string vhost);
  911 +
  912 + // hds section
  913 +private:
  914 + /**
  915 + * get the hds directive of vhost.
  916 + */
  917 + virtual SrsConfDirective* get_hds(const std::string &vhost);
  918 +public:
  919 + /**
  920 + * whether HDS is enabled.
  921 + */
  922 + virtual bool get_hds_enabled(const std::string &vhost);
  923 + /**
  924 + * get the HDS file store path.
  925 + */
  926 + virtual std::string get_hds_path(const std::string &vhost);
  927 + /**
  928 + * get the hds fragment time, in seconds.
  929 + */
  930 + virtual double get_hds_fragment(const std::string &vhost);
  931 + /**
  932 + * get the hds window time, in seconds.
  933 + * a window is a set of hds fragments.
  934 + */
  935 + virtual double get_hds_window(const std::string &vhost);
  936 +
906 // dvr section 937 // dvr section
907 private: 938 private:
908 /** 939 /**
  1 +/*
  2 +The MIT License (MIT)
  3 +
  4 +Copyright (c) 2013-2015 wenjiegit
  5 +Copyright (c) 2013-2015 winlin
  6 +
  7 +Permission is hereby granted, free of charge, to any person obtaining a copy of
  8 +this software and associated documentation files (the "Software"), to deal in
  9 +the Software without restriction, including without limitation the rights to
  10 +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  11 +the Software, and to permit persons to whom the Software is furnished to do so,
  12 +subject to the following conditions:
  13 +
  14 +The above copyright notice and this permission notice shall be included in all
  15 +copies or substantial portions of the Software.
  16 +
  17 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  19 +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  20 +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  21 +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  22 +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23 +*/
  24 +
  25 +#include <string>
  26 +#include <vector>
  27 +#include <sys/types.h>
  28 +#include <sys/stat.h>
  29 +#include <fcntl.h>
  30 +
  31 +#include <srs_app_hds.hpp>
  32 +#include <srs_rtmp_sdk.hpp>
  33 +#include <srs_kernel_log.hpp>
  34 +#include <srs_kernel_codec.hpp>
  35 +#include <srs_rtmp_stack.hpp>
  36 +#include <srs_kernel_stream.hpp>
  37 +#include <srs_core_autofree.hpp>
  38 +#include <srs_kernel_utility.hpp>
  39 +#include <srs_app_config.hpp>
  40 +
  41 +using namespace std;
  42 +
  43 +static void update_box(char *start, int size)
  44 +{
  45 + char *p_size = (char*)&size;
  46 + start[0] = p_size[3];
  47 + start[1] = p_size[2];
  48 + start[2] = p_size[1];
  49 + start[3] = p_size[0];
  50 +}
  51 +
  52 +char flv_header[] = {'F', 'L', 'V',
  53 + 0x01, 0x05, 0x00, 0x00, 0x00, 0x09,
  54 + 0x00, 0x00, 0x00, 0x00};
  55 +
  56 +string serialFlv(SrsSharedPtrMessage *msg)
  57 +{
  58 + SrsStream *stream = new SrsStream;
  59 +
  60 + int size = 15 + msg->size;
  61 + char *byte = new char[size];
  62 + stream->initialize(byte, size);
  63 +
  64 + // tag header
  65 + long long dts = msg->timestamp;
  66 + char type = msg->is_video() ? 0x09 : 0x08;
  67 +
  68 + stream->write_1bytes(type);
  69 + stream->write_3bytes(msg->size);
  70 + stream->write_3bytes(dts);
  71 + stream->write_1bytes(dts >> 24 & 0xFF);
  72 + stream->write_3bytes(0);
  73 + stream->write_bytes(msg->payload, msg->size);
  74 +
  75 + // pre tag size
  76 + int preTagSize = msg->size + 11;
  77 + stream->write_4bytes(preTagSize);
  78 +
  79 + string ret(stream->data(), stream->size());
  80 +
  81 + delete stream;
  82 + delete [] byte;
  83 +
  84 + return ret;
  85 +}
  86 +
  87 +class SrsHdsFragment
  88 +{
  89 +public:
  90 + SrsHdsFragment(SrsRequest *r)
  91 + : req(r)
  92 + , index(-1)
  93 + , start_time(0)
  94 + , videoSh(NULL)
  95 + , audioSh(NULL)
  96 + {
  97 +
  98 + }
  99 +
  100 + ~SrsHdsFragment()
  101 + {
  102 + srs_freep(videoSh);
  103 + srs_freep(audioSh);
  104 +
  105 + // clean msgs
  106 + list<SrsSharedPtrMessage *>::iterator iter;
  107 + for (iter = msgs.begin(); iter != msgs.end(); ++iter) {
  108 + SrsSharedPtrMessage *msg = *iter;
  109 + srs_freep(msg);
  110 + }
  111 + }
  112 +
  113 + void on_video(SrsSharedPtrMessage *msg)
  114 + {
  115 + SrsSharedPtrMessage *_msg = msg->copy();
  116 + msgs.push_back(_msg);
  117 + }
  118 +
  119 + void on_audio(SrsSharedPtrMessage *msg)
  120 + {
  121 + SrsSharedPtrMessage *_msg = msg->copy();
  122 + msgs.push_back(_msg);
  123 + }
  124 +
  125 + /*!
  126 + flush data to disk.
  127 + */
  128 + int flush()
  129 + {
  130 + string data;
  131 + if (videoSh) {
  132 + videoSh->timestamp = start_time;
  133 + data.append(serialFlv(videoSh));
  134 + }
  135 +
  136 + if (audioSh) {
  137 + audioSh->timestamp = start_time;
  138 + data.append(serialFlv(audioSh));
  139 + }
  140 +
  141 + list<SrsSharedPtrMessage *>::iterator iter;
  142 + for (iter = msgs.begin(); iter != msgs.end(); ++iter) {
  143 + SrsSharedPtrMessage *msg = *iter;
  144 + data.append(serialFlv(msg));
  145 + }
  146 +
  147 + static char box_header[8];
  148 + SrsStream ss;
  149 + ss.initialize(box_header, 8);
  150 + ss.write_4bytes(8 + data.size());
  151 + ss.write_string("mdat");
  152 +
  153 + data = string(ss.data(), ss.size()) + data;
  154 +
  155 + const char *file_path = path.c_str();
  156 + int fd = open(file_path, O_WRONLY | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH);
  157 + if (fd < 0) {
  158 + srs_error("open fragment file failed, path=%s", file_path);
  159 + return -1;
  160 + }
  161 +
  162 + if (write(fd, data.data(), data.size()) != (int)data.size()) {
  163 + srs_error("write fragment file failed, path=", file_path);
  164 + close(fd);
  165 + return -1;
  166 + }
  167 + close(fd);
  168 +
  169 + srs_trace("build fragment success=%s", file_path);
  170 +
  171 + return ERROR_SUCCESS;
  172 + }
  173 +
  174 + /*!
  175 + calc the segment duration in milliseconds.
  176 + @return 0 if no msgs
  177 + or the last msg dts minus the first msg dts.
  178 + */
  179 + int duration()
  180 + {
  181 + int duration_ms = 0;
  182 + long long first_msg_ts = 0;
  183 + long long last_msg_ts = 0;
  184 +
  185 + if (msgs.size() >= 2) {
  186 + SrsSharedPtrMessage *first_msg = msgs.front();
  187 + first_msg_ts = first_msg->timestamp;
  188 +
  189 + SrsSharedPtrMessage *last_msg = msgs.back();
  190 + last_msg_ts = last_msg->timestamp;
  191 +
  192 + duration_ms = last_msg_ts - first_msg_ts;
  193 + }
  194 +
  195 + return duration_ms;
  196 + }
  197 +
  198 + /*!
  199 + set/get index
  200 + */
  201 + inline void set_index(int idx)
  202 + {
  203 + char file_path[1024] = {0};
  204 + sprintf(file_path, "%s/%s/%sSeg1-Frag%d", _srs_config->get_hds_path(req->vhost).c_str()
  205 + , req->app.c_str(), req->stream.c_str(), idx);
  206 +
  207 + path = file_path;
  208 + index = idx;
  209 + }
  210 +
  211 + inline int get_index()
  212 + {
  213 + return index;
  214 + }
  215 +
  216 + /*!
  217 + set/get start time
  218 + */
  219 + inline void set_start_time(long long st)
  220 + {
  221 + start_time = st;
  222 + }
  223 +
  224 + inline long long get_start_time()
  225 + {
  226 + return start_time;
  227 + }
  228 +
  229 + void set_video_sh(SrsSharedPtrMessage *msg)
  230 + {
  231 + srs_freep(videoSh);
  232 + videoSh = msg->copy();
  233 + }
  234 +
  235 + void set_audio_sh(SrsSharedPtrMessage *msg)
  236 + {
  237 + srs_freep(audioSh);
  238 + audioSh = msg->copy();
  239 + }
  240 +
  241 + string fragment_path()
  242 + {
  243 + return path;
  244 + }
  245 +
  246 +private:
  247 + SrsRequest *req;
  248 + list<SrsSharedPtrMessage *> msgs;
  249 +
  250 + /*!
  251 + the index of this fragment
  252 + */
  253 + int index;
  254 + long long start_time;
  255 +
  256 + SrsSharedPtrMessage *videoSh;
  257 + SrsSharedPtrMessage *audioSh;
  258 + string path;
  259 +};
  260 +
  261 +SrsHds::SrsHds(SrsSource *s)
  262 + : currentSegment(NULL)
  263 + , source(s)
  264 + , fragment_index(1)
  265 + , video_sh(NULL)
  266 + , audio_sh(NULL)
  267 + , hds_req(NULL)
  268 +{
  269 +
  270 +}
  271 +
  272 +SrsHds::~SrsHds()
  273 +{
  274 +
  275 +}
  276 +
  277 +int SrsHds::on_publish(SrsRequest *req)
  278 +{
  279 + hds_req = req->copy();
  280 +
  281 + return flush_mainfest();
  282 +}
  283 +
  284 +int SrsHds::on_unpublish()
  285 +{
  286 + int ret = ERROR_SUCCESS;
  287 +
  288 + srs_freep(video_sh);
  289 + srs_freep(audio_sh);
  290 + srs_freep(hds_req);
  291 +
  292 + // clean fragments
  293 + list<SrsHdsFragment *>::iterator iter;
  294 + for (iter = fragments.begin(); iter != fragments.end(); ++iter) {
  295 + SrsHdsFragment *st = *iter;
  296 + srs_freep(st);
  297 + }
  298 + fragments.clear();
  299 +
  300 + srs_freep(currentSegment);
  301 +
  302 + srs_trace("HDS un-published");
  303 +
  304 + return ret;
  305 +}
  306 +
  307 +int SrsHds::on_video(SrsSharedPtrMessage* msg)
  308 +{
  309 + int ret = ERROR_SUCCESS;
  310 +
  311 + if (SrsFlvCodec::video_is_sequence_header(msg->payload, msg->size)) {
  312 + srs_freep(video_sh);
  313 + video_sh = msg->copy();
  314 + }
  315 +
  316 + if (!currentSegment) {
  317 + currentSegment = new SrsHdsFragment(hds_req);
  318 + currentSegment->set_index(fragment_index++);
  319 + currentSegment->set_start_time(msg->timestamp);
  320 +
  321 + if (video_sh)
  322 + currentSegment->set_video_sh(video_sh);
  323 +
  324 + if (audio_sh)
  325 + currentSegment->set_audio_sh(audio_sh);
  326 + }
  327 +
  328 + currentSegment->on_video(msg);
  329 +
  330 + double fragment_duration = _srs_config->get_hds_fragment(hds_req->vhost) * 1000;
  331 + if (currentSegment->duration() >= fragment_duration) {
  332 + // flush segment
  333 + if ((ret = currentSegment->flush()) != ERROR_SUCCESS) {
  334 + srs_error("flush segment failed.");
  335 + return ret;
  336 + }
  337 +
  338 + srs_trace("flush Segment success.");
  339 + fragments.push_back(currentSegment);
  340 + currentSegment = NULL;
  341 + adjust_windows();
  342 +
  343 + // flush bootstrap
  344 + if ((ret = flush_bootstrap()) != ERROR_SUCCESS) {
  345 + srs_error("flush bootstrap failed.");
  346 + return ret;
  347 + }
  348 +
  349 + srs_trace("flush BootStrap success.");
  350 + }
  351 +
  352 + return ret;
  353 +}
  354 +
  355 +int SrsHds::on_audio(SrsSharedPtrMessage* msg)
  356 +{
  357 + int ret = ERROR_SUCCESS;
  358 +
  359 + if (SrsFlvCodec::audio_is_sequence_header(msg->payload, msg->size)) {
  360 + srs_freep(audio_sh);
  361 + audio_sh = msg->copy();
  362 + }
  363 +
  364 + if (!currentSegment) {
  365 + currentSegment = new SrsHdsFragment(hds_req);
  366 + currentSegment->set_index(fragment_index++);
  367 + currentSegment->set_start_time(msg->timestamp);
  368 +
  369 + if (video_sh)
  370 + currentSegment->set_video_sh(video_sh);
  371 +
  372 + if (audio_sh)
  373 + currentSegment->set_audio_sh(audio_sh);
  374 + }
  375 +
  376 + currentSegment->on_audio(msg);
  377 +
  378 + double fragment_duration = _srs_config->get_hds_fragment(hds_req->vhost) * 1000;
  379 + if (currentSegment->duration() >= fragment_duration) {
  380 + // flush segment
  381 + if ((ret = currentSegment->flush()) != ERROR_SUCCESS) {
  382 + srs_error("flush segment failed.");
  383 + return ret;
  384 + }
  385 +
  386 + srs_info("flush Segment success.");
  387 +
  388 + // reset the current segment
  389 + fragments.push_back(currentSegment);
  390 + currentSegment = NULL;
  391 + adjust_windows();
  392 +
  393 + // flush bootstrap
  394 + if ((ret = flush_bootstrap()) != ERROR_SUCCESS) {
  395 + srs_error("flush bootstrap failed.");
  396 + return ret;
  397 + }
  398 +
  399 + srs_info("flush BootStrap success.");
  400 + }
  401 +
  402 + return ret;
  403 +}
  404 +
  405 +int SrsHds::flush_mainfest()
  406 +{
  407 + int ret = ERROR_SUCCESS;
  408 +
  409 + char buf[1024] = {0};
  410 + sprintf(buf, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
  411 + "<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n\t"
  412 + "<id>%s.f4m</id>\n\t"
  413 + "<streamType>live</streamType>\n\t"
  414 + "<deliveryType>streaming</deliveryType>\n\t"
  415 + "<bootstrapInfo profile=\"named\" url=\"%s.abst\" id=\"bootstrap0\" />\n\t"
  416 + "<media bitrate=\"0\" url=\"%s\" bootstrapInfoId=\"bootstrap0\"></media>\n"
  417 + "</manifest>"
  418 + , hds_req->stream.c_str(), hds_req->stream.c_str(), hds_req->stream.c_str());
  419 +
  420 + string dir = _srs_config->get_hds_path(hds_req->vhost) + "/" + hds_req->app;
  421 + if ((ret = srs_create_dir_recursively(dir)) != ERROR_SUCCESS) {
  422 + srs_error("hds create dir failed. ret=%d", ret);
  423 + return ret;
  424 + }
  425 + string path = dir + "/" + hds_req->stream + ".f4m";
  426 +
  427 + int fd = open(path.c_str(), O_WRONLY | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH);
  428 + if (fd < 0) {
  429 + srs_error("open manifest file failed, path=%s", path.c_str());
  430 + ret = ERROR_HDS_OPEN_F4M_FAILED;
  431 + return ret;
  432 + }
  433 +
  434 + int f4m_size = strlen(buf);
  435 + if (write(fd, buf, f4m_size) != f4m_size) {
  436 + srs_error("write manifest file failed, path=", path.c_str());
  437 + close(fd);
  438 + ret = ERROR_HDS_WRITE_F4M_FAILED;
  439 + return ret;
  440 + }
  441 + close(fd);
  442 +
  443 + srs_trace("build manifest success=%s", path.c_str());
  444 +
  445 + return ERROR_SUCCESS;
  446 +}
  447 +
  448 +int SrsHds::flush_bootstrap()
  449 +{
  450 + int ret = ERROR_SUCCESS;
  451 +
  452 + SrsStream abst;
  453 +
  454 + int size = 1024*100;
  455 +
  456 + char *start_abst = new char[1024*100];
  457 + SrsAutoFree(char, start_abst);
  458 +
  459 + int size_abst = 0;
  460 + char *start_asrt = NULL;
  461 + int size_asrt = 0;
  462 + char *start_afrt = NULL;
  463 + int size_afrt = 0;
  464 +
  465 + abst.initialize(start_abst, size);
  466 +
  467 + // @see video_file_format_spec_v10_1
  468 + // page: 46
  469 + abst.write_4bytes(0);
  470 + abst.write_string("abst");
  471 + abst.write_1bytes(0x00); // Either 0 or 1
  472 + abst.write_3bytes(0x00); // Flags always 0
  473 + size_abst += 12;
  474 + /*!
  475 + @BootstrapinfoVersion UI32
  476 + The version number of the bootstrap information.
  477 + When the Update field is set, BootstrapinfoVersion
  478 + indicates the version number that is being updated.
  479 + we assume this is the last.
  480 + */
  481 + abst.write_4bytes(fragment_index - 1); // BootstrapinfoVersion
  482 +
  483 + abst.write_1bytes(0x20); // profile, live, update
  484 + abst.write_4bytes(1000); // TimeScale Typically, the value is 1000, for a unit of milliseconds
  485 + size_abst += 9;
  486 + /*!
  487 + The timestamp in TimeScale units of the latest available Fragment in the media presentation.
  488 + This timestamp is used to request the right fragment number.
  489 + The CurrentMedia Time can be the total duration.
  490 + For media presentations that are not live, CurrentMediaTime can be 0.
  491 + */
  492 + SrsHdsFragment *st = fragments.back();
  493 + abst.write_8bytes(st->get_start_time());
  494 +
  495 + // SmpteTimeCodeOffset
  496 + abst.write_8bytes(0);
  497 + size_abst += 16;
  498 +
  499 + /*!
  500 + @MovieIdentifier STRING
  501 + The identifier of this presentation.
  502 + we write null string.
  503 + */
  504 + abst.write_1bytes(0);
  505 + size_abst += 1;
  506 + /*!
  507 + @ServerEntryCount UI8
  508 + The number of ServerEntryTable entries.
  509 + The minimum value is 0.
  510 + */
  511 + abst.write_1bytes(0);
  512 + size_abst += 1;
  513 + /*!
  514 + @ServerEntryTable
  515 + because we write 0 of ServerEntryCount, so this feild is ignored.
  516 + */
  517 +
  518 + /*!
  519 + @QualityEntryCount UI8
  520 + The number of QualityEntryTable entries, which is
  521 + also the number of available quality levels. The
  522 + minimum value is 0. Available quality levels are for,
  523 + for example, multi bit rate files or trick files.
  524 + */
  525 + abst.write_1bytes(0);
  526 + size_abst += 1;
  527 + /*!
  528 + @QualityEntryTable
  529 + because we write 0 of QualityEntryCount, so this feild is ignored.
  530 + */
  531 +
  532 + /*!
  533 + @DrmData STRING
  534 + Null or null-terminated UTF-8 string. This string
  535 + holds Digital Rights Management metadata.
  536 + Encrypted files use this metadata to get the
  537 + necessary keys and licenses for decryption and play back.
  538 + we write null string.
  539 + */
  540 + abst.write_1bytes(0);
  541 + size_abst += 1;
  542 + /*!
  543 + @MetaData STRING
  544 + Null or null-terminated UTF - 8 string that holds metadata.
  545 + we write null string.
  546 + */
  547 + abst.write_1bytes(0);
  548 + size_abst += 1;
  549 + /*!
  550 + @SegmentRunTableCount UI8
  551 + The number of entries in SegmentRunTableEntries.
  552 + The minimum value is 1. Typically, one table
  553 + contains all segment runs. However, this count
  554 + provides the flexibility to define the segment runs
  555 + individually for each quality level (or trick file).
  556 + */
  557 + abst.write_1bytes(1);
  558 + size_abst += 1;
  559 +
  560 + start_asrt = start_abst + size_abst;
  561 +
  562 + // follows by asrt
  563 + abst.write_4bytes(0);
  564 + abst.write_string("asrt");
  565 + size_asrt += 8;
  566 + /*!
  567 + @Version UI8
  568 + @Flags UI24
  569 + */
  570 + abst.write_4bytes(0);
  571 + size_asrt += 4;
  572 + /*!
  573 + @QualityEntryCount UI8
  574 + The number of QualitySegmen tUrlModifiers
  575 + (quality level references) that follow. If 0, this
  576 + Segment Run Table applies to all quality levels,
  577 + and there shall be only one Segment Run Table
  578 + box in the Bootstrap Info box.
  579 + */
  580 + abst.write_1bytes(0);
  581 + size_asrt += 1;
  582 +
  583 + /*!
  584 + @QualitySegmentUrlModifiers
  585 + ignored.
  586 + */
  587 +
  588 + /*!
  589 + @SegmentRunEntryCount
  590 + The number of items in this
  591 + SegmentRunEn tryTable. The minimum value is 1.
  592 + */
  593 + abst.write_4bytes(1);
  594 + size_asrt += 4;
  595 + /*!
  596 + @SegmentRunEntryTable
  597 + */
  598 + for (int i = 0; i < 1; ++i) {
  599 + /*!
  600 + @FirstSegment UI32
  601 + The identifying number of the first segment in the run of
  602 + segments containing the same number of fragments.
  603 + The segment corresponding to the FirstSegment in the next
  604 + SEGMENTRUNENTRY will terminate this run.
  605 + */
  606 + abst.write_4bytes(1);
  607 +
  608 + /*!
  609 + @FragmentsPerSegment UI32
  610 + The number of fragments in each segment in this run.
  611 + */
  612 + abst.write_4bytes(fragment_index - 1);
  613 + size_asrt += 8;
  614 + }
  615 +
  616 + update_box(start_asrt, size_asrt);
  617 + size_abst += size_asrt;
  618 +
  619 + /*!
  620 + @FragmentRunTableCount UI8
  621 + The number of entries in FragmentRunTable-Entries.
  622 + The min i mum value is 1.
  623 + */
  624 + abst.write_1bytes(1);
  625 + size_abst += 1;
  626 +
  627 + // follows by afrt
  628 + start_afrt = start_abst + size_abst;
  629 +
  630 + abst.write_4bytes(0);
  631 + abst.write_string("afrt");
  632 + size_afrt += 8;
  633 +
  634 + /*!
  635 + @Version UI8
  636 + @Flags UI24
  637 + */
  638 + abst.write_4bytes(0);
  639 + size_afrt += 4;
  640 + /*!
  641 + @TimeScale UI32
  642 + The number of time units per second, used in the FirstFragmentTime stamp and
  643 + Fragment Duration fields.
  644 + Typically, the value is 1000.
  645 + */
  646 + abst.write_4bytes(1000);
  647 + size_afrt += 4;
  648 + /*!
  649 + @QualityEntryCount UI8
  650 + The number of QualitySegment Url Modifiers
  651 + (quality level references) that follow.
  652 + If 0, this Fragment Run Table applies to all quality levels,
  653 + and there shall be only one Fragment Run Table
  654 + box in the Bootstrap Info box.
  655 + */
  656 + abst.write_1bytes(0);
  657 + size_afrt += 1;
  658 +
  659 + /*!
  660 + @FragmentRunEntryCount UI32
  661 + The number of items in this FragmentRunEntryTable.
  662 + The minimum value is 1.
  663 + */
  664 + abst.write_4bytes(fragments.size());
  665 + size_afrt += 4;
  666 +
  667 + list<SrsHdsFragment *>::iterator iter;
  668 + for (iter = fragments.begin(); iter != fragments.end(); ++iter) {
  669 + SrsHdsFragment *st = *iter;
  670 + abst.write_4bytes(st->get_index());
  671 + abst.write_8bytes(st->get_start_time());
  672 + abst.write_4bytes(st->duration());
  673 + size_afrt += 16;
  674 + }
  675 +
  676 + update_box(start_afrt, size_afrt);
  677 + size_abst += size_afrt;
  678 + update_box(start_abst, size_abst);
  679 +
  680 + string path = _srs_config->get_hds_path(hds_req->vhost) + "/" + hds_req->app + "/" + hds_req->stream +".abst";
  681 +
  682 + int fd = open(path.c_str(), O_WRONLY | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH);
  683 + if (fd < 0) {
  684 + srs_error("open bootstrap file failed, path=%s", path.c_str());
  685 + ret = ERROR_HDS_OPEN_BOOTSTRAP_FAILED;
  686 + return ret;
  687 + }
  688 +
  689 + if (write(fd, start_abst, size_abst) != size_abst) {
  690 + srs_error("write bootstrap file failed, path=", path.c_str());
  691 + close(fd);
  692 + ret = ERROR_HDS_WRITE_BOOTSTRAP_FAILED;
  693 + return ret;
  694 + }
  695 + close(fd);
  696 +
  697 + srs_trace("build bootstrap success=%s", path.c_str());
  698 +
  699 + return ERROR_SUCCESS;
  700 +}
  701 +
  702 +void SrsHds::adjust_windows()
  703 +{
  704 + int windows_size = 0;
  705 + list<SrsHdsFragment *>::iterator iter;
  706 + for (iter = fragments.begin(); iter != fragments.end(); ++iter) {
  707 + SrsHdsFragment *fragment = *iter;
  708 + windows_size += fragment->duration();
  709 + }
  710 +
  711 + double windows_size_limit = _srs_config->get_hds_window(hds_req->vhost) * 1000;
  712 + if (windows_size > windows_size_limit ) {
  713 + SrsHdsFragment *fragment = fragments.front();
  714 + unlink(fragment->fragment_path().c_str());
  715 + fragments.erase(fragments.begin());
  716 + srs_freep(fragment);
  717 + }
  718 +}
  1 +/*
  2 +The MIT License (MIT)
  3 +
  4 +Copyright (c) 2013-2015 wenjiegit
  5 +Copyright (c) 2013-2015 winlin
  6 +
  7 +Permission is hereby granted, free of charge, to any person obtaining a copy of
  8 +this software and associated documentation files (the "Software"), to deal in
  9 +the Software without restriction, including without limitation the rights to
  10 +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  11 +the Software, and to permit persons to whom the Software is furnished to do so,
  12 +subject to the following conditions:
  13 +
  14 +The above copyright notice and this permission notice shall be included in all
  15 +copies or substantial portions of the Software.
  16 +
  17 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  19 +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  20 +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  21 +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  22 +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23 +*/
  24 +
  25 +#ifndef SRS_APP_HDS_HPP
  26 +#define SRS_APP_HDS_HPP
  27 +
  28 +#include <list>
  29 +
  30 +class SrsRequest;
  31 +class SrsSharedPtrMessage;
  32 +class SrsHdsFragment;
  33 +class SrsSource;
  34 +
  35 +using namespace std;
  36 +
  37 +class SrsHds
  38 +{
  39 +public:
  40 + SrsHds(SrsSource* s);
  41 + ~SrsHds();
  42 +
  43 + int on_publish(SrsRequest* req);
  44 + int on_unpublish();
  45 +
  46 + int on_video(SrsSharedPtrMessage* msg);
  47 + int on_audio(SrsSharedPtrMessage* msg);
  48 +
  49 +private:
  50 + int flush_mainfest();
  51 + int flush_bootstrap();
  52 + void adjust_windows();
  53 +
  54 +private:
  55 + list<SrsHdsFragment *> fragments;
  56 + SrsHdsFragment *currentSegment;
  57 + SrsSource *source;
  58 + int fragment_index;
  59 + SrsSharedPtrMessage *video_sh;
  60 + SrsSharedPtrMessage *audio_sh;
  61 +
  62 + SrsRequest *hds_req;
  63 +};
  64 +
  65 +#endif // SRS_APP_HDS_HPP
@@ -42,6 +42,7 @@ using namespace std; @@ -42,6 +42,7 @@ using namespace std;
42 #include <srs_kernel_utility.hpp> 42 #include <srs_kernel_utility.hpp>
43 #include <srs_kernel_codec.hpp> 43 #include <srs_kernel_codec.hpp>
44 #include <srs_rtmp_msg_array.hpp> 44 #include <srs_rtmp_msg_array.hpp>
  45 +#include <srs_app_hds.hpp>
45 #include <srs_app_statistic.hpp> 46 #include <srs_app_statistic.hpp>
46 47
47 #define CONST_MAX_JITTER_MS 500 48 #define CONST_MAX_JITTER_MS 500
@@ -781,6 +782,8 @@ SrsSource::SrsSource() @@ -781,6 +782,8 @@ SrsSource::SrsSource()
781 #ifdef SRS_AUTO_TRANSCODE 782 #ifdef SRS_AUTO_TRANSCODE
782 encoder = new SrsEncoder(); 783 encoder = new SrsEncoder();
783 #endif 784 #endif
  785 +
  786 + hds = new SrsHds(this);
784 787
785 cache_metadata = cache_sh_video = cache_sh_audio = NULL; 788 cache_metadata = cache_sh_video = cache_sh_audio = NULL;
786 789
@@ -1345,6 +1348,15 @@ int SrsSource::on_audio(SrsCommonMessage* __audio) @@ -1345,6 +1348,15 @@ int SrsSource::on_audio(SrsCommonMessage* __audio)
1345 ret = ERROR_SUCCESS; 1348 ret = ERROR_SUCCESS;
1346 } 1349 }
1347 #endif 1350 #endif
  1351 +
  1352 + if ((ret = hds->on_audio(&msg)) != ERROR_SUCCESS) {
  1353 + // unpublish, ignore ret.
  1354 + hds->on_unpublish();
  1355 + // ignore.
  1356 + ret = ERROR_SUCCESS;
  1357 +
  1358 + srs_warn("hds process audio message failed, ignore and disable dvr. ret=%d", ret);
  1359 + }
1348 1360
1349 // copy to all consumer 1361 // copy to all consumer
1350 int nb_consumers = (int)consumers.size(); 1362 int nb_consumers = (int)consumers.size();
@@ -1485,6 +1497,15 @@ int SrsSource::on_video(SrsCommonMessage* __video) @@ -1485,6 +1497,15 @@ int SrsSource::on_video(SrsCommonMessage* __video)
1485 ret = ERROR_SUCCESS; 1497 ret = ERROR_SUCCESS;
1486 } 1498 }
1487 #endif 1499 #endif
  1500 +
  1501 + if ((ret = hds->on_video(&msg)) != ERROR_SUCCESS) {
  1502 + // unpublish, ignore ret.
  1503 + hds->on_unpublish();
  1504 + // ignore.
  1505 + ret = ERROR_SUCCESS;
  1506 +
  1507 + srs_warn("hds process video message failed, ignore and disable dvr. ret=%d", ret);
  1508 + }
1488 1509
1489 // copy to all consumer 1510 // copy to all consumer
1490 if (true) { 1511 if (true) {
@@ -1730,6 +1751,11 @@ int SrsSource::on_publish() @@ -1730,6 +1751,11 @@ int SrsSource::on_publish()
1730 } 1751 }
1731 #endif 1752 #endif
1732 1753
  1754 + if ((ret = hds->on_publish(_req)) != ERROR_SUCCESS) {
  1755 + srs_error("start hds failed. ret=%d", ret);
  1756 + return ret;
  1757 + }
  1758 +
1733 // notify the handler. 1759 // notify the handler.
1734 srs_assert(handler); 1760 srs_assert(handler);
1735 if ((ret = handler->on_publish(this, _req)) != ERROR_SUCCESS) { 1761 if ((ret = handler->on_publish(this, _req)) != ERROR_SUCCESS) {
@@ -1757,6 +1783,8 @@ void SrsSource::on_unpublish() @@ -1757,6 +1783,8 @@ void SrsSource::on_unpublish()
1757 dvr->on_unpublish(); 1783 dvr->on_unpublish();
1758 #endif 1784 #endif
1759 1785
  1786 + hds->on_unpublish();
  1787 +
1760 gop_cache->clear(); 1788 gop_cache->clear();
1761 1789
1762 srs_freep(cache_metadata); 1790 srs_freep(cache_metadata);
@@ -62,6 +62,7 @@ class SrsEncoder; @@ -62,6 +62,7 @@ class SrsEncoder;
62 #endif 62 #endif
63 class SrsStream; 63 class SrsStream;
64 class ISrsHlsHandler; 64 class ISrsHlsHandler;
  65 +class SrsHds;
65 66
66 /** 67 /**
67 * the time jitter algorithm: 68 * the time jitter algorithm:
@@ -416,6 +417,7 @@ private: @@ -416,6 +417,7 @@ private:
416 #ifdef SRS_AUTO_TRANSCODE 417 #ifdef SRS_AUTO_TRANSCODE
417 SrsEncoder* encoder; 418 SrsEncoder* encoder;
418 #endif 419 #endif
  420 + SrsHds *hds;
419 // edge control service 421 // edge control service
420 SrsPlayEdge* play_edge; 422 SrsPlayEdge* play_edge;
421 SrsPublishEdge* publish_edge; 423 SrsPublishEdge* publish_edge;
@@ -216,6 +216,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. @@ -216,6 +216,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
216 #define ERROR_HTTP_DVR_NO_TAEGET 3054 216 #define ERROR_HTTP_DVR_NO_TAEGET 3054
217 #define ERROR_ADTS_ID_NOT_AAC 3055 217 #define ERROR_ADTS_ID_NOT_AAC 3055
218 218
  219 +// HDS error code
  220 +#define ERROR_HDS_OPEN_F4M_FAILED 3056
  221 +#define ERROR_HDS_WRITE_F4M_FAILED 3057
  222 +#define ERROR_HDS_OPEN_BOOTSTRAP_FAILED 3058
  223 +#define ERROR_HDS_WRITE_BOOTSTRAP_FAILED 3059
  224 +#define ERROR_HDS_OPEN_FRAGMENT_FAILED 3060
  225 +#define ERROR_HDS_WRITE_FRAGMENT_FAILED 3061
  226 +
219 /////////////////////////////////////////////////////// 227 ///////////////////////////////////////////////////////
220 // HTTP/StreamCaster protocol error. 228 // HTTP/StreamCaster protocol error.
221 /////////////////////////////////////////////////////// 229 ///////////////////////////////////////////////////////