1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-03-09 15:49:59 +00:00

refine the hls module, clear logic dead code, rename TSCache to HlsCache, M3u8Muxer to HlsMuxer. that is, make it to more readable.

This commit is contained in:
winlin 2014-03-20 18:19:08 +08:00
parent 6bc18f2e44
commit b708f588fc
3 changed files with 241 additions and 162 deletions

View file

@ -212,7 +212,7 @@ if [ $SRS_ARM_UBUNTU12 = YES ]; then
unzip -q ../3rdparty/st-1.9.zip && cd st-1.9 && unzip -q ../3rdparty/st-1.9.zip && cd st-1.9 &&
patch -p0 < ../../3rdparty/patches/1.st.arm.patch && patch -p0 < ../../3rdparty/patches/1.st.arm.patch &&
make CC=${SrsArmCC} AR=${SrsArmAR} LD=${SrsArmLD} RANDLIB=${SrsArmRANDLIB} linux-debug && 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 cd .. && touch ${SRS_OBJS}/_flag.st.arm.tmp
) )
fi fi
@ -226,7 +226,7 @@ else
rm -rf ${SRS_OBJS}/st-1.9 && cd ${SRS_OBJS} && rm -rf ${SRS_OBJS}/st-1.9 && cd ${SRS_OBJS} &&
unzip -q ../3rdparty/st-1.9.zip && cd st-1.9 && unzip -q ../3rdparty/st-1.9.zip && cd st-1.9 &&
make linux-debug && 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 cd .. && rm -f ${SRS_OBJS}/_flag.st.arm.tmp
) )
fi 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/CPPFLAGS_FAST +=.*$/CPPFLAGS_FAST = \$\(CPPFLAGS_DEBUG\)/g" Makefile &&
sed -i "s/CFLAGS_FAST =.*$/CFLAGS_FAST = \$\(CFLAGS_DEBUG\)/g" Makefile && sed -i "s/CFLAGS_FAST =.*$/CFLAGS_FAST = \$\(CFLAGS_DEBUG\)/g" Makefile &&
make package && 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 fi
# check status # check status
@ -282,7 +282,7 @@ if [ $__SRS_BUILD_NGINX = YES ]; then
rm -rf ${SRS_OBJS}/nginx-1.5.7 && cd ${SRS_OBJS} && rm -rf ${SRS_OBJS}/nginx-1.5.7 && cd ${SRS_OBJS} &&
unzip -q ../3rdparty/nginx-1.5.7.zip && cd nginx-1.5.7 && unzip -q ../3rdparty/nginx-1.5.7.zip && cd nginx-1.5.7 &&
./configure --prefix=`pwd`/_release && make ${SRS_JOBS} && make install && ./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 fi
# check status # check status
@ -356,14 +356,14 @@ else
fi fi
echo "link players to cherrypy static-dir" 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 && ln -sf `pwd`/research/players research/api-server/static-dir/players &&
rm -f research/api-server/static-dir/crossdomain.xml && rm -f research/api-server/static-dir/crossdomain.xml &&
ln -sf `pwd`/research/players/crossdomain.xml 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 && mkdir -p `pwd`/${SRS_OBJS}/nginx/html/live &&
ln -sf `pwd`/${SRS_OBJS}/nginx/html/live research/api-server/static-dir/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 && mkdir -p `pwd`/${SRS_OBJS}/nginx/html/forward &&
ln -sf `pwd`/${SRS_OBJS}/nginx/html/forward research/api-server/static-dir/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 && unzip -q ../3rdparty/openssl-1.0.1f.zip && cd openssl-1.0.1f &&
./Configure --prefix=`pwd`/_release -no-shared no-asm linux-armv4 && ./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 && 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 cd .. && touch ${SRS_OBJS}/_flag.ssl.arm.tmp
) )
fi fi
@ -425,7 +425,7 @@ if [ $SRS_SSL = YES ]; then
unzip -q ../3rdparty/openssl-1.0.1f.zip && cd openssl-1.0.1f && unzip -q ../3rdparty/openssl-1.0.1f.zip && cd openssl-1.0.1f &&
./config --prefix=`pwd`/_release -no-shared && ./config --prefix=`pwd`/_release -no-shared &&
make && make install && 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 cd .. && rm -f ${SRS_OBJS}/_flag.ssl.arm.tmp
) )
fi fi
@ -453,7 +453,7 @@ if [ $SRS_FFMPEG = YES ]; then
cd ${SRS_OBJS} && pwd_dir=`pwd` && cd ${SRS_OBJS} && pwd_dir=`pwd` &&
rm -rf ffmepg.src && mkdir -p ffmpeg.src && cd ffmpeg.src && 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 && 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 fi
# check status # check status
@ -491,7 +491,7 @@ if [ $SRS_UTEST = YES ]; then
( (
rm -rf ${SRS_OBJS}/gtest-1.6.0 && cd ${SRS_OBJS} && rm -rf ${SRS_OBJS}/gtest-1.6.0 && cd ${SRS_OBJS} &&
unzip -q ../3rdparty/gtest-1.6.0.zip && 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 fi
# check status # check status
@ -511,8 +511,8 @@ if [ $SRS_GPERF = YES ]; then
rm -rf ${SRS_OBJS}/gperftools-2.1 && cd ${SRS_OBJS} && rm -rf ${SRS_OBJS}/gperftools-2.1 && cd ${SRS_OBJS} &&
unzip -q ../3rdparty/gperftools-2.1.zip && cd gperftools-2.1 && unzip -q ../3rdparty/gperftools-2.1.zip && cd gperftools-2.1 &&
./configure --prefix=`pwd`/_release --enable-frame-pointers && make ${SRS_JOBS} && make install && ./configure --prefix=`pwd`/_release --enable-frame-pointers && make ${SRS_JOBS} && make install &&
cd .. && rm -f gperf && ln -sf gperftools-2.1/_release gperf && cd .. && rm -rf gperf && ln -sf gperftools-2.1/_release gperf &&
rm -f pprof && ln -sf gperf/bin/pprof pprof rm -rf pprof && ln -sf gperf/bin/pprof pprof
) )
fi fi
# check status # check status

View file

@ -121,14 +121,15 @@ u_int8_t mpegts_header[] = {
}; };
// @see: ngx_rtmp_SrsMpegtsFrame_t // @see: ngx_rtmp_SrsMpegtsFrame_t
struct SrsMpegtsFrame class SrsMpegtsFrame
{ {
int64_t pts; public:
int64_t dts; int64_t pts;
int pid; int64_t dts;
int sid; int pid;
int cc; int sid;
bool key; int cc;
bool key;
SrsMpegtsFrame() SrsMpegtsFrame()
{ {
@ -467,7 +468,7 @@ void SrsTSMuxer::close()
} }
} }
SrsM3u8Segment::SrsM3u8Segment() SrsHlsSegment::SrsHlsSegment()
{ {
duration = 0; duration = 0;
sequence_no = 0; sequence_no = 0;
@ -475,14 +476,14 @@ SrsM3u8Segment::SrsM3u8Segment()
segment_start_dts = 0; segment_start_dts = 0;
} }
SrsM3u8Segment::~SrsM3u8Segment() SrsHlsSegment::~SrsHlsSegment()
{ {
srs_freep(muxer); 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); srs_assert(duration >= 0);
return duration; return duration;
@ -497,19 +498,19 @@ SrsHlsAacJitter::SrsHlsAacJitter()
sync_ms = SRS_CONF_DEFAULT_AAC_SYNC; sync_ms = SRS_CONF_DEFAULT_AAC_SYNC;
} }
SrsM3u8Muxer::SrsM3u8Muxer() SrsHlsMuxer::SrsHlsMuxer()
{ {
hls_fragment = hls_window = 0; hls_fragment = hls_window = 0;
video_stream_dts = 0;
file_index = 0; file_index = 0;
current = NULL; current = NULL;
video_count = 0;
} }
SrsM3u8Muxer::~SrsM3u8Muxer() SrsHlsMuxer::~SrsHlsMuxer()
{ {
std::vector<SrsM3u8Segment*>::iterator it; std::vector<SrsHlsSegment*>::iterator it;
for (it = segments.begin(); it != segments.end(); ++it) { for (it = segments.begin(); it != segments.end(); ++it) {
SrsM3u8Segment* segment = *it; SrsHlsSegment* segment = *it;
srs_freep(segment); srs_freep(segment);
} }
segments.clear(); segments.clear();
@ -517,7 +518,7 @@ SrsM3u8Muxer::~SrsM3u8Muxer()
srs_freep(current); srs_freep(current);
} }
int SrsM3u8Muxer::update_config( int SrsHlsMuxer::update_config(
std::string _app, std::string _stream, std::string _app, std::string _stream,
std::string path, int fragment, int window std::string path, int fragment, int window
) { ) {
@ -532,7 +533,7 @@ int SrsM3u8Muxer::update_config(
return ret; return ret;
} }
int SrsM3u8Muxer::segment_open() int SrsHlsMuxer::segment_open(int64_t segment_start_dts)
{ {
int ret = ERROR_SUCCESS; int ret = ERROR_SUCCESS;
@ -541,6 +542,9 @@ int SrsM3u8Muxer::segment_open()
return ret; return ret;
} }
// reset video count for new publish session.
video_count = 0;
// TODO: create all parents dirs. // TODO: create all parents dirs.
// create dir for app. // create dir for app.
if ((ret = create_dir()) != ERROR_SUCCESS) { if ((ret = create_dir()) != ERROR_SUCCESS) {
@ -551,9 +555,9 @@ int SrsM3u8Muxer::segment_open()
srs_assert(!current); srs_assert(!current);
// new segment. // new segment.
current = new SrsM3u8Segment(); current = new SrsHlsSegment();
current->sequence_no = file_index++; current->sequence_no = file_index++;
current->segment_start_dts = video_stream_dts; current->segment_start_dts = segment_start_dts;
// generate filename. // generate filename.
char filename[128]; char filename[128];
@ -580,7 +584,14 @@ int SrsM3u8Muxer::segment_open()
return ret; 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; int ret = ERROR_SUCCESS;
@ -604,9 +615,8 @@ int SrsM3u8Muxer::flush_audio(SrsMpegtsFrame* af, SrsCodecBuffer* ab)
return ret; return ret;
} }
int SrsM3u8Muxer::flush_video( int SrsHlsMuxer::flush_video(
SrsMpegtsFrame* af, SrsCodecBuffer* ab, SrsMpegtsFrame* af, SrsCodecBuffer* ab, SrsMpegtsFrame* vf, SrsCodecBuffer* vb)
SrsMpegtsFrame* vf, SrsCodecBuffer* vb)
{ {
int ret = ERROR_SUCCESS; int ret = ERROR_SUCCESS;
@ -616,53 +626,19 @@ int SrsM3u8Muxer::flush_video(
return ret; return ret;
} }
video_stream_dts = vf->dts;
srs_assert(current); 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. // 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) { if ((ret = current->muxer->write_video(vf, vb)) != ERROR_SUCCESS) {
return ret; return ret;
} }
// write success, clear and free the buffer
vb->free();
return ret; return ret;
} }
int SrsM3u8Muxer::segment_close() int SrsHlsMuxer::segment_close()
{ {
int ret = ERROR_SUCCESS; int ret = ERROR_SUCCESS;
@ -675,7 +651,7 @@ int SrsM3u8Muxer::segment_close()
srs_assert(current); srs_assert(current);
// assert segment duplicate. // assert segment duplicate.
std::vector<SrsM3u8Segment*>::iterator it; std::vector<SrsHlsSegment*>::iterator it;
it = std::find(segments.begin(), segments.end(), current); it = std::find(segments.begin(), segments.end(), current);
srs_assert(it == segments.end()); srs_assert(it == segments.end());
@ -691,13 +667,13 @@ int SrsM3u8Muxer::segment_close()
current = NULL; current = NULL;
// the segments to remove // the segments to remove
std::vector<SrsM3u8Segment*> segment_to_remove; std::vector<SrsHlsSegment*> segment_to_remove;
// shrink the segments. // shrink the segments.
double duration = 0; double duration = 0;
int remove_index = -1; int remove_index = -1;
for (int i = segments.size() - 1; i >= 0; i--) { for (int i = segments.size() - 1; i >= 0; i--) {
SrsM3u8Segment* segment = segments[i]; SrsHlsSegment* segment = segments[i];
duration += segment->duration; duration += segment->duration;
if ((int)duration > hls_window) { if ((int)duration > hls_window) {
@ -706,7 +682,7 @@ int SrsM3u8Muxer::segment_close()
} }
} }
for (int i = 0; i < remove_index && !segments.empty(); i++) { for (int i = 0; i < remove_index && !segments.empty(); i++) {
SrsM3u8Segment* segment = *segments.begin(); SrsHlsSegment* segment = *segments.begin();
segments.erase(segments.begin()); segments.erase(segments.begin());
segment_to_remove.push_back(segment); segment_to_remove.push_back(segment);
} }
@ -716,7 +692,7 @@ int SrsM3u8Muxer::segment_close()
// remove the ts file. // remove the ts file.
for (int i = 0; i < (int)segment_to_remove.size(); i++) { 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()); unlink(segment->full_path.c_str());
srs_freep(segment); srs_freep(segment);
} }
@ -731,7 +707,7 @@ int SrsM3u8Muxer::segment_close()
return ret; return ret;
} }
int SrsM3u8Muxer::refresh_m3u8() int SrsHlsMuxer::refresh_m3u8()
{ {
int ret = ERROR_SUCCESS; int ret = ERROR_SUCCESS;
@ -761,7 +737,7 @@ int SrsM3u8Muxer::refresh_m3u8()
return ret; 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; int ret = ERROR_SUCCESS;
@ -795,7 +771,7 @@ int SrsM3u8Muxer::_refresh_m3u8(int& fd, std::string m3u8_file)
srs_verbose("write m3u8 header success."); srs_verbose("write m3u8 header success.");
// #EXT-X-MEDIA-SEQUENCE:4294967295\n // #EXT-X-MEDIA-SEQUENCE:4294967295\n
SrsM3u8Segment* first = *segments.begin(); SrsHlsSegment* first = *segments.begin();
char sequence[34] = {}; char sequence[34] = {};
int len = snprintf(sequence, sizeof(sequence), "#EXT-X-MEDIA-SEQUENCE:%d\n", first->sequence_no); int len = snprintf(sequence, sizeof(sequence), "#EXT-X-MEDIA-SEQUENCE:%d\n", first->sequence_no);
if (::write(fd, sequence, len) != len) { 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 // #EXT-X-TARGETDURATION:4294967295\n
int target_duration = 0; int target_duration = 0;
std::vector<SrsM3u8Segment*>::iterator it; std::vector<SrsHlsSegment*>::iterator it;
for (it = segments.begin(); it != segments.end(); ++it) { for (it = segments.begin(); it != segments.end(); ++it) {
SrsM3u8Segment* segment = *it; SrsHlsSegment* segment = *it;
target_duration = srs_max(target_duration, (int)segment->duration); target_duration = srs_max(target_duration, (int)segment->duration);
} }
// TODO: maybe need to take an around value // 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 // write all segments
for (it = segments.begin(); it != segments.end(); ++it) { for (it = segments.begin(); it != segments.end(); ++it) {
SrsM3u8Segment* segment = *it; SrsHlsSegment* segment = *it;
// "#EXTINF:4294967295.208,\n" // "#EXTINF:4294967295.208,\n"
char ext_info[25]; char ext_info[25];
@ -852,7 +828,7 @@ int SrsM3u8Muxer::_refresh_m3u8(int& fd, std::string m3u8_file)
return ret; return ret;
} }
int SrsM3u8Muxer::create_dir() int SrsHlsMuxer::create_dir()
{ {
int ret = ERROR_SUCCESS; int ret = ERROR_SUCCESS;
@ -875,7 +851,7 @@ int SrsM3u8Muxer::create_dir()
return ret; return ret;
} }
SrsTSCache::SrsTSCache() SrsHlsCache::SrsHlsCache()
{ {
aac_jitter = new SrsHlsAacJitter(); aac_jitter = new SrsHlsAacJitter();
@ -886,7 +862,7 @@ SrsTSCache::SrsTSCache()
vf = new SrsMpegtsFrame(); vf = new SrsMpegtsFrame();
} }
SrsTSCache::~SrsTSCache() SrsHlsCache::~SrsHlsCache()
{ {
srs_freep(aac_jitter); srs_freep(aac_jitter);
@ -899,8 +875,52 @@ SrsTSCache::~SrsTSCache()
srs_freep(af); srs_freep(af);
srs_freep(vf); 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; int ret = ERROR_SUCCESS;
@ -939,7 +959,7 @@ int SrsTSCache::write_audio(SrsCodec* codec, SrsM3u8Muxer* muxer, int64_t pts, S
return ret; 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; 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->sid = TS_VIDEO_AVC;
vf->key = sample->frame_type == SrsCodecVideoAVCFrameKeyFrame; 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 // flush video when got one
if ((ret = muxer->flush_video(af, ab, vf, vb)) != ERROR_SUCCESS) { if ((ret = muxer->flush_video(af, ab, vf, vb)) != ERROR_SUCCESS) {
srs_error("m3u8 muxer flush video failed. ret=%d", ret); srs_error("m3u8 muxer flush video failed. ret=%d", ret);
return ret; return ret;
} }
return ret; // write success, clear and free the buffer
} vb->free();
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;
}
return ret; return ret;
} }
int SrsTSCache::cache_audio(SrsCodec* codec, SrsCodecSample* sample) int SrsHlsCache::cache_audio(SrsCodec* codec, SrsCodecSample* sample)
{ {
int ret = ERROR_SUCCESS; int ret = ERROR_SUCCESS;
@ -1042,7 +1077,7 @@ int SrsTSCache::cache_audio(SrsCodec* codec, SrsCodecSample* sample)
return ret; return ret;
} }
int SrsTSCache::cache_video(SrsCodec* codec, SrsCodecSample* sample) int SrsHlsCache::cache_video(SrsCodec* codec, SrsCodecSample* sample)
{ {
int ret = ERROR_SUCCESS; int ret = ERROR_SUCCESS;
@ -1118,10 +1153,11 @@ SrsHls::SrsHls(SrsSource* _source)
sample = new SrsCodecSample(); sample = new SrsCodecSample();
jitter = new SrsRtmpJitter(); jitter = new SrsRtmpJitter();
muxer = new SrsM3u8Muxer(); muxer = new SrsHlsMuxer();
ts_cache = new SrsTSCache(); hls_cache = new SrsHlsCache();
pithy_print = new SrsPithyPrint(SRS_STAGE_HLS); pithy_print = new SrsPithyPrint(SRS_STAGE_HLS);
stream_dts = 0;
} }
SrsHls::~SrsHls() SrsHls::~SrsHls()
@ -1131,7 +1167,7 @@ SrsHls::~SrsHls()
srs_freep(jitter); srs_freep(jitter);
srs_freep(muxer); srs_freep(muxer);
srs_freep(ts_cache); srs_freep(hls_cache);
srs_freep(pithy_print); srs_freep(pithy_print);
} }
@ -1144,32 +1180,13 @@ int SrsHls::on_publish(SrsRequest* req)
if (hls_enabled) { if (hls_enabled) {
return ret; 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)) { if (!_srs_config->get_hls_enabled(vhost)) {
return ret; return ret;
} }
// if enabled, open the muxer. if ((ret = hls_cache->on_publish(muxer, req, stream_dts)) != ERROR_SUCCESS) {
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);
return ret; return ret;
} }
@ -1178,6 +1195,9 @@ int SrsHls::on_publish(SrsRequest* req)
srs_error("callback source hls start failed. ret=%d", ret); srs_error("callback source hls start failed. ret=%d", ret);
return ret; return ret;
} }
// if enabled, open the muxer.
hls_enabled = true;
return ret; return ret;
} }
@ -1190,12 +1210,8 @@ void SrsHls::on_unpublish()
if (!hls_enabled) { if (!hls_enabled) {
return; 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); 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. // the pts calc from rtmp/flv header.
int64_t pts = audio->header.timestamp * 90; int64_t pts = audio->header.timestamp * 90;
if ((ret = ts_cache->write_audio(codec, muxer, pts, sample)) != ERROR_SUCCESS) { // for pure audio, we need to update the stream dts also.
srs_error("ts cache write audio failed. ret=%d", ret); 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; return ret;
} }
@ -1327,8 +1346,9 @@ int SrsHls::on_video(SrsSharedPtrMessage* video)
} }
int64_t dts = video->header.timestamp * 90; int64_t dts = video->header.timestamp * 90;
if ((ret = ts_cache->write_video(codec, muxer, dts, sample)) != ERROR_SUCCESS) { stream_dts = dts;
srs_error("ts cache write video failed. ret=%d", ret); if ((ret = hls_cache->write_video(codec, muxer, dts, sample)) != ERROR_SUCCESS) {
srs_error("hls cache write video failed. ret=%d", ret);
return ret; return ret;
} }

View file

@ -76,7 +76,10 @@ public:
virtual void on_buffer_continue(); 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 class SrsTSMuxer
{ {
private: private:
@ -93,11 +96,14 @@ public:
}; };
/** /**
* the wrapper of m3u8 segment from specification:
*
* 3.3.2. EXTINF * 3.3.2. EXTINF
* The EXTINF tag specifies the duration of a media segment. * The EXTINF tag specifies the duration of a media segment.
*/ */
struct SrsM3u8Segment class SrsHlsSegment
{ {
public:
// duration in seconds in m3u8. // duration in seconds in m3u8.
double duration; double duration;
// sequence number in m3u8. // sequence number in m3u8.
@ -111,19 +117,25 @@ struct SrsM3u8Segment
// current segment start dts for m3u8 // current segment start dts for m3u8
int64_t segment_start_dts; int64_t segment_start_dts;
SrsM3u8Segment(); SrsHlsSegment();
virtual ~SrsM3u8Segment(); virtual ~SrsHlsSegment();
/** /**
* update the segment duration. * 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: private:
std::string app; std::string app;
@ -135,23 +147,39 @@ private:
private: private:
int file_index; int file_index;
std::string m3u8; 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: private:
/** /**
* m3u8 segments. * m3u8 segments.
*/ */
std::vector<SrsM3u8Segment*> segments; std::vector<SrsHlsSegment*> segments;
/** /**
* current writing segment. * current writing segment.
*/ */
SrsM3u8Segment* current; SrsHlsSegment* current;
// last known dts
int64_t video_stream_dts;
public: public:
SrsM3u8Muxer(); SrsHlsMuxer();
virtual ~SrsM3u8Muxer(); virtual ~SrsHlsMuxer();
public: public:
virtual int update_config(std::string _app, std::string _stream, std::string path, int fragment, int window); 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_audio(SrsMpegtsFrame* af, SrsCodecBuffer* ab);
virtual int flush_video(SrsMpegtsFrame* af, SrsCodecBuffer* ab, SrsMpegtsFrame* vf, SrsCodecBuffer* vb); virtual int flush_video(SrsMpegtsFrame* af, SrsCodecBuffer* ab, SrsMpegtsFrame* vf, SrsCodecBuffer* vb);
virtual int segment_close(); 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: private:
// current frame and buffer // current frame and buffer
@ -178,34 +220,37 @@ private:
// time jitter for aac // time jitter for aac
SrsHlsAacJitter* aac_jitter; SrsHlsAacJitter* aac_jitter;
public: public:
SrsTSCache(); SrsHlsCache();
virtual ~SrsTSCache(); virtual ~SrsHlsCache();
public: 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. * 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. * write video to muxer.
*/ */
virtual int write_video(SrsCodec* codec, SrsM3u8Muxer* muxer, int64_t dts, SrsCodecSample* sample); virtual int write_video(SrsCodec* codec, SrsHlsMuxer* muxer, int64_t dts, SrsCodecSample* sample);
/**
* flush audio in cache to muxer.
*/
virtual int flush_audio(SrsM3u8Muxer* muxer);
private: private:
virtual int cache_audio(SrsCodec* codec, SrsCodecSample* sample); virtual int cache_audio(SrsCodec* codec, SrsCodecSample* sample);
virtual int cache_video(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 class SrsHls
{ {
private: private:
SrsM3u8Muxer* muxer; SrsHlsMuxer* muxer;
SrsTSCache* ts_cache; SrsHlsCache* hls_cache;
private: private:
bool hls_enabled; bool hls_enabled;
SrsSource* source; SrsSource* source;
@ -213,6 +258,20 @@ private:
SrsCodecSample* sample; SrsCodecSample* sample;
SrsRtmpJitter* jitter; SrsRtmpJitter* jitter;
SrsPithyPrint* pithy_print; 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: public:
SrsHls(SrsSource* _source); SrsHls(SrsSource* _source);
virtual ~SrsHls(); virtual ~SrsHls();