winlin

refine the hls module, clear logic dead code, rename TSCache to HlsCache, M3u8Mu…

…xer to HlsMuxer. that is, make it to more readable.
... ... @@ -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
... ...
... ... @@ -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<SrsM3u8Segment*>::iterator it;
std::vector<SrsHlsSegment*>::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<SrsM3u8Segment*>::iterator it;
std::vector<SrsHlsSegment*>::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<SrsM3u8Segment*> segment_to_remove;
std::vector<SrsHlsSegment*> 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<SrsM3u8Segment*>::iterator it;
std::vector<SrsHlsSegment*>::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;
}
... ...
... ... @@ -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;
... ... @@ -137,21 +149,37 @@ private:
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<SrsM3u8Segment*> segments;
std::vector<SrsHlsSegment*> 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:
/**
* write audio to cache, if need to flush, flush to muxer.
* when publish or unpublish stream.
*/
virtual int write_audio(SrsCodec* codec, SrsM3u8Muxer* muxer, int64_t pts, SrsCodecSample* sample);
virtual int on_publish(SrsHlsMuxer* muxer, SrsRequest* req, int64_t segment_start_dts);
virtual int on_unpublish(SrsHlsMuxer* muxer);
/**
* write video to muxer.
* write audio to cache, if need to flush, flush to muxer.
*/
virtual int write_video(SrsCodec* codec, SrsM3u8Muxer* muxer, int64_t dts, SrsCodecSample* sample);
virtual int write_audio(SrsCodec* codec, SrsHlsMuxer* muxer, int64_t pts, SrsCodecSample* sample);
/**
* flush audio in cache to muxer.
* write video 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();
... ...