Skip to content
Snippets Groups Projects
hlsenc.c 85.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Apple HTTP Live Streaming segmenter
     * Copyright (c) 2012, Luca Barbato
    
     * 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
     */
    
    
    #include "config.h"
    
    #include <float.h>
    
    #include <stdint.h>
    
    #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"
    
    
    #include "avformat.h"
    
    #if CONFIG_HTTP_PROTOCOL
    
    #include "internal.h"
    
    #include "os_support.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
    
    typedef struct HLSSegment {
        char filename[1024];
    
        char sub_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];
    
    
    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_OMIT_ENDLIST = (1 << 4),
    
        HLS_APPEND_LIST = (1 << 6),
    
        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
    
        HLS_PERIODIC_REKEY = (1 << 12),
    
        HLS_INDEPENDENT_SEGMENTS = (1 << 13),
    
    typedef enum {
        SEGMENT_TYPE_MPEGTS,
        SEGMENT_TYPE_FMP4,
    } SegmentType;
    
    
        unsigned number;
    
        int64_t sequence;
    
        AVOutputFormat *oformat;
    
        AVOutputFormat *vtt_oformat;
    
        AVIOContext *out;
        int packets_written;
        int init_range_length;
    
        AVFormatContext *avf;
    
        AVFormatContext *vtt_avf;
    
        int has_video;
    
        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
    
        HLSSegment *segments;
        HLSSegment *last_segment;
    
        HLSSegment *old_segments;
    
        char *basename;
    
        char *vtt_basename;
        char *vtt_m3u8_name;
    
        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 */
    
        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
    
    
    Steven Liu's avatar
    Steven Liu committed
        char *format_options_str;
    
        char *vtt_format_options_str;
        char *subtitle_filename;
    
    Steven Liu's avatar
    Steven Liu committed
        AVDictionary *format_options;
    
        int encrypt;
        char *key;
        char *key_url;
        char *iv;
        char *key_basename;
    
        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;
    
    
    
        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 */
    
        char *var_stream_map; /* user specified variant stream map string */
    
        char *master_pl_name;
        unsigned int master_publish_rate;
    
        AVIOContext *m3u8_out;
        AVIOContext *sub_m3u8_out;
    
    } HLSContext;
    
    
    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);
    
    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);
    
    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);
    }
    
    
    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;
    
        while (segment) {
            playlist_duration += segment->duration;
            segment = segment->next;
        }
    
    
        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 {
    
            }
            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);
    
                if ((ret = vs->avf->io_open(vs->avf, &out, path, AVIO_FLAG_WRITE, &options)) < 0)
    
            } 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);
    
                    if ((ret = vs->avf->io_open(vs->avf, &out, sub_path, AVIO_FLAG_WRITE, &options)) < 0) {
    
                } 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);
    
            av_freep(&path);
    
            previous_segment = segment;
            segment = previous_segment->next;
            av_free(previous_segment);
        }
    
    fail:
    
    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);
    }
    
    
    static int do_encrypt(AVFormatContext *s, VariantStream *vs)
    
    {
        HLSContext *hls = s->priv_data;
        int ret;
        int len;
        AVIOContext *pb;
        uint8_t key[KEYSIZE];
    
    
        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) {
    
            } 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;
    }
    
    
    static int hls_mux_init(AVFormatContext *s, VariantStream *vs)
    
        AVDictionary *options = NULL;
    
        HLSContext *hls = s->priv_data;
        AVFormatContext *oc;
    
        AVFormatContext *vtt_oc = NULL;
    
        int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);
    
        ret = avformat_alloc_output_context2(&vs->avf, vs->oformat, NULL, NULL);
    
        if (ret < 0)
            return ret;
    
        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);
    
        if(vs->vtt_oformat) {
            ret = avformat_alloc_output_context2(&vs->vtt_avf, vs->vtt_oformat, NULL, NULL);
    
            if (ret < 0)
                return ret;
    
            vtt_oc          = vs->vtt_avf;
            vtt_oc->oformat = vs->vtt_oformat;
    
            av_dict_copy(&vtt_oc->metadata, s->metadata, 0);
        }
    
    
            AVStream *st;
    
            AVFormatContext *loc;
    
            if (vs->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)
    
                loc = vtt_oc;
            else
                loc = oc;
    
            if (!(st = avformat_new_stream(loc, NULL)))
    
                return AVERROR(ENOMEM);
    
            avcodec_parameters_copy(st->codecpar, vs->streams[i]->codecpar);
    
            if (!oc->oformat->codec_tag ||
    
                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;
            }
    
    
            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);
    
    
        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;
            }
    
    
            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) {
    
                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);
            }
    
            av_dict_free(&options);
        }
    
    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;
    }
    
    
    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)) &&
    
            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) {
    
                char * filename = av_strdup(vs->avf->filename);  // %%s will be %s after strftime
    
                if (!filename) {
                    av_free(en);
    
                    return AVERROR(ENOMEM);
    
                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);
    
                    av_free(en);
    
                    return AVERROR(EINVAL);
                }
                av_free(filename);
            }
            if (hls->flags & HLS_SECOND_LEVEL_SEGMENT_DURATION) {
    
                char * filename = av_strdup(vs->avf->filename);  // %%t will be %t after strftime
    
                if (!filename) {
                    av_free(en);
    
                    return AVERROR(ENOMEM);
    
                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);
    
                    av_free(en);
    
                    return AVERROR(EINVAL);
                }
                av_free(filename);
            }
        }
    
        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;
    }
    
    
    static int sls_flag_check_duration_size(HLSContext *hls, VariantStream *vs)
    
        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;
    }
    
    
    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)) &&
    
            strlen(vs->current_segment_final_filename_fmt)) {
            ff_rename(old_filename, vs->avf->filename, hls);
    
    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
    
                filename, 'd', c->wrap ? vs->sequence % c->wrap : 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)) {
    
            av_strlcpy(vs->current_segment_final_filename_fmt, oc->filename,
                       sizeof(vs->current_segment_final_filename_fmt));
    
            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 */
    
    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);
    
        ret = sls_flags_filename_process(s, hls, vs, en, duration, pos, size);
    
        filename = av_basename(vs->avf->filename);
    
        if ((find_segment_by_filename(vs->segments, filename) || find_segment_by_filename(vs->old_segments, filename))
    
            av_log(hls, AV_LOG_WARNING, "Duplicated segment filename detected: %s\n", filename);
        }
    
        av_strlcpy(en->filename, filename, sizeof(en->filename));
    
        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->duration = duration;
    
        en->pos      = pos;
        en->size     = size;
    
        en->next     = NULL;
    
        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));
        }
    
    
        // EVENT or VOD playlists imply sliding window cannot be used
        if (hls->pl_type != PLAYLIST_TYPE_NONE)
            hls->max_nb_segments = 0;
    
    
        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 &&
    
    #if FF_API_HLS_WRAP
    
                    !(hls->flags & HLS_SINGLE_FILE || hls->wrap)) {
    
    #else
                    !(hls->flags & HLS_SINGLE_FILE)) {
    #endif
    
                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);
    
        if (hls->max_seg_size > 0) {
            return 0;
        }
    
    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")) {