mirror of
https://github.com/ossrs/srs.git
synced 2025-03-09 15:49:59 +00:00
for #738, refine the dvr segmenter.
This commit is contained in:
parent
31191f2650
commit
8c01f52372
9 changed files with 477 additions and 293 deletions
13
trunk/conf/dvr.mp4.conf
Normal file
13
trunk/conf/dvr.mp4.conf
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# the config for srs to dvr in session mode
|
||||||
|
# @see https://github.com/ossrs/srs/wiki/v3_CN_DVR
|
||||||
|
# @see full.conf for detail config.
|
||||||
|
|
||||||
|
listen 1935;
|
||||||
|
max_connections 1000;
|
||||||
|
vhost __defaultVhost__ {
|
||||||
|
dvr {
|
||||||
|
enabled on;
|
||||||
|
dvr_path ./objs/nginx/html/[app]/[stream].[timestamp].mp4;
|
||||||
|
dvr_plan session;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1103,9 +1103,9 @@ vhost hds.srs.com {
|
||||||
|
|
||||||
# vhost for dvr
|
# vhost for dvr
|
||||||
vhost dvr.srs.com {
|
vhost dvr.srs.com {
|
||||||
# dvr RTMP stream to file,
|
# DVR RTMP stream to file,
|
||||||
# start to record to file when encoder publish,
|
# start to record to file when encoder publish,
|
||||||
# reap flv according by specified dvr_plan.
|
# reap flv/mp4 according by specified dvr_plan.
|
||||||
dvr {
|
dvr {
|
||||||
# whether enabled dvr features
|
# whether enabled dvr features
|
||||||
# default: off
|
# default: off
|
||||||
|
@ -1118,12 +1118,13 @@ vhost dvr.srs.com {
|
||||||
# default: all
|
# default: all
|
||||||
dvr_apply all;
|
dvr_apply all;
|
||||||
# the dvr plan. canbe:
|
# the dvr plan. canbe:
|
||||||
# session reap flv when session end(unpublish).
|
# session reap flv/mp4 when session end(unpublish).
|
||||||
# segment reap flv when flv duration exceed the specified dvr_duration.
|
# segment reap flv/mp4 when flv duration exceed the specified dvr_duration.
|
||||||
# append always append to flv file, never reap it.
|
# append always append to flv file, never reap it.
|
||||||
|
# @remark MP4 only support session or segment plan, not suport append plan.
|
||||||
# default: session
|
# default: session
|
||||||
dvr_plan session;
|
dvr_plan session;
|
||||||
# the dvr output path.
|
# the dvr output path, *.flv or *.mp4.
|
||||||
# we supports some variables to generate the filename.
|
# we supports some variables to generate the filename.
|
||||||
# [vhost], the vhost of stream.
|
# [vhost], the vhost of stream.
|
||||||
# [app], the app of stream.
|
# [app], the app of stream.
|
||||||
|
@ -1154,6 +1155,10 @@ vhost dvr.srs.com {
|
||||||
# dvr_path /data/[vhost]/[app]/[2006]/[01]/[stream]-[02]-[15].[04].[05].[999].flv;
|
# dvr_path /data/[vhost]/[app]/[2006]/[01]/[stream]-[02]-[15].[04].[05].[999].flv;
|
||||||
# =>
|
# =>
|
||||||
# dvr_path /data/ossrs.net/live/2015/01/livestream-03-10.57.30.776.flv;
|
# dvr_path /data/ossrs.net/live/2015/01/livestream-03-10.57.30.776.flv;
|
||||||
|
# 5. DVR to mp4:
|
||||||
|
# dvr_path ./objs/nginx/html/[app]/[stream].[timestamp].mp4;
|
||||||
|
# =>
|
||||||
|
# dvr_path ./objs/nginx/html/live/livestream.1420254068776.mp4;
|
||||||
# @see https://github.com/ossrs/srs/wiki/v2_CN_DVR#custom-path
|
# @see https://github.com/ossrs/srs/wiki/v2_CN_DVR#custom-path
|
||||||
# @see https://github.com/ossrs/srs/wiki/v2_EN_DVR#custom-path
|
# @see https://github.com/ossrs/srs/wiki/v2_EN_DVR#custom-path
|
||||||
# segment,session apply it.
|
# segment,session apply it.
|
||||||
|
|
|
@ -42,59 +42,57 @@ using namespace std;
|
||||||
#include <srs_kernel_buffer.hpp>
|
#include <srs_kernel_buffer.hpp>
|
||||||
#include <srs_protocol_json.hpp>
|
#include <srs_protocol_json.hpp>
|
||||||
#include <srs_app_utility.hpp>
|
#include <srs_app_utility.hpp>
|
||||||
|
#include <srs_kernel_mp4.hpp>
|
||||||
|
|
||||||
// update the flv duration and filesize every this interval in ms.
|
// update the flv duration and filesize every this interval in ms.
|
||||||
#define SRS_DVR_UPDATE_DURATION_INTERVAL 60000
|
#define SRS_DVR_UPDATE_DURATION_INTERVAL 60000
|
||||||
|
|
||||||
SrsFlvSegment::SrsFlvSegment(SrsDvrPlan* p)
|
SrsDvrSegmenter::SrsDvrSegmenter()
|
||||||
{
|
{
|
||||||
req = NULL;
|
req = NULL;
|
||||||
jitter = NULL;
|
jitter = NULL;
|
||||||
plan = p;
|
plan = NULL;
|
||||||
|
|
||||||
fs = new SrsFileWriter();
|
|
||||||
enc = new SrsFlvEncoder();
|
|
||||||
jitter_algorithm = SrsRtmpJitterAlgorithmOFF;
|
|
||||||
|
|
||||||
path = "";
|
|
||||||
has_keyframe = false;
|
|
||||||
duration = 0;
|
duration = 0;
|
||||||
starttime = -1;
|
|
||||||
stream_starttime = 0;
|
path = "";
|
||||||
stream_previous_pkt_time = -1;
|
fs = new SrsFileWriter();
|
||||||
stream_duration = 0;
|
jitter_algorithm = SrsRtmpJitterAlgorithmOFF;
|
||||||
|
|
||||||
duration_offset = 0;
|
|
||||||
filesize_offset = 0;
|
|
||||||
|
|
||||||
_srs_config->subscribe(this);
|
_srs_config->subscribe(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
SrsFlvSegment::~SrsFlvSegment()
|
SrsDvrSegmenter::~SrsDvrSegmenter()
|
||||||
{
|
{
|
||||||
_srs_config->unsubscribe(this);
|
_srs_config->unsubscribe(this);
|
||||||
|
|
||||||
srs_freep(jitter);
|
srs_freep(jitter);
|
||||||
srs_freep(fs);
|
srs_freep(fs);
|
||||||
srs_freep(enc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int SrsFlvSegment::initialize(SrsRequest* r)
|
int SrsDvrSegmenter::initialize(SrsDvrPlan* p, SrsRequest* r)
|
||||||
{
|
{
|
||||||
int ret = ERROR_SUCCESS;
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
req = r;
|
req = r;
|
||||||
jitter_algorithm = (SrsRtmpJitterAlgorithm)_srs_config->get_dvr_time_jitter(req->vhost);
|
plan = p;
|
||||||
|
|
||||||
|
int jitter = _srs_config->get_dvr_time_jitter(req->vhost);
|
||||||
|
jitter_algorithm = (SrsRtmpJitterAlgorithm)jitter;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SrsFlvSegment::is_overflow(int64_t max_duration)
|
string SrsDvrSegmenter::get_path()
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SrsDvrSegmenter::is_overflow(int64_t max_duration)
|
||||||
{
|
{
|
||||||
return duration >= max_duration;
|
return duration >= max_duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SrsFlvSegment::open(bool use_tmp_file)
|
int SrsDvrSegmenter::open(bool use_tmp_file)
|
||||||
{
|
{
|
||||||
int ret = ERROR_SUCCESS;
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
|
@ -104,7 +102,14 @@ int SrsFlvSegment::open(bool use_tmp_file)
|
||||||
}
|
}
|
||||||
|
|
||||||
path = generate_path();
|
path = generate_path();
|
||||||
bool fresh_flv_file = !srs_path_exists(path);
|
bool can_append = srs_string_ends_with(path, ".flv");
|
||||||
|
|
||||||
|
bool target_exists = srs_path_exists(path);
|
||||||
|
if (!can_append && target_exists) {
|
||||||
|
ret = ERROR_DVR_CANNOT_APPEND;
|
||||||
|
srs_error("DVR can't append to exists path=%s. ret=%d", path.c_str(), ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
// create dir first.
|
// create dir first.
|
||||||
std::string dir = srs_path_dirname(path);
|
std::string dir = srs_path_dirname(path);
|
||||||
|
@ -115,60 +120,46 @@ int SrsFlvSegment::open(bool use_tmp_file)
|
||||||
srs_info("create dir=%s ok", dir.c_str());
|
srs_info("create dir=%s ok", dir.c_str());
|
||||||
|
|
||||||
// create jitter.
|
// create jitter.
|
||||||
if ((ret = create_jitter(!fresh_flv_file)) != ERROR_SUCCESS) {
|
if ((ret = create_jitter(target_exists)) != ERROR_SUCCESS) {
|
||||||
srs_error("create jitter failed, path=%s, fresh=%d. ret=%d", path.c_str(), fresh_flv_file, ret);
|
srs_error("create jitter failed, path=%s, exists=%d. ret=%d", path.c_str(), target_exists, ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate the tmp flv path.
|
// generate the tmp flv path.
|
||||||
if (!fresh_flv_file || !use_tmp_file) {
|
if (target_exists || !use_tmp_file) {
|
||||||
// when path exists, always append to it.
|
// when path exists, always append to it.
|
||||||
// so we must use the target flv path as output flv.
|
// so we must use the target flv path as output flv.
|
||||||
tmp_flv_file = path;
|
tmp_dvr_file = path;
|
||||||
} else {
|
} else {
|
||||||
// when path not exists, dvr to tmp file.
|
// when path not exists, dvr to tmp file.
|
||||||
tmp_flv_file = path + ".tmp";
|
tmp_dvr_file = path + ".tmp";
|
||||||
}
|
}
|
||||||
|
|
||||||
// open file writer, in append or create mode.
|
// open file writer, in append or create mode.
|
||||||
if (!fresh_flv_file) {
|
if (target_exists) {
|
||||||
if ((ret = fs->open_append(tmp_flv_file)) != ERROR_SUCCESS) {
|
if ((ret = fs->open_append(tmp_dvr_file)) != ERROR_SUCCESS) {
|
||||||
srs_error("append file stream for file %s failed. ret=%d", path.c_str(), ret);
|
srs_error("append file stream for file %s failed. ret=%d", path.c_str(), ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
srs_trace("dvr: always append to when exists, file=%s.", path.c_str());
|
srs_trace("dvr: always append to when exists, file=%s.", path.c_str());
|
||||||
} else {
|
} else {
|
||||||
if ((ret = fs->open(tmp_flv_file)) != ERROR_SUCCESS) {
|
if ((ret = fs->open(tmp_dvr_file)) != ERROR_SUCCESS) {
|
||||||
srs_error("open file stream for file %s failed. ret=%d", path.c_str(), ret);
|
srs_error("open file stream for file %s failed. ret=%d", path.c_str(), ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize the encoder.
|
// initialize the encoder.
|
||||||
if ((ret = enc->initialize(fs)) != ERROR_SUCCESS) {
|
if ((ret = open_encoder()) != ERROR_SUCCESS) {
|
||||||
srs_error("initialize enc by fs for file %s failed. ret=%d", path.c_str(), ret);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when exists, donot write flv header.
|
|
||||||
if (fresh_flv_file) {
|
|
||||||
// write the flv header to writer.
|
|
||||||
if ((ret = enc->write_header()) != ERROR_SUCCESS) {
|
|
||||||
srs_error("write flv header failed. ret=%d", ret);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the duration and filesize offset.
|
|
||||||
duration_offset = 0;
|
|
||||||
filesize_offset = 0;
|
|
||||||
|
|
||||||
srs_trace("dvr stream %s to file %s", req->stream.c_str(), path.c_str());
|
srs_trace("dvr stream %s to file %s", req->stream.c_str(), path.c_str());
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SrsFlvSegment::close()
|
int SrsDvrSegmenter::close()
|
||||||
{
|
{
|
||||||
int ret = ERROR_SUCCESS;
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
|
@ -177,19 +168,19 @@ int SrsFlvSegment::close()
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update duration and filesize.
|
// Close the encoder, then close the fs object.
|
||||||
if ((ret = update_flv_metadata()) != ERROR_SUCCESS) {
|
if ((ret = close_encoder()) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs->close();
|
fs->close();
|
||||||
|
|
||||||
// when tmp flv file exists, reap it.
|
// when tmp flv file exists, reap it.
|
||||||
if (tmp_flv_file != path) {
|
if (tmp_dvr_file != path) {
|
||||||
if (rename(tmp_flv_file.c_str(), path.c_str()) < 0) {
|
if (rename(tmp_dvr_file.c_str(), path.c_str()) < 0) {
|
||||||
ret = ERROR_SYSTEM_FILE_RENAME;
|
ret = ERROR_SYSTEM_FILE_RENAME;
|
||||||
srs_error("rename flv file failed, %s => %s. ret=%d",
|
srs_error("rename flv file failed, %s => %s. ret=%d",
|
||||||
tmp_flv_file.c_str(), path.c_str(), ret);
|
tmp_dvr_file.c_str(), path.c_str(), ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,35 +195,179 @@ int SrsFlvSegment::close()
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SrsFlvSegment::write_metadata(SrsSharedPtrMessage* metadata)
|
string SrsDvrSegmenter::generate_path()
|
||||||
|
{
|
||||||
|
// the path in config, for example,
|
||||||
|
// /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv
|
||||||
|
std::string path_config = _srs_config->get_dvr_path(req->vhost);
|
||||||
|
|
||||||
|
// add [stream].[timestamp].flv as filename for dir
|
||||||
|
if (!srs_string_ends_with(path_config, ".flv")) {
|
||||||
|
path_config += "/[stream].[timestamp].flv";
|
||||||
|
}
|
||||||
|
|
||||||
|
// the flv file path
|
||||||
|
std::string flv_path = path_config;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SrsDvrSegmenter::create_jitter(bool target_exists)
|
||||||
{
|
{
|
||||||
int ret = ERROR_SUCCESS;
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
|
// When DVR target file not exists, create new jitter.
|
||||||
|
if (!target_exists) {
|
||||||
|
// jitter when publish, ensure whole stream start from 0.
|
||||||
|
srs_freep(jitter);
|
||||||
|
jitter = new SrsRtmpJitter();
|
||||||
|
|
||||||
|
duration = 0;
|
||||||
|
|
||||||
if (duration_offset || filesize_offset) {
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when jitter ok, do nothing.
|
||||||
|
if (jitter) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// always ensure the jitter crote.
|
||||||
|
// for the first time, initialize jitter from exists file.
|
||||||
|
jitter = new SrsRtmpJitter();
|
||||||
|
|
||||||
|
// TODO: FIXME: implements it.
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SrsDvrSegmenter::on_reload_vhost_dvr(std::string /*vhost*/)
|
||||||
|
{
|
||||||
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
|
jitter_algorithm = (SrsRtmpJitterAlgorithm)_srs_config->get_dvr_time_jitter(req->vhost);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
SrsDvrFlvSegmenter::SrsDvrFlvSegmenter()
|
||||||
|
{
|
||||||
|
enc = new SrsFlvEncoder();
|
||||||
|
|
||||||
|
duration_offset = 0;
|
||||||
|
filesize_offset = 0;
|
||||||
|
|
||||||
|
has_keyframe = false;
|
||||||
|
|
||||||
|
starttime = -1;
|
||||||
|
stream_starttime = 0;
|
||||||
|
stream_previous_pkt_time = -1;
|
||||||
|
stream_duration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SrsDvrFlvSegmenter::~SrsDvrFlvSegmenter()
|
||||||
|
{
|
||||||
|
srs_freep(enc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int SrsDvrFlvSegmenter::open_encoder()
|
||||||
|
{
|
||||||
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
|
if ((ret = enc->initialize(fs)) != ERROR_SUCCESS) {
|
||||||
|
srs_error("initialize enc by fs for file %s failed. ret=%d", path.c_str(), ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool target_exists = srs_path_exists(path);
|
||||||
|
if (target_exists){
|
||||||
|
has_keyframe = false;
|
||||||
|
|
||||||
|
// fresh stream starting.
|
||||||
|
starttime = -1;
|
||||||
|
stream_previous_pkt_time = -1;
|
||||||
|
stream_starttime = srs_update_system_time_ms();
|
||||||
|
stream_duration = 0;
|
||||||
|
|
||||||
|
// write the flv header to writer.
|
||||||
|
if ((ret = enc->write_header()) != ERROR_SUCCESS) {
|
||||||
|
srs_error("write flv header failed. ret=%d", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the duration and filesize offset.
|
||||||
|
duration_offset = 0;
|
||||||
|
filesize_offset = 0;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SrsDvrFlvSegmenter::close_encoder()
|
||||||
|
{
|
||||||
|
return refresh_metadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
int SrsDvrFlvSegmenter::on_update_duration(SrsSharedPtrMessage* msg)
|
||||||
|
{
|
||||||
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
|
// we must assumpt that the stream timestamp is monotonically increase,
|
||||||
|
// that is, always use time jitter to correct the timestamp.
|
||||||
|
// except the time jitter is disabled in config.
|
||||||
|
|
||||||
|
// set the segment starttime at first time
|
||||||
|
if (starttime < 0) {
|
||||||
|
starttime = msg->timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no previous packet or timestamp overflow.
|
||||||
|
if (stream_previous_pkt_time < 0 || stream_previous_pkt_time > msg->timestamp) {
|
||||||
|
stream_previous_pkt_time = msg->timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect segment and stream duration, timestamp overflow is ok.
|
||||||
|
duration += msg->timestamp - stream_previous_pkt_time;
|
||||||
|
stream_duration += msg->timestamp - stream_previous_pkt_time;
|
||||||
|
|
||||||
|
// update previous packet time
|
||||||
|
stream_previous_pkt_time = msg->timestamp;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SrsDvrFlvSegmenter::write_metadata(SrsSharedPtrMessage* metadata)
|
||||||
|
{
|
||||||
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
|
// Ignore when metadata already written.
|
||||||
|
if (duration_offset || filesize_offset) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
SrsBuffer stream;
|
SrsBuffer stream;
|
||||||
if ((ret = stream.initialize(metadata->payload, metadata->size)) != ERROR_SUCCESS) {
|
if ((ret = stream.initialize(metadata->payload, metadata->size)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
SrsAmf0Any* name = SrsAmf0Any::str();
|
SrsAmf0Any* name = SrsAmf0Any::str();
|
||||||
SrsAutoFree(SrsAmf0Any, name);
|
SrsAutoFree(SrsAmf0Any, name);
|
||||||
if ((ret = name->read(&stream)) != ERROR_SUCCESS) {
|
if ((ret = name->read(&stream)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
SrsAmf0Object* obj = SrsAmf0Any::object();
|
SrsAmf0Object* obj = SrsAmf0Any::object();
|
||||||
SrsAutoFree(SrsAmf0Object, obj);
|
SrsAutoFree(SrsAmf0Object, obj);
|
||||||
if ((ret = obj->read(&stream)) != ERROR_SUCCESS) {
|
if ((ret = obj->read(&stream)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove duration and filesize.
|
// remove duration and filesize.
|
||||||
obj->set("filesize", NULL);
|
obj->set("filesize", NULL);
|
||||||
obj->set("duration", NULL);
|
obj->set("duration", NULL);
|
||||||
|
|
||||||
// add properties.
|
// add properties.
|
||||||
obj->set("service", SrsAmf0Any::str(RTMP_SIG_SRS_SERVER));
|
obj->set("service", SrsAmf0Any::str(RTMP_SIG_SRS_SERVER));
|
||||||
obj->set("filesize", SrsAmf0Any::number(0));
|
obj->set("filesize", SrsAmf0Any::number(0));
|
||||||
|
@ -241,12 +376,12 @@ int SrsFlvSegment::write_metadata(SrsSharedPtrMessage* metadata)
|
||||||
int size = name->total_size() + obj->total_size();
|
int size = name->total_size() + obj->total_size();
|
||||||
char* payload = new char[size];
|
char* payload = new char[size];
|
||||||
SrsAutoFreeA(char, payload);
|
SrsAutoFreeA(char, payload);
|
||||||
|
|
||||||
// 11B flv header, 3B object EOF, 8B number value, 1B number flag.
|
// 11B flv header, 3B object EOF, 8B number value, 1B number flag.
|
||||||
duration_offset = fs->tellg() + size + 11 - SrsAmf0Size::object_eof() - SrsAmf0Size::number();
|
duration_offset = fs->tellg() + size + 11 - SrsAmf0Size::object_eof() - SrsAmf0Size::number();
|
||||||
// 2B string flag, 8B number value, 8B string 'duration', 1B number flag
|
// 2B string flag, 8B number value, 8B string 'duration', 1B number flag
|
||||||
filesize_offset = duration_offset - SrsAmf0Size::utf8("duration") - SrsAmf0Size::number();
|
filesize_offset = duration_offset - SrsAmf0Size::utf8("duration") - SrsAmf0Size::number();
|
||||||
|
|
||||||
// convert metadata to bytes.
|
// convert metadata to bytes.
|
||||||
if ((ret = stream.initialize(payload, size)) != ERROR_SUCCESS) {
|
if ((ret = stream.initialize(payload, size)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -266,10 +401,10 @@ int SrsFlvSegment::write_metadata(SrsSharedPtrMessage* metadata)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SrsFlvSegment::write_audio(SrsSharedPtrMessage* shared_audio)
|
int SrsDvrFlvSegmenter::write_audio(SrsSharedPtrMessage* shared_audio)
|
||||||
{
|
{
|
||||||
int ret = ERROR_SUCCESS;
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
SrsSharedPtrMessage* audio = shared_audio->copy();
|
SrsSharedPtrMessage* audio = shared_audio->copy();
|
||||||
SrsAutoFree(SrsSharedPtrMessage, audio);
|
SrsAutoFree(SrsSharedPtrMessage, audio);
|
||||||
|
|
||||||
|
@ -283,7 +418,7 @@ int SrsFlvSegment::write_audio(SrsSharedPtrMessage* shared_audio)
|
||||||
if ((ret = enc->write_audio(timestamp, payload, size)) != ERROR_SUCCESS) {
|
if ((ret = enc->write_audio(timestamp, payload, size)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ret = on_update_duration(audio)) != ERROR_SUCCESS) {
|
if ((ret = on_update_duration(audio)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -291,10 +426,10 @@ int SrsFlvSegment::write_audio(SrsSharedPtrMessage* shared_audio)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SrsFlvSegment::write_video(SrsSharedPtrMessage* shared_video)
|
int SrsDvrFlvSegmenter::write_video(SrsSharedPtrMessage* shared_video)
|
||||||
{
|
{
|
||||||
int ret = ERROR_SUCCESS;
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
SrsSharedPtrMessage* video = shared_video->copy();
|
SrsSharedPtrMessage* video = shared_video->copy();
|
||||||
SrsAutoFree(SrsSharedPtrMessage, video);
|
SrsAutoFree(SrsSharedPtrMessage, video);
|
||||||
|
|
||||||
|
@ -302,7 +437,7 @@ int SrsFlvSegment::write_video(SrsSharedPtrMessage* shared_video)
|
||||||
int size = video->size;
|
int size = video->size;
|
||||||
|
|
||||||
bool is_sequence_header = SrsFlvCodec::video_is_sequence_header(payload, size);
|
bool is_sequence_header = SrsFlvCodec::video_is_sequence_header(payload, size);
|
||||||
bool is_key_frame = SrsFlvCodec::video_is_h264(payload, size)
|
bool is_key_frame = SrsFlvCodec::video_is_h264(payload, size)
|
||||||
&& SrsFlvCodec::video_is_keyframe(payload, size) && !is_sequence_header;
|
&& SrsFlvCodec::video_is_keyframe(payload, size) && !is_sequence_header;
|
||||||
if (is_key_frame) {
|
if (is_key_frame) {
|
||||||
has_keyframe = true;
|
has_keyframe = true;
|
||||||
|
@ -311,7 +446,7 @@ int SrsFlvSegment::write_video(SrsSharedPtrMessage* shared_video)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
srs_verbose("dvr video is key: %d", is_key_frame);
|
srs_verbose("dvr video is key: %d", is_key_frame);
|
||||||
|
|
||||||
// accept the sequence header here.
|
// accept the sequence header here.
|
||||||
// when got no keyframe, ignore when should wait keyframe.
|
// when got no keyframe, ignore when should wait keyframe.
|
||||||
if (!has_keyframe && !is_sequence_header) {
|
if (!has_keyframe && !is_sequence_header) {
|
||||||
|
@ -340,35 +475,35 @@ int SrsFlvSegment::write_video(SrsSharedPtrMessage* shared_video)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SrsFlvSegment::update_flv_metadata()
|
int SrsDvrFlvSegmenter::refresh_metadata()
|
||||||
{
|
{
|
||||||
int ret = ERROR_SUCCESS;
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
// no duration or filesize specified.
|
// no duration or filesize specified.
|
||||||
if (!duration_offset || !filesize_offset) {
|
if (!duration_offset || !filesize_offset) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t cur = fs->tellg();
|
int64_t cur = fs->tellg();
|
||||||
|
|
||||||
// buffer to write the size.
|
// buffer to write the size.
|
||||||
char* buf = new char[SrsAmf0Size::number()];
|
char* buf = new char[SrsAmf0Size::number()];
|
||||||
SrsAutoFreeA(char, buf);
|
SrsAutoFreeA(char, buf);
|
||||||
|
|
||||||
SrsBuffer stream;
|
SrsBuffer stream;
|
||||||
if ((ret = stream.initialize(buf, SrsAmf0Size::number())) != ERROR_SUCCESS) {
|
if ((ret = stream.initialize(buf, SrsAmf0Size::number())) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// filesize to buf.
|
// filesize to buf.
|
||||||
SrsAmf0Any* size = SrsAmf0Any::number((double)cur);
|
SrsAmf0Any* size = SrsAmf0Any::number((double)cur);
|
||||||
SrsAutoFree(SrsAmf0Any, size);
|
SrsAutoFree(SrsAmf0Any, size);
|
||||||
|
|
||||||
stream.skip(-1 * stream.pos());
|
stream.skip(-1 * stream.pos());
|
||||||
if ((ret = size->write(&stream)) != ERROR_SUCCESS) {
|
if ((ret = size->write(&stream)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the flesize.
|
// update the flesize.
|
||||||
fs->seek2(filesize_offset);
|
fs->seek2(filesize_offset);
|
||||||
if ((ret = fs->write(buf, SrsAmf0Size::number(), NULL)) != ERROR_SUCCESS) {
|
if ((ret = fs->write(buf, SrsAmf0Size::number(), NULL)) != ERROR_SUCCESS) {
|
||||||
|
@ -383,114 +518,62 @@ int SrsFlvSegment::update_flv_metadata()
|
||||||
if ((ret = dur->write(&stream)) != ERROR_SUCCESS) {
|
if ((ret = dur->write(&stream)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the duration
|
// update the duration
|
||||||
fs->seek2(duration_offset);
|
fs->seek2(duration_offset);
|
||||||
if ((ret = fs->write(buf, SrsAmf0Size::number(), NULL)) != ERROR_SUCCESS) {
|
if ((ret = fs->write(buf, SrsAmf0Size::number(), NULL)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset the offset.
|
// reset the offset.
|
||||||
fs->seek2(cur);
|
fs->seek2(cur);
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
string SrsFlvSegment::get_path()
|
|
||||||
{
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
string SrsFlvSegment::generate_path()
|
|
||||||
{
|
|
||||||
// the path in config, for example,
|
|
||||||
// /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv
|
|
||||||
std::string path_config = _srs_config->get_dvr_path(req->vhost);
|
|
||||||
|
|
||||||
// add [stream].[timestamp].flv as filename for dir
|
|
||||||
if (!srs_string_ends_with(path_config, ".flv")) {
|
|
||||||
path_config += "/[stream].[timestamp].flv";
|
|
||||||
}
|
|
||||||
|
|
||||||
// the flv file path
|
|
||||||
std::string flv_path = path_config;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SrsFlvSegment::create_jitter(bool loads_from_flv)
|
|
||||||
{
|
|
||||||
int ret = ERROR_SUCCESS;
|
|
||||||
|
|
||||||
// when path exists, use exists jitter.
|
|
||||||
if (!loads_from_flv) {
|
|
||||||
// jitter when publish, ensure whole stream start from 0.
|
|
||||||
srs_freep(jitter);
|
|
||||||
jitter = new SrsRtmpJitter();
|
|
||||||
|
|
||||||
// fresh stream starting.
|
|
||||||
starttime = -1;
|
|
||||||
stream_previous_pkt_time = -1;
|
|
||||||
stream_starttime = srs_update_system_time_ms();
|
|
||||||
stream_duration = 0;
|
|
||||||
|
|
||||||
// fresh segment starting.
|
|
||||||
has_keyframe = false;
|
|
||||||
duration = 0;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// when jitter ok, do nothing.
|
|
||||||
if (jitter) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// always ensure the jitter crote.
|
|
||||||
// for the first time, initialize jitter from exists file.
|
|
||||||
jitter = new SrsRtmpJitter();
|
|
||||||
|
|
||||||
// TODO: FIXME: implements it.
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SrsFlvSegment::on_update_duration(SrsSharedPtrMessage* msg)
|
|
||||||
{
|
|
||||||
int ret = ERROR_SUCCESS;
|
|
||||||
|
|
||||||
// we must assumpt that the stream timestamp is monotonically increase,
|
|
||||||
// that is, always use time jitter to correct the timestamp.
|
|
||||||
// except the time jitter is disabled in config.
|
|
||||||
|
|
||||||
// set the segment starttime at first time
|
|
||||||
if (starttime < 0) {
|
|
||||||
starttime = msg->timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no previous packet or timestamp overflow.
|
|
||||||
if (stream_previous_pkt_time < 0 || stream_previous_pkt_time > msg->timestamp) {
|
|
||||||
stream_previous_pkt_time = msg->timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect segment and stream duration, timestamp overflow is ok.
|
|
||||||
duration += msg->timestamp - stream_previous_pkt_time;
|
|
||||||
stream_duration += msg->timestamp - stream_previous_pkt_time;
|
|
||||||
|
|
||||||
// update previous packet time
|
|
||||||
stream_previous_pkt_time = msg->timestamp;
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SrsFlvSegment::on_reload_vhost_dvr(std::string /*vhost*/)
|
SrsDvrMp4Segmenter::SrsDvrMp4Segmenter()
|
||||||
|
{
|
||||||
|
enc = new SrsMp4Encoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
SrsDvrMp4Segmenter::~SrsDvrMp4Segmenter()
|
||||||
|
{
|
||||||
|
srs_freep(enc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int SrsDvrMp4Segmenter::open_encoder()
|
||||||
|
{
|
||||||
|
int ret = ERROR_SUCCESS;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SrsDvrMp4Segmenter::close_encoder()
|
||||||
|
{
|
||||||
|
int ret = ERROR_SUCCESS;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SrsDvrMp4Segmenter::write_metadata(SrsSharedPtrMessage* metadata)
|
||||||
|
{
|
||||||
|
int ret = ERROR_SUCCESS;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SrsDvrMp4Segmenter::write_audio(SrsSharedPtrMessage* shared_audio)
|
||||||
|
{
|
||||||
|
int ret = ERROR_SUCCESS;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SrsDvrMp4Segmenter::write_video(SrsSharedPtrMessage* shared_video)
|
||||||
|
{
|
||||||
|
int ret = ERROR_SUCCESS;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SrsDvrMp4Segmenter::refresh_metadata()
|
||||||
{
|
{
|
||||||
int ret = ERROR_SUCCESS;
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
jitter_algorithm = (SrsRtmpJitterAlgorithm)_srs_config->get_dvr_time_jitter(req->vhost);
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,7 +636,7 @@ SrsDvrPlan::SrsDvrPlan()
|
||||||
req = NULL;
|
req = NULL;
|
||||||
|
|
||||||
dvr_enabled = false;
|
dvr_enabled = false;
|
||||||
segment = new SrsFlvSegment(this);
|
segment = NULL;
|
||||||
async = new SrsAsyncCallWorker();
|
async = new SrsAsyncCallWorker();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,13 +646,14 @@ SrsDvrPlan::~SrsDvrPlan()
|
||||||
srs_freep(async);
|
srs_freep(async);
|
||||||
}
|
}
|
||||||
|
|
||||||
int SrsDvrPlan::initialize(SrsRequest* r)
|
int SrsDvrPlan::initialize(SrsDvrSegmenter* s, SrsRequest* r)
|
||||||
{
|
{
|
||||||
int ret = ERROR_SUCCESS;
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
req = r;
|
req = r;
|
||||||
|
segment = s;
|
||||||
|
|
||||||
if ((ret = segment->initialize(r)) != ERROR_SUCCESS) {
|
if ((ret = segment->initialize(this, r)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -643,19 +727,31 @@ int SrsDvrPlan::on_reap_segment()
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
SrsDvrPlan* SrsDvrPlan::create_plan(string vhost)
|
int SrsDvrPlan::create_plan(string vhost, SrsDvrPlan** pplan)
|
||||||
{
|
{
|
||||||
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
std::string plan = _srs_config->get_dvr_plan(vhost);
|
std::string plan = _srs_config->get_dvr_plan(vhost);
|
||||||
|
std::string path = _srs_config->get_dvr_path(vhost);
|
||||||
|
bool is_mp4 = srs_string_ends_with(path, ".mp4");
|
||||||
|
|
||||||
if (srs_config_dvr_is_plan_segment(plan)) {
|
if (srs_config_dvr_is_plan_segment(plan)) {
|
||||||
return new SrsDvrSegmentPlan();
|
*pplan = new SrsDvrSegmentPlan();
|
||||||
} else if (srs_config_dvr_is_plan_session(plan)) {
|
} else if (srs_config_dvr_is_plan_session(plan)) {
|
||||||
return new SrsDvrSessionPlan();
|
*pplan = new SrsDvrSessionPlan();
|
||||||
} else if (srs_config_dvr_is_plan_append(plan)) {
|
} else if (srs_config_dvr_is_plan_append(plan)) {
|
||||||
return new SrsDvrAppendPlan();
|
if (is_mp4) {
|
||||||
|
ret = ERROR_DVR_ILLEGAL_PLAN;
|
||||||
|
srs_error("DVR plan append not support MP4. ret=%d", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
*pplan = new SrsDvrAppendPlan();
|
||||||
} else {
|
} else {
|
||||||
srs_error("invalid dvr plan=%s, vhost=%s", plan.c_str(), vhost.c_str());
|
srs_error("invalid dvr plan=%s, vhost=%s", plan.c_str(), vhost.c_str());
|
||||||
srs_assert(false);
|
srs_assert(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
SrsDvrSessionPlan::SrsDvrSessionPlan()
|
SrsDvrSessionPlan::SrsDvrSessionPlan()
|
||||||
|
@ -683,7 +779,7 @@ int SrsDvrSessionPlan::on_publish()
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ret = segment->open()) != ERROR_SUCCESS) {
|
if ((ret = segment->open(true)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -793,7 +889,7 @@ int SrsDvrAppendPlan::update_duration(SrsSharedPtrMessage* msg)
|
||||||
last_update_time = msg->timestamp;
|
last_update_time = msg->timestamp;
|
||||||
|
|
||||||
srs_assert(segment);
|
srs_assert(segment);
|
||||||
if (!segment->update_flv_metadata()) {
|
if ((ret = segment->refresh_metadata()) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -813,11 +909,11 @@ SrsDvrSegmentPlan::~SrsDvrSegmentPlan()
|
||||||
srs_freep(metadata);
|
srs_freep(metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
int SrsDvrSegmentPlan::initialize(SrsRequest* req)
|
int SrsDvrSegmentPlan::initialize(SrsDvrSegmenter* s, SrsRequest* req)
|
||||||
{
|
{
|
||||||
int ret = ERROR_SUCCESS;
|
int ret = ERROR_SUCCESS;
|
||||||
|
|
||||||
if ((ret = SrsDvrPlan::initialize(req)) != ERROR_SUCCESS) {
|
if ((ret = SrsDvrPlan::initialize(s, req)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -845,7 +941,7 @@ int SrsDvrSegmentPlan::on_publish()
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ret = segment->open()) != ERROR_SUCCESS) {
|
if ((ret = segment->open(true)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -946,7 +1042,7 @@ int SrsDvrSegmentPlan::update_duration(SrsSharedPtrMessage* msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// open new flv file
|
// open new flv file
|
||||||
if ((ret = segment->open()) != ERROR_SUCCESS) {
|
if ((ret = segment->open(true)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -992,9 +1088,19 @@ int SrsDvr::initialize(SrsOriginHub* h, SrsRequest* r)
|
||||||
actived = srs_config_apply_filter(conf, r);
|
actived = srs_config_apply_filter(conf, r);
|
||||||
|
|
||||||
srs_freep(plan);
|
srs_freep(plan);
|
||||||
plan = SrsDvrPlan::create_plan(r->vhost);
|
if ((ret = SrsDvrPlan::create_plan(r->vhost, &plan)) != ERROR_SUCCESS) {
|
||||||
|
return ret;
|
||||||
if ((ret = plan->initialize(r)) != ERROR_SUCCESS) {
|
}
|
||||||
|
|
||||||
|
std::string path = _srs_config->get_dvr_path(r->vhost);
|
||||||
|
SrsDvrSegmenter* segmenter = NULL;
|
||||||
|
if (srs_string_ends_with(path, ".mp4")) {
|
||||||
|
segmenter = new SrsDvrMp4Segmenter();
|
||||||
|
} else {
|
||||||
|
segmenter = new SrsDvrFlvSegmenter();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ret = plan->initialize(segmenter, r)) != ERROR_SUCCESS) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,81 +47,43 @@ class SrsDvrPlan;
|
||||||
class SrsJsonAny;
|
class SrsJsonAny;
|
||||||
class SrsJsonObject;
|
class SrsJsonObject;
|
||||||
class SrsThread;
|
class SrsThread;
|
||||||
|
class SrsMp4Encoder;
|
||||||
|
|
||||||
#include <srs_app_source.hpp>
|
#include <srs_app_source.hpp>
|
||||||
#include <srs_app_reload.hpp>
|
#include <srs_app_reload.hpp>
|
||||||
#include <srs_app_async_call.hpp>
|
#include <srs_app_async_call.hpp>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a piece of flv segment.
|
* The segmenter for DVR, to write a segment file in flv/mp4.
|
||||||
* when open segment, support start at 0 or not.
|
*/
|
||||||
*/
|
class SrsDvrSegmenter : public ISrsReloadHandler
|
||||||
class SrsFlvSegment : public ISrsReloadHandler
|
|
||||||
{
|
{
|
||||||
private:
|
protected:
|
||||||
|
SrsFileWriter* fs;
|
||||||
|
// The path of current segment flv file path.
|
||||||
|
std::string path;
|
||||||
|
protected:
|
||||||
|
SrsRtmpJitter* jitter;
|
||||||
|
SrsRtmpJitterAlgorithm jitter_algorithm;
|
||||||
|
// The duration in ms of current segment.
|
||||||
|
int64_t duration;
|
||||||
|
protected:
|
||||||
SrsRequest* req;
|
SrsRequest* req;
|
||||||
SrsDvrPlan* plan;
|
SrsDvrPlan* plan;
|
||||||
private:
|
private:
|
||||||
/**
|
std::string tmp_dvr_file;
|
||||||
* the underlayer dvr stream.
|
|
||||||
* if close, the flv is reap and closed.
|
|
||||||
* if open, new flv file is crote.
|
|
||||||
*/
|
|
||||||
SrsFlvEncoder* enc;
|
|
||||||
SrsRtmpJitter* jitter;
|
|
||||||
SrsRtmpJitterAlgorithm jitter_algorithm;
|
|
||||||
SrsFileWriter* fs;
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* the offset of file for duration value.
|
|
||||||
* the next 8 bytes is the double value.
|
|
||||||
*/
|
|
||||||
int64_t duration_offset;
|
|
||||||
/**
|
|
||||||
* the offset of file for filesize value.
|
|
||||||
* the next 8 bytes is the double value.
|
|
||||||
*/
|
|
||||||
int64_t filesize_offset;
|
|
||||||
private:
|
|
||||||
std::string tmp_flv_file;
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* current segment flv file path.
|
|
||||||
*/
|
|
||||||
std::string path;
|
|
||||||
/**
|
|
||||||
* whether current segment has keyframe.
|
|
||||||
*/
|
|
||||||
bool has_keyframe;
|
|
||||||
/**
|
|
||||||
* current segment starttime, RTMP pkt time.
|
|
||||||
*/
|
|
||||||
int64_t starttime;
|
|
||||||
/**
|
|
||||||
* current segment duration
|
|
||||||
*/
|
|
||||||
int64_t duration;
|
|
||||||
/**
|
|
||||||
* stream start time, to generate atc pts. abs time.
|
|
||||||
*/
|
|
||||||
int64_t stream_starttime;
|
|
||||||
/**
|
|
||||||
* stream duration, to generate atc segment.
|
|
||||||
*/
|
|
||||||
int64_t stream_duration;
|
|
||||||
/**
|
|
||||||
* previous stream RTMP pkt time, used to calc the duration.
|
|
||||||
* for the RTMP timestamp will overflow.
|
|
||||||
*/
|
|
||||||
int64_t stream_previous_pkt_time;
|
|
||||||
public:
|
public:
|
||||||
SrsFlvSegment(SrsDvrPlan* p);
|
SrsDvrSegmenter();
|
||||||
virtual ~SrsFlvSegment();
|
virtual ~SrsDvrSegmenter();
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* initialize the segment.
|
* initialize the segment.
|
||||||
*/
|
*/
|
||||||
virtual int initialize(SrsRequest* r);
|
virtual int initialize(SrsDvrPlan* p, SrsRequest* r);
|
||||||
|
/**
|
||||||
|
* get the current dvr path.
|
||||||
|
*/
|
||||||
|
virtual std::string get_path();
|
||||||
/**
|
/**
|
||||||
* whether segment is overflow.
|
* whether segment is overflow.
|
||||||
*/
|
*/
|
||||||
|
@ -131,32 +93,29 @@ public:
|
||||||
* @remark ignore when already open.
|
* @remark ignore when already open.
|
||||||
* @param use_tmp_file whether use tmp file if possible.
|
* @param use_tmp_file whether use tmp file if possible.
|
||||||
*/
|
*/
|
||||||
virtual int open(bool use_tmp_file = true);
|
virtual int open(bool use_tmp_file);
|
||||||
/**
|
/**
|
||||||
* close current segment.
|
* close current segment.
|
||||||
* @remark ignore when already closed.
|
* @remark ignore when already closed.
|
||||||
*/
|
*/
|
||||||
virtual int close();
|
virtual int close();
|
||||||
/**
|
protected:
|
||||||
* write the metadata to segment.
|
virtual int open_encoder() = 0;
|
||||||
*/
|
public:
|
||||||
virtual int write_metadata(SrsSharedPtrMessage* metadata);
|
// Write the metadata.
|
||||||
/**
|
virtual int write_metadata(SrsSharedPtrMessage* metadata) = 0;
|
||||||
* @param shared_audio, directly ptr, copy it if need to save it.
|
// Write audio packet.
|
||||||
*/
|
// @param shared_audio, directly ptr, copy it if need to save it.
|
||||||
virtual int write_audio(SrsSharedPtrMessage* shared_audio);
|
virtual int write_audio(SrsSharedPtrMessage* shared_audio) = 0;
|
||||||
/**
|
// Write video packet.
|
||||||
* @param shared_video, directly ptr, copy it if need to save it.
|
// @param shared_video, directly ptr, copy it if need to save it.
|
||||||
*/
|
virtual int write_video(SrsSharedPtrMessage* shared_video) = 0;
|
||||||
virtual int write_video(SrsSharedPtrMessage* shared_video);
|
// Refresh the metadata. For example, there is duration in flv metadata,
|
||||||
/**
|
// when DVR in append mode, the duration must be update every some seconds.
|
||||||
* update the flv metadata.
|
// @remark Maybe ignored by concreate segmenter.
|
||||||
*/
|
virtual int refresh_metadata() = 0;
|
||||||
virtual int update_flv_metadata();
|
protected:
|
||||||
/**
|
virtual int close_encoder() = 0;
|
||||||
* get the current dvr path.
|
|
||||||
*/
|
|
||||||
virtual std::string get_path();
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* generate the flv segment path.
|
* generate the flv segment path.
|
||||||
|
@ -164,18 +123,81 @@ private:
|
||||||
virtual std::string generate_path();
|
virtual std::string generate_path();
|
||||||
/**
|
/**
|
||||||
* create flv jitter. load jitter when flv exists.
|
* create flv jitter. load jitter when flv exists.
|
||||||
* @param loads_from_flv whether loads the jitter from exists flv file.
|
* @param target_exists whether loads the jitter from exists flv file.
|
||||||
*/
|
*/
|
||||||
virtual int create_jitter(bool loads_from_flv);
|
virtual int create_jitter(bool target_exists);
|
||||||
/**
|
|
||||||
* when update the duration of segment by rtmp msg.
|
|
||||||
*/
|
|
||||||
virtual int on_update_duration(SrsSharedPtrMessage* msg);
|
|
||||||
// interface ISrsReloadHandler
|
// interface ISrsReloadHandler
|
||||||
public:
|
public:
|
||||||
virtual int on_reload_vhost_dvr(std::string vhost);
|
virtual int on_reload_vhost_dvr(std::string vhost);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The FLV segmenter to use FLV encoder to write file.
|
||||||
|
*/
|
||||||
|
class SrsDvrFlvSegmenter : public SrsDvrSegmenter
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// The FLV encoder, for FLV target.
|
||||||
|
SrsFlvEncoder* enc;
|
||||||
|
private:
|
||||||
|
// The offset of file for duration value.
|
||||||
|
// The next 8 bytes is the double value.
|
||||||
|
int64_t duration_offset;
|
||||||
|
// The offset of file for filesize value.
|
||||||
|
// The next 8 bytes is the double value.
|
||||||
|
int64_t filesize_offset;
|
||||||
|
// Whether current segment has keyframe.
|
||||||
|
bool has_keyframe;
|
||||||
|
private:
|
||||||
|
// The current segment starttime in ms, RTMP pkt time.
|
||||||
|
int64_t starttime;
|
||||||
|
// The stream start time in ms, to generate atc pts. abs time.
|
||||||
|
int64_t stream_starttime;
|
||||||
|
// The stream duration in ms, to generate atc segment.
|
||||||
|
int64_t stream_duration;
|
||||||
|
/**
|
||||||
|
* The previous stream RTMP pkt time in ms, used to calc the duration.
|
||||||
|
* for the RTMP timestamp will overflow.
|
||||||
|
*/
|
||||||
|
// TODO: FIXME: Use utility object to calc it.
|
||||||
|
int64_t stream_previous_pkt_time;
|
||||||
|
public:
|
||||||
|
SrsDvrFlvSegmenter();
|
||||||
|
virtual ~SrsDvrFlvSegmenter();
|
||||||
|
protected:
|
||||||
|
virtual int open_encoder();
|
||||||
|
virtual int close_encoder();
|
||||||
|
private:
|
||||||
|
// When update the duration of segment by rtmp msg.
|
||||||
|
virtual int on_update_duration(SrsSharedPtrMessage* msg);
|
||||||
|
public:
|
||||||
|
virtual int write_metadata(SrsSharedPtrMessage* metadata);
|
||||||
|
virtual int write_audio(SrsSharedPtrMessage* shared_audio);
|
||||||
|
virtual int write_video(SrsSharedPtrMessage* shared_video);
|
||||||
|
virtual int refresh_metadata();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The MP4 segmenter to use MP4 encoder to write file.
|
||||||
|
*/
|
||||||
|
class SrsDvrMp4Segmenter : public SrsDvrSegmenter
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// The MP4 encoder, for MP4 target.
|
||||||
|
SrsMp4Encoder* enc;
|
||||||
|
public:
|
||||||
|
SrsDvrMp4Segmenter();
|
||||||
|
virtual ~SrsDvrMp4Segmenter();
|
||||||
|
protected:
|
||||||
|
virtual int open_encoder();
|
||||||
|
virtual int close_encoder();
|
||||||
|
public:
|
||||||
|
virtual int write_metadata(SrsSharedPtrMessage* metadata);
|
||||||
|
virtual int write_audio(SrsSharedPtrMessage* shared_audio);
|
||||||
|
virtual int write_video(SrsSharedPtrMessage* shared_video);
|
||||||
|
virtual int refresh_metadata();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the dvr async call.
|
* the dvr async call.
|
||||||
*/
|
*/
|
||||||
|
@ -202,19 +224,17 @@ public:
|
||||||
// TODO: FIXME: the plan is too fat, refine me.
|
// TODO: FIXME: the plan is too fat, refine me.
|
||||||
class SrsDvrPlan
|
class SrsDvrPlan
|
||||||
{
|
{
|
||||||
public:
|
|
||||||
friend class SrsFlvSegment;
|
|
||||||
public:
|
public:
|
||||||
SrsRequest* req;
|
SrsRequest* req;
|
||||||
protected:
|
protected:
|
||||||
SrsFlvSegment* segment;
|
SrsDvrSegmenter* segment;
|
||||||
SrsAsyncCallWorker* async;
|
SrsAsyncCallWorker* async;
|
||||||
bool dvr_enabled;
|
bool dvr_enabled;
|
||||||
public:
|
public:
|
||||||
SrsDvrPlan();
|
SrsDvrPlan();
|
||||||
virtual ~SrsDvrPlan();
|
virtual ~SrsDvrPlan();
|
||||||
public:
|
public:
|
||||||
virtual int initialize(SrsRequest* r);
|
virtual int initialize(SrsDvrSegmenter* s, SrsRequest* r);
|
||||||
virtual int on_publish() = 0;
|
virtual int on_publish() = 0;
|
||||||
virtual void on_unpublish() = 0;
|
virtual void on_unpublish() = 0;
|
||||||
/**
|
/**
|
||||||
|
@ -229,12 +249,16 @@ public:
|
||||||
* @param shared_video, directly ptr, copy it if need to save it.
|
* @param shared_video, directly ptr, copy it if need to save it.
|
||||||
*/
|
*/
|
||||||
virtual int on_video(SrsSharedPtrMessage* shared_video);
|
virtual int on_video(SrsSharedPtrMessage* shared_video);
|
||||||
protected:
|
// Internal interface for segmenter.
|
||||||
|
public:
|
||||||
|
// When segmenter close a segment.
|
||||||
virtual int on_reap_segment();
|
virtual int on_reap_segment();
|
||||||
|
// When segmenter got a keyframe.
|
||||||
virtual int on_video_keyframe();
|
virtual int on_video_keyframe();
|
||||||
|
// The plan may need to process the timestamp.
|
||||||
virtual int64_t filter_timestamp(int64_t timestamp);
|
virtual int64_t filter_timestamp(int64_t timestamp);
|
||||||
public:
|
public:
|
||||||
static SrsDvrPlan* create_plan(std::string vhost);
|
static int create_plan(std::string vhost, SrsDvrPlan** pplan);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -284,7 +308,7 @@ public:
|
||||||
SrsDvrSegmentPlan();
|
SrsDvrSegmentPlan();
|
||||||
virtual ~SrsDvrSegmentPlan();
|
virtual ~SrsDvrSegmentPlan();
|
||||||
public:
|
public:
|
||||||
virtual int initialize(SrsRequest* req);
|
virtual int initialize(SrsDvrSegmenter* s, SrsRequest* req);
|
||||||
virtual int on_publish();
|
virtual int on_publish();
|
||||||
virtual void on_unpublish();
|
virtual void on_unpublish();
|
||||||
virtual int on_meta_data(SrsSharedPtrMessage* shared_metadata);
|
virtual int on_meta_data(SrsSharedPtrMessage* shared_metadata);
|
||||||
|
@ -295,9 +319,9 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dvr(digital video recorder) to record RTMP stream to flv file.
|
* DVR(Digital Video Recorder) to record RTMP stream to flv/mp4 file.
|
||||||
* TODO: FIXME: add utest for it.
|
* TODO: FIXME: add utest for it.
|
||||||
*/
|
*/
|
||||||
class SrsDvr : public ISrsReloadHandler
|
class SrsDvr : public ISrsReloadHandler
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -213,7 +213,9 @@ void SrsEdgeIngester::stop()
|
||||||
upstream->close();
|
upstream->close();
|
||||||
|
|
||||||
// notice to unpublish.
|
// notice to unpublish.
|
||||||
source->on_unpublish();
|
if (source) {
|
||||||
|
source->on_unpublish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string SrsEdgeIngester::get_curr_origin()
|
string SrsEdgeIngester::get_curr_origin()
|
||||||
|
|
|
@ -35,7 +35,6 @@ class SrsHttpUri;
|
||||||
class SrsStSocket;
|
class SrsStSocket;
|
||||||
class SrsRequest;
|
class SrsRequest;
|
||||||
class SrsHttpParser;
|
class SrsHttpParser;
|
||||||
class SrsFlvSegment;
|
|
||||||
class SrsHttpClient;
|
class SrsHttpClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -255,6 +255,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
#define ERROR_MP4_MOOV_OVERFLOW 3079
|
#define ERROR_MP4_MOOV_OVERFLOW 3079
|
||||||
#define ERROR_MP4_ILLEGAL_SAMPLES 3080
|
#define ERROR_MP4_ILLEGAL_SAMPLES 3080
|
||||||
#define ERROR_MP4_ILLEGAL_TIMESTAMP 3081
|
#define ERROR_MP4_ILLEGAL_TIMESTAMP 3081
|
||||||
|
#define ERROR_DVR_CANNOT_APPEND 3082
|
||||||
|
#define ERROR_DVR_ILLEGAL_PLAN 3083
|
||||||
|
|
||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
// HTTP/StreamCaster/KAFKA protocol error.
|
// HTTP/StreamCaster/KAFKA protocol error.
|
||||||
|
|
|
@ -3783,3 +3783,18 @@ int SrsMp4Decoder::do_load_next_box(SrsMp4Box** ppbox, uint32_t required_box_typ
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SrsMp4Encoder::SrsMp4Encoder()
|
||||||
|
{
|
||||||
|
writer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SrsMp4Encoder::~SrsMp4Encoder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int SrsMp4Encoder::initialize(ISrsWriter* w)
|
||||||
|
{
|
||||||
|
writer = w;
|
||||||
|
return ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
class ISrsWriter;
|
||||||
class ISrsReadSeeker;
|
class ISrsReadSeeker;
|
||||||
class SrsMp4TrackBox;
|
class SrsMp4TrackBox;
|
||||||
class SrsMp4MediaBox;
|
class SrsMp4MediaBox;
|
||||||
|
@ -1538,8 +1539,7 @@ public:
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Initialize the decoder with a reader r.
|
* Initialize the decoder with a reader r.
|
||||||
* @param r The underlayer io reader, user must manage it for decoder never open/free it,
|
* @param r The underlayer io reader, user must manage it.
|
||||||
* the decoder just read data from the reader.
|
|
||||||
*/
|
*/
|
||||||
virtual int initialize(ISrsReadSeeker* rs);
|
virtual int initialize(ISrsReadSeeker* rs);
|
||||||
/**
|
/**
|
||||||
|
@ -1567,5 +1567,23 @@ private:
|
||||||
virtual int do_load_next_box(SrsMp4Box** ppbox, uint32_t required_box_type);
|
virtual int do_load_next_box(SrsMp4Box** ppbox, uint32_t required_box_type);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The MP4 muxer.
|
||||||
|
*/
|
||||||
|
class SrsMp4Encoder
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
ISrsWriter* writer;
|
||||||
|
public:
|
||||||
|
SrsMp4Encoder();
|
||||||
|
virtual ~SrsMp4Encoder();
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Initialize the encoder with a writer w.
|
||||||
|
* @param w The underlayer io writer, user must manage it.
|
||||||
|
*/
|
||||||
|
virtual int initialize(ISrsWriter* w);
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue