1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-02-15 04:42:04 +00:00
srs/trunk/src/app/srs_app_hls.cpp

1213 lines
35 KiB
C++
Raw Normal View History

2017-03-25 09:21:39 +00:00
/**
* The MIT License (MIT)
*
2017-03-25 13:29:29 +00:00
* Copyright (c) 2013-2017 OSSRS(winlin)
2017-03-25 09:21:39 +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.
*/
2013-11-26 02:56:29 +00:00
#include <srs_app_hls.hpp>
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>
#include <math.h>
2013-11-26 02:56:29 +00:00
2013-12-02 03:46:39 +00:00
#include <algorithm>
#include <sstream>
2014-03-21 05:10:47 +00:00
using namespace std;
2013-12-02 03:46:39 +00:00
#include <srs_kernel_error.hpp>
2014-05-28 09:37:15 +00:00
#include <srs_kernel_codec.hpp>
2015-09-22 01:05:21 +00:00
#include <srs_protocol_amf0.hpp>
2015-01-23 02:07:20 +00:00
#include <srs_rtmp_stack.hpp>
#include <srs_app_config.hpp>
#include <srs_app_source.hpp>
#include <srs_core_autofree.hpp>
2015-06-13 08:04:59 +00:00
#include <srs_rtmp_stack.hpp>
#include <srs_app_pithy_print.hpp>
#include <srs_kernel_utility.hpp>
#include <srs_kernel_codec.hpp>
#include <srs_kernel_file.hpp>
2015-09-22 01:01:47 +00:00
#include <srs_protocol_stream.hpp>
#include <srs_kernel_ts.hpp>
#include <srs_app_utility.hpp>
#include <srs_app_http_hooks.hpp>
#include <srs_protocol_format.hpp>
2013-11-26 02:56:29 +00:00
// drop the segment when duration of ts too small.
#define SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS 100
// fragment plus the deviation percent.
#define SRS_HLS_FLOOR_REAP_PERCENT 0.3
// reset the piece id when deviation overflow this.
#define SRS_JUMP_WHEN_PIECE_DEVIATION 20
2017-02-12 12:38:39 +00:00
SrsHlsSegment::SrsHlsSegment(SrsTsContext* c, SrsAudioCodecId ac, SrsVideoCodecId vc)
2013-12-02 06:24:09 +00:00
{
2014-03-18 03:32:58 +00:00
sequence_no = 0;
writer = new SrsFileWriter();
tscw = new SrsTsContextWriter(writer, c, ac, vc);
2013-12-02 06:24:09 +00:00
}
SrsHlsSegment::~SrsHlsSegment()
2013-11-26 02:56:29 +00:00
{
srs_freep(tscw);
srs_freep(writer);
2013-12-02 07:55:10 +00:00
}
SrsDvrAsyncCallOnHls::SrsDvrAsyncCallOnHls(int c, SrsRequest* r, string p, string t, string m, string mu, int s, double d)
{
2015-05-22 23:46:45 +00:00
req = r->copy();
cid = c;
path = p;
ts_url = t;
m3u8 = m;
m3u8_url = mu;
seq_no = s;
2015-04-03 05:46:44 +00:00
duration = d;
}
SrsDvrAsyncCallOnHls::~SrsDvrAsyncCallOnHls()
{
2015-05-22 23:46:45 +00:00
srs_freep(req);
}
int SrsDvrAsyncCallOnHls::call()
{
int ret = ERROR_SUCCESS;
if (!_srs_config->get_vhost_http_hooks_enabled(req->vhost)) {
return ret;
}
// the http hooks will cause context switch,
// so we must copy all hooks for the on_connect may freed.
2015-11-11 02:37:50 +00:00
// @see https://github.com/ossrs/srs/issues/475
vector<string> hooks;
if (true) {
SrsConfDirective* conf = _srs_config->get_vhost_on_hls(req->vhost);
if (!conf) {
srs_info("ignore the empty http callback: on_hls");
return ret;
}
hooks = conf->args;
}
for (int i = 0; i < (int)hooks.size(); i++) {
std::string url = hooks.at(i);
if ((ret = SrsHttpHooks::on_hls(cid, url, req, path, ts_url, m3u8, m3u8_url, seq_no, duration)) != ERROR_SUCCESS) {
srs_error("hook client on_hls failed. url=%s, ret=%d", url.c_str(), ret);
return ret;
}
}
return ret;
}
string SrsDvrAsyncCallOnHls::to_string()
{
return "on_hls: " + path;
}
SrsDvrAsyncCallOnHlsNotify::SrsDvrAsyncCallOnHlsNotify(int c, SrsRequest* r, string u)
{
cid = c;
2015-05-22 23:46:45 +00:00
req = r->copy();
ts_url = u;
}
SrsDvrAsyncCallOnHlsNotify::~SrsDvrAsyncCallOnHlsNotify()
{
2015-05-22 23:46:45 +00:00
srs_freep(req);
}
int SrsDvrAsyncCallOnHlsNotify::call()
{
int ret = ERROR_SUCCESS;
if (!_srs_config->get_vhost_http_hooks_enabled(req->vhost)) {
return ret;
}
// the http hooks will cause context switch,
// so we must copy all hooks for the on_connect may freed.
2015-11-11 02:37:50 +00:00
// @see https://github.com/ossrs/srs/issues/475
vector<string> hooks;
if (true) {
SrsConfDirective* conf = _srs_config->get_vhost_on_hls_notify(req->vhost);
if (!conf) {
srs_info("ignore the empty http callback: on_hls_notify");
return ret;
}
hooks = conf->args;
}
int nb_notify = _srs_config->get_vhost_hls_nb_notify(req->vhost);
for (int i = 0; i < (int)hooks.size(); i++) {
std::string url = hooks.at(i);
if ((ret = SrsHttpHooks::on_hls_notify(cid, url, req, ts_url, nb_notify)) != ERROR_SUCCESS) {
srs_error("hook client on_hls_notify failed. url=%s, ret=%d", url.c_str(), ret);
return ret;
}
}
return ret;
}
string SrsDvrAsyncCallOnHlsNotify::to_string()
{
return "on_hls_notify: " + ts_url;
}
SrsHlsMuxer::SrsHlsMuxer()
2013-12-02 07:55:10 +00:00
{
req = NULL;
2014-03-18 03:32:58 +00:00
hls_fragment = hls_window = 0;
hls_aof_ratio = 1.0;
deviation_ts = 0;
hls_cleanup = true;
hls_wait_keyframe = true;
previous_floor_ts = 0;
2015-03-31 09:03:14 +00:00
accept_floor_ts = 0;
hls_ts_floor = false;
max_td = 0;
2014-05-04 02:45:13 +00:00
_sequence_no = 0;
2014-03-18 03:32:58 +00:00
current = NULL;
async = new SrsAsyncCallWorker();
context = new SrsTsContext();
segments = new SrsFragmentWindow();
2013-12-02 07:55:10 +00:00
}
2013-11-26 08:06:58 +00:00
SrsHlsMuxer::~SrsHlsMuxer()
2013-12-02 07:55:10 +00:00
{
2014-03-18 03:32:58 +00:00
srs_freep(current);
srs_freep(req);
srs_freep(async);
srs_freep(context);
2013-12-02 07:55:10 +00:00
}
void SrsHlsMuxer::dispose()
{
int ret = ERROR_SUCCESS;
segments->dispose();
if (current) {
if ((ret = current->unlink_tmpfile()) != ERROR_SUCCESS) {
srs_warn("Unlink tmp ts failed, ret=%d", ret);
}
srs_freep(current);
}
if (unlink(m3u8.c_str()) < 0) {
srs_warn("dispose unlink path failed. file=%s", m3u8.c_str());
}
srs_trace("gracefully dispose hls %s", req? req->get_stream_url().c_str() : "");
}
2014-05-04 02:45:13 +00:00
int SrsHlsMuxer::sequence_no()
{
return _sequence_no;
}
string SrsHlsMuxer::ts_url()
{
return current? current->uri:"";
}
double SrsHlsMuxer::duration()
{
return current? current->duration()/1000.0:0;
}
int SrsHlsMuxer::deviation()
{
// no floor, no deviation.
if (!hls_ts_floor) {
return 0;
}
return deviation_ts;
}
2016-12-15 06:54:09 +00:00
int SrsHlsMuxer::initialize()
{
int ret = ERROR_SUCCESS;
if ((ret = async->start()) != ERROR_SUCCESS) {
return ret;
}
2017-03-25 09:21:39 +00:00
return ret;
}
int SrsHlsMuxer::update_config(SrsRequest* r, string entry_prefix,
string path, string m3u8_file, string ts_file, double fragment, double window,
bool ts_floor, double aof_ratio, bool cleanup, bool wait_keyframe
) {
2014-03-18 03:32:58 +00:00
int ret = ERROR_SUCCESS;
srs_freep(req);
req = r->copy();
2017-03-25 09:21:39 +00:00
hls_entry_prefix = entry_prefix;
2014-03-18 03:32:58 +00:00
hls_path = path;
hls_ts_file = ts_file;
2014-03-18 03:32:58 +00:00
hls_fragment = fragment;
hls_aof_ratio = aof_ratio;
hls_ts_floor = ts_floor;
hls_cleanup = cleanup;
hls_wait_keyframe = wait_keyframe;
previous_floor_ts = 0;
2015-03-31 09:03:14 +00:00
accept_floor_ts = 0;
2014-03-18 03:32:58 +00:00
hls_window = window;
deviation_ts = 0;
2015-03-30 06:08:17 +00:00
// generate the m3u8 dir and path.
m3u8_url = srs_path_build_stream(m3u8_file, req->vhost, req->app, req->stream);
m3u8 = path + "/" + m3u8_url;
2017-03-25 09:21:39 +00:00
// when update config, reset the history target duration.
max_td = (int)(fragment * _srs_config->get_hls_td_ratio(r->vhost));
2016-12-15 06:54:09 +00:00
2015-04-03 08:42:32 +00:00
// create m3u8 dir once.
m3u8_dir = srs_path_dirname(m3u8);
if ((ret = srs_create_dir_recursively(m3u8_dir)) != ERROR_SUCCESS) {
2015-04-03 08:42:32 +00:00
srs_error("create app dir %s failed. ret=%d", m3u8_dir.c_str(), ret);
return ret;
}
srs_info("create m3u8 dir %s ok", m3u8_dir.c_str());
2014-03-18 03:32:58 +00:00
return ret;
2013-11-26 02:56:29 +00:00
}
int SrsHlsMuxer::segment_open()
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;
}
// when segment open, the current segment must be NULL.
srs_assert(!current);
2017-03-25 09:21:39 +00:00
// load the default acodec from config.
2017-02-12 12:38:39 +00:00
SrsAudioCodecId default_acodec = SrsAudioCodecIdAAC;
if (true) {
std::string default_acodec_str = _srs_config->get_hls_acodec(req->vhost);
if (default_acodec_str == "mp3") {
2017-02-12 12:38:39 +00:00
default_acodec = SrsAudioCodecIdMP3;
srs_info("hls: use default mp3 acodec");
} else if (default_acodec_str == "aac") {
2017-02-12 12:38:39 +00:00
default_acodec = SrsAudioCodecIdAAC;
srs_info("hls: use default aac acodec");
} else if (default_acodec_str == "an") {
2017-02-12 12:38:39 +00:00
default_acodec = SrsAudioCodecIdDisabled;
srs_info("hls: use default an acodec for pure video");
} else {
srs_warn("hls: use aac for other codec=%s", default_acodec_str.c_str());
}
}
2017-03-25 09:21:39 +00:00
// load the default vcodec from config.
2017-02-12 12:38:39 +00:00
SrsVideoCodecId default_vcodec = SrsVideoCodecIdAVC;
if (true) {
std::string default_vcodec_str = _srs_config->get_hls_vcodec(req->vhost);
if (default_vcodec_str == "h264") {
2017-02-12 12:38:39 +00:00
default_vcodec = SrsVideoCodecIdAVC;
srs_info("hls: use default h264 vcodec");
} else if (default_vcodec_str == "vn") {
2017-02-12 12:38:39 +00:00
default_vcodec = SrsVideoCodecIdDisabled;
srs_info("hls: use default vn vcodec for pure audio");
} else {
srs_warn("hls: use h264 for other codec=%s", default_vcodec_str.c_str());
}
}
2014-03-18 03:32:58 +00:00
// new segment.
current = new SrsHlsSegment(context, default_acodec, default_vcodec);
2014-05-04 02:45:13 +00:00
current->sequence_no = _sequence_no++;
2014-03-18 03:32:58 +00:00
// generate filename.
std::string ts_file = hls_ts_file;
ts_file = srs_path_build_stream(ts_file, req->vhost, req->app, req->stream);
if (hls_ts_floor) {
2015-03-31 09:03:14 +00:00
// accept the floor ts for the first piece.
int64_t current_floor_ts = (int64_t)(srs_update_system_time_ms() / (1000 * hls_fragment));
2015-03-31 09:03:14 +00:00
if (!accept_floor_ts) {
accept_floor_ts = current_floor_ts - 1;
2015-03-31 09:03:14 +00:00
} else {
accept_floor_ts++;
}
// jump when deviation more than 10p
if (accept_floor_ts - current_floor_ts > SRS_JUMP_WHEN_PIECE_DEVIATION) {
srs_warn("hls: jmp for ts deviation, current=%"PRId64", accept=%"PRId64, current_floor_ts, accept_floor_ts);
accept_floor_ts = current_floor_ts - 1;
}
// when reap ts, adjust the deviation.
deviation_ts = (int)(accept_floor_ts - current_floor_ts);
// dup/jmp detect for ts in floor mode.
if (previous_floor_ts && previous_floor_ts != current_floor_ts - 1) {
srs_warn("hls: dup/jmp ts, previous=%"PRId64", current=%"PRId64", accept=%"PRId64", deviation=%d",
previous_floor_ts, current_floor_ts, accept_floor_ts, deviation_ts);
}
previous_floor_ts = current_floor_ts;
// we always ensure the piece is increase one by one.
std::stringstream ts_floor;
ts_floor << accept_floor_ts;
ts_file = srs_string_replace(ts_file, "[timestamp]", ts_floor.str());
// TODO: FIMXE: we must use the accept ts floor time to generate the hour variable.
ts_file = srs_path_build_timestamp(ts_file);
} else {
ts_file = srs_path_build_timestamp(ts_file);
}
if (true) {
std::stringstream ss;
ss << current->sequence_no;
ts_file = srs_string_replace(ts_file, "[seq]", ss.str());
}
current->set_path(hls_path + "/" + ts_file);
srs_info("hls: generate ts path %s, tmpl=%s, floor=%d", ts_file.c_str(), hls_ts_file.c_str(), hls_ts_floor);
2014-03-18 03:32:58 +00:00
2015-03-30 06:08:17 +00:00
// the ts url, relative or absolute url.
// TODO: FIXME: Use url and path manager.
std::string ts_url = current->fullpath();
2015-03-30 06:08:17 +00:00
if (srs_string_starts_with(ts_url, m3u8_dir)) {
ts_url = ts_url.substr(m3u8_dir.length());
}
while (srs_string_starts_with(ts_url, "/")) {
ts_url = ts_url.substr(1);
}
current->uri += hls_entry_prefix;
if (!hls_entry_prefix.empty() && !srs_string_ends_with(hls_entry_prefix, "/")) {
current->uri += "/";
2015-12-11 03:29:10 +00:00
// add the http dir to uri.
string http_dir = srs_path_dirname(m3u8_url);
if (!http_dir.empty()) {
current->uri += http_dir + "/";
}
}
2015-03-30 06:08:17 +00:00
current->uri += ts_url;
// create dir recursively for hls.
2017-03-18 13:41:01 +00:00
if ((ret = current->create_dir()) != ERROR_SUCCESS) {
2015-03-30 06:08:17 +00:00
return ret;
}
2014-03-18 03:32:58 +00:00
2015-03-30 06:08:17 +00:00
// open temp ts file.
std::string tmp_file = current->tmppath();
if ((ret = current->tscw->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;
}
srs_info("open HLS muxer success. path=%s, tmp=%s", current->fullpath().c_str(), tmp_file.c_str());
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->set_sequence_header(true);
2014-03-21 09:10:24 +00:00
return ret;
}
bool SrsHlsMuxer::is_segment_overflow()
{
srs_assert(current);
// to prevent very small segment.
if (current->duration() < 2 * SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) {
return false;
}
// use N% deviation, to smoother.
double deviation = hls_ts_floor? SRS_HLS_FLOOR_REAP_PERCENT * deviation_ts * hls_fragment : 0.0;
2017-03-25 04:52:54 +00:00
srs_info("hls: dur=%"PRId64"ms, tar=%.2f, dev=%.2fms/%dp, frag=%.2f",
2017-03-25 09:21:39 +00:00
current->duration(), hls_fragment + deviation, deviation, deviation_ts, hls_fragment);
return current->duration() >= (hls_fragment + deviation) * 1000;
}
bool SrsHlsMuxer::wait_keyframe()
{
return hls_wait_keyframe;
}
bool SrsHlsMuxer::is_segment_absolutely_overflow()
{
2015-11-11 02:37:50 +00:00
// @see https://github.com/ossrs/srs/issues/151#issuecomment-83553950
srs_assert(current);
// to prevent very small segment.
if (current->duration() < 2 * SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) {
return false;
}
// use N% deviation, to smoother.
double deviation = hls_ts_floor? SRS_HLS_FLOOR_REAP_PERCENT * deviation_ts * hls_fragment : 0.0;
2017-03-25 04:52:54 +00:00
srs_info("hls: dur=%"PRId64"ms, tar=%.2f, dev=%.2fms/%dp, frag=%.2f",
current->duration(), hls_fragment + deviation, deviation, deviation_ts, hls_fragment);
return current->duration() >= (hls_aof_ratio * hls_fragment + deviation) * 1000;
}
bool SrsHlsMuxer::pure_audio()
{
return current && current->tscw && current->tscw->video_codec() == SrsVideoCodecIdDisabled;
}
int SrsHlsMuxer::flush_audio(SrsTsMessageCache* cache)
2013-11-26 08:06:58 +00:00
{
2014-03-18 03:32:58 +00:00
int ret = ERROR_SUCCESS;
2017-03-25 09:21:39 +00:00
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;
}
2015-02-15 08:37:28 +00:00
if (!cache->audio || cache->audio->payload->length() <= 0) {
2014-03-18 03:32:58 +00:00
return ret;
}
// update the duration of segment.
current->append(cache->audio->pts / 90);
if ((ret = current->tscw->write_audio(cache->audio)) != ERROR_SUCCESS) {
2014-03-18 03:32:58 +00:00
return ret;
}
2015-02-15 08:37:28 +00:00
// write success, clear and free the msg
srs_freep(cache->audio);
2017-03-25 09:21:39 +00:00
2014-03-18 03:32:58 +00:00
return ret;
2013-11-26 08:06:58 +00:00
}
int SrsHlsMuxer::flush_video(SrsTsMessageCache* cache)
2013-11-26 08:06:58 +00:00
{
2014-03-18 03:32:58 +00:00
int ret = ERROR_SUCCESS;
2017-03-25 09:21:39 +00:00
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;
}
2015-02-15 08:37:28 +00:00
if (!cache->video || cache->video->payload->length() <= 0) {
return ret;
}
2014-03-18 03:32:58 +00:00
srs_assert(current);
// update the duration of segment.
current->append(cache->video->dts / 90);
2014-03-18 03:32:58 +00:00
if ((ret = current->tscw->write_video(cache->video)) != ERROR_SUCCESS) {
2014-03-18 03:32:58 +00:00
return ret;
}
2015-02-15 08:37:28 +00:00
// write success, clear and free the msg
srs_freep(cache->video);
2014-03-18 03:32:58 +00:00
return ret;
2013-12-02 07:55:10 +00:00
}
int SrsHlsMuxer::segment_close()
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);
2017-03-25 09:21:39 +00:00
// valid, add to segments if segment duration is ok
// when too small, it maybe not enough data to play.
// when too large, it maybe timestamp corrupt.
// make the segment more acceptable, when in [min, max_td * 2], it's ok.
if (current->duration() >= SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS && (int)current->duration() <= max_td * 2 * 1000) {
// use async to call the http hooks, for it will cause thread switch.
if ((ret = async->execute(new SrsDvrAsyncCallOnHls(
_srs_context->get_id(), req,
current->fullpath(), current->uri, m3u8, m3u8_url,
current->sequence_no, current->duration() / 1000.0))) != ERROR_SUCCESS)
{
return ret;
}
2017-03-25 09:21:39 +00:00
// use async to call the http hooks, for it will cause thread switch.
if ((ret = async->execute(new SrsDvrAsyncCallOnHlsNotify(_srs_context->get_id(), req, current->uri))) != ERROR_SUCCESS) {
return ret;
}
2017-03-25 04:52:54 +00:00
srs_info("Reap ts segment, sequence_no=%d, uri=%s, duration=%"PRId64"ms", current->sequence_no, current->uri.c_str(), current->duration());
// close the muxer of finished segment.
srs_freep(current->tscw);
2017-03-25 09:21:39 +00:00
// rename from tmp to real path
if ((ret = current->rename()) != ERROR_SUCCESS) {
return ret;
}
segments->append(current);
current = NULL;
} else {
// reuse current segment index.
2014-05-04 02:45:13 +00:00
_sequence_no--;
2017-03-25 09:21:39 +00:00
2017-03-25 04:52:54 +00:00
srs_trace("Drop ts segment, sequence_no=%d, uri=%s, duration=%"PRId64"ms", current->sequence_no, current->uri.c_str(), current->duration());
2017-03-25 09:21:39 +00:00
// rename from tmp to real path
if ((ret = current->unlink_tmpfile()) != ERROR_SUCCESS) {
return ret;
}
srs_freep(current);
2014-03-21 05:10:47 +00:00
}
2017-03-25 09:21:39 +00:00
2014-03-18 03:32:58 +00:00
// shrink the segments.
segments->shrink(hls_window * 1000);
2017-03-25 09:21:39 +00:00
2014-03-18 03:32:58 +00:00
// refresh the m3u8, donot contains the removed ts
ret = refresh_m3u8();
2017-03-25 09:21:39 +00:00
2014-03-18 03:32:58 +00:00
// remove the ts file.
segments->clear_expired(hls_cleanup);
2017-03-25 09:21:39 +00:00
2014-03-18 03:32:58 +00:00
// check ret of refresh m3u8
if (ret != ERROR_SUCCESS) {
srs_error("refresh m3u8 failed. ret=%d", ret);
return ret;
}
2017-03-25 09:21:39 +00:00
2014-03-18 03:32:58 +00:00
return ret;
2013-11-26 08:06:58 +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;
// no segments, also no m3u8, return.
if (segments->empty()) {
return ret;
}
2015-03-30 06:08:17 +00:00
std::string temp_m3u8 = m3u8 + ".temp";
if ((ret = _refresh_m3u8(temp_m3u8)) == ERROR_SUCCESS) {
if (rename(temp_m3u8.c_str(), m3u8.c_str()) < 0) {
2014-03-18 03:32:58 +00:00
ret = ERROR_HLS_WRITE_FAILED;
2015-03-30 06:08:17 +00:00
srs_error("rename m3u8 file failed. %s => %s, ret=%d", temp_m3u8.c_str(), m3u8.c_str(), ret);
2014-03-18 03:32:58 +00:00
}
}
// remove the temp file.
2015-07-14 03:00:58 +00:00
if (srs_path_exists(temp_m3u8)) {
if (unlink(temp_m3u8.c_str()) < 0) {
srs_warn("ignore remove m3u8 failed, %s", temp_m3u8.c_str());
}
}
2014-03-18 03:32:58 +00:00
return ret;
2013-11-26 08:06:58 +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->empty()) {
2014-03-18 03:32:58 +00:00
return ret;
}
2017-03-25 09:21:39 +00:00
SrsFileWriter writer;
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
// #EXT-X-ALLOW-CACHE:YES\n
std::stringstream ss;
ss << "#EXTM3U" << SRS_CONSTS_LF
2017-03-25 09:21:39 +00:00
<< "#EXT-X-VERSION:3" << SRS_CONSTS_LF
<< "#EXT-X-ALLOW-CACHE:YES" << SRS_CONSTS_LF;
2014-03-18 03:32:58 +00:00
srs_verbose("write m3u8 header success.");
// #EXT-X-MEDIA-SEQUENCE:4294967295\n
SrsHlsSegment* first = dynamic_cast<SrsHlsSegment*>(segments->first());
ss << "#EXT-X-MEDIA-SEQUENCE:" << first->sequence_no << SRS_CONSTS_LF;
2014-03-18 03:32:58 +00:00
srs_verbose("write m3u8 sequence success.");
// iterator shared for td generation and segemnts wrote.
std::vector<SrsHlsSegment*>::iterator it;
2014-03-18 03:32:58 +00:00
// #EXT-X-TARGETDURATION:4294967295\n
/**
2017-03-25 09:21:39 +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.
*/
2015-11-11 02:37:50 +00:00
// @see https://github.com/ossrs/srs/issues/304#issuecomment-74000081
int target_duration = (int)ceil(segments->max_duration() / 1000.0);
target_duration = srs_max(target_duration, max_td);
ss << "#EXT-X-TARGETDURATION:" << target_duration << SRS_CONSTS_LF;
2014-03-18 03:32:58 +00:00
srs_verbose("write m3u8 duration success.");
// write all segments
for (int i = 0; i < segments->size(); i++) {
SrsHlsSegment* segment = dynamic_cast<SrsHlsSegment*>(segments->at(i));
2014-03-18 03:32:58 +00:00
if (segment->is_sequence_header()) {
2014-03-21 09:10:24 +00:00
// #EXT-X-DISCONTINUITY\n
ss << "#EXT-X-DISCONTINUITY" << SRS_CONSTS_LF;
2014-03-21 09:10:24 +00:00
srs_verbose("write m3u8 segment discontinuity success.");
}
2014-03-18 03:32:58 +00:00
// "#EXTINF:4294967295.208,\n"
ss.precision(3);
ss.setf(std::ios::fixed, std::ios::floatfield);
ss << "#EXTINF:" << segment->duration() / 1000.0 << ", no desc" << SRS_CONSTS_LF;
2014-03-21 09:10:24 +00:00
srs_verbose("write m3u8 segment info success.");
2014-03-18 03:32:58 +00:00
// {file name}\n
ss << segment->uri << SRS_CONSTS_LF;
2014-03-18 03:32:58 +00:00
srs_verbose("write m3u8 segment uri success.");
}
2017-03-25 09:21:39 +00:00
// write m3u8 to writer.
std::string m3u8 = ss.str();
if ((ret = writer.write((char*)m3u8.c_str(), (int)m3u8.length(), NULL)) != ERROR_SUCCESS) {
srs_error("write m3u8 failed. ret=%d", ret);
return ret;
}
2014-03-18 03:32:58 +00:00
srs_info("write m3u8 %s success.", m3u8_file.c_str());
return ret;
2013-11-26 09:21:49 +00:00
}
SrsHlsController::SrsHlsController()
{
tsmc = new SrsTsMessageCache();
muxer = new SrsHlsMuxer();
}
SrsHlsController::~SrsHlsController()
{
srs_freep(muxer);
srs_freep(tsmc);
}
int SrsHlsController::initialize()
{
return muxer->initialize();
}
void SrsHlsController::dispose()
{
muxer->dispose();
}
int SrsHlsController::sequence_no()
2013-12-02 07:55:10 +00:00
{
return muxer->sequence_no();
2013-12-02 07:55:10 +00:00
}
string SrsHlsController::ts_url()
2013-12-02 07:55:10 +00:00
{
return muxer->ts_url();
}
double SrsHlsController::duration()
{
return muxer->duration();
}
int SrsHlsController::deviation()
{
return muxer->deviation();
}
int SrsHlsController::on_publish(SrsRequest* req)
{
int ret = ERROR_SUCCESS;
2017-03-25 09:21:39 +00:00
std::string vhost = req->vhost;
std::string stream = req->stream;
std::string app = req->app;
2014-03-18 03:32:58 +00:00
double hls_fragment = _srs_config->get_hls_fragment(vhost);
double hls_window = _srs_config->get_hls_window(vhost);
2015-03-12 16:22:55 +00:00
// get the hls m3u8 ts list entry prefix config
std::string entry_prefix = _srs_config->get_hls_entry_prefix(vhost);
// get the hls path config
std::string path = _srs_config->get_hls_path(vhost);
std::string m3u8_file = _srs_config->get_hls_m3u8_file(vhost);
std::string ts_file = _srs_config->get_hls_ts_file(vhost);
bool cleanup = _srs_config->get_hls_cleanup(vhost);
bool wait_keyframe = _srs_config->get_hls_wait_keyframe(vhost);
// the audio overflow, for pure audio to reap segment.
double hls_aof_ratio = _srs_config->get_hls_aof_ratio(vhost);
// whether use floor(timestamp/hls_fragment) for variable timestamp
bool ts_floor = _srs_config->get_hls_ts_floor(vhost);
2015-06-12 08:08:47 +00:00
// the seconds to dispose the hls.
int hls_dispose = _srs_config->get_hls_dispose(vhost);
// TODO: FIXME: support load exists m3u8, to continue publish stream.
// for the HLS donot requires the EXT-X-MEDIA-SEQUENCE be monotonically increase.
// open muxer
if ((ret = muxer->update_config(req, entry_prefix,
path, m3u8_file, ts_file, hls_fragment, hls_window, ts_floor, hls_aof_ratio,
cleanup, wait_keyframe)) != 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;
}
2015-06-12 08:08:47 +00:00
srs_trace("hls: win=%.2f, frag=%.2f, prefix=%s, path=%s, m3u8=%s, ts=%s, aof=%.2f, floor=%d, clean=%d, waitk=%d, dispose=%d",
2017-03-25 09:21:39 +00:00
hls_window, hls_fragment, entry_prefix.c_str(), path.c_str(), m3u8_file.c_str(),
ts_file.c_str(), hls_aof_ratio, ts_floor, cleanup, wait_keyframe, hls_dispose);
return ret;
}
int SrsHlsController::on_unpublish()
{
int ret = ERROR_SUCCESS;
if ((ret = muxer->flush_audio(tsmc)) != 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;
}
2014-03-21 09:10:24 +00:00
int SrsHlsController::on_sequence_header()
2014-03-21 09:10:24 +00:00
{
// TODO: support discontinuity for the same stream
// currently we reap and insert discontinity when encoder republish,
2017-03-25 09:21:39 +00:00
// but actually, event when stream is not republish, the
2014-03-21 09:10:24 +00:00
// 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();
}
int SrsHlsController::write_audio(SrsAudioFrame* frame, int64_t pts)
{
2014-03-18 03:32:58 +00:00
int ret = ERROR_SUCCESS;
// write audio to cache.
if ((ret = tsmc->cache_audio(frame, pts)) != ERROR_SUCCESS) {
2014-03-18 03:32:58 +00:00
return ret;
}
// 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.
2015-11-11 02:37:50 +00:00
// @see https://github.com/ossrs/srs/issues/151
// we use absolutely overflow of segment to make jwplayer/ffplay happy
2015-11-11 02:37:50 +00:00
// @see https://github.com/ossrs/srs/issues/151#issuecomment-71155184
if (tsmc->audio && muxer->is_segment_absolutely_overflow()) {
srs_info("hls: absolute audio reap segment.");
if ((ret = reap_segment()) != ERROR_SUCCESS) {
return ret;
}
}
// for pure audio, aggregate some frame to one.
2017-04-16 13:43:30 +00:00
// TODO: FIXME: Check whether it's necessary.
if (muxer->pure_audio() && tsmc->audio) {
if (pts - tsmc->audio->start_pts < SRS_CONSTS_HLS_PURE_AUDIO_AGGREGATE) {
return ret;
}
}
// directly write the audio frame by frame to ts,
// it's ok for the hls overload, or maybe cause the audio corrupt,
// which introduced by aggregate the audios to a big one.
2015-11-11 02:37:50 +00:00
// @see https://github.com/ossrs/srs/issues/512
if ((ret = muxer->flush_audio(tsmc)) != ERROR_SUCCESS) {
return ret;
}
2014-03-18 03:32:58 +00:00
return ret;
}
2017-03-25 09:21:39 +00:00
int SrsHlsController::write_video(SrsVideoFrame* frame, int64_t dts)
{
2014-03-18 03:32:58 +00:00
int ret = ERROR_SUCCESS;
// write video to cache.
if ((ret = tsmc->cache_video(frame, dts)) != ERROR_SUCCESS) {
2014-03-18 03:32:58 +00:00
return ret;
}
// when segment overflow, reap if possible.
if (muxer->is_segment_overflow()) {
// do reap ts if any of:
// a. wait keyframe and got keyframe.
// b. always reap when not wait keyframe.
2017-02-12 12:38:39 +00:00
if (!muxer->wait_keyframe() || frame->frame_type == SrsVideoAvcFrameTypeKeyFrame) {
// reap the segment, which will also flush the video.
if ((ret = reap_segment()) != ERROR_SUCCESS) {
return ret;
}
}
}
2014-03-18 03:32:58 +00:00
// flush video when got one
if ((ret = muxer->flush_video(tsmc)) != ERROR_SUCCESS) {
2014-03-18 03:32:58 +00:00
srs_error("m3u8 muxer flush video failed. ret=%d", ret);
return ret;
}
return ret;
}
int SrsHlsController::reap_segment()
{
int ret = ERROR_SUCCESS;
2015-04-03 09:20:41 +00:00
// TODO: flush audio before or after segment?
// TODO: fresh segment begin with audio or video?
2017-03-25 09:21:39 +00:00
2015-04-03 09:20:41 +00:00
// close current ts.
if ((ret = muxer->segment_close()) != ERROR_SUCCESS) {
srs_error("m3u8 muxer close segment failed. ret=%d", ret);
return ret;
}
2015-04-03 09:20:41 +00:00
// open new ts.
if ((ret = muxer->segment_open()) != ERROR_SUCCESS) {
srs_error("m3u8 muxer open segment failed. ret=%d", ret);
return ret;
}
// segment open, flush video first.
if ((ret = muxer->flush_video(tsmc)) != ERROR_SUCCESS) {
srs_error("m3u8 muxer flush video failed. ret=%d", ret);
return ret;
}
2015-04-03 09:20:41 +00:00
// segment open, flush the audio.
// @see: ngx_rtmp_hls_open_fragment
/* start fragment with audio to make iPhone happy */
if ((ret = muxer->flush_audio(tsmc)) != ERROR_SUCCESS) {
srs_error("m3u8 muxer flush audio failed. ret=%d", ret);
return ret;
}
2014-03-18 03:32:58 +00:00
return ret;
}
SrsHls::SrsHls()
{
req = NULL;
hub = NULL;
2014-03-18 03:32:58 +00:00
2017-02-11 13:14:28 +00:00
enabled = false;
disposable = false;
last_update_time = 0;
2017-03-25 09:21:39 +00:00
previous_audio_dts = 0;
aac_samples = 0;
2014-03-18 03:32:58 +00:00
jitter = new SrsRtmpJitter();
controller = new SrsHlsController();
2017-03-25 09:21:39 +00:00
pprint = SrsPithyPrint::create_hls();
}
SrsHls::~SrsHls()
{
2014-03-18 03:32:58 +00:00
srs_freep(jitter);
srs_freep(controller);
srs_freep(pprint);
2013-12-02 07:55:10 +00:00
}
void SrsHls::dispose()
{
2017-02-11 13:14:28 +00:00
if (enabled) {
on_unpublish();
}
controller->dispose();
}
int SrsHls::cycle()
{
int ret = ERROR_SUCCESS;
if (last_update_time <= 0) {
last_update_time = srs_get_system_time_ms();
}
if (!req) {
return ret;
}
int hls_dispose = _srs_config->get_hls_dispose(req->vhost) * 1000;
2015-06-12 08:08:47 +00:00
if (hls_dispose <= 0) {
return ret;
}
if (srs_get_system_time_ms() - last_update_time <= hls_dispose) {
return ret;
}
last_update_time = srs_get_system_time_ms();
2017-02-11 13:14:28 +00:00
if (!disposable) {
return ret;
}
2017-02-11 13:14:28 +00:00
disposable = false;
srs_trace("hls cycle to dispose hls %s, timeout=%dms", req->get_stream_url().c_str(), hls_dispose);
dispose();
return ret;
}
int SrsHls::initialize(SrsOriginHub* h, SrsRequest* r)
{
int ret = ERROR_SUCCESS;
2017-03-25 09:21:39 +00:00
hub = h;
req = r;
2017-03-25 09:21:39 +00:00
if ((ret = controller->initialize()) != ERROR_SUCCESS) {
return ret;
}
2017-03-25 09:21:39 +00:00
return ret;
}
2017-02-11 13:14:28 +00:00
int SrsHls::on_publish()
2013-12-02 07:55:10 +00:00
{
2014-03-18 03:32:58 +00:00
int ret = ERROR_SUCCESS;
// update the hls time, for hls_dispose.
last_update_time = srs_get_system_time_ms();
2014-03-18 03:32:58 +00:00
// support multiple publish.
2017-02-11 13:14:28 +00:00
if (enabled) {
2014-03-18 03:32:58 +00:00
return ret;
}
2017-02-11 13:14:28 +00:00
if (!_srs_config->get_hls_enabled(req->vhost)) {
2014-03-18 03:32:58 +00:00
return ret;
}
if ((ret = controller->on_publish(req)) != 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.
2017-02-11 13:14:28 +00:00
enabled = true;
2014-04-17 10:13:59 +00:00
// ok, the hls can be dispose, or need to be dispose.
2017-02-11 13:14:28 +00:00
disposable = true;
2017-03-25 09:21:39 +00:00
2014-03-18 03:32:58 +00:00
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.
2017-02-11 13:14:28 +00:00
if (!enabled) {
2014-03-18 03:32:58 +00:00
return;
}
2017-03-25 09:21:39 +00:00
if ((ret = controller->on_unpublish()) != ERROR_SUCCESS) {
2014-03-18 03:32:58 +00:00
srs_error("ignore m3u8 muxer flush/close audio failed. ret=%d", ret);
}
2017-02-11 13:14:28 +00:00
enabled = false;
2013-12-02 07:55:10 +00:00
}
int SrsHls::on_audio(SrsSharedPtrMessage* shared_audio, SrsFormat* format)
2013-12-02 07:55:10 +00:00
{
2014-03-18 03:32:58 +00:00
int ret = ERROR_SUCCESS;
2017-02-11 13:14:28 +00:00
if (!enabled) {
2014-03-18 03:32:58 +00:00
return ret;
}
// update the hls time, for hls_dispose.
last_update_time = srs_get_system_time_ms();
2017-03-25 09:21:39 +00:00
SrsSharedPtrMessage* audio = shared_audio->copy();
SrsAutoFree(SrsSharedPtrMessage, audio);
2014-03-18 03:32:58 +00:00
// ts support audio codec: aac/mp3
srs_assert(format->acodec);
2017-02-12 12:38:39 +00:00
SrsAudioCodecId acodec = format->acodec->id;
if (acodec != SrsAudioCodecIdAAC && acodec != SrsAudioCodecIdMP3) {
return ret;
}
2014-03-18 03:32:58 +00:00
// ignore sequence header
srs_assert(format->audio);
2017-02-12 12:38:39 +00:00
if (acodec == SrsAudioCodecIdAAC && format->audio->aac_packet_type == SrsAudioAacFrameTraitSequenceHeader) {
return controller->on_sequence_header();
2014-03-18 03:32:58 +00:00
}
// TODO: FIXME: config the jitter of HLS.
if ((ret = jitter->correct(audio, SrsRtmpJitterAlgorithmOFF)) != ERROR_SUCCESS) {
2014-03-18 03:32:58 +00:00
srs_error("rtmp jitter correct audio failed. ret=%d", ret);
return ret;
}
// Reset the aac samples counter when DTS jitter.
if (previous_audio_dts > audio->timestamp) {
previous_audio_dts = audio->timestamp;
aac_samples = 0;
}
// Use the diff to guess whether the samples is 1024 or 960.
int nb_samples_per_frame = 1024;
int diff = ::abs((int)(audio->timestamp - previous_audio_dts)) * srs_flv_srates[format->acodec->sound_rate];
if (diff > 100 && diff < 950) {
nb_samples_per_frame = 960;
}
// Recalc the DTS by the samples of AAC.
int64_t dts = 90000 * aac_samples / srs_flv_srates[format->acodec->sound_rate];
aac_samples += nb_samples_per_frame;
2014-03-18 03:32:58 +00:00
if ((ret = controller->write_audio(format->audio, dts)) != ERROR_SUCCESS) {
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
}
int SrsHls::on_video(SrsSharedPtrMessage* shared_video, SrsFormat* format)
2013-12-02 07:55:10 +00:00
{
2014-03-18 03:32:58 +00:00
int ret = ERROR_SUCCESS;
2017-02-11 13:14:28 +00:00
if (!enabled) {
2014-03-18 03:32:58 +00:00
return ret;
}
// update the hls time, for hls_dispose.
last_update_time = srs_get_system_time_ms();
2017-03-25 09:21:39 +00:00
SrsSharedPtrMessage* video = shared_video->copy();
SrsAutoFree(SrsSharedPtrMessage, video);
2014-03-18 03:32:58 +00:00
// ignore info frame,
2015-11-11 02:37:50 +00:00
// @see https://github.com/ossrs/srs/issues/288#issuecomment-69863909
srs_assert(format->video);
2017-02-12 12:38:39 +00:00
if (format->video->frame_type == SrsVideoAvcFrameTypeVideoInfoFrame) {
return ret;
}
srs_assert(format->vcodec);
2017-02-12 12:38:39 +00:00
if (format->vcodec->id != SrsVideoCodecIdAVC) {
2014-03-18 03:32:58 +00:00
return ret;
}
// ignore sequence header
2017-02-12 12:38:39 +00:00
if (format->video->avc_packet_type == SrsVideoAvcFrameTraitSequenceHeader) {
return controller->on_sequence_header();
2014-03-18 03:32:58 +00:00
}
// TODO: FIXME: config the jitter of HLS.
if ((ret = jitter->correct(video, SrsRtmpJitterAlgorithmOFF)) != ERROR_SUCCESS) {
2014-03-18 03:32:58 +00:00
srs_error("rtmp jitter correct video failed. ret=%d", ret);
return ret;
}
int64_t dts = video->timestamp * 90;
if ((ret = controller->write_video(format->video, dts)) != ERROR_SUCCESS) {
srs_error("hls cache write video failed. ret=%d", ret);
2014-03-18 03:32:58 +00:00
return ret;
}
// pithy print message.
hls_show_mux_log();
2014-03-18 03:32:58 +00:00
return ret;
}
void SrsHls::hls_show_mux_log()
2013-12-05 15:34:26 +00:00
{
pprint->elapse();
2017-03-25 09:21:39 +00:00
if (!pprint->can_print()) {
return;
2014-03-18 03:32:58 +00:00
}
// the run time is not equals to stream time,
// @see: https://github.com/ossrs/srs/issues/81#issuecomment-48100994
// it's ok.
srs_trace("-> "SRS_CONSTS_LOG_HLS" time=%"PRId64", sno=%d, ts=%s, dur=%.2f, dva=%dp",
2017-03-25 09:21:39 +00:00
pprint->age(), controller->sequence_no(), controller->ts_url().c_str(),
controller->duration(), controller->deviation());
2013-12-05 15:34:26 +00:00
}
2014-08-02 14:18:39 +00:00