mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2025-04-11 03:32:39 +00:00
hlsenc: Support recovery from an already present playlist
Parse the playlist to recover the start sequence and previously generated segments and continue muxing from there. Mainly useful for near-seamless recovery in live scenarios.
This commit is contained in:
parent
91622f6446
commit
16cb06bb30
@ -119,7 +119,7 @@ static int setup_encryption(AVFormatContext *s)
|
|||||||
AVIOContext *out = NULL;
|
AVIOContext *out = NULL;
|
||||||
int len, ret;
|
int len, ret;
|
||||||
uint8_t buf[16];
|
uint8_t buf[16];
|
||||||
uint8_t *k;
|
uint8_t *k = NULL;
|
||||||
|
|
||||||
len = strlen(hls->basename) + 4 + 1;
|
len = strlen(hls->basename) + 4 + 1;
|
||||||
hls->key_basename = av_mallocz(len);
|
hls->key_basename = av_mallocz(len);
|
||||||
@ -141,9 +141,22 @@ static int setup_encryption(AVFormatContext *s)
|
|||||||
return ret;
|
return ret;
|
||||||
k = hls->key;
|
k = hls->key;
|
||||||
} else {
|
} else {
|
||||||
if ((ret = randomize(buf, sizeof(buf))) < 0) {
|
if (hls->start_sequence < 0) {
|
||||||
av_log(s, AV_LOG_ERROR, "Cannot generate a strong random key\n");
|
ret = s->io_open(s, &out, hls->key_basename, AVIO_FLAG_READ, NULL);
|
||||||
return ret;
|
if (ret < 0) {
|
||||||
|
av_log(s, AV_LOG_WARNING,
|
||||||
|
"Cannot recover the key, generating a new one.\n");
|
||||||
|
} else {
|
||||||
|
avio_read(out, buf, 16);
|
||||||
|
k = buf;
|
||||||
|
avio_close(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!k) {
|
||||||
|
if ((ret = randomize(buf, sizeof(buf))) < 0) {
|
||||||
|
av_log(s, AV_LOG_ERROR, "Cannot generate a strong random key\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ret = dict_set_bin(&hls->enc_opts, "key", buf, sizeof(buf))) < 0)
|
if ((ret = dict_set_bin(&hls->enc_opts, "key", buf, sizeof(buf))) < 0)
|
||||||
@ -201,14 +214,14 @@ static int hls_mux_init(AVFormatContext *s)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int append_entry(HLSContext *hls, int64_t duration)
|
static int append_entry(HLSContext *hls, int64_t duration, const char *name)
|
||||||
{
|
{
|
||||||
ListEntry *en = av_malloc(sizeof(*en));
|
ListEntry *en = av_malloc(sizeof(*en));
|
||||||
|
|
||||||
if (!en)
|
if (!en)
|
||||||
return AVERROR(ENOMEM);
|
return AVERROR(ENOMEM);
|
||||||
|
|
||||||
av_strlcpy(en->name, av_basename(hls->avf->filename), sizeof(en->name));
|
av_strlcpy(en->name, name, sizeof(en->name));
|
||||||
|
|
||||||
en->duration = duration;
|
en->duration = duration;
|
||||||
en->next = NULL;
|
en->next = NULL;
|
||||||
@ -356,12 +369,67 @@ fail:
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
|
||||||
|
{
|
||||||
|
int len = ff_get_line(s, buf, maxlen);
|
||||||
|
while (len > 0 && av_isspace(buf[len - 1]))
|
||||||
|
buf[--len] = '\0';
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hls_recover(AVFormatContext *s)
|
||||||
|
{
|
||||||
|
HLSContext *hls = s->priv_data;
|
||||||
|
char line[1024];
|
||||||
|
AVIOContext *io;
|
||||||
|
const char *ptr;
|
||||||
|
int ret, is_segment = 0;
|
||||||
|
int64_t duration = 0;
|
||||||
|
|
||||||
|
ret = s->io_open(s, &io, s->filename, AVIO_FLAG_READ, NULL);
|
||||||
|
if (ret < 0) {
|
||||||
|
av_log(s, AV_LOG_WARNING,
|
||||||
|
"Cannot recover the playlist, generating a new one.\n");
|
||||||
|
hls->start_sequence = 0;
|
||||||
|
hls->sequence = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
read_chomp_line(io, line, sizeof(line));
|
||||||
|
if (strcmp(line, "#EXTM3U")) {
|
||||||
|
av_log(s, AV_LOG_ERROR,
|
||||||
|
"The playlist file is present but unparsable."
|
||||||
|
" Please remove it.\n");
|
||||||
|
return AVERROR_INVALIDDATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!io->eof_reached) {
|
||||||
|
read_chomp_line(io, line, sizeof(line));
|
||||||
|
if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) {
|
||||||
|
hls->sequence = hls->start_sequence = atoi(ptr);
|
||||||
|
} else if (av_strstart(line, "#EXTINF:", &ptr)) {
|
||||||
|
is_segment = 1;
|
||||||
|
duration = atof(ptr) * AV_TIME_BASE;
|
||||||
|
} else if (av_strstart(line, "#", NULL)) {
|
||||||
|
continue;
|
||||||
|
} else if (line[0]) {
|
||||||
|
if (is_segment) {
|
||||||
|
append_entry(hls, duration, av_basename(line));
|
||||||
|
is_segment = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int hls_setup(AVFormatContext *s)
|
static int hls_setup(AVFormatContext *s)
|
||||||
{
|
{
|
||||||
HLSContext *hls = s->priv_data;
|
HLSContext *hls = s->priv_data;
|
||||||
const char *pattern = "%d.ts";
|
const char *pattern = "%d.ts";
|
||||||
int basename_size = strlen(s->filename) + strlen(pattern) + 1;
|
int basename_size = strlen(s->filename) + strlen(pattern) + 1;
|
||||||
char *p;
|
char *p;
|
||||||
|
int ret;
|
||||||
|
|
||||||
if (hls->encrypt)
|
if (hls->encrypt)
|
||||||
basename_size += 7;
|
basename_size += 7;
|
||||||
@ -382,7 +450,13 @@ static int hls_setup(AVFormatContext *s)
|
|||||||
*p = '\0';
|
*p = '\0';
|
||||||
|
|
||||||
if (hls->encrypt) {
|
if (hls->encrypt) {
|
||||||
int ret = setup_encryption(s);
|
ret = setup_encryption(s);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hls->start_sequence < 0) {
|
||||||
|
ret = hls_recover(s);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -465,7 +539,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
|
|||||||
hls->duration = pts - hls->end_pts;
|
hls->duration = pts - hls->end_pts;
|
||||||
|
|
||||||
if (can_split && pts - hls->start_pts >= end_pts) {
|
if (can_split && pts - hls->start_pts >= end_pts) {
|
||||||
ret = append_entry(hls, hls->duration);
|
ret = append_entry(hls, hls->duration, av_basename(hls->avf->filename));
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
@ -500,7 +574,7 @@ static int hls_write_trailer(struct AVFormatContext *s)
|
|||||||
ff_format_io_close(s, &oc->pb);
|
ff_format_io_close(s, &oc->pb);
|
||||||
avformat_free_context(oc);
|
avformat_free_context(oc);
|
||||||
av_free(hls->basename);
|
av_free(hls->basename);
|
||||||
append_entry(hls, hls->duration);
|
append_entry(hls, hls->duration, av_basename(hls->avf->filename));
|
||||||
hls_window(s, 1);
|
hls_window(s, 1);
|
||||||
|
|
||||||
free_entries(hls);
|
free_entries(hls);
|
||||||
@ -511,7 +585,8 @@ static int hls_write_trailer(struct AVFormatContext *s)
|
|||||||
#define OFFSET(x) offsetof(HLSContext, x)
|
#define OFFSET(x) offsetof(HLSContext, x)
|
||||||
#define E AV_OPT_FLAG_ENCODING_PARAM
|
#define E AV_OPT_FLAG_ENCODING_PARAM
|
||||||
static const AVOption options[] = {
|
static const AVOption options[] = {
|
||||||
{"start_number", "first number in the sequence", OFFSET(start_sequence),AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, E},
|
{"start_number", "first number in the sequence", OFFSET(start_sequence),AV_OPT_TYPE_INT64, {.i64 = 0}, -1, INT64_MAX, E, "start_number"},
|
||||||
|
{"recover", "If there is already a m3u8 file in the path, populate the sequence from it", 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, E, "start_number"},
|
||||||
{"hls_time", "segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E},
|
{"hls_time", "segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E},
|
||||||
{"hls_list_size", "maximum number of playlist entries", OFFSET(size), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E},
|
{"hls_list_size", "maximum number of playlist entries", OFFSET(size), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E},
|
||||||
{"hls_wrap", "number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E},
|
{"hls_wrap", "number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E},
|
||||||
|
Loading…
Reference in New Issue
Block a user