int discont;
int64_t pos;
int64_t size;
+ unsigned var_stream_idx;
char key_uri[LINE_BUFFER_SIZE + 1];
char iv_string[KEYSIZE*2 + 1];
} SegmentType;
typedef struct VariantStream {
+ unsigned var_stream_idx;
unsigned number;
int64_t sequence;
AVOutputFormat *oformat;
float time; // Set by a private option.
float init_time; // Set by a private option.
int max_nb_segments; // Set by a private option.
+ int hls_delete_threshold; // Set by a private option.
#if FF_API_HLS_WRAP
int wrap; // Set by a private option.
#endif
HLSSegment *segment, *previous_segment = NULL;
float playlist_duration = 0.0f;
int ret = 0, path_size, sub_path_size;
+ int segment_cnt = 0;
char *dirname = NULL, *p, *sub_path;
char *path = NULL;
AVDictionary *options = NULL;
}
segment = vs->old_segments;
+ segment_cnt = 0;
while (segment) {
playlist_duration -= segment->duration;
previous_segment = segment;
segment = previous_segment->next;
+ segment_cnt++;
if (playlist_duration <= -previous_segment->duration) {
previous_segment->next = NULL;
break;
}
+ if (segment_cnt >= hls->hls_delete_threshold) {
+ previous_segment->next = NULL;
+ break;
+ }
}
if (segment && !hls->use_localtime_mkdir) {
}
p = (char *)av_basename(dirname);
*p = '\0';
+
}
while (segment) {
+ char * r_dirname = dirname;
+
+ /* if %v is present in the file's directory */
+ if (av_stristr(dirname, "%v")) {
+
+ if (replace_int_data_in_filename(&r_dirname, dirname, 'v', segment->var_stream_idx) < 1) {
+ ret = AVERROR(EINVAL);
+ goto fail;
+ }
+ av_free(dirname);
+ dirname = r_dirname;
+ }
+
av_log(hls, AV_LOG_DEBUG, "deleting old segment %s\n",
segment->filename);
path_size = (hls->use_localtime_mkdir ? 0 : strlen(dirname)) + strlen(segment->filename) + 1;
return 0;
}
-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_mux_init(AVFormatContext *s, VariantStream *vs)
{
AVDictionary *options = NULL;
if ((ret = avio_open_dyn_buf(&oc->pb)) < 0)
return ret;
- ret = hlsenc_io_open(s, &vs->out, vs->base_output_dirname, &options);
+ if (byterange_mode) {
+ ret = hlsenc_io_open(s, &vs->out, vs->basename, &options);
+ } else {
+ ret = hlsenc_io_open(s, &vs->out, vs->base_output_dirname, &options);
+ }
av_dict_free(&options);
if (ret < 0) {
av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", vs->fmp4_init_filename);
if (!en)
return AVERROR(ENOMEM);
+ en->var_stream_idx = vs->var_stream_idx;
ret = sls_flags_filename_process(s, hls, vs, en, duration, pos, size);
if (ret < 0) {
return ret;
s->protocol_whitelist, s->protocol_blacklist)) < 0)
return ret;
- read_chomp_line(in, line, sizeof(line));
+ ff_get_chomp_line(in, line, sizeof(line));
if (strcmp(line, "#EXTM3U")) {
ret = AVERROR_INVALIDDATA;
goto fail;
vs->discontinuity = 0;
while (!avio_feof(in)) {
- read_chomp_line(in, line, sizeof(line));
+ ff_get_chomp_line(in, line, sizeof(line));
if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) {
int64_t tmp_sequence = strtoll(ptr, NULL, 10);
if (tmp_sequence < vs->sequence)
return 0;
}
+static int64_t get_stream_bit_rate(AVStream *stream) {
+ AVCPBProperties *props = (AVCPBProperties*)av_stream_get_side_data(
+ stream,
+ AV_PKT_DATA_CPB_PROPERTIES,
+ NULL
+ );
+
+ if (stream->codecpar->bit_rate)
+ return stream->codecpar->bit_rate;
+ else if (props)
+ return props->max_bitrate;
+
+ return 0;
+}
+
static int create_master_playlist(AVFormatContext *s,
VariantStream * const input_vs)
{
bandwidth = 0;
if (vid_st)
- bandwidth += vid_st->codecpar->bit_rate;
+ bandwidth += get_stream_bit_rate(vid_st);
if (aud_st)
- bandwidth += aud_st->codecpar->bit_rate;
+ bandwidth += get_stream_bit_rate(aud_st);
bandwidth += bandwidth / 10;
ccgroup = NULL;
}
if ((hls->segment_type == SEGMENT_TYPE_FMP4) && (en == vs->segments)) {
- ff_hls_write_init_file(hls->m3u8_out, vs->fmp4_init_filename,
- hls->flags & HLS_SINGLE_FILE, en->size, en->pos);
+ ff_hls_write_init_file(hls->m3u8_out, (hls->flags & HLS_SINGLE_FILE) ? en->filename : vs->fmp4_init_filename,
+ hls->flags & HLS_SINGLE_FILE, vs->init_range_length, 0);
}
ret = ff_hls_write_file_entry(hls->m3u8_out, en->discont, byterange_mode,
}
if (c->key_info_file || c->encrypt) {
+ if (c->segment_type == SEGMENT_TYPE_FMP4) {
+ av_log(s, AV_LOG_ERROR, "Encrypted fmp4 not yet supported\n");
+ return AVERROR_PATCHWELCOME;
+ }
+
if (c->key_info_file && c->encrypt) {
av_log(s, AV_LOG_WARNING, "Cannot use both -hls_key_info_file and -hls_enc,"
- " will use -hls_key_info_file priority\n");
+ " ignoring -hls_enc\n");
}
if (!c->encrypt_started || (c->flags & HLS_PERIODIC_REKEY)) {
while (varstr = av_strtok(p, " \t", &saveptr1)) {
p = NULL;
- if (nb_varstreams < hls->nb_varstreams)
- vs = &(hls->var_streams[nb_varstreams++]);
- else
+ if (nb_varstreams < hls->nb_varstreams) {
+ vs = &(hls->var_streams[nb_varstreams]);
+ vs->var_stream_idx = nb_varstreams;
+ nb_varstreams++;
+ } else
return AVERROR(EINVAL);
q = varstr;
{
HLSContext *hls = s->priv_data;
int nb_ccstreams;
- char *p, *q, *saveptr1, *saveptr2, *ccstr, *keyval;
+ char *p, *q, *ccstr, *keyval;
+ char *saveptr1 = NULL, *saveptr2 = NULL;
const char *val;
ClosedCaptionsStream *ccs;
if (!hls->var_streams)
return AVERROR(ENOMEM);
+ hls->var_streams[0].var_stream_idx = 0;
hls->var_streams[0].nb_streams = s->nb_streams;
hls->var_streams[0].streams = av_mallocz(sizeof(AVStream *) *
hls->var_streams[0].nb_streams);
}
if (vs->packets_written && can_split && av_compare_ts(pkt->pts - vs->start_pts, st->time_base,
- end_pts, AV_TIME_BASE_Q) >= 0) {
+ end_pts, AV_TIME_BASE_Q) >= 0) {
int64_t new_start_pos;
char *old_filename = NULL;
int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);
av_write_frame(vs->avf, NULL); /* Flush any buffered data */
new_start_pos = avio_tell(vs->avf->pb);
- vs->size = new_start_pos - vs->start_pos;
+ if (hls->segment_type != SEGMENT_TYPE_FMP4) {
+ vs->size = new_start_pos - vs->start_pos;
+ } else {
+ vs->size = new_start_pos;
+ }
- if (!byterange_mode) {
- if (hls->segment_type == SEGMENT_TYPE_FMP4) {
- if (!vs->init_range_length) {
- avio_flush(oc->pb);
- range_length = avio_close_dyn_buf(oc->pb, &buffer);
- avio_write(vs->out, buffer, range_length);
- vs->init_range_length = range_length;
- avio_open_dyn_buf(&oc->pb);
- vs->packets_written = 0;
+ if (hls->segment_type == SEGMENT_TYPE_FMP4) {
+ if (!vs->init_range_length) {
+ avio_flush(oc->pb);
+ range_length = avio_close_dyn_buf(oc->pb, &buffer);
+ avio_write(vs->out, buffer, range_length);
+ vs->init_range_length = range_length;
+ avio_open_dyn_buf(&oc->pb);
+ vs->packets_written = 0;
+ vs->start_pos = range_length;
+ if (!byterange_mode) {
ff_format_io_close(s, &vs->out);
hlsenc_io_close(s, &vs->out, vs->base_output_dirname);
}
- } else {
+ }
+ } else {
+ if (!byterange_mode) {
hlsenc_io_close(s, &oc->pb, oc->url);
}
+ }
+ if (!byterange_mode) {
if (vs->vtt_avf) {
hlsenc_io_close(s, &vs->vtt_avf->pb, vs->vtt_avf->url);
}
}
if (hls->segment_type == SEGMENT_TYPE_FMP4) {
- ret = hlsenc_io_open(s, &vs->out, vs->avf->url, NULL);
- if (ret < 0) {
- av_log(NULL, AV_LOG_ERROR, "Failed to open file '%s'\n",
- vs->avf->url);
- return ret;
- }
- write_styp(vs->out);
- ret = flush_dynbuf(vs, &range_length);
- if (ret < 0) {
- return ret;
+ if (hls->flags & HLS_SINGLE_FILE) {
+ ret = flush_dynbuf(vs, &range_length);
+ if (ret < 0) {
+ av_free(old_filename);
+ return ret;
+ }
+ vs->size = range_length;
+ } else {
+ ret = hlsenc_io_open(s, &vs->out, vs->avf->url, NULL);
+ if (ret < 0) {
+ av_log(s, AV_LOG_ERROR, "Failed to open file '%s'\n",
+ vs->avf->url);
+ return ret;
+ }
+ write_styp(vs->out);
+ ret = flush_dynbuf(vs, &range_length);
+ if (ret < 0) {
+ return ret;
+ }
+ ff_format_io_close(s, &vs->out);
}
- ff_format_io_close(s, &vs->out);
}
old_filename = av_strdup(vs->avf->url);
return AVERROR(ENOMEM);
}
- ret = hls_append_segment(s, hls, vs, vs->duration, vs->start_pos, vs->size);
- vs->start_pos = new_start_pos;
- if (ret < 0) {
- av_free(old_filename);
- return ret;
+ if (vs->start_pos || hls->segment_type != SEGMENT_TYPE_FMP4) {
+ ret = hls_append_segment(s, hls, vs, vs->duration, vs->start_pos, vs->size);
+ vs->end_pts = pkt->pts;
+ vs->duration = 0;
+ if (ret < 0) {
+ av_free(old_filename);
+ return ret;
+ }
}
- vs->end_pts = pkt->pts;
- vs->duration = 0;
+ if (hls->segment_type != SEGMENT_TYPE_FMP4) {
+ vs->start_pos = new_start_pos;
+ } else {
+ vs->start_pos += vs->size;
+ }
vs->fmp4_init_mode = 0;
if (hls->flags & HLS_SINGLE_FILE) {
for (i = 0; i < hls->nb_varstreams; i++) {
vs = &hls->var_streams[i];
- oc = vs->avf;
- vtt_oc = vs->vtt_avf;
- old_filename = av_strdup(vs->avf->url);
+ oc = vs->avf;
+ vtt_oc = vs->vtt_avf;
+ old_filename = av_strdup(vs->avf->url);
- if (!old_filename) {
- return AVERROR(ENOMEM);
- }
- if ( hls->segment_type == SEGMENT_TYPE_FMP4) {
- int range_length = 0;
- ret = hlsenc_io_open(s, &vs->out, vs->avf->url, NULL);
- if (ret < 0) {
- av_log(NULL, AV_LOG_ERROR, "Failed to open file '%s'\n", vs->avf->url);
- goto failed;
+ if (!old_filename) {
+ return AVERROR(ENOMEM);
}
- write_styp(vs->out);
- ret = flush_dynbuf(vs, &range_length);
- if (ret < 0) {
- goto failed;
+ if ( hls->segment_type == SEGMENT_TYPE_FMP4) {
+ int range_length = 0;
+ if (!(hls->flags & HLS_SINGLE_FILE)) {
+ ret = hlsenc_io_open(s, &vs->out, vs->avf->url, NULL);
+ if (ret < 0) {
+ av_log(s, AV_LOG_ERROR, "Failed to open file '%s'\n", vs->avf->url);
+ goto failed;
+ }
+ write_styp(vs->out);
+ }
+ ret = flush_dynbuf(vs, &range_length);
+ if (ret < 0) {
+ goto failed;
+ }
+ ff_format_io_close(s, &vs->out);
}
- ff_format_io_close(s, &vs->out);
- }
failed:
- av_write_trailer(oc);
- if (oc->pb) {
- vs->size = avio_tell(vs->avf->pb) - vs->start_pos;
- if (hls->segment_type != SEGMENT_TYPE_FMP4)
- ff_format_io_close(s, &oc->pb);
+ av_write_trailer(oc);
+ if (oc->pb) {
+ if (hls->segment_type != SEGMENT_TYPE_FMP4) {
+ vs->size = avio_tell(vs->avf->pb) - vs->start_pos;
+ } else {
+ vs->size = avio_tell(vs->avf->pb);
+ }
+ if (hls->segment_type != SEGMENT_TYPE_FMP4)
+ ff_format_io_close(s, &oc->pb);
- if ((hls->flags & HLS_TEMP_FILE) && oc->url[0]) {
- hls_rename_temp_file(s, oc);
- av_free(old_filename);
- old_filename = av_strdup(vs->avf->url);
+ if ((hls->flags & HLS_TEMP_FILE) && oc->url[0]) {
+ hls_rename_temp_file(s, oc);
+ av_free(old_filename);
+ old_filename = av_strdup(vs->avf->url);
- if (!old_filename) {
- return AVERROR(ENOMEM);
+ if (!old_filename) {
+ return AVERROR(ENOMEM);
+ }
}
- }
- /* after av_write_trailer, then duration + 1 duration per packet */
- hls_append_segment(s, hls, vs, vs->duration + vs->dpp, vs->start_pos, vs->size);
- }
+ /* after av_write_trailer, then duration + 1 duration per packet */
+ hls_append_segment(s, hls, vs, vs->duration + vs->dpp, vs->start_pos, vs->size);
+ }
- sls_flag_file_rename(hls, vs, old_filename);
+ sls_flag_file_rename(hls, vs, old_filename);
- if (vtt_oc) {
- if (vtt_oc->pb)
- av_write_trailer(vtt_oc);
- vs->size = avio_tell(vs->vtt_avf->pb) - vs->start_pos;
- ff_format_io_close(s, &vtt_oc->pb);
- }
- av_freep(&vs->basename);
- av_freep(&vs->base_output_dirname);
- avformat_free_context(oc);
+ if (vtt_oc) {
+ if (vtt_oc->pb)
+ av_write_trailer(vtt_oc);
+ vs->size = avio_tell(vs->vtt_avf->pb) - vs->start_pos;
+ ff_format_io_close(s, &vtt_oc->pb);
+ }
+ av_freep(&vs->basename);
+ av_freep(&vs->base_output_dirname);
+ avformat_free_context(oc);
- vs->avf = NULL;
- hls_window(s, 1, vs);
+ vs->avf = NULL;
+ hls_window(s, 1, vs);
- av_freep(&vs->fmp4_init_filename);
- if (vtt_oc) {
- av_freep(&vs->vtt_basename);
- av_freep(&vs->vtt_m3u8_name);
- avformat_free_context(vtt_oc);
- }
+ av_freep(&vs->fmp4_init_filename);
+ if (vtt_oc) {
+ av_freep(&vs->vtt_basename);
+ av_freep(&vs->vtt_m3u8_name);
+ avformat_free_context(vtt_oc);
+ }
- hls_free_segments(vs->segments);
- hls_free_segments(vs->old_segments);
- av_free(old_filename);
- av_freep(&vs->m3u8_name);
- av_freep(&vs->streams);
- av_freep(&vs->agroup);
- av_freep(&vs->ccgroup);
- av_freep(&vs->baseurl);
+ hls_free_segments(vs->segments);
+ hls_free_segments(vs->old_segments);
+ av_free(old_filename);
+ av_freep(&vs->m3u8_name);
+ av_freep(&vs->streams);
+ av_freep(&vs->agroup);
+ av_freep(&vs->ccgroup);
+ av_freep(&vs->baseurl);
}
for (i = 0; i < hls->nb_ccstreams; i++) {
ret = update_variant_stream_info(s);
if (ret < 0) {
av_log(s, AV_LOG_ERROR, "Variant stream info update failed with status %x\n",
- ret);
+ ret);
goto fail;
}
//TODO: Updates needed to encryption functionality with periodic re-key when more than one variant streams are present
ret = update_master_pl_info(s);
if (ret < 0) {
av_log(s, AV_LOG_ERROR, "Master stream info update failed with status %x\n",
- ret);
+ ret);
goto fail;
}
}
if (hls->segment_type == SEGMENT_TYPE_FMP4) {
if (hls->nb_varstreams > 1)
fmp4_init_filename_len += strlen(POSTFIX_PATTERN);
- vs->fmp4_init_filename = av_malloc(fmp4_init_filename_len);
- if (!vs->fmp4_init_filename ) {
- ret = AVERROR(ENOMEM);
- goto fail;
- }
- av_strlcpy(vs->fmp4_init_filename, hls->fmp4_init_filename,
- fmp4_init_filename_len);
+ if (hls->flags & HLS_SINGLE_FILE) {
+ vs->fmp4_init_filename = av_strdup(vs->basename);
+ if (!vs->fmp4_init_filename) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ } else {
+ vs->fmp4_init_filename = av_malloc(fmp4_init_filename_len);
+ if (!vs->fmp4_init_filename ) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ av_strlcpy(vs->fmp4_init_filename, hls->fmp4_init_filename,
+ fmp4_init_filename_len);
if (hls->nb_varstreams > 1) {
ret = append_postfix(vs->fmp4_init_filename, fmp4_init_filename_len, i);
if (ret < 0)
- goto fail;
+ goto fail;
}
fmp4_init_filename_len = strlen(vs->m3u8_name) +
av_strlcpy(vs->base_output_dirname, vs->fmp4_init_filename,
fmp4_init_filename_len);
}
+ }
}
if (!hls->use_localtime) {
}
}
- if ((hls->flags & HLS_SINGLE_FILE) && (hls->segment_type == SEGMENT_TYPE_FMP4)) {
- vs->fmp4_init_filename = av_strdup(vs->basename);
- if (!vs->fmp4_init_filename) {
- ret = AVERROR(ENOMEM);
- goto fail;
- }
- }
if ((ret = hls_mux_init(s, vs)) < 0)
goto fail;
{"hls_time", "set segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E},
{"hls_init_time", "set segment length in seconds at init list", OFFSET(init_time), AV_OPT_TYPE_FLOAT, {.dbl = 0}, 0, FLT_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_delete_threshold", "set number of unreferenced segments to keep before deleting", OFFSET(hls_delete_threshold), AV_OPT_TYPE_INT, {.i64 = 1}, 1, INT_MAX, E},
{"hls_ts_options","set hls mpegts list of options for the container format used for hls", OFFSET(format_options_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
{"hls_vtt_options","set hls vtt list of options for the container format used for hls", OFFSET(vtt_format_options_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
#if FF_API_HLS_WRAP