diff --git a/trunk/configure b/trunk/configure index e4a2060e8..3c5ea50a3 100755 --- a/trunk/configure +++ b/trunk/configure @@ -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 diff --git a/trunk/ide/srs_xcode/srs_xcode.xcodeproj/project.pbxproj b/trunk/ide/srs_xcode/srs_xcode.xcodeproj/project.pbxproj index 15eb8186d..4825714be 100644 --- a/trunk/ide/srs_xcode/srs_xcode.xcodeproj/project.pbxproj +++ b/trunk/ide/srs_xcode/srs_xcode.xcodeproj/project.pbxproj @@ -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 = ""; }; 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 = ""; }; 3C4AB9341B8C9FF9006627D3 /* exec.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = exec.conf; path = ../../../conf/exec.conf; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; 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 = ""; }; 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 = ""; }; 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 = ""; }; @@ -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 */, diff --git a/trunk/src/app/srs_app_dash.cpp b/trunk/src/app/srs_app_dash.cpp index a87720529..f12f0e958 100644 --- a/trunk/src/app/srs_app_dash.cpp +++ b/trunk/src/app/srs_app_dash.cpp @@ -158,6 +158,13 @@ SrsDashController::SrsDashController() SrsDashController::~SrsDashController() { srs_freep(mpd); + + vector::iterator it; + for (it = fragments.begin(); it != fragments.end(); ++it) { + SrsFragmentedMp4* fragment = *it; + srs_freep(fragment); + } + fragments.clear(); } int SrsDashController::initialize(SrsRequest* r) diff --git a/trunk/src/app/srs_app_dash.hpp b/trunk/src/app/srs_app_dash.hpp index d76879722..cda6f6520 100644 --- a/trunk/src/app/srs_app_dash.hpp +++ b/trunk/src/app/srs_app_dash.hpp @@ -30,6 +30,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#include + +#include 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 fragments; private: std::string home; int video_tack_id; diff --git a/trunk/src/app/srs_app_fragment.cpp b/trunk/src/app/srs_app_fragment.cpp new file mode 100644 index 000000000..b2225709a --- /dev/null +++ b/trunk/src/app/srs_app_fragment.cpp @@ -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 + +#include +#include +#include + +#include +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::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::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::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::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); +} + diff --git a/trunk/src/app/srs_app_fragment.hpp b/trunk/src/app/srs_app_fragment.hpp new file mode 100644 index 000000000..020d2fd14 --- /dev/null +++ b/trunk/src/app/srs_app_fragment.hpp @@ -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 +*/ +#include + +#include +#include + +/** + * 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 fragments; + // The expired fragments, need to be free in future. + std::vector 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 + diff --git a/trunk/src/app/srs_app_hls.cpp b/trunk/src/app/srs_app_hls.cpp index 758b2ccfa..ba9df5366 100644 --- a/trunk/src/app/srs_app_hls.cpp +++ b/trunk/src/app/srs_app_hls.cpp @@ -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::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::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::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 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(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(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()); } diff --git a/trunk/src/app/srs_app_hls.hpp b/trunk/src/app/srs_app_hls.hpp index 973ea1c28..c7d794d16 100644 --- a/trunk/src/app/srs_app_hls.hpp +++ b/trunk/src/app/srs_app_hls.hpp @@ -35,6 +35,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include 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 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. - */ - virtual int segment_open(int64_t segment_start_dts); + * open a new segment(a new ts file) + */ + 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. - */ - virtual int segment_close(std::string log_desc); + * Close segment(ts). + */ + 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(); diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index e603f819d..52f8e50a6 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -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.