For #299, extract fragment and fragment window for hls

pull/816/head
winlin 8 years ago
parent 6da6e0511d
commit da4c390d69

2
trunk/configure vendored

@ -187,7 +187,7 @@ if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
"srs_app_recv_thread" "srs_app_security" "srs_app_statistic" "srs_app_hds"
"srs_app_mpegts_udp" "srs_app_rtsp" "srs_app_listener" "srs_app_async_call"
"srs_app_caster_flv" "srs_app_process" "srs_app_ng_exec" "srs_app_kafka"
"srs_app_hourglass" "srs_app_dash")
"srs_app_hourglass" "srs_app_dash" "srs_app_fragment")
DEFINES=""
# add each modules for app
for SRS_MODULE in ${SRS_MODULES[*]}; do

@ -84,6 +84,7 @@
3C36DB5D1ABD1CB90066CCAF /* srs_librtmp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C36DB591ABD1CB90066CCAF /* srs_librtmp.cpp */; };
3C44AACF1E3AF50200D4ABC3 /* srs_kernel_mp4.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C44AACD1E3AF50200D4ABC3 /* srs_kernel_mp4.cpp */; };
3C4AB9331B8C9148006627D3 /* srs_app_ng_exec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C4AB9311B8C9148006627D3 /* srs_app_ng_exec.cpp */; };
3C4D184C1E73F133008806F7 /* srs_app_fragment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C4D184A1E73F133008806F7 /* srs_app_fragment.cpp */; };
3C4F97121B8B466D00FF0E46 /* srs_app_process.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C4F97101B8B466D00FF0E46 /* srs_app_process.cpp */; };
3C5265B41B241BF0009CA186 /* srs_core_mem_watch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C5265B21B241BF0009CA186 /* srs_core_mem_watch.cpp */; };
3C663F0F1AB0155100286D8B /* srs_aac_raw_publish.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F021AB0155100286D8B /* srs_aac_raw_publish.c */; };
@ -352,6 +353,8 @@
3C4AB9311B8C9148006627D3 /* srs_app_ng_exec.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_ng_exec.cpp; path = ../../../src/app/srs_app_ng_exec.cpp; sourceTree = "<group>"; };
3C4AB9321B8C9148006627D3 /* srs_app_ng_exec.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_ng_exec.hpp; path = ../../../src/app/srs_app_ng_exec.hpp; sourceTree = "<group>"; };
3C4AB9341B8C9FF9006627D3 /* exec.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = exec.conf; path = ../../../conf/exec.conf; sourceTree = "<group>"; };
3C4D184A1E73F133008806F7 /* srs_app_fragment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_fragment.cpp; path = ../../../src/app/srs_app_fragment.cpp; sourceTree = "<group>"; };
3C4D184B1E73F133008806F7 /* srs_app_fragment.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_fragment.hpp; path = ../../../src/app/srs_app_fragment.hpp; sourceTree = "<group>"; };
3C4F97101B8B466D00FF0E46 /* srs_app_process.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_process.cpp; path = ../../../src/app/srs_app_process.cpp; sourceTree = "<group>"; };
3C4F97111B8B466D00FF0E46 /* srs_app_process.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_process.hpp; path = ../../../src/app/srs_app_process.hpp; sourceTree = "<group>"; };
3C5265B21B241BF0009CA186 /* srs_core_mem_watch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_core_mem_watch.cpp; path = ../../../src/core/srs_core_mem_watch.cpp; sourceTree = "<group>"; };
@ -609,6 +612,8 @@
3C12325B1AAE81D900CE8F6C /* srs_app_ffmpeg.hpp */,
3C12325C1AAE81D900CE8F6C /* srs_app_forward.cpp */,
3C12325D1AAE81D900CE8F6C /* srs_app_forward.hpp */,
3C4D184A1E73F133008806F7 /* srs_app_fragment.cpp */,
3C4D184B1E73F133008806F7 /* srs_app_fragment.hpp */,
3C1EE6AC1AB1055800576EE9 /* srs_app_hds.cpp */,
3C1EE6AD1AB1055800576EE9 /* srs_app_hds.hpp */,
3C12325E1AAE81D900CE8F6C /* srs_app_heartbeat.cpp */,
@ -1025,6 +1030,7 @@
3CA432AB1E40AEBC001DA0C6 /* Makefile in Sources */,
3C1232B21AAE81D900CE8F6C /* srs_app_source.cpp in Sources */,
3C1231F71AAE652D00CE8F6C /* srs_core_performance.cpp in Sources */,
3C4D184C1E73F133008806F7 /* srs_app_fragment.cpp in Sources */,
3CC52DD81ACE4023006FEB01 /* srs_utest_amf0.cpp in Sources */,
3C4F97121B8B466D00FF0E46 /* srs_app_process.cpp in Sources */,
3C1232981AAE81D900CE8F6C /* srs_app_edge.cpp in Sources */,

@ -158,6 +158,13 @@ SrsDashController::SrsDashController()
SrsDashController::~SrsDashController()
{
srs_freep(mpd);
vector<SrsFragmentedMp4*>::iterator it;
for (it = fragments.begin(); it != fragments.end(); ++it) {
SrsFragmentedMp4* fragment = *it;
srs_freep(fragment);
}
fragments.clear();
}
int SrsDashController::initialize(SrsRequest* r)

@ -30,6 +30,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <srs_core.hpp>
#include <string>
#include <vector>
#include <srs_app_fragment.hpp>
class SrsRequest;
class SrsOriginHub;
@ -39,7 +42,7 @@ class SrsFormat;
/**
* The FMP4(Fragmented MP4) for DASH streaming.
*/
class SrsFragmentedMp4
class SrsFragmentedMp4 : public SrsFragment
{
public:
SrsFragmentedMp4();
@ -82,6 +85,7 @@ class SrsDashController
private:
SrsRequest* req;
SrsMpdWriter* mpd;
std::vector<SrsFragmentedMp4*> fragments;
private:
std::string home;
int video_tack_id;

@ -0,0 +1,252 @@
/*
The MIT License (MIT)
Copyright (c) 2013-2017 SRS(ossrs)
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 <srs_app_fragment.hpp>
#include <srs_kernel_utility.hpp>
#include <srs_kernel_log.hpp>
#include <srs_kernel_error.hpp>
#include <unistd.h>
using namespace std;
SrsFragment::SrsFragment()
{
dur = 0;
start_dts = -1;
sequence_header = false;
}
SrsFragment::~SrsFragment()
{
}
void SrsFragment::append(int64_t dts)
{
if (start_dts == -1) {
start_dts = dts;
}
// TODO: FIXME: Use cumulus dts.
start_dts = srs_min(start_dts, dts);
dur = dts - start_dts;
}
int64_t SrsFragment::duration()
{
return dur;
}
bool SrsFragment::is_sequence_header()
{
return sequence_header;
}
void SrsFragment::set_sequence_header(bool v)
{
sequence_header = v;
}
string SrsFragment::fullpath()
{
return filepath;
}
void SrsFragment::set_path(string v)
{
filepath = v;
}
int SrsFragment::unlink_file()
{
int ret = ERROR_SUCCESS;
if (::unlink(filepath.c_str()) < 0) {
ret = ERROR_SYSTEM_FRAGMENT_UNLINK;
srs_error("Unlink fragment failed, file=%s, ret=%d.", filepath.c_str(), ret);
return ret;
}
return ret;
}
string SrsFragment::tmppath()
{
return filepath + ".tmp";
}
int SrsFragment::unlink_tmpfile()
{
int ret = ERROR_SUCCESS;
string filepath = tmppath();
if (::unlink(filepath.c_str()) < 0) {
ret = ERROR_SYSTEM_FRAGMENT_UNLINK;
srs_error("Unlink temporary fragment failed, file=%s, ret=%d.", filepath.c_str(), ret);
return ret;
}
return ret;
}
int SrsFragment::rename()
{
int ret = ERROR_SUCCESS;
string full_path = fullpath();
string tmp_file = tmppath();
if (::rename(tmp_file.c_str(), full_path.c_str()) < 0) {
ret = ERROR_SYSTEM_FRAGMENT_RENAME;
srs_error("rename ts file failed, %s => %s. ret=%d", tmp_file.c_str(), full_path.c_str(), ret);
return ret;
}
return ret;
}
SrsFragmentWindow::SrsFragmentWindow()
{
}
SrsFragmentWindow::~SrsFragmentWindow()
{
vector<SrsFragment*>::iterator it;
for (it = fragments.begin(); it != fragments.end(); ++it) {
SrsFragment* fragment = *it;
srs_freep(fragment);
}
fragments.clear();
for (it = expired_fragments.begin(); it != expired_fragments.end(); ++it) {
SrsFragment* fragment = *it;
srs_freep(fragment);
}
expired_fragments.clear();
}
void SrsFragmentWindow::dispose()
{
int ret = ERROR_SUCCESS;
std::vector<SrsFragment*>::iterator it;
for (it = fragments.begin(); it != fragments.end(); ++it) {
SrsFragment* fragment = *it;
if ((ret = fragment->unlink_file()) != ERROR_SUCCESS) {
srs_warn("Unlink ts failed, file=%s, ret=%d", fragment->fullpath().c_str(), ret);
}
srs_freep(fragment);
}
fragments.clear();
for (it = expired_fragments.begin(); it != expired_fragments.end(); ++it) {
SrsFragment* fragment = *it;
if ((ret = fragment->unlink_file()) != ERROR_SUCCESS) {
srs_warn("Unlink ts failed, file=%s, ret=%d", fragment->fullpath().c_str(), ret);
}
srs_freep(fragment);
}
expired_fragments.clear();
}
void SrsFragmentWindow::append(SrsFragment* fragment)
{
fragments.push_back(fragment);
}
void SrsFragmentWindow::shrink(int64_t window)
{
int64_t duration = 0;
int remove_index = -1;
for (int i = (int)fragments.size() - 1; i >= 0; i--) {
SrsFragment* fragment = fragments[i];
duration += fragment->duration();
if (duration > window) {
remove_index = i;
break;
}
}
for (int i = 0; i < remove_index && !fragments.empty(); i++) {
SrsFragment* fragment = *fragments.begin();
fragments.erase(fragments.begin());
expired_fragments.push_back(fragment);
}
}
void SrsFragmentWindow::clear_expired(bool delete_files)
{
int ret = ERROR_SUCCESS;
std::vector<SrsFragment*>::iterator it;
for (it = expired_fragments.begin(); it != expired_fragments.end(); ++it) {
SrsFragment* fragment = *it;
if (delete_files && (ret = fragment->unlink_file()) != ERROR_SUCCESS) {
srs_warn("Unlink ts failed, file=%s, ret=%d", fragment->fullpath().c_str(), ret);
}
srs_freep(fragment);
}
expired_fragments.clear();
}
int64_t SrsFragmentWindow::max_duration()
{
int64_t v = 0;
std::vector<SrsFragment*>::iterator it;
for (it = fragments.begin(); it != fragments.end(); ++it) {
SrsFragment* fragment = *it;
v = srs_max(v, fragment->duration());
}
return v;
}
bool SrsFragmentWindow::empty()
{
return fragments.empty();
}
SrsFragment* SrsFragmentWindow::first()
{
return fragments.at(0);
}
int SrsFragmentWindow::size()
{
return (int)fragments.size();
}
SrsFragment* SrsFragmentWindow::at(int index)
{
return fragments.at(index);
}

@ -0,0 +1,110 @@
/*
The MIT License (MIT)
Copyright (c) 2013-2017 SRS(ossrs)
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_FRAGMENT_HPP
#define SRS_APP_FRAGMENT_HPP
/*
#include <srs_app_fragment.hpp>
*/
#include <srs_core.hpp>
#include <string>
#include <vector>
/**
* Represent a fragment, such as HLS segment, DVR segment or DASH segment.
* It's a media file, for example FLV or MP4, with duration.
*/
class SrsFragment
{
private:
// The duration in ms.
int64_t dur;
// The full file path of fragment.
std::string filepath;
// The start DTS in ms of segment.
int64_t start_dts;
// Whether current segement contains sequence header.
bool sequence_header;
public:
SrsFragment();
virtual ~SrsFragment();
public:
// Append a frame with dts into fragment.
// @dts The dts of frame in ms.
virtual void append(int64_t dts);
// Get the duration of fragment in ms.
virtual int64_t duration();
// Whether the fragment contains any sequence header.
virtual bool is_sequence_header();
// Set whether contains sequence header.
virtual void set_sequence_header(bool v);
// Get the full path of fragment.
virtual std::string fullpath();
// Set the full path of fragment.
virtual void set_path(std::string v);
// Unlink the fragment, to delete the file.
// @remark Ignore any error.
virtual int unlink_file();
public:
// Get the temporary path for file.
virtual std::string tmppath();
// Unlink the temporary file.
virtual int unlink_tmpfile();
// Rename the temp file to final file.
virtual int rename();
};
/**
* The fragment window manage a series of fragment.
*/
class SrsFragmentWindow
{
private:
std::vector<SrsFragment*> fragments;
// The expired fragments, need to be free in future.
std::vector<SrsFragment*> expired_fragments;
public:
SrsFragmentWindow();
virtual ~SrsFragmentWindow();
public:
// Dispose all fragments, delete the files.
virtual void dispose();
// Append a new fragment, which is ready to delivery to client.
virtual void append(SrsFragment* fragment);
// Shrink the window, push the expired fragment to a queue.
virtual void shrink(int64_t window);
// Clear the expired fragments.
virtual void clear_expired(bool delete_files);
// Get the max duration in ms of all fragments.
virtual int64_t max_duration();
public:
virtual bool empty();
virtual SrsFragment* first();
virtual int size();
virtual SrsFragment* at(int index);
};
#endif

@ -54,8 +54,6 @@ using namespace std;
// drop the segment when duration of ts too small.
#define SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS 100
// when hls timestamp jump, reset it.
#define SRS_AUTO_HLS_SEGMENT_TIMESTAMP_JUMP_MS 300
// fragment plus the deviation percent.
#define SRS_HLS_FLOOR_REAP_PERCENT 0.3
@ -64,10 +62,7 @@ using namespace std;
SrsHlsSegment::SrsHlsSegment(SrsTsContext* c, SrsAudioCodecId ac, SrsVideoCodecId vc)
{
duration = 0;
sequence_no = 0;
segment_start_dts = 0;
is_sequence_header = false;
writer = new SrsFileWriter();
tscw = new SrsTsContextWriter(writer, c, ac, vc);
}
@ -78,27 +73,6 @@ SrsHlsSegment::~SrsHlsSegment()
srs_freep(writer);
}
void SrsHlsSegment::update_duration(int64_t current_frame_dts)
{
// we use video/audio to update segment duration,
// so when reap segment, some previous audio frame will
// update the segment duration, which is nagetive,
// just ignore it.
if (current_frame_dts < segment_start_dts) {
// for atc and timestamp jump, reset the start dts.
if (current_frame_dts < segment_start_dts - SRS_AUTO_HLS_SEGMENT_TIMESTAMP_JUMP_MS * 90) {
srs_warn("hls timestamp jump %"PRId64"=>%"PRId64, segment_start_dts, current_frame_dts);
segment_start_dts = current_frame_dts;
}
return;
}
duration = (current_frame_dts - segment_start_dts) / 90000.0;
srs_assert(duration >= 0);
return;
}
SrsDvrAsyncCallOnHls::SrsDvrAsyncCallOnHls(int c, SrsRequest* r, string p, string t, string m, string mu, int s, double d)
{
req = r->copy();
@ -225,17 +199,11 @@ SrsHlsMuxer::SrsHlsMuxer()
current = NULL;
async = new SrsAsyncCallWorker();
context = new SrsTsContext();
segments = new SrsFragmentWindow();
}
SrsHlsMuxer::~SrsHlsMuxer()
{
std::vector<SrsHlsSegment*>::iterator it;
for (it = segments.begin(); it != segments.end(); ++it) {
SrsHlsSegment* segment = *it;
srs_freep(segment);
}
segments.clear();
srs_freep(current);
srs_freep(req);
srs_freep(async);
@ -244,20 +212,13 @@ SrsHlsMuxer::~SrsHlsMuxer()
void SrsHlsMuxer::dispose()
{
std::vector<SrsHlsSegment*>::iterator it;
for (it = segments.begin(); it != segments.end(); ++it) {
SrsHlsSegment* segment = *it;
if (unlink(segment->full_path.c_str()) < 0) {
srs_warn("dispose unlink path failed, file=%s.", segment->full_path.c_str());
}
srs_freep(segment);
}
segments.clear();
int ret = ERROR_SUCCESS;
segments->dispose();
if (current) {
std::string path = current->full_path + ".tmp";
if (unlink(path.c_str()) < 0) {
srs_warn("dispose unlink path failed, file=%s", path.c_str());
if ((ret = current->unlink_tmpfile()) != ERROR_SUCCESS) {
srs_warn("Unlink tmp ts failed, ret=%d", ret);
}
srs_freep(current);
}
@ -266,8 +227,6 @@ void SrsHlsMuxer::dispose()
srs_warn("dispose unlink path failed. file=%s", m3u8.c_str());
}
// TODO: FIXME: support hls dispose in HTTP cache.
srs_trace("gracefully dispose hls %s", req? req->get_stream_url().c_str() : "");
}
@ -283,7 +242,7 @@ string SrsHlsMuxer::ts_url()
double SrsHlsMuxer::duration()
{
return current? current->duration:0;
return current? current->duration()/1000.0:0;
}
int SrsHlsMuxer::deviation()
@ -347,7 +306,7 @@ int SrsHlsMuxer::update_config(SrsRequest* r, string entry_prefix,
return ret;
}
int SrsHlsMuxer::segment_open(int64_t segment_start_dts)
int SrsHlsMuxer::segment_open()
{
int ret = ERROR_SUCCESS;
@ -395,7 +354,6 @@ int SrsHlsMuxer::segment_open(int64_t segment_start_dts)
// new segment.
current = new SrsHlsSegment(context, default_acodec, default_vcodec);
current->sequence_no = _sequence_no++;
current->segment_start_dts = segment_start_dts;
// generate filename.
std::string ts_file = hls_ts_file;
@ -440,11 +398,12 @@ int SrsHlsMuxer::segment_open(int64_t segment_start_dts)
ss << current->sequence_no;
ts_file = srs_string_replace(ts_file, "[seq]", ss.str());
}
current->full_path = hls_path + "/" + ts_file;
current->set_path(hls_path + "/" + ts_file);
srs_info("hls: generate ts path %s, tmpl=%s, floor=%d", ts_file.c_str(), hls_ts_file.c_str(), hls_ts_floor);
// the ts url, relative or absolute url.
std::string ts_url = current->full_path;
// TODO: FIXME: Use url and path manager.
std::string ts_url = current->fullpath();
if (srs_string_starts_with(ts_url, m3u8_dir)) {
ts_url = ts_url.substr(m3u8_dir.length());
}
@ -464,7 +423,7 @@ int SrsHlsMuxer::segment_open(int64_t segment_start_dts)
current->uri += ts_url;
// create dir recursively for hls.
std::string ts_dir = srs_path_dirname(current->full_path);
std::string ts_dir = srs_path_dirname(current->fullpath());
if ((ret = srs_create_dir_recursively(ts_dir)) != ERROR_SUCCESS) {
srs_error("create app dir %s failed. ret=%d", ts_dir.c_str(), ret);
return ret;
@ -472,7 +431,7 @@ int SrsHlsMuxer::segment_open(int64_t segment_start_dts)
srs_info("create ts dir %s ok", ts_dir.c_str());
// open temp ts file.
std::string tmp_file = current->full_path + ".tmp";
std::string tmp_file = current->tmppath();
if ((ret = current->tscw->open(tmp_file.c_str())) != ERROR_SUCCESS) {
srs_error("open hls muxer failed. ret=%d", ret);
return ret;
@ -490,7 +449,7 @@ int SrsHlsMuxer::on_sequence_header()
// set the current segment to sequence header,
// when close the segement, it will write a discontinuity to m3u8 file.
current->is_sequence_header = true;
current->set_sequence_header(true);
return ret;
}
@ -500,7 +459,7 @@ bool SrsHlsMuxer::is_segment_overflow()
srs_assert(current);
// to prevent very small segment.
if (current->duration * 1000 < 2 * SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) {
if (current->duration() < 2 * SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) {
return false;
}
@ -509,7 +468,7 @@ bool SrsHlsMuxer::is_segment_overflow()
srs_info("hls: dur=%.2f, tar=%.2f, dev=%.2fms/%dp, frag=%.2f",
current->duration, hls_fragment + deviation, deviation, deviation_ts, hls_fragment);
return current->duration >= hls_fragment + deviation;
return current->duration() >= (hls_fragment + deviation) * 1000;
}
bool SrsHlsMuxer::wait_keyframe()
@ -523,7 +482,7 @@ bool SrsHlsMuxer::is_segment_absolutely_overflow()
srs_assert(current);
// to prevent very small segment.
if (current->duration * 1000 < 2 * SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) {
if (current->duration() < 2 * SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) {
return false;
}
@ -532,7 +491,7 @@ bool SrsHlsMuxer::is_segment_absolutely_overflow()
srs_info("hls: dur=%.2f, tar=%.2f, dev=%.2fms/%dp, frag=%.2f",
current->duration, hls_fragment + deviation, deviation, deviation_ts, hls_fragment);
return current->duration >= hls_aof_ratio * hls_fragment + deviation;
return current->duration() >= (hls_aof_ratio * hls_fragment + deviation) * 1000;
}
bool SrsHlsMuxer::pure_audio()
@ -555,7 +514,7 @@ int SrsHlsMuxer::flush_audio(SrsTsMessageCache* cache)
}
// update the duration of segment.
current->update_duration(cache->audio->pts);
current->append(cache->audio->pts / 90);
if ((ret = current->tscw->write_audio(cache->audio)) != ERROR_SUCCESS) {
return ret;
@ -584,7 +543,7 @@ int SrsHlsMuxer::flush_video(SrsTsMessageCache* cache)
srs_assert(current);
// update the duration of segment.
current->update_duration(cache->video->dts);
current->append(cache->video->dts / 90);
if ((ret = current->tscw->write_video(cache->video)) != ERROR_SUCCESS) {
return ret;
@ -596,7 +555,7 @@ int SrsHlsMuxer::flush_video(SrsTsMessageCache* cache)
return ret;
}
int SrsHlsMuxer::segment_close(string log_desc)
int SrsHlsMuxer::segment_close()
{
int ret = ERROR_SUCCESS;
@ -608,23 +567,16 @@ int SrsHlsMuxer::segment_close(string log_desc)
// when close current segment, the current segment must not be NULL.
srs_assert(current);
// assert segment duplicate.
std::vector<SrsHlsSegment*>::iterator it;
it = std::find(segments.begin(), segments.end(), current);
srs_assert(it == segments.end());
// valid, add to segments if segment duration is ok
// when too small, it maybe not enough data to play.
// when too large, it maybe timestamp corrupt.
// make the segment more acceptable, when in [min, max_td * 2], it's ok.
if (current->duration * 1000 >= SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS && (int)current->duration <= max_td * 2) {
segments.push_back(current);
if (current->duration() >= SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS && (int)current->duration() <= max_td * 2 * 1000) {
// use async to call the http hooks, for it will cause thread switch.
if ((ret = async->execute(new SrsDvrAsyncCallOnHls(
_srs_context->get_id(), req,
current->full_path, current->uri, m3u8, m3u8_url,
current->sequence_no, current->duration))) != ERROR_SUCCESS)
current->fullpath(), current->uri, m3u8, m3u8_url,
current->sequence_no, current->duration() / 1000.0))) != ERROR_SUCCESS)
{
return ret;
}
@ -640,69 +592,35 @@ int SrsHlsMuxer::segment_close(string log_desc)
// close the muxer of finished segment.
srs_freep(current->tscw);
std::string full_path = current->full_path;
current = NULL;
// rename from tmp to real path
std::string tmp_file = full_path + ".tmp";
if (rename(tmp_file.c_str(), full_path.c_str()) < 0) {
ret = ERROR_HLS_WRITE_FAILED;
srs_error("rename ts file failed, %s => %s. ret=%d",
tmp_file.c_str(), full_path.c_str(), ret);
if ((ret = current->rename()) != ERROR_SUCCESS) {
return ret;
}
segments->append(current);
current = NULL;
} else {
// reuse current segment index.
_sequence_no--;
srs_trace("%s drop ts segment, sequence_no=%d, uri=%s, duration=%.2f, start=%"PRId64"",
log_desc.c_str(), current->sequence_no, current->uri.c_str(), current->duration,
current->segment_start_dts);
srs_trace("Drop ts segment, sequence_no=%d, uri=%s, duration=%dms", current->sequence_no, current->uri.c_str(), current->duration());
// rename from tmp to real path
std::string tmp_file = current->full_path + ".tmp";
if (unlink(tmp_file.c_str()) < 0) {
srs_warn("ignore unlink path failed, file=%s.", tmp_file.c_str());
if ((ret = current->unlink_tmpfile()) != ERROR_SUCCESS) {
return ret;
}
srs_freep(current);
}
// the segments to remove
std::vector<SrsHlsSegment*> segment_to_remove;
// shrink the segments.
double duration = 0;
int remove_index = -1;
for (int i = (int)segments.size() - 1; i >= 0; i--) {
SrsHlsSegment* segment = segments[i];
duration += segment->duration;
if ((int)duration > hls_window) {
remove_index = i;
break;
}
}
for (int i = 0; i < remove_index && !segments.empty(); i++) {
SrsHlsSegment* segment = *segments.begin();
segments.erase(segments.begin());
segment_to_remove.push_back(segment);
}
segments->shrink(hls_window * 1000);
// refresh the m3u8, donot contains the removed ts
ret = refresh_m3u8();
// remove the ts file.
for (int i = 0; i < (int)segment_to_remove.size(); i++) {
SrsHlsSegment* segment = segment_to_remove[i];
if (hls_cleanup && unlink(segment->full_path.c_str()) < 0) {
srs_warn("cleanup unlink path failed, file=%s.", segment->full_path.c_str());
}
srs_freep(segment);
}
segment_to_remove.clear();
segments->clear_expired(hls_cleanup);
// check ret of refresh m3u8
if (ret != ERROR_SUCCESS) {
@ -718,7 +636,7 @@ int SrsHlsMuxer::refresh_m3u8()
int ret = ERROR_SUCCESS;
// no segments, also no m3u8, return.
if (segments.size() == 0) {
if (segments->empty()) {
return ret;
}
@ -745,7 +663,7 @@ int SrsHlsMuxer::_refresh_m3u8(string m3u8_file)
int ret = ERROR_SUCCESS;
// no segments, return.
if (segments.size() == 0) {
if (segments->empty()) {
return ret;
}
@ -766,7 +684,7 @@ int SrsHlsMuxer::_refresh_m3u8(string m3u8_file)
srs_verbose("write m3u8 header success.");
// #EXT-X-MEDIA-SEQUENCE:4294967295\n
SrsHlsSegment* first = *segments.begin();
SrsHlsSegment* first = dynamic_cast<SrsHlsSegment*>(segments->first());
ss << "#EXT-X-MEDIA-SEQUENCE:" << first->sequence_no << SRS_CONSTS_LF;
srs_verbose("write m3u8 sequence success.");
@ -783,21 +701,17 @@ int SrsHlsMuxer::_refresh_m3u8(string m3u8_file)
* typical target duration is 10 seconds.
*/
// @see https://github.com/ossrs/srs/issues/304#issuecomment-74000081
int target_duration = 0;
for (it = segments.begin(); it != segments.end(); ++it) {
SrsHlsSegment* segment = *it;
target_duration = srs_max(target_duration, (int)ceil(segment->duration));
}
int target_duration = (int)ceil(segments->max_duration() / 1000.0);
target_duration = srs_max(target_duration, max_td);
ss << "#EXT-X-TARGETDURATION:" << target_duration << SRS_CONSTS_LF;
srs_verbose("write m3u8 duration success.");
// write all segments
for (it = segments.begin(); it != segments.end(); ++it) {
SrsHlsSegment* segment = *it;
for (int i = 0; i < segments->size(); i++) {
SrsHlsSegment* segment = dynamic_cast<SrsHlsSegment*>(segments->at(i));
if (segment->is_sequence_header) {
if (segment->is_sequence_header()) {
// #EXT-X-DISCONTINUITY\n
ss << "#EXT-X-DISCONTINUITY" << SRS_CONSTS_LF;
srs_verbose("write m3u8 segment discontinuity success.");
@ -806,7 +720,7 @@ int SrsHlsMuxer::_refresh_m3u8(string m3u8_file)
// "#EXTINF:4294967295.208,\n"
ss.precision(3);
ss.setf(std::ios::fixed, std::ios::floatfield);
ss << "#EXTINF:" << segment->duration << ", no desc" << SRS_CONSTS_LF;
ss << "#EXTINF:" << segment->duration() / 1000.0 << ", no desc" << SRS_CONSTS_LF;
srs_verbose("write m3u8 segment info success.");
// {file name}\n
@ -867,7 +781,7 @@ int SrsHlsController::deviation()
return muxer->deviation();
}
int SrsHlsController::on_publish(SrsRequest* req, int64_t segment_start_dts)
int SrsHlsController::on_publish(SrsRequest* req)
{
int ret = ERROR_SUCCESS;
@ -905,7 +819,7 @@ int SrsHlsController::on_publish(SrsRequest* req, int64_t segment_start_dts)
return ret;
}
if ((ret = muxer->segment_open(segment_start_dts)) != ERROR_SUCCESS) {
if ((ret = muxer->segment_open()) != ERROR_SUCCESS) {
srs_error("m3u8 muxer open segment failed. ret=%d", ret);
return ret;
}
@ -925,7 +839,7 @@ int SrsHlsController::on_unpublish()
return ret;
}
if ((ret = muxer->segment_close("unpublish")) != ERROR_SUCCESS) {
if ((ret = muxer->segment_close()) != ERROR_SUCCESS) {
return ret;
}
@ -962,7 +876,7 @@ int SrsHlsController::write_audio(SrsAudioFrame* frame, int64_t pts)
// @see https://github.com/ossrs/srs/issues/151#issuecomment-71155184
if (tsmc->audio && muxer->is_segment_absolutely_overflow()) {
srs_info("hls: absolute audio reap segment.");
if ((ret = reap_segment("audio", tsmc->audio->pts)) != ERROR_SUCCESS) {
if ((ret = reap_segment()) != ERROR_SUCCESS) {
return ret;
}
}
@ -1001,7 +915,7 @@ int SrsHlsController::write_video(SrsVideoFrame* frame, int64_t dts)
// b. always reap when not wait keyframe.
if (!muxer->wait_keyframe() || frame->frame_type == SrsVideoAvcFrameTypeKeyFrame) {
// reap the segment, which will also flush the video.
if ((ret = reap_segment("video", tsmc->video->dts)) != ERROR_SUCCESS) {
if ((ret = reap_segment()) != ERROR_SUCCESS) {
return ret;
}
}
@ -1016,7 +930,7 @@ int SrsHlsController::write_video(SrsVideoFrame* frame, int64_t dts)
return ret;
}
int SrsHlsController::reap_segment(string log_desc, int64_t segment_start_dts)
int SrsHlsController::reap_segment()
{
int ret = ERROR_SUCCESS;
@ -1024,13 +938,13 @@ int SrsHlsController::reap_segment(string log_desc, int64_t segment_start_dts)
// TODO: fresh segment begin with audio or video?
// close current ts.
if ((ret = muxer->segment_close(log_desc)) != ERROR_SUCCESS) {
if ((ret = muxer->segment_close()) != ERROR_SUCCESS) {
srs_error("m3u8 muxer close segment failed. ret=%d", ret);
return ret;
}
// open new ts.
if ((ret = muxer->segment_open(segment_start_dts)) != ERROR_SUCCESS) {
if ((ret = muxer->segment_open()) != ERROR_SUCCESS) {
srs_error("m3u8 muxer open segment failed. ret=%d", ret);
return ret;
}
@ -1065,7 +979,6 @@ SrsHls::SrsHls()
controller = new SrsHlsController();
pprint = SrsPithyPrint::create_hls();
stream_dts = 0;
}
SrsHls::~SrsHls()
@ -1148,7 +1061,7 @@ int SrsHls::on_publish()
return ret;
}
if ((ret = controller->on_publish(req, stream_dts)) != ERROR_SUCCESS) {
if ((ret = controller->on_publish(req)) != ERROR_SUCCESS) {
return ret;
}
@ -1213,9 +1126,6 @@ int SrsHls::on_audio(SrsSharedPtrMessage* shared_audio, SrsFormat* format)
// the dts calc from rtmp/flv header.
int64_t dts = audio->timestamp * 90;
// for pure audio, we need to update the stream dts also.
stream_dts = dts;
if ((ret = controller->write_audio(format->audio, dts)) != ERROR_SUCCESS) {
srs_error("hls cache write audio failed. ret=%d", ret);
return ret;
@ -1262,7 +1172,6 @@ int SrsHls::on_video(SrsSharedPtrMessage* shared_video, SrsFormat* format)
}
int64_t dts = video->timestamp * 90;
stream_dts = dts;
if ((ret = controller->write_video(format->video, dts)) != ERROR_SUCCESS) {
srs_error("hls cache write video failed. ret=%d", ret);
return ret;
@ -1285,8 +1194,8 @@ void SrsHls::hls_show_mux_log()
// the run time is not equals to stream time,
// @see: https://github.com/ossrs/srs/issues/81#issuecomment-48100994
// it's ok.
srs_trace("-> "SRS_CONSTS_LOG_HLS" time=%"PRId64", stream dts=%"PRId64"(%"PRId64"ms), sno=%d, ts=%s, dur=%.2f, dva=%dp",
pprint->age(), stream_dts, stream_dts / 90, controller->sequence_no(), controller->ts_url().c_str(),
srs_trace("-> "SRS_CONSTS_LOG_HLS" time=%"PRId64", sno=%d, ts=%s, dur=%.2f, dva=%dp",
pprint->age(), controller->sequence_no(), controller->ts_url().c_str(),
controller->duration(), controller->deviation());
}

@ -35,6 +35,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <srs_kernel_codec.hpp>
#include <srs_kernel_file.hpp>
#include <srs_app_async_call.hpp>
#include <srs_app_fragment.hpp>
class SrsFormat;
class SrsSharedPtrMessage;
@ -58,34 +59,20 @@ class SrsTsContext;
* 3.3.2. EXTINF
* The EXTINF tag specifies the duration of a media segment.
*/
class SrsHlsSegment
class SrsHlsSegment : public SrsFragment
{
public:
// duration in seconds in m3u8.
double duration;
// sequence number in m3u8.
int sequence_no;
// ts uri in m3u8.
std::string uri;
// ts full file to write.
std::string full_path;
// the underlayer file writer.
SrsFileWriter* writer;
// The TS context writer to write TS to file.
SrsTsContextWriter* tscw;
// current segment start dts for m3u8
int64_t segment_start_dts;
// whether current segement is sequence header.
bool is_sequence_header;
public:
SrsHlsSegment(SrsTsContext* c, SrsAudioCodecId ac, SrsVideoCodecId vc);
virtual ~SrsHlsSegment();
public:
/**
* update the segment duration.
* @current_frame_dts the dts of frame, in tbn of ts.
*/
virtual void update_duration(int64_t current_frame_dts);
};
/**
@ -103,6 +90,7 @@ private:
SrsRequest* req;
double duration;
public:
// TODO: FIXME: Use TBN 1000.
SrsDvrAsyncCallOnHls(int c, SrsRequest* r, std::string p, std::string t, std::string m, std::string mu, int s, double d);
virtual ~SrsDvrAsyncCallOnHls();
public:
@ -147,6 +135,7 @@ private:
bool hls_wait_keyframe;
std::string m3u8_dir;
double hls_aof_ratio;
// TODO: FIXME: Use TBN 1000.
double hls_fragment;
double hls_window;
SrsAsyncCallWorker* async;
@ -166,13 +155,9 @@ private:
std::string m3u8;
std::string m3u8_url;
private:
/**
* m3u8 segments.
*/
std::vector<SrsHlsSegment*> segments;
/**
* current writing segment.
*/
// The available cached segments in m3u8.
SrsFragmentWindow* segments;
// The current writing segment.
SrsHlsSegment* current;
/**
* the ts context, to keep cc continous between ts.
@ -202,11 +187,9 @@ public:
double fragment, double window, bool ts_floor, double aof_ratio,
bool cleanup, bool wait_keyframe);
/**
* open a new segment(a new ts file),
* @param segment_start_dts use to calc the segment duration,
* use 0 for the first segment of HLS.
* open a new segment(a new ts file)
*/
virtual int segment_open(int64_t segment_start_dts);
virtual int segment_open();
virtual int on_sequence_header();
/**
* whether segment overflow,
@ -231,10 +214,9 @@ public:
virtual int flush_audio(SrsTsMessageCache* cache);
virtual int flush_video(SrsTsMessageCache* cache);
/**
* close segment(ts).
* @param log_desc the description for log.
* Close segment(ts).
*/
virtual int segment_close(std::string log_desc);
virtual int segment_close();
private:
virtual int refresh_m3u8();
virtual int _refresh_m3u8(std::string m3u8_file);
@ -279,7 +261,7 @@ public:
/**
* when publish or unpublish stream.
*/
virtual int on_publish(SrsRequest* req, int64_t segment_start_dts);
virtual int on_publish(SrsRequest* req);
virtual int on_unpublish();
/**
* when get sequence header,
@ -303,7 +285,7 @@ private:
* then write the key frame to the new segment.
* so, user must reap_segment then flush_video to hls muxer.
*/
virtual int reap_segment(std::string log_desc, int64_t segment_start_dts);
virtual int reap_segment();
};
/**
@ -323,20 +305,6 @@ private:
SrsOriginHub* hub;
SrsRtmpJitter* jitter;
SrsPithyPrint* pprint;
/**
* we store the stream dts,
* for when we notice the hls cache to publish,
* it need to know the segment start dts.
*
* for example. when republish, the stream dts will
* monotonically increase, and the ts dts should start
* from current dts.
*
* or, simply because the HlsCache never free when unpublish,
* so when publish or republish it must start at stream dts,
* not zero dts.
*/
int64_t stream_dts;
public:
SrsHls();
virtual ~SrsHls();

@ -105,6 +105,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#define ERROR_SYSTEM_FILE_NOT_EXISTS 1064
#define ERROR_SYSTEM_HOURGLASS_RESOLUTION 1065
#define ERROR_SYSTEM_DNS_RESOLVE 1066
#define ERROR_SYSTEM_FRAGMENT_UNLINK 1067
#define ERROR_SYSTEM_FRAGMENT_RENAME 1068
///////////////////////////////////////////////////////
// RTMP protocol error.

Loading…
Cancel
Save