mirror of
https://github.com/ossrs/srs.git
synced 2025-02-13 11:51:57 +00:00
add hds supported.
This commit is contained in:
parent
1cb8e44b1b
commit
d4198ee8ba
8 changed files with 939 additions and 2 deletions
2
trunk/configure
vendored
2
trunk/configure
vendored
|
@ -392,7 +392,7 @@ if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
|
|||
"srs_app_json" "srs_app_ingest" "srs_app_ffmpeg" "srs_app_utility" "srs_app_dvr" "srs_app_edge"
|
||||
"srs_app_kbps" "srs_app_heartbeat" "srs_app_empty" "srs_app_http_client"
|
||||
"srs_app_recv_thread" "srs_app_security" "srs_app_statistic"
|
||||
"srs_app_mpegts_udp" "srs_app_rtsp" "srs_app_listener")
|
||||
"srs_app_mpegts_udp" "srs_app_rtsp" "srs_app_listener" "srs_app_hds")
|
||||
APP_INCS="src/app"; MODULE_DIR=${APP_INCS} . auto/modules.sh
|
||||
APP_OBJS="${MODULE_OBJS[@]}"
|
||||
fi
|
||||
|
|
|
@ -1407,6 +1407,7 @@ int SrsConfig::check_config()
|
|||
&& n != "mr" && n != "mw_latency" && n != "min_latency"
|
||||
&& n != "security" && n != "http_remux"
|
||||
&& n != "http" && n != "http_static"
|
||||
&& n != "hds"
|
||||
) {
|
||||
ret = ERROR_SYSTEM_CONFIG_INVALID;
|
||||
srs_error("unsupported vhost directive %s, ret=%d", n.c_str(), ret);
|
||||
|
@ -3310,6 +3311,85 @@ string SrsConfig::get_hls_vcodec(string vhost)
|
|||
return conf->arg0();
|
||||
}
|
||||
|
||||
SrsConfDirective *SrsConfig::get_hds(const string &vhost)
|
||||
{
|
||||
SrsConfDirective* conf = get_vhost(vhost);
|
||||
|
||||
if (!conf) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return conf->get("hds");
|
||||
}
|
||||
|
||||
bool SrsConfig::get_hds_enabled(const string &vhost)
|
||||
{
|
||||
SrsConfDirective* hds = get_hds(vhost);
|
||||
|
||||
if (!hds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SrsConfDirective* conf = hds->get("enabled");
|
||||
|
||||
if (!conf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return conf->arg0() == "on";
|
||||
}
|
||||
|
||||
string SrsConfig::get_hds_path(const string &vhost)
|
||||
{
|
||||
SrsConfDirective* hds = get_hds(vhost);
|
||||
|
||||
if (!hds) {
|
||||
return SRS_CONF_DEFAULT_HDS_PATH;
|
||||
}
|
||||
|
||||
SrsConfDirective* conf = hds->get("hds_path");
|
||||
|
||||
if (!conf) {
|
||||
return SRS_CONF_DEFAULT_HDS_PATH;
|
||||
}
|
||||
|
||||
return conf->arg0();
|
||||
}
|
||||
|
||||
double SrsConfig::get_hds_fragment(const string &vhost)
|
||||
{
|
||||
SrsConfDirective* hds = get_hds(vhost);
|
||||
|
||||
if (!hds) {
|
||||
return SRS_CONF_DEFAULT_HDS_FRAGMENT;
|
||||
}
|
||||
|
||||
SrsConfDirective* conf = hds->get("hds_fragment");
|
||||
|
||||
if (!conf) {
|
||||
return SRS_CONF_DEFAULT_HDS_FRAGMENT;
|
||||
}
|
||||
|
||||
return ::atof(conf->arg0().c_str());
|
||||
}
|
||||
|
||||
double SrsConfig::get_hds_window(const string &vhost)
|
||||
{
|
||||
SrsConfDirective* hds = get_hds(vhost);
|
||||
|
||||
if (!hds) {
|
||||
return SRS_CONF_DEFAULT_HDS_WINDOW;
|
||||
}
|
||||
|
||||
SrsConfDirective* conf = hds->get("hds_window");
|
||||
|
||||
if (!conf) {
|
||||
return SRS_CONF_DEFAULT_HDS_WINDOW;
|
||||
}
|
||||
|
||||
return ::atof(conf->arg0().c_str());
|
||||
}
|
||||
|
||||
SrsConfDirective* SrsConfig::get_dvr(string vhost)
|
||||
{
|
||||
SrsConfDirective* conf = get_vhost(vhost);
|
||||
|
|
|
@ -102,10 +102,15 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
#define SRS_CONF_DEFAULT_TRANSCODE_IFORMAT "flv"
|
||||
#define SRS_CONF_DEFAULT_TRANSCODE_OFORMAT "flv"
|
||||
|
||||
// hds default value
|
||||
#define SRS_CONF_DEFAULT_HDS_PATH "./objs/nginx/html"
|
||||
#define SRS_CONF_DEFAULT_HDS_WINDOW (60)
|
||||
#define SRS_CONF_DEFAULT_HDS_FRAGMENT (10)
|
||||
|
||||
namespace _srs_internal
|
||||
{
|
||||
class SrsConfigBuffer;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* the config directive.
|
||||
|
@ -912,6 +917,32 @@ public:
|
|||
* get the HLS default video codec.
|
||||
*/
|
||||
virtual std::string get_hls_vcodec(std::string vhost);
|
||||
|
||||
// hds section
|
||||
private:
|
||||
/**
|
||||
* get the hds directive of vhost.
|
||||
*/
|
||||
virtual SrsConfDirective* get_hds(const std::string &vhost);
|
||||
public:
|
||||
/**
|
||||
* whether HDS is enabled.
|
||||
*/
|
||||
virtual bool get_hds_enabled(const std::string &vhost);
|
||||
/**
|
||||
* get the HDS file store path.
|
||||
*/
|
||||
virtual std::string get_hds_path(const std::string &vhost);
|
||||
/**
|
||||
* get the hds fragment time, in seconds.
|
||||
*/
|
||||
virtual double get_hds_fragment(const std::string &vhost);
|
||||
/**
|
||||
* get the hds window time, in seconds.
|
||||
* a window is a set of hds fragments.
|
||||
*/
|
||||
virtual double get_hds_window(const std::string &vhost);
|
||||
|
||||
// dvr section
|
||||
private:
|
||||
/**
|
||||
|
|
724
trunk/src/app/srs_app_hds.cpp
Normal file
724
trunk/src/app/srs_app_hds.cpp
Normal file
|
@ -0,0 +1,724 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2015 wenjiegit
|
||||
Copyright (c) 2013-2015 winlin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <srs_app_hds.hpp>
|
||||
#include <srs_rtmp_sdk.hpp>
|
||||
#include <srs_kernel_log.hpp>
|
||||
#include <srs_kernel_codec.hpp>
|
||||
#include <srs_rtmp_stack.hpp>
|
||||
#include <srs_kernel_stream.hpp>
|
||||
#include <srs_core_autofree.hpp>
|
||||
#include <srs_kernel_utility.hpp>
|
||||
#include <srs_app_config.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
static void update_box(char *start, int size)
|
||||
{
|
||||
char *p_size = (char*)&size;
|
||||
start[0] = p_size[3];
|
||||
start[1] = p_size[2];
|
||||
start[2] = p_size[1];
|
||||
start[3] = p_size[0];
|
||||
}
|
||||
|
||||
char flv_header[] = {'F', 'L', 'V',
|
||||
0x01, 0x05, 0x00, 0x00, 0x00, 0x09,
|
||||
0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
string serialFlv(SrsSharedPtrMessage *msg)
|
||||
{
|
||||
SrsStream *stream = new SrsStream;
|
||||
|
||||
int size = 15 + msg->size;
|
||||
char *byte = new char[size];
|
||||
stream->initialize(byte, size);
|
||||
|
||||
// tag header
|
||||
long long dts = msg->timestamp;
|
||||
char type = msg->is_video() ? 0x09 : 0x08;
|
||||
|
||||
stream->write_1bytes(type);
|
||||
stream->write_3bytes(msg->size);
|
||||
stream->write_3bytes(dts);
|
||||
stream->write_1bytes(dts >> 24 & 0xFF);
|
||||
stream->write_3bytes(0);
|
||||
stream->write_bytes(msg->payload, msg->size);
|
||||
|
||||
// pre tag size
|
||||
int preTagSize = msg->size + 11;
|
||||
stream->write_4bytes(preTagSize);
|
||||
|
||||
string ret(stream->data(), stream->size());
|
||||
|
||||
delete stream;
|
||||
delete [] byte;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
class SrsHdsFragment
|
||||
{
|
||||
public:
|
||||
SrsHdsFragment(SrsRequest *r)
|
||||
: req(r)
|
||||
, index(-1)
|
||||
, start_time(0)
|
||||
, videoSh(NULL)
|
||||
, audioSh(NULL)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~SrsHdsFragment()
|
||||
{
|
||||
srs_freep(videoSh);
|
||||
srs_freep(audioSh);
|
||||
|
||||
// clean msgs
|
||||
list<SrsSharedPtrMessage *>::iterator iter;
|
||||
for (iter = msgs.begin(); iter != msgs.end(); ++iter) {
|
||||
SrsSharedPtrMessage *msg = *iter;
|
||||
srs_freep(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void on_video(SrsSharedPtrMessage *msg)
|
||||
{
|
||||
SrsSharedPtrMessage *_msg = msg->copy();
|
||||
msgs.push_back(_msg);
|
||||
}
|
||||
|
||||
void on_audio(SrsSharedPtrMessage *msg)
|
||||
{
|
||||
SrsSharedPtrMessage *_msg = msg->copy();
|
||||
msgs.push_back(_msg);
|
||||
}
|
||||
|
||||
/*!
|
||||
flush data to disk.
|
||||
*/
|
||||
int flush()
|
||||
{
|
||||
string data;
|
||||
if (videoSh) {
|
||||
videoSh->timestamp = start_time;
|
||||
data.append(serialFlv(videoSh));
|
||||
}
|
||||
|
||||
if (audioSh) {
|
||||
audioSh->timestamp = start_time;
|
||||
data.append(serialFlv(audioSh));
|
||||
}
|
||||
|
||||
list<SrsSharedPtrMessage *>::iterator iter;
|
||||
for (iter = msgs.begin(); iter != msgs.end(); ++iter) {
|
||||
SrsSharedPtrMessage *msg = *iter;
|
||||
data.append(serialFlv(msg));
|
||||
}
|
||||
|
||||
static char box_header[8];
|
||||
SrsStream ss;
|
||||
ss.initialize(box_header, 8);
|
||||
ss.write_4bytes(8 + data.size());
|
||||
ss.write_string("mdat");
|
||||
|
||||
data = string(ss.data(), ss.size()) + data;
|
||||
|
||||
char file_path[1024] = {0};
|
||||
sprintf(file_path, "%s/%s/%sSeg1-Frag%d", _srs_config->get_hds_path(req->vhost).c_str()
|
||||
, req->app.c_str(), req->stream.c_str(), index);
|
||||
|
||||
int fd = open(file_path, O_WRONLY | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH);
|
||||
if (fd < 0) {
|
||||
srs_error("open fragment file failed, path=%s", file_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (write(fd, data.data(), data.size()) != (int)data.size()) {
|
||||
srs_error("write fragment file failed, path=", file_path);
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
srs_trace("build fragment success=%s", file_path);
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
/*!
|
||||
calc the segment duration in milliseconds.
|
||||
@return 0 if no msgs
|
||||
or the last msg dts minus the first msg dts.
|
||||
*/
|
||||
int duration()
|
||||
{
|
||||
int duration_ms = 0;
|
||||
long long first_msg_ts = 0;
|
||||
long long last_msg_ts = 0;
|
||||
|
||||
if (msgs.size() >= 2) {
|
||||
SrsSharedPtrMessage *first_msg = msgs.front();
|
||||
first_msg_ts = first_msg->timestamp;
|
||||
|
||||
SrsSharedPtrMessage *last_msg = msgs.back();
|
||||
last_msg_ts = last_msg->timestamp;
|
||||
|
||||
duration_ms = last_msg_ts - first_msg_ts;
|
||||
}
|
||||
|
||||
return duration_ms;
|
||||
}
|
||||
|
||||
/*!
|
||||
set/get index
|
||||
*/
|
||||
inline void set_index(int idx)
|
||||
{
|
||||
char fg_name[1024] = {0};
|
||||
sprintf(fg_name, "/var/www/live/stream0Seg1-Frag%d", idx);
|
||||
path = fg_name;
|
||||
index = idx;
|
||||
}
|
||||
|
||||
inline int get_index()
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
/*!
|
||||
set/get start time
|
||||
*/
|
||||
inline void set_start_time(long long st)
|
||||
{
|
||||
start_time = st;
|
||||
}
|
||||
|
||||
inline long long get_start_time()
|
||||
{
|
||||
return start_time;
|
||||
}
|
||||
|
||||
void set_video_sh(SrsSharedPtrMessage *msg)
|
||||
{
|
||||
srs_freep(videoSh);
|
||||
videoSh = msg->copy();
|
||||
}
|
||||
|
||||
void set_audio_sh(SrsSharedPtrMessage *msg)
|
||||
{
|
||||
srs_freep(audioSh);
|
||||
audioSh = msg->copy();
|
||||
}
|
||||
|
||||
string fragment_path()
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
private:
|
||||
SrsRequest *req;
|
||||
list<SrsSharedPtrMessage *> msgs;
|
||||
|
||||
/*!
|
||||
the index of this fragment
|
||||
*/
|
||||
int index;
|
||||
long long start_time;
|
||||
|
||||
SrsSharedPtrMessage *videoSh;
|
||||
SrsSharedPtrMessage *audioSh;
|
||||
string path;
|
||||
};
|
||||
|
||||
SrsHds::SrsHds(SrsSource *s)
|
||||
: currentSegment(NULL)
|
||||
, source(s)
|
||||
, fragment_index(1)
|
||||
, video_sh(NULL)
|
||||
, audio_sh(NULL)
|
||||
, hds_req(NULL)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
SrsHds::~SrsHds()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int SrsHds::on_publish(SrsRequest *req)
|
||||
{
|
||||
hds_req = req->copy();
|
||||
|
||||
return flush_mainfest();
|
||||
}
|
||||
|
||||
int SrsHds::on_unpublish()
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
srs_freep(video_sh);
|
||||
srs_freep(audio_sh);
|
||||
srs_freep(hds_req);
|
||||
|
||||
// clean fragments
|
||||
list<SrsHdsFragment *>::iterator iter;
|
||||
for (iter = fragments.begin(); iter != fragments.end(); ++iter) {
|
||||
SrsHdsFragment *st = *iter;
|
||||
srs_freep(st);
|
||||
}
|
||||
fragments.clear();
|
||||
|
||||
srs_freep(currentSegment);
|
||||
|
||||
srs_trace("HDS un-published");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsHds::on_video(SrsSharedPtrMessage* msg)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
if (SrsFlvCodec::video_is_sequence_header(msg->payload, msg->size)) {
|
||||
srs_freep(video_sh);
|
||||
video_sh = msg->copy();
|
||||
}
|
||||
|
||||
if (!currentSegment) {
|
||||
currentSegment = new SrsHdsFragment(hds_req);
|
||||
currentSegment->set_index(fragment_index++);
|
||||
currentSegment->set_start_time(msg->timestamp);
|
||||
|
||||
if (video_sh)
|
||||
currentSegment->set_video_sh(video_sh);
|
||||
|
||||
if (audio_sh)
|
||||
currentSegment->set_audio_sh(audio_sh);
|
||||
}
|
||||
|
||||
currentSegment->on_video(msg);
|
||||
|
||||
double fragment_duration = _srs_config->get_hds_fragment(hds_req->vhost) * 1000;
|
||||
if (currentSegment->duration() >= fragment_duration) {
|
||||
// flush segment
|
||||
if ((ret = currentSegment->flush()) != ERROR_SUCCESS) {
|
||||
srs_error("flush segment failed.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
srs_trace("flush Segment success.");
|
||||
fragments.push_back(currentSegment);
|
||||
currentSegment = NULL;
|
||||
adjust_windows();
|
||||
|
||||
// flush bootstrap
|
||||
if ((ret = flush_bootstrap()) != ERROR_SUCCESS) {
|
||||
srs_error("flush bootstrap failed.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
srs_trace("flush BootStrap success.");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsHds::on_audio(SrsSharedPtrMessage* msg)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
if (SrsFlvCodec::audio_is_sequence_header(msg->payload, msg->size)) {
|
||||
srs_freep(audio_sh);
|
||||
audio_sh = msg->copy();
|
||||
}
|
||||
|
||||
if (!currentSegment) {
|
||||
currentSegment = new SrsHdsFragment(hds_req);
|
||||
currentSegment->set_index(fragment_index++);
|
||||
currentSegment->set_start_time(msg->timestamp);
|
||||
|
||||
if (video_sh)
|
||||
currentSegment->set_video_sh(video_sh);
|
||||
|
||||
if (audio_sh)
|
||||
currentSegment->set_audio_sh(audio_sh);
|
||||
}
|
||||
|
||||
currentSegment->on_audio(msg);
|
||||
|
||||
double fragment_duration = _srs_config->get_hds_fragment(hds_req->vhost) * 1000;
|
||||
if (currentSegment->duration() >= fragment_duration) {
|
||||
// flush segment
|
||||
if ((ret = currentSegment->flush()) != ERROR_SUCCESS) {
|
||||
srs_error("flush segment failed.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
srs_info("flush Segment success.");
|
||||
|
||||
// reset the current segment
|
||||
fragments.push_back(currentSegment);
|
||||
currentSegment = NULL;
|
||||
adjust_windows();
|
||||
|
||||
// flush bootstrap
|
||||
if ((ret = flush_bootstrap()) != ERROR_SUCCESS) {
|
||||
srs_error("flush bootstrap failed.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
srs_info("flush BootStrap success.");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SrsHds::flush_mainfest()
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
char buf[1024] = {0};
|
||||
sprintf(buf, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
||||
"<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n\t"
|
||||
"<id>%s.f4m</id>\n\t"
|
||||
"<streamType>live</streamType>\n\t"
|
||||
"<deliveryType>streaming</deliveryType>\n\t"
|
||||
"<bootstrapInfo profile=\"named\" url=\"%s.abst\" id=\"bootstrap0\" />\n\t"
|
||||
"<media bitrate=\"0\" url=\"%s\" bootstrapInfoId=\"bootstrap0\"></media>\n"
|
||||
"</manifest>"
|
||||
, hds_req->stream.c_str(), hds_req->stream.c_str(), hds_req->stream.c_str());
|
||||
|
||||
string dir = _srs_config->get_hds_path(hds_req->vhost) + "/" + hds_req->app;
|
||||
if ((ret = srs_create_dir_recursively(dir)) != ERROR_SUCCESS) {
|
||||
srs_error("hds create dir failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
string path = dir + "/" + hds_req->stream + ".f4m";
|
||||
|
||||
int fd = open(path.c_str(), O_WRONLY | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH);
|
||||
if (fd < 0) {
|
||||
srs_error("open manifest file failed, path=%s", path.c_str());
|
||||
ret = ERROR_HDS_OPEN_F4M_FAILED;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int f4m_size = strlen(buf);
|
||||
if (write(fd, buf, f4m_size) != f4m_size) {
|
||||
srs_error("write manifest file failed, path=", path.c_str());
|
||||
close(fd);
|
||||
ret = ERROR_HDS_WRITE_F4M_FAILED;
|
||||
return ret;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
srs_trace("build manifest success=%s", path.c_str());
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
int SrsHds::flush_bootstrap()
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
|
||||
SrsStream abst;
|
||||
|
||||
int size = 1024*100;
|
||||
|
||||
char *start_abst = new char[1024*100];
|
||||
SrsAutoFree(char, start_abst);
|
||||
|
||||
int size_abst = 0;
|
||||
char *start_asrt = NULL;
|
||||
int size_asrt = 0;
|
||||
char *start_afrt = NULL;
|
||||
int size_afrt = 0;
|
||||
|
||||
abst.initialize(start_abst, size);
|
||||
|
||||
// @see video_file_format_spec_v10_1
|
||||
// page: 46
|
||||
abst.write_4bytes(0);
|
||||
abst.write_string("abst");
|
||||
abst.write_1bytes(0x00); // Either 0 or 1
|
||||
abst.write_3bytes(0x00); // Flags always 0
|
||||
size_abst += 12;
|
||||
/*!
|
||||
@BootstrapinfoVersion UI32
|
||||
The version number of the bootstrap information.
|
||||
When the Update field is set, BootstrapinfoVersion
|
||||
indicates the version number that is being updated.
|
||||
we assume this is the last.
|
||||
*/
|
||||
abst.write_4bytes(fragment_index - 1); // BootstrapinfoVersion
|
||||
|
||||
abst.write_1bytes(0x20); // profile, live, update
|
||||
abst.write_4bytes(1000); // TimeScale Typically, the value is 1000, for a unit of milliseconds
|
||||
size_abst += 9;
|
||||
/*!
|
||||
The timestamp in TimeScale units of the latest available Fragment in the media presentation.
|
||||
This timestamp is used to request the right fragment number.
|
||||
The CurrentMedia Time can be the total duration.
|
||||
For media presentations that are not live, CurrentMediaTime can be 0.
|
||||
*/
|
||||
SrsHdsFragment *st = fragments.back();
|
||||
abst.write_8bytes(st->get_start_time());
|
||||
|
||||
// SmpteTimeCodeOffset
|
||||
abst.write_8bytes(0);
|
||||
size_abst += 16;
|
||||
|
||||
/*!
|
||||
@MovieIdentifier STRING
|
||||
The identifier of this presentation.
|
||||
we write null string.
|
||||
*/
|
||||
abst.write_1bytes(0);
|
||||
size_abst += 1;
|
||||
/*!
|
||||
@ServerEntryCount UI8
|
||||
The number of ServerEntryTable entries.
|
||||
The minimum value is 0.
|
||||
*/
|
||||
abst.write_1bytes(0);
|
||||
size_abst += 1;
|
||||
/*!
|
||||
@ServerEntryTable
|
||||
because we write 0 of ServerEntryCount, so this feild is ignored.
|
||||
*/
|
||||
|
||||
/*!
|
||||
@QualityEntryCount UI8
|
||||
The number of QualityEntryTable entries, which is
|
||||
also the number of available quality levels. The
|
||||
minimum value is 0. Available quality levels are for,
|
||||
for example, multi bit rate files or trick files.
|
||||
*/
|
||||
abst.write_1bytes(0);
|
||||
size_abst += 1;
|
||||
/*!
|
||||
@QualityEntryTable
|
||||
because we write 0 of QualityEntryCount, so this feild is ignored.
|
||||
*/
|
||||
|
||||
/*!
|
||||
@DrmData STRING
|
||||
Null or null-terminated UTF-8 string. This string
|
||||
holds Digital Rights Management metadata.
|
||||
Encrypted files use this metadata to get the
|
||||
necessary keys and licenses for decryption and play back.
|
||||
we write null string.
|
||||
*/
|
||||
abst.write_1bytes(0);
|
||||
size_abst += 1;
|
||||
/*!
|
||||
@MetaData STRING
|
||||
Null or null-terminated UTF - 8 string that holds metadata.
|
||||
we write null string.
|
||||
*/
|
||||
abst.write_1bytes(0);
|
||||
size_abst += 1;
|
||||
/*!
|
||||
@SegmentRunTableCount UI8
|
||||
The number of entries in SegmentRunTableEntries.
|
||||
The minimum value is 1. Typically, one table
|
||||
contains all segment runs. However, this count
|
||||
provides the flexibility to define the segment runs
|
||||
individually for each quality level (or trick file).
|
||||
*/
|
||||
abst.write_1bytes(1);
|
||||
size_abst += 1;
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
start_asrt = start_abst + size_abst;
|
||||
// MStream asrt;
|
||||
// follows by asrt
|
||||
abst.write_4bytes(0);
|
||||
abst.write_string("asrt");
|
||||
size_asrt += 8;
|
||||
/*!
|
||||
@Version UI8
|
||||
@Flags UI24
|
||||
*/
|
||||
abst.write_4bytes(0);
|
||||
size_asrt += 4;
|
||||
/*!
|
||||
@QualityEntryCount UI8
|
||||
The number of QualitySegmen tUrlModifiers
|
||||
(quality level references) that follow. If 0, this
|
||||
Segment Run Table applies to all quality levels,
|
||||
and there shall be only one Segment Run Table
|
||||
box in the Bootstrap Info box.
|
||||
*/
|
||||
abst.write_1bytes(0);
|
||||
size_asrt += 1;
|
||||
|
||||
/*!
|
||||
@QualitySegmentUrlModifiers
|
||||
ignored.
|
||||
*/
|
||||
|
||||
/*!
|
||||
@SegmentRunEntryCount
|
||||
The number of items in this
|
||||
SegmentRunEn tryTable. The minimum value is 1.
|
||||
*/
|
||||
abst.write_4bytes(1);
|
||||
size_asrt += 4;
|
||||
/*!
|
||||
@SegmentRunEntryTable
|
||||
*/
|
||||
for (int i = 0; i < 1; ++i) {
|
||||
/*!
|
||||
@FirstSegment UI32
|
||||
The identifying number of the first segment in the run of
|
||||
segments containing the same number of fragments.
|
||||
The segment corresponding to the FirstSegment in the next
|
||||
SEGMENTRUNENTRY will terminate this run.
|
||||
*/
|
||||
abst.write_4bytes(1);
|
||||
|
||||
/*!
|
||||
@FragmentsPerSegment UI32
|
||||
The number of fragments in each segment in this run.
|
||||
*/
|
||||
abst.write_4bytes(fragment_index - 1);
|
||||
size_asrt += 8;
|
||||
}
|
||||
|
||||
update_box(start_asrt, size_asrt);
|
||||
size_abst += size_asrt;
|
||||
|
||||
/*!
|
||||
@FragmentRunTableCount UI8
|
||||
The number of entries in FragmentRunTable-Entries.
|
||||
The min i mum value is 1.
|
||||
*/
|
||||
abst.write_1bytes(1);
|
||||
size_abst += 1;
|
||||
|
||||
/////////////////////////////////////
|
||||
//MStream afrt;
|
||||
// follows by afrt
|
||||
start_afrt = start_abst + size_abst;
|
||||
|
||||
abst.write_4bytes(0);
|
||||
abst.write_string("afrt");
|
||||
size_afrt += 8;
|
||||
|
||||
/*!
|
||||
@Version UI8
|
||||
@Flags UI24
|
||||
*/
|
||||
abst.write_4bytes(0);
|
||||
size_afrt += 4;
|
||||
/*!
|
||||
@TimeScale UI32
|
||||
The number of time units per second, used in the FirstFragmentTime stamp and
|
||||
Fragment Duration fields.
|
||||
Typically, the value is 1000.
|
||||
*/
|
||||
abst.write_4bytes(1000);
|
||||
size_afrt += 4;
|
||||
/*!
|
||||
@QualityEntryCount UI8
|
||||
The number of QualitySegment Url Modifiers
|
||||
(quality level references) that follow.
|
||||
If 0, this Fragment Run Table applies to all quality levels,
|
||||
and there shall be only one Fragment Run Table
|
||||
box in the Bootstrap Info box.
|
||||
*/
|
||||
abst.write_1bytes(0);
|
||||
size_afrt += 1;
|
||||
|
||||
/*!
|
||||
@FragmentRunEntryCount UI32
|
||||
The number of items in this FragmentRunEntryTable.
|
||||
The minimum value is 1.
|
||||
*/
|
||||
abst.write_4bytes(fragments.size());
|
||||
size_afrt += 4;
|
||||
|
||||
list<SrsHdsFragment *>::iterator iter;
|
||||
for (iter = fragments.begin(); iter != fragments.end(); ++iter) {
|
||||
SrsHdsFragment *st = *iter;
|
||||
abst.write_4bytes(st->get_index());
|
||||
abst.write_8bytes(st->get_start_time());
|
||||
abst.write_4bytes(st->duration());
|
||||
size_afrt += 16;
|
||||
}
|
||||
|
||||
update_box(start_afrt, size_afrt);
|
||||
// abst.append(afrt);
|
||||
|
||||
size_abst += size_afrt;
|
||||
update_box(start_abst, size_abst);
|
||||
|
||||
string path = _srs_config->get_hds_path(hds_req->vhost) + "/" + hds_req->app + "/" + hds_req->stream +".abst";
|
||||
|
||||
int fd = open(path.c_str(), O_WRONLY | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH);
|
||||
if (fd < 0) {
|
||||
srs_error("open bootstrap file failed, path=%s", path.c_str());
|
||||
ret = ERROR_HDS_OPEN_BOOTSTRAP_FAILED;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (write(fd, start_abst, size_abst) != size_abst) {
|
||||
srs_error("write bootstrap file failed, path=", path.c_str());
|
||||
close(fd);
|
||||
ret = ERROR_HDS_WRITE_BOOTSTRAP_FAILED;
|
||||
return ret;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
srs_trace("build bootstrap success=%s", path.c_str());
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
void SrsHds::adjust_windows()
|
||||
{
|
||||
int windows_size = 0;
|
||||
list<SrsHdsFragment *>::iterator iter;
|
||||
for (iter = fragments.begin(); iter != fragments.end(); ++iter) {
|
||||
SrsHdsFragment *fragment = *iter;
|
||||
windows_size += fragment->duration();
|
||||
}
|
||||
|
||||
double windows_size_limit = _srs_config->get_hds_window(hds_req->vhost) * 1000;
|
||||
if (windows_size > windows_size_limit ) {
|
||||
SrsHdsFragment *fragment = fragments.front();
|
||||
unlink(fragment->fragment_path().c_str());
|
||||
fragments.erase(fragments.begin());
|
||||
srs_freep(fragment);
|
||||
}
|
||||
}
|
65
trunk/src/app/srs_app_hds.hpp
Normal file
65
trunk/src/app/srs_app_hds.hpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2015 wenjiegit
|
||||
Copyright (c) 2013-2015 winlin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef SRS_APP_HDS_HPP
|
||||
#define SRS_APP_HDS_HPP
|
||||
|
||||
#include <list>
|
||||
|
||||
class SrsRequest;
|
||||
class SrsSharedPtrMessage;
|
||||
class SrsHdsFragment;
|
||||
class SrsSource;
|
||||
|
||||
using namespace std;
|
||||
|
||||
class SrsHds
|
||||
{
|
||||
public:
|
||||
SrsHds(SrsSource* s);
|
||||
~SrsHds();
|
||||
|
||||
int on_publish(SrsRequest* req);
|
||||
int on_unpublish();
|
||||
|
||||
int on_video(SrsSharedPtrMessage* msg);
|
||||
int on_audio(SrsSharedPtrMessage* msg);
|
||||
|
||||
private:
|
||||
int flush_mainfest();
|
||||
int flush_bootstrap();
|
||||
void adjust_windows();
|
||||
|
||||
private:
|
||||
list<SrsHdsFragment *> fragments;
|
||||
SrsHdsFragment *currentSegment;
|
||||
SrsSource *source;
|
||||
int fragment_index;
|
||||
SrsSharedPtrMessage *video_sh;
|
||||
SrsSharedPtrMessage *audio_sh;
|
||||
|
||||
SrsRequest *hds_req;
|
||||
};
|
||||
|
||||
#endif // SRS_APP_HDS_HPP
|
|
@ -42,6 +42,7 @@ using namespace std;
|
|||
#include <srs_kernel_utility.hpp>
|
||||
#include <srs_kernel_codec.hpp>
|
||||
#include <srs_rtmp_msg_array.hpp>
|
||||
#include <srs_app_hds.hpp>
|
||||
|
||||
#define CONST_MAX_JITTER_MS 500
|
||||
#define DEFAULT_FRAME_TIME_MS 40
|
||||
|
@ -770,6 +771,8 @@ SrsSource::SrsSource(ISrsHlsHandler* hh)
|
|||
encoder = new SrsEncoder();
|
||||
#endif
|
||||
|
||||
hds = new SrsHds(this);
|
||||
|
||||
cache_metadata = cache_sh_video = cache_sh_audio = NULL;
|
||||
|
||||
frame_rate = sample_rate = 0;
|
||||
|
@ -1324,6 +1327,15 @@ int SrsSource::on_audio(SrsCommonMessage* __audio)
|
|||
}
|
||||
#endif
|
||||
|
||||
if ((ret = hds->on_audio(&msg)) != ERROR_SUCCESS) {
|
||||
// unpublish, ignore ret.
|
||||
hds->on_unpublish();
|
||||
// ignore.
|
||||
ret = ERROR_SUCCESS;
|
||||
|
||||
srs_warn("hds process audio message failed, ignore and disable dvr. ret=%d", ret);
|
||||
}
|
||||
|
||||
// copy to all consumer
|
||||
int nb_consumers = (int)consumers.size();
|
||||
if (nb_consumers > 0) {
|
||||
|
@ -1456,6 +1468,15 @@ int SrsSource::on_video(SrsCommonMessage* __video)
|
|||
}
|
||||
#endif
|
||||
|
||||
if ((ret = hds->on_video(&msg)) != ERROR_SUCCESS) {
|
||||
// unpublish, ignore ret.
|
||||
hds->on_unpublish();
|
||||
// ignore.
|
||||
ret = ERROR_SUCCESS;
|
||||
|
||||
srs_warn("hds process video message failed, ignore and disable dvr. ret=%d", ret);
|
||||
}
|
||||
|
||||
// copy to all consumer
|
||||
if (true) {
|
||||
for (int i = 0; i < (int)consumers.size(); i++) {
|
||||
|
@ -1693,6 +1714,11 @@ int SrsSource::on_publish()
|
|||
}
|
||||
#endif
|
||||
|
||||
if ((ret = hds->on_publish(_req)) != ERROR_SUCCESS) {
|
||||
srs_error("start hds failed. ret=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// notify the handler.
|
||||
srs_assert(handler);
|
||||
if ((ret = handler->on_publish(this, _req)) != ERROR_SUCCESS) {
|
||||
|
@ -1720,6 +1746,8 @@ void SrsSource::on_unpublish()
|
|||
dvr->on_unpublish();
|
||||
#endif
|
||||
|
||||
hds->on_unpublish();
|
||||
|
||||
gop_cache->clear();
|
||||
|
||||
srs_freep(cache_metadata);
|
||||
|
|
|
@ -62,6 +62,7 @@ class SrsEncoder;
|
|||
#endif
|
||||
class SrsStream;
|
||||
class ISrsHlsHandler;
|
||||
class SrsHds;
|
||||
|
||||
/**
|
||||
* the time jitter algorithm:
|
||||
|
@ -411,6 +412,7 @@ private:
|
|||
#ifdef SRS_AUTO_TRANSCODE
|
||||
SrsEncoder* encoder;
|
||||
#endif
|
||||
SrsHds *hds;
|
||||
// edge control service
|
||||
SrsPlayEdge* play_edge;
|
||||
SrsPublishEdge* publish_edge;
|
||||
|
|
|
@ -213,6 +213,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
#define ERROR_HTTP_DVR_REQUEST 3051
|
||||
#define ERROR_HTTP_JSON_REQUIRED 3052
|
||||
#define ERROR_HTTP_DVR_CREATE_REQUEST 3053
|
||||
// HDS error code
|
||||
#define ERROR_HDS_OPEN_F4M_FAILED 3054
|
||||
#define ERROR_HDS_WRITE_F4M_FAILED 3055
|
||||
#define ERROR_HDS_OPEN_BOOTSTRAP_FAILED 3056
|
||||
#define ERROR_HDS_WRITE_BOOTSTRAP_FAILED 3057
|
||||
#define ERROR_HDS_OPEN_FRAGMENT_FAILED 3058
|
||||
#define ERROR_HDS_WRITE_FRAGMENT_FAILED 3059
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// HTTP/StreamCaster protocol error.
|
||||
|
|
Loading…
Reference in a new issue