|
|
|
@ -31,11 +31,11 @@ |
|
|
|
|
#include "avformat.h" |
|
|
|
|
#include "internal.h" |
|
|
|
|
|
|
|
|
|
typedef struct ListEntry { |
|
|
|
|
char name[1024]; |
|
|
|
|
typedef struct HLSSegment { |
|
|
|
|
char filename[1024]; |
|
|
|
|
double duration; /* in seconds */ |
|
|
|
|
struct ListEntry *next; |
|
|
|
|
} ListEntry; |
|
|
|
|
struct HLSSegment *next; |
|
|
|
|
} HLSSegment; |
|
|
|
|
|
|
|
|
|
typedef struct HLSContext { |
|
|
|
|
const AVClass *class; // Class for private options.
|
|
|
|
@ -45,7 +45,7 @@ typedef struct HLSContext { |
|
|
|
|
AVOutputFormat *oformat; |
|
|
|
|
AVFormatContext *avf; |
|
|
|
|
float time; // Set by a private option.
|
|
|
|
|
int size; // Set by a private option.
|
|
|
|
|
int max_nb_segments; // Set by a private option.
|
|
|
|
|
int wrap; // Set by a private option.
|
|
|
|
|
int64_t recording_time; |
|
|
|
|
int has_video; |
|
|
|
@ -53,8 +53,8 @@ typedef struct HLSContext { |
|
|
|
|
int64_t end_pts; |
|
|
|
|
double duration; // last segment duration computed so far, in seconds
|
|
|
|
|
int nb_entries; |
|
|
|
|
ListEntry *list; |
|
|
|
|
ListEntry *end_list; |
|
|
|
|
HLSSegment *segments; |
|
|
|
|
HLSSegment *last_segment; |
|
|
|
|
char *basename; |
|
|
|
|
char *baseurl; |
|
|
|
|
AVIOContext *pb; |
|
|
|
@ -86,28 +86,28 @@ static int hls_mux_init(AVFormatContext *s) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Create a new segment and append it to the segment list */ |
|
|
|
|
static int append_entry(HLSContext *hls, double duration) |
|
|
|
|
static int hls_append_segment(HLSContext *hls, double duration) |
|
|
|
|
{ |
|
|
|
|
ListEntry *en = av_malloc(sizeof(*en)); |
|
|
|
|
HLSSegment *en = av_malloc(sizeof(*en)); |
|
|
|
|
|
|
|
|
|
if (!en) |
|
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
|
|
|
|
|
av_strlcpy(en->name, av_basename(hls->avf->filename), sizeof(en->name)); |
|
|
|
|
av_strlcpy(en->filename, av_basename(hls->avf->filename), sizeof(en->filename)); |
|
|
|
|
|
|
|
|
|
en->duration = duration; |
|
|
|
|
en->next = NULL; |
|
|
|
|
|
|
|
|
|
if (!hls->list) |
|
|
|
|
hls->list = en; |
|
|
|
|
if (!hls->segments) |
|
|
|
|
hls->segments = en; |
|
|
|
|
else |
|
|
|
|
hls->end_list->next = en; |
|
|
|
|
hls->last_segment->next = en; |
|
|
|
|
|
|
|
|
|
hls->end_list = en; |
|
|
|
|
hls->last_segment = en; |
|
|
|
|
|
|
|
|
|
if (hls->size && hls->nb_entries >= hls->size) { |
|
|
|
|
en = hls->list; |
|
|
|
|
hls->list = en->next; |
|
|
|
|
if (hls->max_nb_segments && hls->nb_entries >= hls->max_nb_segments) { |
|
|
|
|
en = hls->segments; |
|
|
|
|
hls->segments = en->next; |
|
|
|
|
av_free(en); |
|
|
|
|
} else |
|
|
|
|
hls->nb_entries++; |
|
|
|
@ -117,9 +117,9 @@ static int append_entry(HLSContext *hls, double duration) |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void free_entries(HLSContext *hls) |
|
|
|
|
static void hls_free_segments(HLSContext *hls) |
|
|
|
|
{ |
|
|
|
|
ListEntry *p = hls->list, *en; |
|
|
|
|
HLSSegment *p = hls->segments, *en; |
|
|
|
|
|
|
|
|
|
while(p) { |
|
|
|
|
en = p; |
|
|
|
@ -131,7 +131,7 @@ static void free_entries(HLSContext *hls) |
|
|
|
|
static int hls_window(AVFormatContext *s, int last) |
|
|
|
|
{ |
|
|
|
|
HLSContext *hls = s->priv_data; |
|
|
|
|
ListEntry *en; |
|
|
|
|
HLSSegment *en; |
|
|
|
|
int target_duration = 0; |
|
|
|
|
int ret = 0; |
|
|
|
|
int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries); |
|
|
|
@ -140,7 +140,7 @@ static int hls_window(AVFormatContext *s, int last) |
|
|
|
|
&s->interrupt_callback, NULL)) < 0) |
|
|
|
|
goto fail; |
|
|
|
|
|
|
|
|
|
for (en = hls->list; en; en = en->next) { |
|
|
|
|
for (en = hls->segments; en; en = en->next) { |
|
|
|
|
if (target_duration < en->duration) |
|
|
|
|
target_duration = ceil(en->duration); |
|
|
|
|
} |
|
|
|
@ -153,11 +153,11 @@ static int hls_window(AVFormatContext *s, int last) |
|
|
|
|
av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", |
|
|
|
|
sequence); |
|
|
|
|
|
|
|
|
|
for (en = hls->list; en; en = en->next) { |
|
|
|
|
for (en = hls->segments; en; en = en->next) { |
|
|
|
|
avio_printf(hls->pb, "#EXTINF:%f,\n", en->duration); |
|
|
|
|
if (hls->baseurl) |
|
|
|
|
avio_printf(hls->pb, "%s", hls->baseurl); |
|
|
|
|
avio_printf(hls->pb, "%s\n", en->name); |
|
|
|
|
avio_printf(hls->pb, "%s\n", en->filename); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (last) |
|
|
|
@ -282,7 +282,7 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) |
|
|
|
|
|
|
|
|
|
if (can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base, |
|
|
|
|
end_pts, AV_TIME_BASE_Q) >= 0) { |
|
|
|
|
ret = append_entry(hls, hls->duration); |
|
|
|
|
ret = hls_append_segment(hls, hls->duration); |
|
|
|
|
if (ret) |
|
|
|
|
return ret; |
|
|
|
|
|
|
|
|
@ -317,10 +317,10 @@ static int hls_write_trailer(struct AVFormatContext *s) |
|
|
|
|
avio_closep(&oc->pb); |
|
|
|
|
avformat_free_context(oc); |
|
|
|
|
av_free(hls->basename); |
|
|
|
|
append_entry(hls, hls->duration); |
|
|
|
|
hls_append_segment(hls, hls->duration); |
|
|
|
|
hls_window(s, 1); |
|
|
|
|
|
|
|
|
|
free_entries(hls); |
|
|
|
|
hls_free_segments(hls); |
|
|
|
|
avio_close(hls->pb); |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
@ -330,7 +330,7 @@ static int hls_write_trailer(struct AVFormatContext *s) |
|
|
|
|
static const AVOption options[] = { |
|
|
|
|
{"start_number", "set first number in the sequence", OFFSET(start_sequence),AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, E}, |
|
|
|
|
{"hls_time", "set segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E}, |
|
|
|
|
{"hls_list_size", "set maximum number of playlist entries", OFFSET(size), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E}, |
|
|
|
|
{"hls_list_size", "set maximum number of playlist entries", OFFSET(max_nb_segments), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E}, |
|
|
|
|
{"hls_wrap", "set number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E}, |
|
|
|
|
{"hls_base_url", "url to prepend to each playlist entry", OFFSET(baseurl), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, |
|
|
|
|
{ NULL }, |
|
|
|
|