diff --git a/trunk/auto/depends.sh b/trunk/auto/depends.sh index 3b9ed2bd2..7ae345be4 100755 --- a/trunk/auto/depends.sh +++ b/trunk/auto/depends.sh @@ -212,7 +212,7 @@ if [ $SRS_ARM_UBUNTU12 = YES ]; then unzip -q ../3rdparty/st-1.9.zip && cd st-1.9 && patch -p0 < ../../3rdparty/patches/1.st.arm.patch && make CC=${SrsArmCC} AR=${SrsArmAR} LD=${SrsArmLD} RANDLIB=${SrsArmRANDLIB} linux-debug && - cd .. && rm -f st && ln -sf st-1.9/obj st && + cd .. && rm -rf st && ln -sf st-1.9/obj st && cd .. && touch ${SRS_OBJS}/_flag.st.arm.tmp ) fi @@ -226,7 +226,7 @@ else rm -rf ${SRS_OBJS}/st-1.9 && cd ${SRS_OBJS} && unzip -q ../3rdparty/st-1.9.zip && cd st-1.9 && make linux-debug && - cd .. && rm -f st && ln -sf st-1.9/obj st && + cd .. && rm -rf st && ln -sf st-1.9/obj st && cd .. && rm -f ${SRS_OBJS}/_flag.st.arm.tmp ) fi @@ -250,7 +250,7 @@ if [ $SRS_HTTP_CALLBACK = YES ]; then sed -i "s/CPPFLAGS_FAST +=.*$/CPPFLAGS_FAST = \$\(CPPFLAGS_DEBUG\)/g" Makefile && sed -i "s/CFLAGS_FAST =.*$/CFLAGS_FAST = \$\(CFLAGS_DEBUG\)/g" Makefile && make package && - cd .. && rm -f hp && ln -sf http-parser-2.1 hp + cd .. && rm -rf hp && ln -sf http-parser-2.1 hp ) fi # check status @@ -282,7 +282,7 @@ if [ $__SRS_BUILD_NGINX = YES ]; then rm -rf ${SRS_OBJS}/nginx-1.5.7 && cd ${SRS_OBJS} && unzip -q ../3rdparty/nginx-1.5.7.zip && cd nginx-1.5.7 && ./configure --prefix=`pwd`/_release && make ${SRS_JOBS} && make install && - cd .. && ln -sf nginx-1.5.7/_release nginx + cd .. && rm -rf nginx && ln -sf nginx-1.5.7/_release nginx ) fi # check status @@ -356,14 +356,14 @@ else fi echo "link players to cherrypy static-dir" -rm -f research/api-server/static-dir/players && +rm -rf research/api-server/static-dir/players && ln -sf `pwd`/research/players research/api-server/static-dir/players && rm -f research/api-server/static-dir/crossdomain.xml && ln -sf `pwd`/research/players/crossdomain.xml research/api-server/static-dir/crossdomain.xml && -rm -f research/api-server/static-dir/live && +rm -rf research/api-server/static-dir/live && mkdir -p `pwd`/${SRS_OBJS}/nginx/html/live && ln -sf `pwd`/${SRS_OBJS}/nginx/html/live research/api-server/static-dir/live && -rm -f research/api-server/static-dir/forward && +rm -rf research/api-server/static-dir/forward && mkdir -p `pwd`/${SRS_OBJS}/nginx/html/forward && ln -sf `pwd`/${SRS_OBJS}/nginx/html/forward research/api-server/static-dir/forward @@ -410,7 +410,7 @@ if [ $SRS_SSL = YES ]; then unzip -q ../3rdparty/openssl-1.0.1f.zip && cd openssl-1.0.1f && ./Configure --prefix=`pwd`/_release -no-shared no-asm linux-armv4 && make CC=${SrsArmCC} GCC=${SrsArmGCC} AR="${SrsArmAR} r" LD=${SrsArmLD} LINK=${SrsArmGCC} RANDLIB=${SrsArmRANDLIB} && make install && - cd .. && ln -sf openssl-1.0.1f/_release openssl && + cd .. && rm -rf openssl && ln -sf openssl-1.0.1f/_release openssl && cd .. && touch ${SRS_OBJS}/_flag.ssl.arm.tmp ) fi @@ -425,7 +425,7 @@ if [ $SRS_SSL = YES ]; then unzip -q ../3rdparty/openssl-1.0.1f.zip && cd openssl-1.0.1f && ./config --prefix=`pwd`/_release -no-shared && make && make install && - cd .. && ln -sf openssl-1.0.1f/_release openssl && + cd .. && rm -rf openssl && ln -sf openssl-1.0.1f/_release openssl && cd .. && rm -f ${SRS_OBJS}/_flag.ssl.arm.tmp ) fi @@ -453,7 +453,7 @@ if [ $SRS_FFMPEG = YES ]; then cd ${SRS_OBJS} && pwd_dir=`pwd` && rm -rf ffmepg.src && mkdir -p ffmpeg.src && cd ffmpeg.src && rm -f build_ffmpeg.sh && ln -sf ../../auto/build_ffmpeg.sh && . build_ffmpeg.sh && - cd ${pwd_dir} && ln -sf ffmpeg.src/_release ffmpeg + cd ${pwd_dir} && rm -rf ffmpeg && ln -sf ffmpeg.src/_release ffmpeg ) fi # check status @@ -491,7 +491,7 @@ if [ $SRS_UTEST = YES ]; then ( rm -rf ${SRS_OBJS}/gtest-1.6.0 && cd ${SRS_OBJS} && unzip -q ../3rdparty/gtest-1.6.0.zip && - rm -f gtest && ln -sf gtest-1.6.0 gtest + rm -rf gtest && ln -sf gtest-1.6.0 gtest ) fi # check status @@ -511,8 +511,8 @@ if [ $SRS_GPERF = YES ]; then rm -rf ${SRS_OBJS}/gperftools-2.1 && cd ${SRS_OBJS} && unzip -q ../3rdparty/gperftools-2.1.zip && cd gperftools-2.1 && ./configure --prefix=`pwd`/_release --enable-frame-pointers && make ${SRS_JOBS} && make install && - cd .. && rm -f gperf && ln -sf gperftools-2.1/_release gperf && - rm -f pprof && ln -sf gperf/bin/pprof pprof + cd .. && rm -rf gperf && ln -sf gperftools-2.1/_release gperf && + rm -rf pprof && ln -sf gperf/bin/pprof pprof ) fi # check status diff --git a/trunk/src/app/srs_app_hls.cpp b/trunk/src/app/srs_app_hls.cpp index eaf9e1bfb..a286dd624 100644 --- a/trunk/src/app/srs_app_hls.cpp +++ b/trunk/src/app/srs_app_hls.cpp @@ -121,14 +121,15 @@ u_int8_t mpegts_header[] = { }; // @see: ngx_rtmp_SrsMpegtsFrame_t -struct SrsMpegtsFrame +class SrsMpegtsFrame { - int64_t pts; - int64_t dts; - int pid; - int sid; - int cc; - bool key; +public: + int64_t pts; + int64_t dts; + int pid; + int sid; + int cc; + bool key; SrsMpegtsFrame() { @@ -467,7 +468,7 @@ void SrsTSMuxer::close() } } -SrsM3u8Segment::SrsM3u8Segment() +SrsHlsSegment::SrsHlsSegment() { duration = 0; sequence_no = 0; @@ -475,14 +476,14 @@ SrsM3u8Segment::SrsM3u8Segment() segment_start_dts = 0; } -SrsM3u8Segment::~SrsM3u8Segment() +SrsHlsSegment::~SrsHlsSegment() { srs_freep(muxer); } -double SrsM3u8Segment::update_duration(int64_t video_stream_dts) +double SrsHlsSegment::update_duration(int64_t current_frame_dts) { - duration = (video_stream_dts - segment_start_dts) / 90000.0; + duration = (current_frame_dts - segment_start_dts) / 90000.0; srs_assert(duration >= 0); return duration; @@ -497,19 +498,19 @@ SrsHlsAacJitter::SrsHlsAacJitter() sync_ms = SRS_CONF_DEFAULT_AAC_SYNC; } -SrsM3u8Muxer::SrsM3u8Muxer() +SrsHlsMuxer::SrsHlsMuxer() { hls_fragment = hls_window = 0; - video_stream_dts = 0; file_index = 0; current = NULL; + video_count = 0; } -SrsM3u8Muxer::~SrsM3u8Muxer() +SrsHlsMuxer::~SrsHlsMuxer() { - std::vector::iterator it; + std::vector::iterator it; for (it = segments.begin(); it != segments.end(); ++it) { - SrsM3u8Segment* segment = *it; + SrsHlsSegment* segment = *it; srs_freep(segment); } segments.clear(); @@ -517,7 +518,7 @@ SrsM3u8Muxer::~SrsM3u8Muxer() srs_freep(current); } -int SrsM3u8Muxer::update_config( +int SrsHlsMuxer::update_config( std::string _app, std::string _stream, std::string path, int fragment, int window ) { @@ -532,7 +533,7 @@ int SrsM3u8Muxer::update_config( return ret; } -int SrsM3u8Muxer::segment_open() +int SrsHlsMuxer::segment_open(int64_t segment_start_dts) { int ret = ERROR_SUCCESS; @@ -541,6 +542,9 @@ int SrsM3u8Muxer::segment_open() return ret; } + // reset video count for new publish session. + video_count = 0; + // TODO: create all parents dirs. // create dir for app. if ((ret = create_dir()) != ERROR_SUCCESS) { @@ -551,9 +555,9 @@ int SrsM3u8Muxer::segment_open() srs_assert(!current); // new segment. - current = new SrsM3u8Segment(); + current = new SrsHlsSegment(); current->sequence_no = file_index++; - current->segment_start_dts = video_stream_dts; + current->segment_start_dts = segment_start_dts; // generate filename. char filename[128]; @@ -580,7 +584,14 @@ int SrsM3u8Muxer::segment_open() return ret; } -int SrsM3u8Muxer::flush_audio(SrsMpegtsFrame* af, SrsCodecBuffer* ab) +bool SrsHlsMuxer::is_segment_overflow() +{ + srs_assert(current); + + return current->duration >= hls_fragment; +} + +int SrsHlsMuxer::flush_audio(SrsMpegtsFrame* af, SrsCodecBuffer* ab) { int ret = ERROR_SUCCESS; @@ -604,9 +615,8 @@ int SrsM3u8Muxer::flush_audio(SrsMpegtsFrame* af, SrsCodecBuffer* ab) return ret; } -int SrsM3u8Muxer::flush_video( - SrsMpegtsFrame* af, SrsCodecBuffer* ab, - SrsMpegtsFrame* vf, SrsCodecBuffer* vb) +int SrsHlsMuxer::flush_video( + SrsMpegtsFrame* af, SrsCodecBuffer* ab, SrsMpegtsFrame* vf, SrsCodecBuffer* vb) { int ret = ERROR_SUCCESS; @@ -616,53 +626,19 @@ int SrsM3u8Muxer::flush_video( return ret; } - video_stream_dts = vf->dts; - srs_assert(current); - // reopen the muxer for a gop - if (vf->key && current->duration >= hls_fragment) { - // TODO: flush audio before or after segment? - /* - if ((ret = flush_audio(af, ab)) != ERROR_SUCCESS) { - srs_error("m3u8 muxer flush audio failed. ret=%d", ret); - return ret; - } - */ - - if ((ret = segment_close()) != ERROR_SUCCESS) { - srs_error("m3u8 muxer close segment failed. ret=%d", ret); - return ret; - } - - if ((ret = segment_open()) != ERROR_SUCCESS) { - srs_error("m3u8 muxer open segment failed. ret=%d", ret); - return ret; - } - - // TODO: flush audio before or after segment? - // segment open, flush the audio. - // @see: ngx_rtmp_hls_open_fragment - /* start fragment with audio to make iPhone happy */ - if ((ret = flush_audio(af, ab)) != ERROR_SUCCESS) { - srs_error("m3u8 muxer flush audio failed. ret=%d", ret); - return ret; - } - } // update the duration of segment. - current->update_duration(video_stream_dts); + current->update_duration(vf->dts); if ((ret = current->muxer->write_video(vf, vb)) != ERROR_SUCCESS) { return ret; } - - // write success, clear and free the buffer - vb->free(); return ret; } -int SrsM3u8Muxer::segment_close() +int SrsHlsMuxer::segment_close() { int ret = ERROR_SUCCESS; @@ -675,7 +651,7 @@ int SrsM3u8Muxer::segment_close() srs_assert(current); // assert segment duplicate. - std::vector::iterator it; + std::vector::iterator it; it = std::find(segments.begin(), segments.end(), current); srs_assert(it == segments.end()); @@ -691,13 +667,13 @@ int SrsM3u8Muxer::segment_close() current = NULL; // the segments to remove - std::vector segment_to_remove; + std::vector segment_to_remove; // shrink the segments. double duration = 0; int remove_index = -1; for (int i = segments.size() - 1; i >= 0; i--) { - SrsM3u8Segment* segment = segments[i]; + SrsHlsSegment* segment = segments[i]; duration += segment->duration; if ((int)duration > hls_window) { @@ -706,7 +682,7 @@ int SrsM3u8Muxer::segment_close() } } for (int i = 0; i < remove_index && !segments.empty(); i++) { - SrsM3u8Segment* segment = *segments.begin(); + SrsHlsSegment* segment = *segments.begin(); segments.erase(segments.begin()); segment_to_remove.push_back(segment); } @@ -716,7 +692,7 @@ int SrsM3u8Muxer::segment_close() // remove the ts file. for (int i = 0; i < (int)segment_to_remove.size(); i++) { - SrsM3u8Segment* segment = segment_to_remove[i]; + SrsHlsSegment* segment = segment_to_remove[i]; unlink(segment->full_path.c_str()); srs_freep(segment); } @@ -731,7 +707,7 @@ int SrsM3u8Muxer::segment_close() return ret; } -int SrsM3u8Muxer::refresh_m3u8() +int SrsHlsMuxer::refresh_m3u8() { int ret = ERROR_SUCCESS; @@ -761,7 +737,7 @@ int SrsM3u8Muxer::refresh_m3u8() return ret; } -int SrsM3u8Muxer::_refresh_m3u8(int& fd, std::string m3u8_file) +int SrsHlsMuxer::_refresh_m3u8(int& fd, std::string m3u8_file) { int ret = ERROR_SUCCESS; @@ -795,7 +771,7 @@ int SrsM3u8Muxer::_refresh_m3u8(int& fd, std::string m3u8_file) srs_verbose("write m3u8 header success."); // #EXT-X-MEDIA-SEQUENCE:4294967295\n - SrsM3u8Segment* first = *segments.begin(); + SrsHlsSegment* first = *segments.begin(); char sequence[34] = {}; int len = snprintf(sequence, sizeof(sequence), "#EXT-X-MEDIA-SEQUENCE:%d\n", first->sequence_no); if (::write(fd, sequence, len) != len) { @@ -807,9 +783,9 @@ int SrsM3u8Muxer::_refresh_m3u8(int& fd, std::string m3u8_file) // #EXT-X-TARGETDURATION:4294967295\n int target_duration = 0; - std::vector::iterator it; + std::vector::iterator it; for (it = segments.begin(); it != segments.end(); ++it) { - SrsM3u8Segment* segment = *it; + SrsHlsSegment* segment = *it; target_duration = srs_max(target_duration, (int)segment->duration); } // TODO: maybe need to take an around value @@ -825,7 +801,7 @@ int SrsM3u8Muxer::_refresh_m3u8(int& fd, std::string m3u8_file) // write all segments for (it = segments.begin(); it != segments.end(); ++it) { - SrsM3u8Segment* segment = *it; + SrsHlsSegment* segment = *it; // "#EXTINF:4294967295.208,\n" char ext_info[25]; @@ -852,7 +828,7 @@ int SrsM3u8Muxer::_refresh_m3u8(int& fd, std::string m3u8_file) return ret; } -int SrsM3u8Muxer::create_dir() +int SrsHlsMuxer::create_dir() { int ret = ERROR_SUCCESS; @@ -875,7 +851,7 @@ int SrsM3u8Muxer::create_dir() return ret; } -SrsTSCache::SrsTSCache() +SrsHlsCache::SrsHlsCache() { aac_jitter = new SrsHlsAacJitter(); @@ -886,7 +862,7 @@ SrsTSCache::SrsTSCache() vf = new SrsMpegtsFrame(); } -SrsTSCache::~SrsTSCache() +SrsHlsCache::~SrsHlsCache() { srs_freep(aac_jitter); @@ -899,8 +875,52 @@ SrsTSCache::~SrsTSCache() srs_freep(af); srs_freep(vf); } + +int SrsHlsCache::on_publish(SrsHlsMuxer* muxer, SrsRequest* req, int64_t segment_start_dts) +{ + int ret = ERROR_SUCCESS; + + std::string vhost = req->vhost; + std::string stream = req->stream; + std::string app = req->app; -int SrsTSCache::write_audio(SrsCodec* codec, SrsM3u8Muxer* muxer, int64_t pts, SrsCodecSample* sample) + int hls_fragment = _srs_config->get_hls_fragment(vhost); + int hls_window = _srs_config->get_hls_window(vhost); + + // get the hls path config + std::string hls_path = _srs_config->get_hls_path(vhost); + + // open muxer + if ((ret = muxer->update_config(app, stream, hls_path, hls_fragment, hls_window)) != ERROR_SUCCESS) { + srs_error("m3u8 muxer update config failed. ret=%d", ret); + return ret; + } + + if ((ret = muxer->segment_open(segment_start_dts)) != ERROR_SUCCESS) { + srs_error("m3u8 muxer open segment failed. ret=%d", ret); + return ret; + } + + return ret; +} + +int SrsHlsCache::on_unpublish(SrsHlsMuxer* muxer) +{ + int ret = ERROR_SUCCESS; + + if ((ret = muxer->flush_audio(af, ab)) != ERROR_SUCCESS) { + srs_error("m3u8 muxer flush audio failed. ret=%d", ret); + return ret; + } + + if ((ret = muxer->segment_close()) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsHlsCache::write_audio(SrsCodec* codec, SrsHlsMuxer* muxer, int64_t pts, SrsCodecSample* sample) { int ret = ERROR_SUCCESS; @@ -939,7 +959,7 @@ int SrsTSCache::write_audio(SrsCodec* codec, SrsM3u8Muxer* muxer, int64_t pts, S return ret; } -int SrsTSCache::write_video(SrsCodec* codec, SrsM3u8Muxer* muxer, int64_t dts, SrsCodecSample* sample) +int SrsHlsCache::write_video(SrsCodec* codec, SrsHlsMuxer* muxer, int64_t dts, SrsCodecSample* sample) { int ret = ERROR_SUCCESS; @@ -954,28 +974,43 @@ int SrsTSCache::write_video(SrsCodec* codec, SrsM3u8Muxer* muxer, int64_t dts, S vf->sid = TS_VIDEO_AVC; vf->key = sample->frame_type == SrsCodecVideoAVCFrameKeyFrame; + // reopen the muxer for a gop + // close current segment, open a new segment, + // then write the key frame to the new segment. + if (vf->key && muxer->is_segment_overflow()) { + if ((ret = muxer->segment_close()) != ERROR_SUCCESS) { + srs_error("m3u8 muxer close segment failed. ret=%d", ret); + return ret; + } + + if ((ret = muxer->segment_open(vf->dts)) != ERROR_SUCCESS) { + srs_error("m3u8 muxer open segment failed. ret=%d", ret); + return ret; + } + + // TODO: flush audio before or after segment? + // segment open, flush the audio. + // @see: ngx_rtmp_hls_open_fragment + /* start fragment with audio to make iPhone happy */ + if ((ret = muxer->flush_audio(af, ab)) != ERROR_SUCCESS) { + srs_error("m3u8 muxer flush audio failed. ret=%d", ret); + return ret; + } + } + // flush video when got one if ((ret = muxer->flush_video(af, ab, vf, vb)) != ERROR_SUCCESS) { srs_error("m3u8 muxer flush video failed. ret=%d", ret); return ret; } - return ret; -} - -int SrsTSCache::flush_audio(SrsM3u8Muxer* muxer) -{ - int ret = ERROR_SUCCESS; - - if ((ret = muxer->flush_audio(af, ab)) != ERROR_SUCCESS) { - srs_error("m3u8 muxer flush audio failed. ret=%d", ret); - return ret; - } + // write success, clear and free the buffer + vb->free(); return ret; } -int SrsTSCache::cache_audio(SrsCodec* codec, SrsCodecSample* sample) +int SrsHlsCache::cache_audio(SrsCodec* codec, SrsCodecSample* sample) { int ret = ERROR_SUCCESS; @@ -1042,7 +1077,7 @@ int SrsTSCache::cache_audio(SrsCodec* codec, SrsCodecSample* sample) return ret; } -int SrsTSCache::cache_video(SrsCodec* codec, SrsCodecSample* sample) +int SrsHlsCache::cache_video(SrsCodec* codec, SrsCodecSample* sample) { int ret = ERROR_SUCCESS; @@ -1118,10 +1153,11 @@ SrsHls::SrsHls(SrsSource* _source) sample = new SrsCodecSample(); jitter = new SrsRtmpJitter(); - muxer = new SrsM3u8Muxer(); - ts_cache = new SrsTSCache(); + muxer = new SrsHlsMuxer(); + hls_cache = new SrsHlsCache(); pithy_print = new SrsPithyPrint(SRS_STAGE_HLS); + stream_dts = 0; } SrsHls::~SrsHls() @@ -1131,7 +1167,7 @@ SrsHls::~SrsHls() srs_freep(jitter); srs_freep(muxer); - srs_freep(ts_cache); + srs_freep(hls_cache); srs_freep(pithy_print); } @@ -1144,32 +1180,13 @@ int SrsHls::on_publish(SrsRequest* req) if (hls_enabled) { return ret; } - - std::string vhost = req->vhost; - std::string stream = req->stream; - std::string app = req->app; + std::string vhost = req->vhost; if (!_srs_config->get_hls_enabled(vhost)) { return ret; } - // if enabled, open the muxer. - hls_enabled = true; - - int hls_fragment = _srs_config->get_hls_fragment(vhost); - int hls_window = _srs_config->get_hls_window(vhost); - - // get the hls path config - std::string hls_path = _srs_config->get_hls_path(vhost); - - // open muxer - if ((ret = muxer->update_config(app, stream, hls_path, hls_fragment, hls_window)) != ERROR_SUCCESS) { - srs_error("m3u8 muxer update config failed. ret=%d", ret); - return ret; - } - - if ((ret = muxer->segment_open()) != ERROR_SUCCESS) { - srs_error("m3u8 muxer open segment failed. ret=%d", ret); + if ((ret = hls_cache->on_publish(muxer, req, stream_dts)) != ERROR_SUCCESS) { return ret; } @@ -1178,6 +1195,9 @@ int SrsHls::on_publish(SrsRequest* req) srs_error("callback source hls start failed. ret=%d", ret); return ret; } + + // if enabled, open the muxer. + hls_enabled = true; return ret; } @@ -1190,12 +1210,8 @@ void SrsHls::on_unpublish() if (!hls_enabled) { return; } - - // close muxer when unpublish. - ret = ts_cache->flush_audio(muxer); - ret += muxer->segment_close(); - if (ret != ERROR_SUCCESS) { + if ((ret = hls_cache->on_unpublish(muxer)) != ERROR_SUCCESS) { srs_error("ignore m3u8 muxer flush/close audio failed. ret=%d", ret); } @@ -1287,8 +1303,11 @@ int SrsHls::on_audio(SrsSharedPtrMessage* audio) // the pts calc from rtmp/flv header. int64_t pts = audio->header.timestamp * 90; - if ((ret = ts_cache->write_audio(codec, muxer, pts, sample)) != ERROR_SUCCESS) { - srs_error("ts cache write audio failed. ret=%d", ret); + // for pure audio, we need to update the stream dts also. + stream_dts = pts; + + if ((ret = hls_cache->write_audio(codec, muxer, pts, sample)) != ERROR_SUCCESS) { + srs_error("hls cache write audio failed. ret=%d", ret); return ret; } @@ -1327,8 +1346,9 @@ int SrsHls::on_video(SrsSharedPtrMessage* video) } int64_t dts = video->header.timestamp * 90; - if ((ret = ts_cache->write_video(codec, muxer, dts, sample)) != ERROR_SUCCESS) { - srs_error("ts cache write video failed. ret=%d", ret); + stream_dts = dts; + if ((ret = hls_cache->write_video(codec, muxer, dts, sample)) != ERROR_SUCCESS) { + srs_error("hls cache write video failed. ret=%d", ret); return ret; } diff --git a/trunk/src/app/srs_app_hls.hpp b/trunk/src/app/srs_app_hls.hpp index eb0e1ef35..89a465317 100644 --- a/trunk/src/app/srs_app_hls.hpp +++ b/trunk/src/app/srs_app_hls.hpp @@ -76,7 +76,10 @@ public: virtual void on_buffer_continue(); }; -//TODO: refine the ts muxer, do more jobs. +/** +* write data from frame(header info) and buffer(data) to ts file. +* it's a simple object wrapper for utility from nginx-rtmp: SrsMpegtsWriter +*/ class SrsTSMuxer { private: @@ -93,11 +96,14 @@ public: }; /** +* the wrapper of m3u8 segment from specification: +* * 3.3.2. EXTINF * The EXTINF tag specifies the duration of a media segment. */ -struct SrsM3u8Segment +class SrsHlsSegment { +public: // duration in seconds in m3u8. double duration; // sequence number in m3u8. @@ -111,19 +117,25 @@ struct SrsM3u8Segment // current segment start dts for m3u8 int64_t segment_start_dts; - SrsM3u8Segment(); - virtual ~SrsM3u8Segment(); + SrsHlsSegment(); + virtual ~SrsHlsSegment(); /** * update the segment duration. + * @current_frame_dts the dts of frame, in tbn of ts. */ - virtual double update_duration(int64_t video_stream_dts); + virtual double update_duration(int64_t current_frame_dts); }; /** -* muxer the m3u8 and ts files. +* muxer the HLS stream(m3u8 and ts files). +* generally, the m3u8 muxer only provides methods to open/close segments, +* to flush video/audio, without any mechenisms. +* +* that is, user must use HlsCache, which will control the methods of muxer, +* and provides HLS mechenisms. */ -class SrsM3u8Muxer +class SrsHlsMuxer { private: std::string app; @@ -135,23 +147,39 @@ private: private: int file_index; std::string m3u8; +private: + /** + * for pure audio HLS application, + * the video count used to count the video, + * if zero and audio buffer overflow, reap the ts, + * just like we got a keyframe. + */ + u_int32_t video_count; private: /** * m3u8 segments. */ - std::vector segments; + std::vector segments; /** * current writing segment. */ - SrsM3u8Segment* current; - // last known dts - int64_t video_stream_dts; + SrsHlsSegment* current; public: - SrsM3u8Muxer(); - virtual ~SrsM3u8Muxer(); + SrsHlsMuxer(); + virtual ~SrsHlsMuxer(); public: virtual int update_config(std::string _app, std::string _stream, std::string path, int fragment, int window); - virtual int segment_open(); + /** + * 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); + /** + * whether video overflow, + * that is whether the current segment duration >= the segment in config + */ + virtual bool is_segment_overflow(); virtual int flush_audio(SrsMpegtsFrame* af, SrsCodecBuffer* ab); virtual int flush_video(SrsMpegtsFrame* af, SrsCodecBuffer* ab, SrsMpegtsFrame* vf, SrsCodecBuffer* vb); virtual int segment_close(); @@ -162,9 +190,23 @@ private: }; /** -* ts need to cache some audio then flush +* hls stream cache, +* use to cache hls stream and flush to hls muxer. +* +* when write stream to ts file: +* video frame will directly flush to M3u8Muxer, +* audio frame need to cache, because it's small and flv tbn problem. +* +* whatever, the Hls cache used to cache video/audio, +* and flush video/audio to m3u8 muxer if needed. +* +* about the flv tbn problem: +* flv tbn is 1/1000, ts tbn is 1/90000, +* when timestamp convert to flv tbn, it will loose precise, +* so we must gather audio frame together, and recalc the timestamp @see SrsHlsAacJitter, +* we use a aac jitter to correct the audio pts. */ -class SrsTSCache +class SrsHlsCache { private: // current frame and buffer @@ -178,34 +220,37 @@ private: // time jitter for aac SrsHlsAacJitter* aac_jitter; public: - SrsTSCache(); - virtual ~SrsTSCache(); + SrsHlsCache(); + virtual ~SrsHlsCache(); public: + /** + * when publish or unpublish stream. + */ + virtual int on_publish(SrsHlsMuxer* muxer, SrsRequest* req, int64_t segment_start_dts); + virtual int on_unpublish(SrsHlsMuxer* muxer); /** * write audio to cache, if need to flush, flush to muxer. */ - virtual int write_audio(SrsCodec* codec, SrsM3u8Muxer* muxer, int64_t pts, SrsCodecSample* sample); + virtual int write_audio(SrsCodec* codec, SrsHlsMuxer* muxer, int64_t pts, SrsCodecSample* sample); /** * write video to muxer. */ - virtual int write_video(SrsCodec* codec, SrsM3u8Muxer* muxer, int64_t dts, SrsCodecSample* sample); - /** - * flush audio in cache to muxer. - */ - virtual int flush_audio(SrsM3u8Muxer* muxer); + virtual int write_video(SrsCodec* codec, SrsHlsMuxer* muxer, int64_t dts, SrsCodecSample* sample); private: virtual int cache_audio(SrsCodec* codec, SrsCodecSample* sample); virtual int cache_video(SrsCodec* codec, SrsCodecSample* sample); }; /** -* write m3u8 hls. +* delivery RTMP stream to HLS(m3u8 and ts), +* SrsHls provides interface with SrsSource. +* */ class SrsHls { private: - SrsM3u8Muxer* muxer; - SrsTSCache* ts_cache; + SrsHlsMuxer* muxer; + SrsHlsCache* hls_cache; private: bool hls_enabled; SrsSource* source; @@ -213,6 +258,20 @@ private: SrsCodecSample* sample; SrsRtmpJitter* jitter; SrsPithyPrint* pithy_print; + /** + * 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(SrsSource* _source); virtual ~SrsHls();