Newer
Older
/*
* Apple HTTP Live Streaming segmenter
* Copyright (c) 2012, Luca Barbato
Vishwanath Dixit
committed
* Copyright (c) 2017 Akamai Technologies, Inc.
* This file is part of FFmpeg.
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if CONFIG_GCRYPT
#include <gcrypt.h>
#elif CONFIG_OPENSSL
#include <openssl/rand.h>
#endif
#include "libavutil/avassert.h"
#include "libavutil/mathematics.h"
#include "libavutil/parseutils.h"
#include "libavutil/avstring.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/random_seed.h"
#include "libavutil/opt.h"
#include "libavutil/log.h"
LiuQi
committed
#include "libavutil/time_internal.h"
#include "avio_internal.h"
#if CONFIG_HTTP_PROTOCOL
#include "http.h"
#include "hlsplaylist.h"
typedef enum {
HLS_START_SEQUENCE_AS_START_NUMBER = 0,
HLS_START_SEQUENCE_AS_SECONDS_SINCE_EPOCH = 1,
HLS_START_SEQUENCE_AS_FORMATTED_DATETIME = 2, // YYYYMMDDhhmmss
} StartSequenceSourceType;
#define KEYSIZE 16
#define LINE_BUFFER_SIZE 1024
#define HLS_MICROSECOND_UNIT 1000000
Vishwanath Dixit
committed
#define POSTFIX_PATTERN "_%d"
Nicolas Martyanoff
committed
typedef struct HLSSegment {
char filename[1024];
double duration; /* in seconds */
int64_t pos;
int64_t size;
char key_uri[LINE_BUFFER_SIZE + 1];
char iv_string[KEYSIZE*2 + 1];
Nicolas Martyanoff
committed
struct HLSSegment *next;
} HLSSegment;
typedef enum HLSFlags {
// Generate a single media file and use byte ranges in the playlist.
HLS_SINGLE_FILE = (1 << 0),
HLS_DELETE_SEGMENTS = (1 << 1),
HLS_ROUND_DURATIONS = (1 << 2),
MrBoogs
committed
HLS_DISCONT_START = (1 << 3),
HLS_OMIT_ENDLIST = (1 << 4),
HLS_SPLIT_BY_TIME = (1 << 5),
HLS_PROGRAM_DATE_TIME = (1 << 7),
HLS_SECOND_LEVEL_SEGMENT_INDEX = (1 << 8), // include segment index in segment filenames when use_localtime e.g.: %%03d
HLS_SECOND_LEVEL_SEGMENT_DURATION = (1 << 9), // include segment duration (microsec) in segment filenames when use_localtime e.g.: %%09t
HLS_SECOND_LEVEL_SEGMENT_SIZE = (1 << 10), // include segment size (bytes) in segment filenames when use_localtime e.g.: %%014s
Aman Gupta
committed
HLS_TEMP_FILE = (1 << 11),
HLS_PERIODIC_REKEY = (1 << 12),
HLS_INDEPENDENT_SEGMENTS = (1 << 13),
typedef enum {
SEGMENT_TYPE_MPEGTS,
SEGMENT_TYPE_FMP4,
} SegmentType;
Vishwanath Dixit
committed
typedef struct VariantStream {
AVOutputFormat *vtt_oformat;
AVIOContext *out;
int packets_written;
int init_range_length;
AVFormatContext *vtt_avf;
int new_start;
double dpp; // duration per packet
int64_t start_pts;
int64_t end_pts;
double duration; // last segment duration computed so far, in seconds
int64_t start_pos; // last segment starting position
int64_t size; // last segment size
MrBoogs
committed
int discontinuity_set;
Nicolas Martyanoff
committed
HLSSegment *segments;
HLSSegment *last_segment;
HLSSegment *old_segments;
char *vtt_basename;
char *vtt_m3u8_name;
Vishwanath Dixit
committed
char *m3u8_name;
double initial_prog_date_time;
char current_segment_final_filename_fmt[1024]; // when renaming segments
char *fmp4_init_filename;
char *base_output_dirname;
int fmp4_init_mode;
AVStream **streams;
unsigned int nb_streams;
int m3u8_created; /* status of media play-list creation */
Vishwanath Dixit
committed
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
char *baseurl;
} VariantStream;
typedef struct HLSContext {
const AVClass *class; // Class for private options.
int64_t start_sequence;
uint32_t start_sequence_source_type; // enum StartSequenceSourceType
float time; // Set by a private option.
float init_time; // Set by a private option.
int max_nb_segments; // Set by a private option.
#if FF_API_HLS_WRAP
int wrap; // Set by a private option.
#endif
uint32_t flags; // enum HLSFlags
uint32_t pl_type; // enum PlaylistType
char *segment_filename;
char *fmp4_init_filename;
int segment_type;
int use_localtime; ///< flag to expand filename with localtime
int use_localtime_mkdir;///< flag to mkdir dirname in timebased filename
int allowcache;
int64_t recording_time;
int64_t max_seg_size; // every segment file max size
char *baseurl;
char *vtt_format_options_str;
char *subtitle_filename;
int encrypt;
char *key;
char *key_url;
char *iv;
char *key_basename;
Vishwanath Dixit
committed
int encrypt_started;
char *key_info_file;
char key_file[LINE_BUFFER_SIZE + 1];
char key_uri[LINE_BUFFER_SIZE + 1];
char key_string[KEYSIZE*2 + 1];
char iv_string[KEYSIZE*2 + 1];
AVDictionary *vtt_format_options;
char *method;
char *user_agent;
Vishwanath Dixit
committed
VariantStream *var_streams;
unsigned int nb_varstreams;
int master_m3u8_created; /* status of master play-list creation */
char *master_m3u8_url; /* URL of the master m3u8 file */
int version; /* HLS version */
Vishwanath Dixit
committed
char *var_stream_map; /* user specified variant stream map string */
char *master_pl_name;
unsigned int master_publish_rate;
int http_persistent;
AVIOContext *m3u8_out;
AVIOContext *sub_m3u8_out;
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
static int mkdir_p(const char *path) {
int ret = 0;
char *temp = av_strdup(path);
char *pos = temp;
char tmp_ch = '\0';
if (!path || !temp) {
return -1;
}
if (!strncmp(temp, "/", 1) || !strncmp(temp, "\\", 1)) {
pos++;
} else if (!strncmp(temp, "./", 2) || !strncmp(temp, ".\\", 2)) {
pos += 2;
}
for ( ; *pos != '\0'; ++pos) {
if (*pos == '/' || *pos == '\\') {
tmp_ch = *pos;
*pos = '\0';
ret = mkdir(temp, 0755);
*pos = tmp_ch;
}
}
if ((*(pos - 1) != '/') || (*(pos - 1) != '\\')) {
ret = mkdir(temp, 0755);
}
av_free(temp);
return ret;
}
static int is_http_proto(char *filename) {
const char *proto = avio_find_protocol_name(filename);
return proto ? (!av_strcasecmp(proto, "http") || !av_strcasecmp(proto, "https")) : 0;
}
static int hlsenc_io_open(AVFormatContext *s, AVIOContext **pb, char *filename,
AVDictionary **options) {
HLSContext *hls = s->priv_data;
int http_base_proto = filename ? is_http_proto(filename) : 0;
int err = AVERROR_MUXER_NOT_FOUND;
if (!*pb || !http_base_proto || !hls->http_persistent) {
err = s->io_open(s, pb, filename, AVIO_FLAG_WRITE, options);
#if CONFIG_HTTP_PROTOCOL
} else {
URLContext *http_url_context = ffio_geturlcontext(*pb);
av_assert0(http_url_context);
err = ff_http_do_new_request(http_url_context, filename);
}
return err;
}
static void hlsenc_io_close(AVFormatContext *s, AVIOContext **pb, char *filename) {
HLSContext *hls = s->priv_data;
int http_base_proto = filename ? is_http_proto(filename) : 0;
if (!http_base_proto || !hls->http_persistent || hls->key_info_file || hls->encrypt) {
ff_format_io_close(s, pb);
} else {
avio_flush(*pb);
}
}
static void set_http_options(AVFormatContext *s, AVDictionary **options, HLSContext *c)
{
int http_base_proto = is_http_proto(s->filename);
if (c->method) {
av_dict_set(options, "method", c->method, 0);
} else if (http_base_proto) {
av_log(c, AV_LOG_WARNING, "No HTTP method set, hls muxer defaulting to method PUT.\n");
av_dict_set(options, "method", "PUT", 0);
}
if (c->user_agent)
av_dict_set(options, "user_agent", c->user_agent, 0);
if (c->http_persistent)
av_dict_set_int(options, "multiple_requests", 1, 0);
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
static int replace_int_data_in_filename(char *buf, int buf_size, const char *filename, char placeholder, int64_t number)
{
const char *p;
char *q, buf1[20], c;
int nd, len, addchar_count;
int found_count = 0;
q = buf;
p = filename;
for (;;) {
c = *p;
if (c == '\0')
break;
if (c == '%' && *(p+1) == '%') // %%
addchar_count = 2;
else if (c == '%' && (av_isdigit(*(p+1)) || *(p+1) == placeholder)) {
nd = 0;
addchar_count = 1;
while (av_isdigit(*(p + addchar_count))) {
nd = nd * 10 + *(p + addchar_count) - '0';
addchar_count++;
}
if (*(p + addchar_count) == placeholder) {
len = snprintf(buf1, sizeof(buf1), "%0*"PRId64, (number < 0) ? nd : nd++, number);
if (len < 1) // returned error or empty buf1
goto fail;
if ((q - buf + len) > buf_size - 1)
goto fail;
memcpy(q, buf1, len);
q += len;
p += (addchar_count + 1);
addchar_count = 0;
found_count++;
}
} else
addchar_count = 1;
while (addchar_count--)
if ((q - buf) < buf_size - 1)
*q++ = *p++;
else
goto fail;
}
*q = '\0';
return found_count;
fail:
*q = '\0';
return -1;
}
static void write_styp(AVIOContext *pb)
{
avio_wb32(pb, 24);
ffio_wfourcc(pb, "styp");
ffio_wfourcc(pb, "msdh");
avio_wb32(pb, 0); /* minor */
ffio_wfourcc(pb, "msdh");
ffio_wfourcc(pb, "msix");
}
static int flush_dynbuf(VariantStream *vs, int *range_length)
{
AVFormatContext *ctx = vs->avf;
uint8_t *buffer;
if (!ctx->pb) {
return AVERROR(EINVAL);
}
// flush
av_write_frame(ctx, NULL);
avio_flush(ctx->pb);
// write out to file
*range_length = avio_close_dyn_buf(ctx->pb, &buffer);
ctx->pb = NULL;
avio_write(vs->out, buffer, *range_length);
av_free(buffer);
// re-open buffer
return avio_open_dyn_buf(&ctx->pb);
}
Vishwanath Dixit
committed
static int hls_delete_old_segments(AVFormatContext *s, HLSContext *hls,
VariantStream *vs) {
HLSSegment *segment, *previous_segment = NULL;
float playlist_duration = 0.0f;
int ret = 0, path_size, sub_path_size;
char *dirname = NULL, *p, *sub_path;
char *path = NULL;
AVDictionary *options = NULL;
AVIOContext *out = NULL;
const char *proto = NULL;
Vishwanath Dixit
committed
segment = vs->segments;
while (segment) {
playlist_duration += segment->duration;
segment = segment->next;
}
Vishwanath Dixit
committed
segment = vs->old_segments;
while (segment) {
playlist_duration -= segment->duration;
previous_segment = segment;
segment = previous_segment->next;
if (playlist_duration <= -previous_segment->duration) {
previous_segment->next = NULL;
break;
}
}
if (segment && !hls->use_localtime_mkdir) {
if (hls->segment_filename) {
dirname = av_strdup(hls->segment_filename);
} else {
Vishwanath Dixit
committed
dirname = av_strdup(vs->avf->filename);
}
if (!dirname) {
ret = AVERROR(ENOMEM);
goto fail;
}
p = (char *)av_basename(dirname);
*p = '\0';
}
while (segment) {
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;
path = av_malloc(path_size);
if (!path) {
ret = AVERROR(ENOMEM);
goto fail;
}
if (hls->use_localtime_mkdir)
av_strlcpy(path, segment->filename, path_size);
else { // segment->filename contains basename only
av_strlcpy(path, dirname, path_size);
av_strlcat(path, segment->filename, path_size);
}
proto = avio_find_protocol_name(s->filename);
if (hls->method || (proto && !av_strcasecmp(proto, "http"))) {
av_dict_set(&options, "method", "DELETE", 0);
Vishwanath Dixit
committed
if ((ret = vs->avf->io_open(vs->avf, &out, path, AVIO_FLAG_WRITE, &options)) < 0)
Vishwanath Dixit
committed
ff_format_io_close(vs->avf, &out);
} else if (unlink(path) < 0) {
av_log(hls, AV_LOG_ERROR, "failed to delete old segment %s: %s\n",
path, strerror(errno));
}
if ((segment->sub_filename[0] != '\0')) {
sub_path_size = strlen(segment->sub_filename) + 1 + (dirname ? strlen(dirname) : 0);
sub_path = av_malloc(sub_path_size);
if (!sub_path) {
ret = AVERROR(ENOMEM);
goto fail;
}
av_strlcpy(sub_path, dirname, sub_path_size);
av_strlcat(sub_path, segment->sub_filename, sub_path_size);
if (hls->method || (proto && !av_strcasecmp(proto, "http"))) {
av_dict_set(&options, "method", "DELETE", 0);
Vishwanath Dixit
committed
if ((ret = vs->avf->io_open(vs->avf, &out, sub_path, AVIO_FLAG_WRITE, &options)) < 0) {
av_free(sub_path);
goto fail;
}
Vishwanath Dixit
committed
ff_format_io_close(vs->avf, &out);
} else if (unlink(sub_path) < 0) {
av_log(hls, AV_LOG_ERROR, "failed to delete old segment %s: %s\n",
sub_path, strerror(errno));
}
av_free(sub_path);
previous_segment = segment;
segment = previous_segment->next;
av_free(previous_segment);
}
fail:
av_free(dirname);
return ret;
}
static int randomize(uint8_t *buf, int len)
{
#if CONFIG_GCRYPT
gcry_randomize(buf, len, GCRY_VERY_STRONG_RANDOM);
return 0;
#elif CONFIG_OPENSSL
if (RAND_bytes(buf, len))
return 0;
#else
return AVERROR(ENOSYS);
#endif
return AVERROR(EINVAL);
}
Vishwanath Dixit
committed
static int do_encrypt(AVFormatContext *s, VariantStream *vs)
{
HLSContext *hls = s->priv_data;
int ret;
int len;
AVIOContext *pb;
uint8_t key[KEYSIZE];
Vishwanath Dixit
committed
len = strlen(s->filename) + 4 + 1;
hls->key_basename = av_mallocz(len);
if (!hls->key_basename)
return AVERROR(ENOMEM);
av_strlcpy(hls->key_basename, s->filename, len);
av_strlcat(hls->key_basename, ".key", len);
if (hls->key_url) {
av_strlcpy(hls->key_file, hls->key_url, sizeof(hls->key_file));
av_strlcpy(hls->key_uri, hls->key_url, sizeof(hls->key_uri));
av_strlcpy(hls->key_file, hls->key_basename, sizeof(hls->key_file));
av_strlcpy(hls->key_uri, hls->key_basename, sizeof(hls->key_uri));
}
if (!*hls->iv_string) {
uint8_t iv[16] = { 0 };
char buf[33];
if (!hls->iv) {
Vishwanath Dixit
committed
AV_WB64(iv + 8, vs->sequence);
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
} else {
memcpy(iv, hls->iv, sizeof(iv));
}
ff_data_to_hex(buf, iv, sizeof(iv), 0);
buf[32] = '\0';
memcpy(hls->iv_string, buf, sizeof(hls->iv_string));
}
if (!*hls->key_uri) {
av_log(hls, AV_LOG_ERROR, "no key URI specified in key info file\n");
return AVERROR(EINVAL);
}
if (!*hls->key_file) {
av_log(hls, AV_LOG_ERROR, "no key file specified in key info file\n");
return AVERROR(EINVAL);
}
if (!*hls->key_string) {
if (!hls->key) {
if ((ret = randomize(key, sizeof(key))) < 0) {
av_log(s, AV_LOG_ERROR, "Cannot generate a strong random key\n");
return ret;
}
} else {
memcpy(key, hls->key, sizeof(key));
}
ff_data_to_hex(hls->key_string, key, sizeof(key), 0);
if ((ret = s->io_open(s, &pb, hls->key_file, AVIO_FLAG_WRITE, NULL)) < 0)
return ret;
avio_seek(pb, 0, SEEK_CUR);
avio_write(pb, key, KEYSIZE);
avio_close(pb);
}
return 0;
}
static int hls_encryption_start(AVFormatContext *s)
{
HLSContext *hls = s->priv_data;
int ret;
AVIOContext *pb;
uint8_t key[KEYSIZE];
if ((ret = s->io_open(s, &pb, hls->key_info_file, AVIO_FLAG_READ, NULL)) < 0) {
av_log(hls, AV_LOG_ERROR,
"error opening key info file %s\n", hls->key_info_file);
return ret;
}
ff_get_line(pb, hls->key_uri, sizeof(hls->key_uri));
hls->key_uri[strcspn(hls->key_uri, "\r\n")] = '\0';
ff_get_line(pb, hls->key_file, sizeof(hls->key_file));
hls->key_file[strcspn(hls->key_file, "\r\n")] = '\0';
ff_get_line(pb, hls->iv_string, sizeof(hls->iv_string));
hls->iv_string[strcspn(hls->iv_string, "\r\n")] = '\0';
ff_format_io_close(s, &pb);
if (!*hls->key_uri) {
av_log(hls, AV_LOG_ERROR, "no key URI specified in key info file\n");
return AVERROR(EINVAL);
}
if (!*hls->key_file) {
av_log(hls, AV_LOG_ERROR, "no key file specified in key info file\n");
return AVERROR(EINVAL);
}
if ((ret = s->io_open(s, &pb, hls->key_file, AVIO_FLAG_READ, NULL)) < 0) {
av_log(hls, AV_LOG_ERROR, "error opening key file %s\n", hls->key_file);
return ret;
}
ret = avio_read(pb, key, sizeof(key));
ff_format_io_close(s, &pb);
if (ret != sizeof(key)) {
av_log(hls, AV_LOG_ERROR, "error reading key file %s\n", hls->key_file);
if (ret >= 0 || ret == AVERROR_EOF)
ret = AVERROR(EINVAL);
return ret;
}
ff_data_to_hex(hls->key_string, key, sizeof(key), 0);
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;
}
Vishwanath Dixit
committed
static int hls_mux_init(AVFormatContext *s, VariantStream *vs)
HLSContext *hls = s->priv_data;
AVFormatContext *oc;
AVFormatContext *vtt_oc = NULL;
int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);
Vishwanath Dixit
committed
ret = avformat_alloc_output_context2(&vs->avf, vs->oformat, NULL, NULL);
if (ret < 0)
return ret;
Vishwanath Dixit
committed
oc = vs->avf;
Aman Gupta
committed
oc->filename[0] = '\0';
Vishwanath Dixit
committed
oc->oformat = vs->oformat;
oc->interrupt_callback = s->interrupt_callback;
oc->max_delay = s->max_delay;
oc->opaque = s->opaque;
oc->io_open = s->io_open;
oc->io_close = s->io_close;
av_dict_copy(&oc->metadata, s->metadata, 0);
Vishwanath Dixit
committed
if(vs->vtt_oformat) {
ret = avformat_alloc_output_context2(&vs->vtt_avf, vs->vtt_oformat, NULL, NULL);
if (ret < 0)
return ret;
Vishwanath Dixit
committed
vtt_oc = vs->vtt_avf;
vtt_oc->oformat = vs->vtt_oformat;
av_dict_copy(&vtt_oc->metadata, s->metadata, 0);
}
Vishwanath Dixit
committed
for (i = 0; i < vs->nb_streams; i++) {
Vishwanath Dixit
committed
if (vs->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)
loc = vtt_oc;
else
loc = oc;
if (!(st = avformat_new_stream(loc, NULL)))
Vishwanath Dixit
committed
avcodec_parameters_copy(st->codecpar, vs->streams[i]->codecpar);
if (!oc->oformat->codec_tag ||
Vishwanath Dixit
committed
av_codec_get_id (oc->oformat->codec_tag, vs->streams[i]->codecpar->codec_tag) == st->codecpar->codec_id ||
av_codec_get_tag(oc->oformat->codec_tag, vs->streams[i]->codecpar->codec_id) <= 0) {
st->codecpar->codec_tag = vs->streams[i]->codecpar->codec_tag;
} else {
st->codecpar->codec_tag = 0;
}
Vishwanath Dixit
committed
st->sample_aspect_ratio = vs->streams[i]->sample_aspect_ratio;
st->time_base = vs->streams[i]->time_base;
av_dict_copy(&st->metadata, vs->streams[i]->metadata, 0);
Vishwanath Dixit
committed
vs->packets_written = 1;
vs->start_pos = 0;
vs->new_start = 1;
vs->fmp4_init_mode = 0;
if (hls->segment_type == SEGMENT_TYPE_FMP4) {
if (hls->max_seg_size > 0) {
av_log(s, AV_LOG_WARNING, "Multi-file byterange mode is currently unsupported in the HLS muxer.\n");
return AVERROR_PATCHWELCOME;
}
Vishwanath Dixit
committed
vs->packets_written = 0;
vs->init_range_length = 0;
vs->fmp4_init_mode = !byterange_mode;
set_http_options(s, &options, hls);
if ((ret = avio_open_dyn_buf(&oc->pb)) < 0)
return ret;
ret = hlsenc_io_open(s, &vs->out, vs->base_output_dirname, &options);
av_dict_free(&options);
if (ret < 0) {
Vishwanath Dixit
committed
av_log(s, AV_LOG_ERROR, "Failed to open segment '%s'\n", vs->fmp4_init_filename);
return ret;
}
if (hls->format_options_str) {
ret = av_dict_parse_string(&hls->format_options, hls->format_options_str, "=", ":", 0);
if (ret < 0) {
av_log(s, AV_LOG_ERROR, "Could not parse format options list '%s'\n",
hls->format_options_str);
return ret;
}
}
av_dict_copy(&options, hls->format_options, 0);
av_dict_set(&options, "fflags", "-autobsf", 0);
av_dict_set(&options, "movflags", "frag_custom+dash+delay_moov", 0);
ret = avformat_init_output(oc, &options);
if (ret < 0)
return ret;
if (av_dict_count(options)) {
av_log(s, AV_LOG_ERROR, "Some of the provided format options in '%s' are not recognized\n", hls->format_options_str);
av_dict_free(&options);
return AVERROR(EINVAL);
}
avio_flush(oc->pb);
static HLSSegment *find_segment_by_filename(HLSSegment *segment, const char *filename)
{
while (segment) {
if (!av_strcasecmp(segment->filename,filename))
return segment;
segment = segment->next;
}
return (HLSSegment *) NULL;
}
Vishwanath Dixit
committed
static int sls_flags_filename_process(struct AVFormatContext *s, HLSContext *hls,
VariantStream *vs, HLSSegment *en,
double duration, int64_t pos, int64_t size)
if ((hls->flags & (HLS_SECOND_LEVEL_SEGMENT_SIZE | HLS_SECOND_LEVEL_SEGMENT_DURATION)) &&
Vishwanath Dixit
committed
strlen(vs->current_segment_final_filename_fmt)) {
av_strlcpy(vs->avf->filename, vs->current_segment_final_filename_fmt, sizeof(vs->avf->filename));
if (hls->flags & HLS_SECOND_LEVEL_SEGMENT_SIZE) {
Vishwanath Dixit
committed
char * filename = av_strdup(vs->avf->filename); // %%s will be %s after strftime
return AVERROR(ENOMEM);
Vishwanath Dixit
committed
if (replace_int_data_in_filename(vs->avf->filename, sizeof(vs->avf->filename),
filename, 's', pos + size) < 1) {
av_log(hls, AV_LOG_ERROR,
"Invalid second level segment filename template '%s', "
"you can try to remove second_level_segment_size flag\n",
filename);
av_free(filename);
return AVERROR(EINVAL);
}
av_free(filename);
}
if (hls->flags & HLS_SECOND_LEVEL_SEGMENT_DURATION) {
Vishwanath Dixit
committed
char * filename = av_strdup(vs->avf->filename); // %%t will be %t after strftime
return AVERROR(ENOMEM);
Vishwanath Dixit
committed
if (replace_int_data_in_filename(vs->avf->filename, sizeof(vs->avf->filename),
filename, 't', (int64_t)round(duration * HLS_MICROSECOND_UNIT)) < 1) {
av_log(hls, AV_LOG_ERROR,
"Invalid second level segment filename template '%s', "
"you can try to remove second_level_segment_time flag\n",
filename);
av_free(filename);
return AVERROR(EINVAL);
}
av_free(filename);
}
}
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
return 0;
}
static int sls_flag_check_duration_size_index(HLSContext *hls)
{
int ret = 0;
if (hls->flags & HLS_SECOND_LEVEL_SEGMENT_DURATION) {
av_log(hls, AV_LOG_ERROR,
"second_level_segment_duration hls_flag requires use_localtime to be true\n");
ret = AVERROR(EINVAL);
}
if (hls->flags & HLS_SECOND_LEVEL_SEGMENT_SIZE) {
av_log(hls, AV_LOG_ERROR,
"second_level_segment_size hls_flag requires use_localtime to be true\n");
ret = AVERROR(EINVAL);
}
if (hls->flags & HLS_SECOND_LEVEL_SEGMENT_INDEX) {
av_log(hls, AV_LOG_ERROR,
"second_level_segment_index hls_flag requires use_localtime to be true\n");
ret = AVERROR(EINVAL);
}
return ret;
}
Vishwanath Dixit
committed
static int sls_flag_check_duration_size(HLSContext *hls, VariantStream *vs)
Vishwanath Dixit
committed
const char *proto = avio_find_protocol_name(vs->basename);
int segment_renaming_ok = proto && !strcmp(proto, "file");
int ret = 0;
if ((hls->flags & HLS_SECOND_LEVEL_SEGMENT_DURATION) && !segment_renaming_ok) {
av_log(hls, AV_LOG_ERROR,
"second_level_segment_duration hls_flag works only with file protocol segment names\n");
ret = AVERROR(EINVAL);
}
if ((hls->flags & HLS_SECOND_LEVEL_SEGMENT_SIZE) && !segment_renaming_ok) {
av_log(hls, AV_LOG_ERROR,
"second_level_segment_size hls_flag works only with file protocol segment names\n");
ret = AVERROR(EINVAL);
}
return ret;
}
Vishwanath Dixit
committed
static void sls_flag_file_rename(HLSContext *hls, VariantStream *vs, char *old_filename) {
if ((hls->flags & (HLS_SECOND_LEVEL_SEGMENT_SIZE | HLS_SECOND_LEVEL_SEGMENT_DURATION)) &&
Vishwanath Dixit
committed
strlen(vs->current_segment_final_filename_fmt)) {
ff_rename(old_filename, vs->avf->filename, hls);
Vishwanath Dixit
committed
static int sls_flag_use_localtime_filename(AVFormatContext *oc, HLSContext *c, VariantStream *vs)
{
if (c->flags & HLS_SECOND_LEVEL_SEGMENT_INDEX) {
char * filename = av_strdup(oc->filename); // %%d will be %d after strftime
if (!filename)
return AVERROR(ENOMEM);
if (replace_int_data_in_filename(oc->filename, sizeof(oc->filename),
#if FF_API_HLS_WRAP
Vishwanath Dixit
committed
filename, 'd', c->wrap ? vs->sequence % c->wrap : vs->sequence) < 1) {
Vishwanath Dixit
committed
filename, 'd', vs->sequence) < 1) {
#endif
av_log(c, AV_LOG_ERROR, "Invalid second level segment filename template '%s', "
"you can try to remove second_level_segment_index flag\n",
filename);
av_free(filename);
return AVERROR(EINVAL);
}
av_free(filename);
}
if (c->flags & (HLS_SECOND_LEVEL_SEGMENT_SIZE | HLS_SECOND_LEVEL_SEGMENT_DURATION)) {
Vishwanath Dixit
committed
av_strlcpy(vs->current_segment_final_filename_fmt, oc->filename,
sizeof(vs->current_segment_final_filename_fmt));
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
if (c->flags & HLS_SECOND_LEVEL_SEGMENT_SIZE) {
char * filename = av_strdup(oc->filename); // %%s will be %s after strftime
if (!filename)
return AVERROR(ENOMEM);
if (replace_int_data_in_filename(oc->filename, sizeof(oc->filename), filename, 's', 0) < 1) {
av_log(c, AV_LOG_ERROR, "Invalid second level segment filename template '%s', "
"you can try to remove second_level_segment_size flag\n",
filename);
av_free(filename);
return AVERROR(EINVAL);
}
av_free(filename);
}
if (c->flags & HLS_SECOND_LEVEL_SEGMENT_DURATION) {
char * filename = av_strdup(oc->filename); // %%t will be %t after strftime
if (!filename)
return AVERROR(ENOMEM);
if (replace_int_data_in_filename(oc->filename, sizeof(oc->filename), filename, 't', 0) < 1) {
av_log(c, AV_LOG_ERROR, "Invalid second level segment filename template '%s', "
"you can try to remove second_level_segment_time flag\n",
filename);
av_free(filename);
return AVERROR(EINVAL);
}
av_free(filename);
}
}
return 0;
}
/* Create a new segment and append it to the segment list */
Vishwanath Dixit
committed
static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls,
VariantStream *vs, double duration, int64_t pos,
int64_t size)
{
HLSSegment *en = av_malloc(sizeof(*en));
const char *filename;
int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);
if (!en)
return AVERROR(ENOMEM);
Vishwanath Dixit
committed
ret = sls_flags_filename_process(s, hls, vs, en, duration, pos, size);
if (ret < 0) {
return ret;
}
Vishwanath Dixit
committed
filename = av_basename(vs->avf->filename);
Johan Ström
committed
if (hls->use_localtime_mkdir) {
Vishwanath Dixit
committed
filename = vs->avf->filename;
Johan Ström
committed
}
Vishwanath Dixit
committed
if ((find_segment_by_filename(vs->segments, filename) || find_segment_by_filename(vs->old_segments, filename))
&& !byterange_mode) {
av_log(hls, AV_LOG_WARNING, "Duplicated segment filename detected: %s\n", filename);
}
Johan Ström
committed
av_strlcpy(en->filename, filename, sizeof(en->filename));
Vishwanath Dixit
committed
if(vs->has_subtitle)
av_strlcpy(en->sub_filename, av_basename(vs->vtt_avf->filename), sizeof(en->sub_filename));
else
en->sub_filename[0] = '\0';
en->pos = pos;
en->size = size;
Vishwanath Dixit
committed
if (vs->discontinuity) {
Vishwanath Dixit
committed
vs->discontinuity = 0;
if (hls->key_info_file || hls->encrypt) {
av_strlcpy(en->key_uri, hls->key_uri, sizeof(en->key_uri));
av_strlcpy(en->iv_string, hls->iv_string, sizeof(en->iv_string));
}
Vishwanath Dixit
committed
if (!vs->segments)
vs->segments = en;
Vishwanath Dixit
committed
vs->last_segment->next = en;
Vishwanath Dixit
committed
vs->last_segment = en;
// EVENT or VOD playlists imply sliding window cannot be used
if (hls->pl_type != PLAYLIST_TYPE_NONE)
hls->max_nb_segments = 0;
Vishwanath Dixit
committed
if (hls->max_nb_segments && vs->nb_entries >= hls->max_nb_segments) {
en = vs->segments;
vs->initial_prog_date_time += en->duration;
vs->segments = en->next;
if (en && hls->flags & HLS_DELETE_SEGMENTS &&
!(hls->flags & HLS_SINGLE_FILE || hls->wrap)) {
#else
!(hls->flags & HLS_SINGLE_FILE)) {
#endif
Vishwanath Dixit
committed
en->next = vs->old_segments;
vs->old_segments = en;
if ((ret = hls_delete_old_segments(s, hls, vs)) < 0)
return ret;
} else
av_free(en);
Vishwanath Dixit
committed
vs->nb_entries++;
if (hls->max_seg_size > 0) {
return 0;
}
Vishwanath Dixit
committed
vs->sequence++;
Vishwanath Dixit
committed
static int parse_playlist(AVFormatContext *s, const char *url, VariantStream *vs)
{
HLSContext *hls = s->priv_data;
AVIOContext *in;
int ret = 0, is_segment = 0;
int64_t new_start_pos;
char line[1024];
const char *ptr;
if ((ret = ffio_open_whitelist(&in, url, AVIO_FLAG_READ,
&s->interrupt_callback, NULL,
s->protocol_whitelist, s->protocol_blacklist)) < 0)
return ret;
read_chomp_line(in, line, sizeof(line));
if (strcmp(line, "#EXTM3U")) {