2013-11-26 02:56:29 +00:00
|
|
|
/*
|
|
|
|
The MIT License (MIT)
|
|
|
|
|
2014-12-31 12:32:09 +00:00
|
|
|
Copyright (c) 2013-2015 winlin
|
2013-11-26 02:56:29 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2014-03-02 13:49:09 +00:00
|
|
|
#include <srs_app_hls.hpp>
|
2013-11-26 02:56:29 +00:00
|
|
|
|
2014-07-13 05:42:08 +00:00
|
|
|
/**
|
|
|
|
* the HLS section, only available when HLS enabled.
|
|
|
|
*/
|
2014-04-15 06:01:57 +00:00
|
|
|
#ifdef SRS_AUTO_HLS
|
2013-11-27 14:41:58 +00:00
|
|
|
|
2013-11-26 02:56:29 +00:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2013-12-02 03:46:39 +00:00
|
|
|
#include <algorithm>
|
2014-03-21 05:10:47 +00:00
|
|
|
using namespace std;
|
2013-12-02 03:46:39 +00:00
|
|
|
|
2014-03-01 02:30:16 +00:00
|
|
|
#include <srs_kernel_error.hpp>
|
2014-05-28 09:37:15 +00:00
|
|
|
#include <srs_kernel_codec.hpp>
|
2015-01-23 02:07:20 +00:00
|
|
|
#include <srs_rtmp_amf0.hpp>
|
|
|
|
#include <srs_rtmp_stack.hpp>
|
2014-03-02 13:49:09 +00:00
|
|
|
#include <srs_app_config.hpp>
|
|
|
|
#include <srs_app_source.hpp>
|
2013-11-26 03:48:18 +00:00
|
|
|
#include <srs_core_autofree.hpp>
|
2015-01-23 02:07:20 +00:00
|
|
|
#include <srs_rtmp_sdk.hpp>
|
2014-03-02 13:49:09 +00:00
|
|
|
#include <srs_app_pithy_print.hpp>
|
2014-06-08 05:03:03 +00:00
|
|
|
#include <srs_kernel_utility.hpp>
|
2015-01-31 13:16:42 +00:00
|
|
|
#include <srs_kernel_codec.hpp>
|
2014-07-04 23:33:18 +00:00
|
|
|
#include <srs_kernel_file.hpp>
|
2015-01-23 02:07:20 +00:00
|
|
|
#include <srs_rtmp_buffer.hpp>
|
2015-01-25 02:54:25 +00:00
|
|
|
#include <srs_kernel_ts.hpp>
|
2013-11-26 02:56:29 +00:00
|
|
|
|
2014-03-21 09:35:27 +00:00
|
|
|
// drop the segment when duration of ts too small.
|
2014-04-15 06:01:57 +00:00
|
|
|
#define SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS 100
|
2014-03-21 09:35:27 +00:00
|
|
|
|
2015-02-03 08:01:07 +00:00
|
|
|
ISrsHlsHandler::ISrsHlsHandler()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ISrsHlsHandler::~ISrsHlsHandler()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
SrsHlsCacheWriter::SrsHlsCacheWriter(bool write_cache, bool write_file)
|
|
|
|
{
|
|
|
|
should_write_cache = write_cache;
|
|
|
|
should_write_file = write_file;
|
|
|
|
}
|
|
|
|
|
|
|
|
SrsHlsCacheWriter::~SrsHlsCacheWriter()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
int SrsHlsCacheWriter::open(string file)
|
|
|
|
{
|
|
|
|
if (!should_write_file) {
|
|
|
|
return ERROR_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
return impl.open(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SrsHlsCacheWriter::close()
|
|
|
|
{
|
|
|
|
if (!should_write_file) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SrsHlsCacheWriter::is_open()
|
|
|
|
{
|
|
|
|
if (!should_write_file) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return impl.is_open();
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t SrsHlsCacheWriter::tellg()
|
|
|
|
{
|
|
|
|
if (!should_write_file) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return impl.tellg();
|
|
|
|
}
|
|
|
|
|
|
|
|
int SrsHlsCacheWriter::write(void* buf, size_t count, ssize_t* pnwrite)
|
|
|
|
{
|
|
|
|
if (should_write_cache) {
|
|
|
|
if (count > 0) {
|
|
|
|
data.append((char*)buf, count);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (should_write_file) {
|
|
|
|
return impl.write(buf, count, pnwrite);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ERROR_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
string SrsHlsCacheWriter::cache()
|
|
|
|
{
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
SrsHlsSegment::SrsHlsSegment(bool write_cache, bool write_file)
|
2013-12-02 06:24:09 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
duration = 0;
|
|
|
|
sequence_no = 0;
|
|
|
|
segment_start_dts = 0;
|
2014-03-21 09:10:24 +00:00
|
|
|
is_sequence_header = false;
|
2015-02-03 08:01:07 +00:00
|
|
|
writer = new SrsHlsCacheWriter(write_cache, write_file);
|
2015-01-22 10:32:10 +00:00
|
|
|
muxer = new SrsTSMuxer(writer);
|
2013-12-02 06:24:09 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment::~SrsHlsSegment()
|
2013-11-26 02:56:29 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_freep(muxer);
|
2015-01-22 10:32:10 +00:00
|
|
|
srs_freep(writer);
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
2013-11-27 09:30:16 +00:00
|
|
|
|
2014-03-20 10:55:45 +00:00
|
|
|
void SrsHlsSegment::update_duration(int64_t current_frame_dts)
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2014-03-20 10:55:45 +00:00
|
|
|
// 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) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
duration = (current_frame_dts - segment_start_dts) / 90000.0;
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_assert(duration >= 0);
|
|
|
|
|
2014-03-20 10:55:45 +00:00
|
|
|
return;
|
2013-11-26 02:56:29 +00:00
|
|
|
}
|
|
|
|
|
2015-02-03 08:01:07 +00:00
|
|
|
SrsHlsMuxer::SrsHlsMuxer(ISrsHlsHandler* h)
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2015-02-03 08:01:07 +00:00
|
|
|
req = NULL;
|
|
|
|
handler = h;
|
2014-03-18 03:32:58 +00:00
|
|
|
hls_fragment = hls_window = 0;
|
2014-05-04 02:45:13 +00:00
|
|
|
_sequence_no = 0;
|
2014-03-18 03:32:58 +00:00
|
|
|
current = NULL;
|
2015-01-25 09:06:49 +00:00
|
|
|
acodec = SrsCodecAudioReserved1;
|
2015-02-03 08:01:07 +00:00
|
|
|
should_write_cache = false;
|
|
|
|
should_write_file = true;
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
2013-11-26 08:06:58 +00:00
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsMuxer::~SrsHlsMuxer()
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2014-03-20 10:19:08 +00:00
|
|
|
std::vector<SrsHlsSegment*>::iterator it;
|
2014-03-18 03:32:58 +00:00
|
|
|
for (it = segments.begin(); it != segments.end(); ++it) {
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment* segment = *it;
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_freep(segment);
|
|
|
|
}
|
|
|
|
segments.clear();
|
|
|
|
|
|
|
|
srs_freep(current);
|
2015-02-03 08:01:07 +00:00
|
|
|
srs_freep(req);
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
2014-05-04 02:45:13 +00:00
|
|
|
int SrsHlsMuxer::sequence_no()
|
|
|
|
{
|
|
|
|
return _sequence_no;
|
|
|
|
}
|
|
|
|
|
2015-02-03 08:01:07 +00:00
|
|
|
int SrsHlsMuxer::update_config(SrsRequest* r, string path, int fragment, int window)
|
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
2015-02-03 08:01:07 +00:00
|
|
|
srs_freep(req);
|
|
|
|
req = r->copy();
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
hls_path = path;
|
|
|
|
hls_fragment = fragment;
|
|
|
|
hls_window = window;
|
2015-02-03 08:01:07 +00:00
|
|
|
|
|
|
|
std::string storage = _srs_config->get_hls_storage(r->vhost);
|
|
|
|
if (storage == "ram") {
|
|
|
|
should_write_cache = true;
|
|
|
|
should_write_file = false;
|
|
|
|
} else if (storage == "disk") {
|
|
|
|
should_write_cache = false;
|
|
|
|
should_write_file = true;
|
|
|
|
} else {
|
|
|
|
srs_assert(storage == "both");
|
|
|
|
should_write_cache = true;
|
|
|
|
should_write_file = true;
|
|
|
|
}
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
return ret;
|
2013-11-26 02:56:29 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
int SrsHlsMuxer::segment_open(int64_t segment_start_dts)
|
2013-11-26 08:06:58 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
if (current) {
|
|
|
|
srs_warn("ignore the segment open, for segment is already open.");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: create all parents dirs.
|
|
|
|
// create dir for app.
|
2015-02-03 08:01:07 +00:00
|
|
|
if (should_write_file && (ret = create_dir()) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// when segment open, the current segment must be NULL.
|
|
|
|
srs_assert(!current);
|
|
|
|
|
|
|
|
// new segment.
|
2015-02-03 08:01:07 +00:00
|
|
|
current = new SrsHlsSegment(should_write_cache, should_write_file);
|
2014-05-04 02:45:13 +00:00
|
|
|
current->sequence_no = _sequence_no++;
|
2014-03-20 10:19:08 +00:00
|
|
|
current->segment_start_dts = segment_start_dts;
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
// generate filename.
|
|
|
|
char filename[128];
|
|
|
|
snprintf(filename, sizeof(filename),
|
2015-02-03 08:01:07 +00:00
|
|
|
"%s-%d.ts", req->stream.c_str(), current->sequence_no);
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
// TODO: use temp file and rename it.
|
|
|
|
current->full_path = hls_path;
|
|
|
|
current->full_path += "/";
|
2015-02-03 08:01:07 +00:00
|
|
|
current->full_path += req->app;
|
2014-03-18 03:32:58 +00:00
|
|
|
current->full_path += "/";
|
|
|
|
current->full_path += filename;
|
|
|
|
|
|
|
|
// TODO: support base url, and so on.
|
|
|
|
current->uri = filename;
|
|
|
|
|
2014-03-21 05:10:47 +00:00
|
|
|
std::string tmp_file = current->full_path + ".tmp";
|
|
|
|
if ((ret = current->muxer->open(tmp_file.c_str())) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("open hls muxer failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
2014-04-03 10:32:51 +00:00
|
|
|
srs_info("open HLS muxer success. path=%s, tmp=%s",
|
|
|
|
current->full_path.c_str(), tmp_file.c_str());
|
2015-01-25 09:06:49 +00:00
|
|
|
|
|
|
|
// set the segment muxer audio codec.
|
|
|
|
if (acodec != SrsCodecAudioReserved1) {
|
|
|
|
current->muxer->update_acodec(acodec);
|
|
|
|
}
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
return ret;
|
2013-11-26 08:06:58 +00:00
|
|
|
}
|
|
|
|
|
2014-03-21 09:10:24 +00:00
|
|
|
int SrsHlsMuxer::on_sequence_header()
|
|
|
|
{
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
srs_assert(current);
|
|
|
|
|
|
|
|
// set the current segment to sequence header,
|
|
|
|
// when close the segement, it will write a discontinuity to m3u8 file.
|
|
|
|
current->is_sequence_header = true;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
bool SrsHlsMuxer::is_segment_overflow()
|
|
|
|
{
|
|
|
|
srs_assert(current);
|
|
|
|
return current->duration >= hls_fragment;
|
|
|
|
}
|
|
|
|
|
2015-01-23 07:29:14 +00:00
|
|
|
bool SrsHlsMuxer::is_segment_absolutely_overflow()
|
|
|
|
{
|
|
|
|
srs_assert(current);
|
|
|
|
return current->duration >= 2 * hls_fragment;
|
|
|
|
}
|
|
|
|
|
2015-01-25 09:06:49 +00:00
|
|
|
int SrsHlsMuxer::update_acodec(SrsCodecAudio ac)
|
2015-01-25 08:42:22 +00:00
|
|
|
{
|
|
|
|
srs_assert(current);
|
|
|
|
srs_assert(current->muxer);
|
2015-01-25 09:06:49 +00:00
|
|
|
acodec = ac;
|
|
|
|
return current->muxer->update_acodec(ac);
|
2015-01-25 08:42:22 +00:00
|
|
|
}
|
|
|
|
|
2014-12-04 03:27:07 +00:00
|
|
|
int SrsHlsMuxer::flush_audio(SrsMpegtsFrame* af, SrsSimpleBuffer* ab)
|
2013-11-26 08:06:58 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
2015-02-10 08:58:13 +00:00
|
|
|
// if flushed yet, ignore.
|
|
|
|
if (ab->length() == 0) {
|
|
|
|
srs_info("ignore hls segment audio flushed yet.");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
// if current is NULL, segment is not open, ignore the flush event.
|
|
|
|
if (!current) {
|
|
|
|
srs_warn("flush audio ignored, for segment is not open.");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-07-15 02:44:06 +00:00
|
|
|
if (ab->length() <= 0) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:55:45 +00:00
|
|
|
// update the duration of segment.
|
|
|
|
current->update_duration(af->pts);
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
if ((ret = current->muxer->write_audio(af, ab)) != ERROR_SUCCESS) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// write success, clear and free the buffer
|
2014-07-15 02:44:06 +00:00
|
|
|
ab->erase(ab->length());
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
return ret;
|
2013-11-26 08:06:58 +00:00
|
|
|
}
|
|
|
|
|
2014-12-04 03:27:07 +00:00
|
|
|
int SrsHlsMuxer::flush_video(SrsMpegtsFrame* /*af*/, SrsSimpleBuffer* /*ab*/, SrsMpegtsFrame* vf, SrsSimpleBuffer* vb)
|
2013-11-26 08:06:58 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
2015-02-10 08:58:13 +00:00
|
|
|
// if flushed yet, ignore.
|
|
|
|
if (vb->length() == 0) {
|
|
|
|
srs_info("ignore hls segment video flushed yet.");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
// if current is NULL, segment is not open, ignore the flush event.
|
|
|
|
if (!current) {
|
|
|
|
srs_warn("flush video ignored, for segment is not open.");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
srs_assert(current);
|
|
|
|
|
|
|
|
// update the duration of segment.
|
2014-03-20 10:19:08 +00:00
|
|
|
current->update_duration(vf->dts);
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
if ((ret = current->muxer->write_video(vf, vb)) != ERROR_SUCCESS) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:55:45 +00:00
|
|
|
// write success, clear and free the buffer
|
2014-07-15 02:44:06 +00:00
|
|
|
vb->erase(vb->length());
|
2014-03-20 10:55:45 +00:00
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
2014-03-21 05:10:47 +00:00
|
|
|
int SrsHlsMuxer::segment_close(string log_desc)
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
if (!current) {
|
|
|
|
srs_warn("ignore the segment close, for segment is not open.");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// when close current segment, the current segment must not be NULL.
|
|
|
|
srs_assert(current);
|
|
|
|
|
|
|
|
// assert segment duplicate.
|
2014-03-20 10:19:08 +00:00
|
|
|
std::vector<SrsHlsSegment*>::iterator it;
|
2014-03-18 03:32:58 +00:00
|
|
|
it = std::find(segments.begin(), segments.end(), current);
|
|
|
|
srs_assert(it == segments.end());
|
|
|
|
|
2014-03-21 09:35:27 +00:00
|
|
|
// valid, add to segments if segment duration is ok
|
2014-04-15 06:01:57 +00:00
|
|
|
if (current->duration * 1000 >= SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) {
|
2014-03-21 09:35:27 +00:00
|
|
|
segments.push_back(current);
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2014-05-04 02:45:13 +00:00
|
|
|
srs_info("%s reap ts segment, sequence_no=%d, uri=%s, duration=%.2f, start=%"PRId64"",
|
2014-03-21 09:35:27 +00:00
|
|
|
log_desc.c_str(), current->sequence_no, current->uri.c_str(), current->duration,
|
|
|
|
current->segment_start_dts);
|
2015-02-03 08:01:07 +00:00
|
|
|
|
|
|
|
// notify handler for update ts.
|
|
|
|
srs_assert(current->writer);
|
|
|
|
if (handler && (ret = handler->on_update_ts(req, current->uri, current->writer->cache())) != ERROR_SUCCESS) {
|
|
|
|
srs_error("notify handler for update ts failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2014-03-21 09:35:27 +00:00
|
|
|
// close the muxer of finished segment.
|
|
|
|
srs_freep(current->muxer);
|
2014-03-26 10:13:33 +00:00
|
|
|
std::string full_path = current->full_path;
|
|
|
|
current = NULL;
|
|
|
|
|
2014-03-21 09:35:27 +00:00
|
|
|
// rename from tmp to real path
|
2014-03-26 10:13:33 +00:00
|
|
|
std::string tmp_file = full_path + ".tmp";
|
2015-02-03 08:01:07 +00:00
|
|
|
if (should_write_file && rename(tmp_file.c_str(), full_path.c_str()) < 0) {
|
2014-03-21 09:35:27 +00:00
|
|
|
ret = ERROR_HLS_WRITE_FAILED;
|
|
|
|
srs_error("rename ts file failed, %s => %s. ret=%d",
|
2014-03-26 10:13:33 +00:00
|
|
|
tmp_file.c_str(), full_path.c_str(), ret);
|
2014-03-21 09:35:27 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// reuse current segment index.
|
2014-05-04 02:45:13 +00:00
|
|
|
_sequence_no--;
|
2014-03-21 09:35:27 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
// rename from tmp to real path
|
|
|
|
std::string tmp_file = current->full_path + ".tmp";
|
2015-02-03 08:01:07 +00:00
|
|
|
if (should_write_file) {
|
|
|
|
unlink(tmp_file.c_str());
|
|
|
|
}
|
2014-03-21 09:35:27 +00:00
|
|
|
|
|
|
|
srs_freep(current);
|
2014-03-21 05:10:47 +00:00
|
|
|
}
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
// the segments to remove
|
2014-03-20 10:19:08 +00:00
|
|
|
std::vector<SrsHlsSegment*> segment_to_remove;
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
// shrink the segments.
|
|
|
|
double duration = 0;
|
|
|
|
int remove_index = -1;
|
|
|
|
for (int i = segments.size() - 1; i >= 0; i--) {
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment* segment = segments[i];
|
2014-03-18 03:32:58 +00:00
|
|
|
duration += segment->duration;
|
|
|
|
|
|
|
|
if ((int)duration > hls_window) {
|
|
|
|
remove_index = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (int i = 0; i < remove_index && !segments.empty(); i++) {
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment* segment = *segments.begin();
|
2014-03-18 03:32:58 +00:00
|
|
|
segments.erase(segments.begin());
|
|
|
|
segment_to_remove.push_back(segment);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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++) {
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment* segment = segment_to_remove[i];
|
2014-03-18 03:32:58 +00:00
|
|
|
unlink(segment->full_path.c_str());
|
|
|
|
srs_freep(segment);
|
|
|
|
}
|
|
|
|
segment_to_remove.clear();
|
|
|
|
|
|
|
|
// check ret of refresh m3u8
|
|
|
|
if (ret != ERROR_SUCCESS) {
|
|
|
|
srs_error("refresh m3u8 failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2013-11-26 08:06:58 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
int SrsHlsMuxer::refresh_m3u8()
|
2013-11-26 08:06:58 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
std::string m3u8_file = hls_path;
|
|
|
|
m3u8_file += "/";
|
2015-02-03 08:01:07 +00:00
|
|
|
m3u8_file += req->app;
|
2014-03-18 03:32:58 +00:00
|
|
|
m3u8_file += "/";
|
2015-02-03 08:01:07 +00:00
|
|
|
m3u8_file += req->stream;
|
2014-03-18 03:32:58 +00:00
|
|
|
m3u8_file += ".m3u8";
|
|
|
|
|
|
|
|
m3u8 = m3u8_file;
|
|
|
|
m3u8_file += ".temp";
|
|
|
|
|
2015-02-03 08:01:07 +00:00
|
|
|
if ((ret = _refresh_m3u8(m3u8_file)) == ERROR_SUCCESS) {
|
|
|
|
if (should_write_file && rename(m3u8_file.c_str(), m3u8.c_str()) < 0) {
|
2014-03-18 03:32:58 +00:00
|
|
|
ret = ERROR_HLS_WRITE_FAILED;
|
2015-02-03 08:01:07 +00:00
|
|
|
srs_error("rename m3u8 file failed. %s => %s, ret=%d", m3u8_file.c_str(), m3u8.c_str(), ret);
|
2014-03-18 03:32:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove the temp file.
|
|
|
|
unlink(m3u8_file.c_str());
|
|
|
|
|
|
|
|
return ret;
|
2013-11-26 08:06:58 +00:00
|
|
|
}
|
|
|
|
|
2015-02-03 08:01:07 +00:00
|
|
|
int SrsHlsMuxer::_refresh_m3u8(string m3u8_file)
|
2013-11-26 08:06:58 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
// no segments, return.
|
|
|
|
if (segments.size() == 0) {
|
|
|
|
return ret;
|
|
|
|
}
|
2015-02-03 08:01:07 +00:00
|
|
|
|
|
|
|
SrsHlsCacheWriter writer(should_write_cache, should_write_file);
|
|
|
|
if ((ret = writer.open(m3u8_file)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("open m3u8 file %s failed. ret=%d", m3u8_file.c_str(), ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
srs_info("open m3u8 file %s success.", m3u8_file.c_str());
|
|
|
|
|
|
|
|
// #EXTM3U\n#EXT-X-VERSION:3\n
|
|
|
|
char header[] = {
|
|
|
|
// #EXTM3U\n
|
2015-02-10 03:38:14 +00:00
|
|
|
0x23, 0x45, 0x58, 0x54, 0x4d, 0x33, 0x55, SRS_CONSTS_LF,
|
2014-03-18 03:32:58 +00:00
|
|
|
// #EXT-X-VERSION:3\n
|
|
|
|
0x23, 0x45, 0x58, 0x54, 0x2d, 0x58, 0x2d, 0x56, 0x45, 0x52,
|
2015-02-10 03:38:14 +00:00
|
|
|
0x53, 0x49, 0x4f, 0x4e, 0x3a, 0x33, SRS_CONSTS_LF,
|
|
|
|
// #EXT-X-ALLOW-CACHE:NO\n
|
2014-07-04 01:53:50 +00:00
|
|
|
0x23, 0x45, 0x58, 0x54, 0x2d, 0x58, 0x2d, 0x41, 0x4c, 0x4c,
|
2015-02-10 03:38:14 +00:00
|
|
|
0x4f, 0x57, 0x2d, 0x43, 0x41, 0x43, 0x48, 0x45, 0x3a, 0x4e, 0x4f, SRS_CONSTS_LF
|
2014-03-18 03:32:58 +00:00
|
|
|
};
|
2015-02-03 08:01:07 +00:00
|
|
|
if ((ret = writer.write(header, sizeof(header), NULL)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("write m3u8 header failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
srs_verbose("write m3u8 header success.");
|
|
|
|
|
|
|
|
// #EXT-X-MEDIA-SEQUENCE:4294967295\n
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment* first = *segments.begin();
|
2014-03-18 03:32:58 +00:00
|
|
|
char sequence[34] = {};
|
2015-02-10 03:38:14 +00:00
|
|
|
int len = snprintf(sequence, sizeof(sequence), "#EXT-X-MEDIA-SEQUENCE:%d%c", first->sequence_no, SRS_CONSTS_LF);
|
2015-02-03 08:01:07 +00:00
|
|
|
if ((ret = writer.write(sequence, len, NULL)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("write m3u8 sequence failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
srs_verbose("write m3u8 sequence success.");
|
|
|
|
|
|
|
|
// #EXT-X-TARGETDURATION:4294967295\n
|
|
|
|
int target_duration = 0;
|
2015-02-10 07:18:20 +00:00
|
|
|
/**
|
|
|
|
* @see hls-m3u8-draft-pantos-http-live-streaming-12.pdf, page 25
|
|
|
|
* The Media Playlist file MUST contain an EXT-X-TARGETDURATION tag.
|
|
|
|
* Its value MUST be equal to or greater than the EXTINF duration of any
|
|
|
|
* media segment that appears or will appear in the Playlist file,
|
|
|
|
* rounded to the nearest integer. Its value MUST NOT change. A
|
|
|
|
* typical target duration is 10 seconds.
|
|
|
|
*/
|
|
|
|
// TODO: FIXME: finger it out whether it should not changed.
|
2014-03-20 10:19:08 +00:00
|
|
|
std::vector<SrsHlsSegment*>::iterator it;
|
2014-03-18 03:32:58 +00:00
|
|
|
for (it = segments.begin(); it != segments.end(); ++it) {
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment* segment = *it;
|
2014-03-18 03:32:58 +00:00
|
|
|
target_duration = srs_max(target_duration, (int)segment->duration);
|
|
|
|
}
|
|
|
|
// TODO: maybe need to take an around value
|
|
|
|
target_duration += 1;
|
2014-03-21 09:10:24 +00:00
|
|
|
char duration[34]; // 23+10+1
|
2015-02-10 03:38:14 +00:00
|
|
|
len = snprintf(duration, sizeof(duration), "#EXT-X-TARGETDURATION:%d%c", target_duration, SRS_CONSTS_LF);
|
2015-02-03 08:01:07 +00:00
|
|
|
if ((ret = writer.write(duration, len, NULL)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("write m3u8 duration failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
srs_verbose("write m3u8 duration success.");
|
|
|
|
|
|
|
|
// write all segments
|
|
|
|
for (it = segments.begin(); it != segments.end(); ++it) {
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment* segment = *it;
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2014-03-21 09:10:24 +00:00
|
|
|
if (segment->is_sequence_header) {
|
|
|
|
// #EXT-X-DISCONTINUITY\n
|
|
|
|
char ext_discon[22]; // 21+1
|
2015-02-10 03:38:14 +00:00
|
|
|
len = snprintf(ext_discon, sizeof(ext_discon), "#EXT-X-DISCONTINUITY%c", SRS_CONSTS_LF);
|
2015-02-03 08:01:07 +00:00
|
|
|
if ((ret = writer.write(ext_discon, len, NULL)) != ERROR_SUCCESS) {
|
2014-03-21 09:10:24 +00:00
|
|
|
srs_error("write m3u8 segment discontinuity failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
srs_verbose("write m3u8 segment discontinuity success.");
|
|
|
|
}
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
// "#EXTINF:4294967295.208,\n"
|
2014-03-21 09:10:24 +00:00
|
|
|
char ext_info[25]; // 14+10+1
|
2015-02-10 03:38:14 +00:00
|
|
|
len = snprintf(ext_info, sizeof(ext_info), "#EXTINF:%.3f,%c", segment->duration, SRS_CONSTS_LF);
|
2015-02-03 08:01:07 +00:00
|
|
|
if ((ret = writer.write(ext_info, len, NULL)) != ERROR_SUCCESS) {
|
2014-03-21 09:10:24 +00:00
|
|
|
srs_error("write m3u8 segment info failed. ret=%d", ret);
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2014-03-21 09:10:24 +00:00
|
|
|
srs_verbose("write m3u8 segment info success.");
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2015-02-10 03:38:14 +00:00
|
|
|
// {file name}\n
|
2014-03-18 03:32:58 +00:00
|
|
|
std::string filename = segment->uri;
|
2015-02-10 03:38:14 +00:00
|
|
|
filename += SRS_CONSTS_LF;
|
2015-02-03 08:01:07 +00:00
|
|
|
if ((ret = writer.write((char*)filename.c_str(), (int)filename.length(), NULL)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("write m3u8 segment uri failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
srs_verbose("write m3u8 segment uri success.");
|
|
|
|
}
|
|
|
|
srs_info("write m3u8 %s success.", m3u8_file.c_str());
|
2015-02-03 08:01:07 +00:00
|
|
|
|
|
|
|
// notify handler for update m3u8.
|
|
|
|
if (handler && (ret = handler->on_update_m3u8(req, writer.cache())) != ERROR_SUCCESS) {
|
|
|
|
srs_error("notify handler for update m3u8 failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
return ret;
|
2013-11-26 09:21:49 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
int SrsHlsMuxer::create_dir()
|
2013-11-26 09:21:49 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
2015-02-03 08:01:07 +00:00
|
|
|
|
|
|
|
if (!should_write_file) {
|
|
|
|
return ret;
|
|
|
|
}
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
std::string app_dir = hls_path;
|
|
|
|
app_dir += "/";
|
2015-02-03 08:01:07 +00:00
|
|
|
app_dir += req->app;
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
// TODO: cleanup the dir when startup.
|
2015-01-03 04:54:54 +00:00
|
|
|
|
|
|
|
if ((ret = srs_create_dir_recursively(app_dir)) != ERROR_SUCCESS) {
|
|
|
|
srs_error("create app dir %s failed. ret=%d", app_dir.c_str(), ret);
|
|
|
|
return ret;
|
2014-03-18 03:32:58 +00:00
|
|
|
}
|
2015-01-03 04:54:54 +00:00
|
|
|
srs_info("create app dir %s ok", app_dir.c_str());
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
return ret;
|
2013-11-26 08:06:58 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsCache::SrsHlsCache()
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2015-01-22 10:13:33 +00:00
|
|
|
cache = new SrsTsCache();
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsCache::~SrsHlsCache()
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2015-01-22 10:13:33 +00:00
|
|
|
srs_freep(cache);
|
2013-12-02 14:09:10 +00:00
|
|
|
}
|
2014-03-20 10:19:08 +00:00
|
|
|
|
|
|
|
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;
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2014-08-02 14:18:39 +00:00
|
|
|
int hls_fragment = (int)_srs_config->get_hls_fragment(vhost);
|
|
|
|
int hls_window = (int)_srs_config->get_hls_window(vhost);
|
2014-03-20 10:19:08 +00:00
|
|
|
|
|
|
|
// get the hls path config
|
|
|
|
std::string hls_path = _srs_config->get_hls_path(vhost);
|
|
|
|
|
2014-03-21 09:35:27 +00:00
|
|
|
// TODO: FIXME: support load exists m3u8, to continue publish stream.
|
|
|
|
// for the HLS donot requires the EXT-X-MEDIA-SEQUENCE be monotonically increase.
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
// open muxer
|
2015-02-03 08:01:07 +00:00
|
|
|
if ((ret = muxer->update_config(req, hls_path, hls_fragment, hls_window)) != ERROR_SUCCESS) {
|
2014-03-20 10:19:08 +00:00
|
|
|
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;
|
|
|
|
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = muxer->flush_audio(cache->af, cache->ab)) != ERROR_SUCCESS) {
|
2014-03-20 10:19:08 +00:00
|
|
|
srs_error("m3u8 muxer flush audio failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-21 05:10:47 +00:00
|
|
|
if ((ret = muxer->segment_close("unpublish")) != ERROR_SUCCESS) {
|
2014-03-20 10:19:08 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2014-03-21 09:10:24 +00:00
|
|
|
|
|
|
|
int SrsHlsCache::on_sequence_header(SrsHlsMuxer* muxer)
|
|
|
|
{
|
|
|
|
// TODO: support discontinuity for the same stream
|
|
|
|
// currently we reap and insert discontinity when encoder republish,
|
|
|
|
// but actually, event when stream is not republish, the
|
|
|
|
// sequence header may change, for example,
|
|
|
|
// ffmpeg ingest a external rtmp stream and push to srs,
|
|
|
|
// when the sequence header changed, the stream is not republish.
|
|
|
|
return muxer->on_sequence_header();
|
|
|
|
}
|
2014-03-20 10:19:08 +00:00
|
|
|
|
2014-06-08 14:36:17 +00:00
|
|
|
int SrsHlsCache::write_audio(SrsAvcAacCodec* codec, SrsHlsMuxer* muxer, int64_t pts, SrsCodecSample* sample)
|
2013-12-02 14:09:10 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
// write audio to cache.
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = cache->cache_audio(codec, pts, sample)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// flush if buffer exceed max size.
|
2015-01-22 10:13:33 +00:00
|
|
|
if (cache->ab->length() > SRS_AUTO_HLS_AUDIO_CACHE_SIZE) {
|
|
|
|
if ((ret = muxer->flush_audio(cache->af, cache->ab)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
2015-01-22 10:32:10 +00:00
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
// TODO: config it.
|
|
|
|
// in ms, audio delay to flush the audios.
|
|
|
|
int64_t audio_delay = SRS_CONF_DEFAULT_AAC_DELAY;
|
|
|
|
// flush if audio delay exceed
|
2015-01-25 08:42:22 +00:00
|
|
|
if (pts - cache->audio_buffer_start_pts > audio_delay * 90) {
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = muxer->flush_audio(cache->af, cache->ab)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-08 07:15:57 +00:00
|
|
|
// reap when current source is pure audio.
|
|
|
|
// it maybe changed when stream info changed,
|
|
|
|
// for example, pure audio when start, audio/video when publishing,
|
|
|
|
// pure audio again for audio disabled.
|
|
|
|
// so we reap event when the audio incoming when segment overflow.
|
|
|
|
// @see https://github.com/winlinvip/simple-rtmp-server/issues/151
|
2015-01-23 07:29:14 +00:00
|
|
|
// we use absolutely overflow of segment to make jwplayer/ffplay happy
|
|
|
|
// @see https://github.com/winlinvip/simple-rtmp-server/issues/151#issuecomment-71155184
|
|
|
|
if (muxer->is_segment_absolutely_overflow()) {
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = reap_segment("audio", muxer, cache->af->pts)) != ERROR_SUCCESS) {
|
2014-03-20 10:55:45 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
2013-12-02 14:09:10 +00:00
|
|
|
}
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2015-01-22 10:13:33 +00:00
|
|
|
int SrsHlsCache::write_video(SrsAvcAacCodec* codec, SrsHlsMuxer* muxer, int64_t dts, SrsCodecSample* sample)
|
2013-12-02 14:09:10 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
// write video to cache.
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = cache->cache_video(codec, dts, sample)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:55:45 +00:00
|
|
|
// new segment when:
|
|
|
|
// 1. base on gop.
|
|
|
|
// 2. some gops duration overflow.
|
2015-02-10 11:09:31 +00:00
|
|
|
if (sample->frame_type == SrsCodecVideoAVCFrameKeyFrame && muxer->is_segment_overflow()) {
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = reap_segment("video", muxer, cache->vf->dts)) != ERROR_SUCCESS) {
|
2014-03-20 10:19:08 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
// flush video when got one
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = muxer->flush_video(cache->af, cache->ab, cache->vf, cache->vb)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("m3u8 muxer flush video failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:55:45 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-21 05:10:47 +00:00
|
|
|
int SrsHlsCache::reap_segment(string log_desc, SrsHlsMuxer* muxer, int64_t segment_start_dts)
|
2014-03-20 10:55:45 +00:00
|
|
|
{
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
2014-03-21 05:10:47 +00:00
|
|
|
if ((ret = muxer->segment_close(log_desc)) != ERROR_SUCCESS) {
|
2014-03-20 10:55:45 +00:00
|
|
|
srs_error("m3u8 muxer close segment 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: flush audio before or after segment?
|
2015-02-10 11:09:31 +00:00
|
|
|
// TODO: fresh segment begin with audio or video?
|
2015-02-10 08:58:13 +00:00
|
|
|
|
|
|
|
// segment open, flush video first.
|
|
|
|
if ((ret = muxer->flush_video(cache->af, cache->ab, cache->vf, cache->vb)) != ERROR_SUCCESS) {
|
|
|
|
srs_error("m3u8 muxer flush video failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:55:45 +00:00
|
|
|
// segment open, flush the audio.
|
|
|
|
// @see: ngx_rtmp_hls_open_fragment
|
|
|
|
/* start fragment with audio to make iPhone happy */
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = muxer->flush_audio(cache->af, cache->ab)) != ERROR_SUCCESS) {
|
2014-03-20 10:55:45 +00:00
|
|
|
srs_error("m3u8 muxer flush audio failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
return ret;
|
2013-12-02 14:09:10 +00:00
|
|
|
}
|
|
|
|
|
2015-02-03 08:01:07 +00:00
|
|
|
SrsHls::SrsHls(SrsSource* s, ISrsHlsHandler* h)
|
2013-12-02 14:09:10 +00:00
|
|
|
{
|
2015-02-03 08:01:07 +00:00
|
|
|
source = s;
|
|
|
|
handler = h;
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2015-02-03 08:01:07 +00:00
|
|
|
hls_enabled = false;
|
|
|
|
|
2014-06-08 14:36:17 +00:00
|
|
|
codec = new SrsAvcAacCodec();
|
2014-03-18 03:32:58 +00:00
|
|
|
sample = new SrsCodecSample();
|
|
|
|
jitter = new SrsRtmpJitter();
|
|
|
|
|
2015-02-03 08:01:07 +00:00
|
|
|
muxer = new SrsHlsMuxer(h);
|
2014-03-20 10:19:08 +00:00
|
|
|
hls_cache = new SrsHlsCache();
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2014-07-20 05:33:21 +00:00
|
|
|
pithy_print = new SrsPithyPrint(SRS_CONSTS_STAGE_HLS);
|
2014-03-20 10:19:08 +00:00
|
|
|
stream_dts = 0;
|
2013-12-02 14:09:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SrsHls::~SrsHls()
|
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_freep(codec);
|
|
|
|
srs_freep(sample);
|
|
|
|
srs_freep(jitter);
|
|
|
|
|
|
|
|
srs_freep(muxer);
|
2014-03-20 10:19:08 +00:00
|
|
|
srs_freep(hls_cache);
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
srs_freep(pithy_print);
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int SrsHls::on_publish(SrsRequest* req)
|
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
// support multiple publish.
|
|
|
|
if (hls_enabled) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
std::string vhost = req->vhost;
|
2014-03-18 03:32:58 +00:00
|
|
|
if (!_srs_config->get_hls_enabled(vhost)) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
if ((ret = hls_cache->on_publish(muxer, req, stream_dts)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-04-17 10:13:59 +00:00
|
|
|
// if enabled, open the muxer.
|
|
|
|
hls_enabled = true;
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
// notice the source to get the cached sequence header.
|
2014-04-16 03:11:53 +00:00
|
|
|
// when reload to start hls, hls will never get the sequence header in stream,
|
|
|
|
// use the SrsSource.on_hls_start to push the sequence header to HLS.
|
2014-03-18 03:32:58 +00:00
|
|
|
if ((ret = source->on_hls_start()) != ERROR_SUCCESS) {
|
|
|
|
srs_error("callback source hls start failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SrsHls::on_unpublish()
|
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
// support multiple unpublish.
|
|
|
|
if (!hls_enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
if ((ret = hls_cache->on_unpublish(muxer)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("ignore m3u8 muxer flush/close audio failed. ret=%d", ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
hls_enabled = false;
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
2013-12-15 12:29:18 +00:00
|
|
|
int SrsHls::on_meta_data(SrsAmf0Object* metadata)
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
if (!metadata) {
|
|
|
|
srs_trace("no metadata persent, hls ignored it.");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-07-16 01:37:27 +00:00
|
|
|
if (metadata->count() <= 0) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_trace("no metadata persent, hls ignored it.");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
2014-12-05 15:49:53 +00:00
|
|
|
int SrsHls::on_audio(SrsSharedPtrMessage* __audio)
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
if (!hls_enabled) {
|
|
|
|
return ret;
|
|
|
|
}
|
2014-12-05 15:49:53 +00:00
|
|
|
|
|
|
|
SrsSharedPtrMessage* audio = __audio->copy();
|
|
|
|
SrsAutoFree(SrsSharedPtrMessage, audio);
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
sample->clear();
|
|
|
|
if ((ret = codec->audio_aac_demux(audio->payload, audio->size, sample)) != ERROR_SUCCESS) {
|
2015-01-25 08:42:22 +00:00
|
|
|
if (ret != ERROR_HLS_TRY_MP3) {
|
|
|
|
srs_error("hls aac demux audio failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
if ((ret = codec->audio_mp3_demux(audio->payload, audio->size, sample)) != ERROR_SUCCESS) {
|
|
|
|
srs_error("hls mp3 demux audio failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
2014-03-18 03:32:58 +00:00
|
|
|
}
|
2015-01-25 08:42:22 +00:00
|
|
|
SrsCodecAudio acodec = (SrsCodecAudio)codec->audio_codec_id;
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2015-01-25 08:42:22 +00:00
|
|
|
// ts support audio codec: aac/mp3
|
|
|
|
if (acodec != SrsCodecAudioAAC && acodec != SrsCodecAudioMP3) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// when codec changed, write new header.
|
|
|
|
if ((ret = muxer->update_acodec(acodec)) != ERROR_SUCCESS) {
|
|
|
|
srs_error("http: ts audio write header failed. ret=%d", ret);
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ignore sequence header
|
2015-01-25 09:06:49 +00:00
|
|
|
if (acodec == SrsCodecAudioAAC && sample->aac_packet_type == SrsCodecAudioTypeSequenceHeader) {
|
2014-03-21 09:10:24 +00:00
|
|
|
return hls_cache->on_sequence_header(muxer);
|
2014-03-18 03:32:58 +00:00
|
|
|
}
|
|
|
|
|
2014-06-25 09:14:11 +00:00
|
|
|
if ((ret = jitter->correct(audio, 0, 0, SrsRtmpJitterAlgorithmFULL)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("rtmp jitter correct audio failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-01-22 09:35:07 +00:00
|
|
|
// the dts calc from rtmp/flv header.
|
|
|
|
int64_t dts = audio->timestamp * 90;
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
// for pure audio, we need to update the stream dts also.
|
2015-01-22 09:35:07 +00:00
|
|
|
stream_dts = dts;
|
2014-03-20 10:19:08 +00:00
|
|
|
|
2015-01-22 09:35:07 +00:00
|
|
|
if ((ret = hls_cache->write_audio(codec, muxer, dts, sample)) != ERROR_SUCCESS) {
|
2014-03-20 10:19:08 +00:00
|
|
|
srs_error("hls cache write audio failed. ret=%d", ret);
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
2014-12-05 15:49:53 +00:00
|
|
|
int SrsHls::on_video(SrsSharedPtrMessage* __video)
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
if (!hls_enabled) {
|
|
|
|
return ret;
|
|
|
|
}
|
2014-12-05 15:49:53 +00:00
|
|
|
|
|
|
|
SrsSharedPtrMessage* video = __video->copy();
|
|
|
|
SrsAutoFree(SrsSharedPtrMessage, video);
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
sample->clear();
|
|
|
|
if ((ret = codec->video_avc_demux(video->payload, video->size, sample)) != ERROR_SUCCESS) {
|
2014-12-20 03:45:59 +00:00
|
|
|
srs_error("hls codec demux video failed. ret=%d", ret);
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-01-14 03:32:19 +00:00
|
|
|
// ignore info frame,
|
|
|
|
// @see https://github.com/winlinvip/simple-rtmp-server/issues/288#issuecomment-69863909
|
|
|
|
if (sample->frame_type == SrsCodecVideoAVCFrameVideoInfoFrame) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
if (codec->video_codec_id != SrsCodecVideoAVC) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ignore sequence header
|
|
|
|
if (sample->frame_type == SrsCodecVideoAVCFrameKeyFrame
|
|
|
|
&& sample->avc_packet_type == SrsCodecVideoAVCTypeSequenceHeader) {
|
2014-03-21 09:10:24 +00:00
|
|
|
return hls_cache->on_sequence_header(muxer);
|
2014-03-18 03:32:58 +00:00
|
|
|
}
|
|
|
|
|
2014-06-25 09:14:11 +00:00
|
|
|
if ((ret = jitter->correct(video, 0, 0, SrsRtmpJitterAlgorithmFULL)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("rtmp jitter correct video failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-12-07 03:25:05 +00:00
|
|
|
int64_t dts = video->timestamp * 90;
|
2014-03-20 10:19:08 +00:00
|
|
|
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);
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
hls_mux();
|
|
|
|
|
|
|
|
return ret;
|
2013-11-27 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
2013-12-15 12:36:59 +00:00
|
|
|
void SrsHls::hls_mux()
|
2013-12-05 15:34:26 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
// reportable
|
|
|
|
if (pithy_print->can_print()) {
|
2014-07-06 05:48:03 +00:00
|
|
|
// the run time is not equals to stream time,
|
|
|
|
// @see: https://github.com/winlinvip/simple-rtmp-server/issues/81#issuecomment-48100994
|
|
|
|
// it's ok.
|
2014-07-20 05:23:45 +00:00
|
|
|
srs_trace("-> "SRS_CONSTS_LOG_HLS
|
2014-07-06 05:48:03 +00:00
|
|
|
" time=%"PRId64", stream dts=%"PRId64"(%"PRId64"ms), sequence_no=%d",
|
2014-07-06 01:59:41 +00:00
|
|
|
pithy_print->age(), stream_dts, stream_dts / 90, muxer->sequence_no());
|
2014-03-18 03:32:58 +00:00
|
|
|
}
|
|
|
|
|
2014-04-26 13:41:18 +00:00
|
|
|
pithy_print->elapse();
|
2013-12-05 15:34:26 +00:00
|
|
|
}
|
|
|
|
|
2013-11-27 14:41:58 +00:00
|
|
|
#endif
|
|
|
|
|
2014-08-02 14:18:39 +00:00
|
|
|
|