Skip to content
Snippets Groups Projects
hlsenc.c 42.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Apple HTTP Live Streaming segmenter
     * Copyright (c) 2012, Luca Barbato
     *
    
     * 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
    
    #include "libavutil/avassert.h"
    
    #include "libavutil/mathematics.h"
    #include "libavutil/parseutils.h"
    #include "libavutil/avstring.h"
    #include "libavutil/opt.h"
    #include "libavutil/log.h"
    
    
    #include "avformat.h"
    
    #include "internal.h"
    
    #include "os_support.h"
    
    #define KEYSIZE 16
    #define LINE_BUFFER_SIZE 1024
    
    
    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
    
    typedef enum {
        PLAYLIST_TYPE_NONE,
        PLAYLIST_TYPE_EVENT,
        PLAYLIST_TYPE_VOD,
        PLAYLIST_TYPE_NB,
    } PlaylistType;
    
    
    typedef struct HLSContext {
        const AVClass *class;  // Class for private options.
    
        unsigned number;
    
        int64_t sequence;
    
        int64_t start_sequence;
    
        AVOutputFormat *oformat;
    
        AVOutputFormat *vtt_oformat;
    
        AVFormatContext *avf;
    
        AVFormatContext *vtt_avf;
    
        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  wrap;             // Set by a private option.
    
        uint32_t flags;        // enum HLSFlags
    
        uint32_t pl_type;      // enum PlaylistType
    
        char *segment_filename;
    
        int use_localtime;      ///< flag to expand filename with localtime
    
        int use_localtime_mkdir;///< flag to mkdir dirname in timebased filename
    
        int64_t recording_time;
        int has_video;
    
        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
    
        int64_t max_seg_size; // every segment file max size
    
        HLSSegment *segments;
        HLSSegment *last_segment;
    
        HLSSegment *old_segments;
    
        char *basename;
    
        char *vtt_basename;
        char *vtt_m3u8_name;
    
    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;
    
    
        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;
    
    
        double initial_prog_date_time;
    
    } 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 hls_delete_old_segments(HLSContext *hls) {
    
        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;
    
    
        segment = hls->segments;
        while (segment) {
            playlist_duration += segment->duration;
            segment = segment->next;
        }
    
        segment = hls->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 {
                dirname = av_strdup(hls->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);
            }
    
    
            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(dirname) + strlen(segment->sub_filename) + 1;
                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 (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 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)
    {
        HLSContext *hls = s->priv_data;
        AVFormatContext *oc;
    
        AVFormatContext *vtt_oc = NULL;
    
        ret = avformat_alloc_output_context2(&hls->avf, hls->oformat, NULL, NULL);
        if (ret < 0)
            return ret;
        oc = hls->avf;
    
    
        oc->oformat            = hls->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);
    
        if(hls->vtt_oformat) {
            ret = avformat_alloc_output_context2(&hls->vtt_avf, hls->vtt_oformat, NULL, NULL);
            if (ret < 0)
                return ret;
            vtt_oc          = hls->vtt_avf;
            vtt_oc->oformat = hls->vtt_oformat;
            av_dict_copy(&vtt_oc->metadata, s->metadata, 0);
        }
    
    
        for (i = 0; i < s->nb_streams; i++) {
            AVStream *st;
    
            AVFormatContext *loc;
    
            if (s->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, s->streams[i]->codecpar);
    
            st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio;
    
            st->time_base = s->streams[i]->time_base;
    
        hls->start_pos = 0;
    
    /* Create a new segment and append it to the segment list */
    
    static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double duration,
                                  int64_t pos, int64_t size)
    
        HLSSegment *en = av_malloc(sizeof(*en));
    
    
        if (!en)
            return AVERROR(ENOMEM);
    
    
        filename = av_basename(hls->avf->filename);
    
        if (hls->use_localtime_mkdir) {
    
            filename = hls->avf->filename;
    
        }
        av_strlcpy(en->filename, filename, sizeof(en->filename));
    
        if(hls->has_subtitle)
            av_strlcpy(en->sub_filename, av_basename(hls->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;
    
        en->discont  = 0;
    
        if (hls->discontinuity) {
            en->discont = 1;
            hls->discontinuity = 0;
        }
    
        if (hls->key_info_file) {
            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 && hls->nb_entries >= hls->max_nb_segments) {
            en = hls->segments;
    
            hls->initial_prog_date_time += en->duration;
    
            if (en && hls->flags & HLS_DELETE_SEGMENTS &&
                    !(hls->flags & HLS_SINGLE_FILE || hls->wrap)) {
                en->next = hls->old_segments;
                hls->old_segments = en;
                if ((ret = hls_delete_old_segments(hls)) < 0)
                    return ret;
            } else
                av_free(en);
    
        if (hls->max_seg_size > 0) {
            return 0;
        }
    
    static int parse_playlist(AVFormatContext *s, const char *url)
    {
        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")) {
            ret = AVERROR_INVALIDDATA;
            goto fail;
        }
    
    
        hls->discontinuity = 0;
    
        while (!avio_feof(in)) {
            read_chomp_line(in, line, sizeof(line));
            if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) {
                hls->sequence = atoi(ptr);
    
            } else if (av_strstart(line, "#EXT-X-DISCONTINUITY", &ptr)) {
                is_segment = 1;
                hls->discontinuity = 1;
    
            } else if (av_strstart(line, "#EXTINF:", &ptr)) {
                is_segment = 1;
                hls->duration = atof(ptr);
            } else if (av_strstart(line, "#", NULL)) {
                continue;
            } else if (line[0]) {
                if (is_segment) {
                    is_segment = 0;
                    new_start_pos = avio_tell(hls->avf->pb);
                    hls->size = new_start_pos - hls->start_pos;
                    av_strlcpy(hls->avf->filename, line, sizeof(line));
                    ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size);
                    if (ret < 0)
                        goto fail;
                    hls->start_pos = new_start_pos;
                }
            }
        }
    
    fail:
        avio_close(in);
        return ret;
    }
    
    
    static void hls_free_segments(HLSSegment *p)
    
    
        while(p) {
            en = p;
            p = p->next;
            av_free(en);
        }
    }
    
    
    static void set_http_options(AVDictionary **options, HLSContext *c)
    {
        if (c->method)
            av_dict_set(options, "method", c->method, 0);
    }
    
    
    static int hls_window(AVFormatContext *s, int last)
    {
        HLSContext *hls = s->priv_data;
    
        int target_duration = 0;
    
        int ret = 0;
    
        AVIOContext *sub_out = NULL;
    
        int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries);
    
        const char *proto = avio_find_protocol_name(s->filename);
        int use_rename = proto && !strcmp(proto, "file");
    
        static unsigned warned_non_file;
    
        char *key_uri = NULL;
        char *iv_string = NULL;
    
        AVDictionary *options = NULL;
    
        double prog_date_time = hls->initial_prog_date_time;
    
        int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0);
    
        if (byterange_mode) {
            version = 4;
            sequence = 0;
        }
    
    
        if (!use_rename && !warned_non_file++)
    
    Moritz Barsnick's avatar
    Moritz Barsnick committed
            av_log(s, AV_LOG_ERROR, "Cannot use rename on non file protocol, this may lead to races and temporary partial files\n");
    
        set_http_options(&options, hls);
    
        snprintf(temp_filename, sizeof(temp_filename), use_rename ? "%s.tmp" : "%s", s->filename);
    
        if ((ret = s->io_open(s, &out, temp_filename, AVIO_FLAG_WRITE, &options)) < 0)
    
        for (en = hls->segments; en; en = en->next) {
    
            if (target_duration < en->duration)
    
                target_duration = ceil(en->duration);
    
        avio_printf(out, "#EXTM3U\n");
        avio_printf(out, "#EXT-X-VERSION:%d\n", version);
    
        if (hls->allowcache == 0 || hls->allowcache == 1) {
    
            avio_printf(out, "#EXT-X-ALLOW-CACHE:%s\n", hls->allowcache == 0 ? "NO" : "YES");
    
        avio_printf(out, "#EXT-X-TARGETDURATION:%d\n", target_duration);
        avio_printf(out, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence);
    
        if (hls->pl_type == PLAYLIST_TYPE_EVENT) {
            avio_printf(out, "#EXT-X-PLAYLIST-TYPE:EVENT\n");
        } else if (hls->pl_type == PLAYLIST_TYPE_VOD) {
            avio_printf(out, "#EXT-X-PLAYLIST-TYPE:VOD\n");
        }
    
        av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n",
    
               sequence);
    
        if((hls->flags & HLS_DISCONT_START) && sequence==hls->start_sequence && hls->discontinuity_set==0 ){
            avio_printf(out, "#EXT-X-DISCONTINUITY\n");
            hls->discontinuity_set = 1;
        }
    
        for (en = hls->segments; en; en = en->next) {
    
            if (hls->key_info_file && (!key_uri || strcmp(en->key_uri, key_uri) ||
                                        av_strcasecmp(en->iv_string, iv_string))) {
                avio_printf(out, "#EXT-X-KEY:METHOD=AES-128,URI=\"%s\"", en->key_uri);
                if (*en->iv_string)
                    avio_printf(out, ",IV=0x%s", en->iv_string);
                avio_printf(out, "\n");
                key_uri = en->key_uri;
                iv_string = en->iv_string;
            }
    
    
            if (en->discont) {
                avio_printf(out, "#EXT-X-DISCONTINUITY\n");
            }
    
    
            if (hls->flags & HLS_ROUND_DURATIONS)
    
                avio_printf(out, "#EXTINF:%ld,\n",  lrint(en->duration));
    
            else
                avio_printf(out, "#EXTINF:%f,\n", en->duration);
    
                 avio_printf(out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n",
    
                             en->size, en->pos);
    
            if (hls->flags & HLS_PROGRAM_DATE_TIME) {
    
                int milli;
                struct tm *tm, tmpbuf;
                char buf0[128], buf1[128];
                tt = (int64_t)prog_date_time;
                milli = av_clip(lrint(1000*(prog_date_time - tt)), 0, 999);
                tm = localtime_r(&tt, &tmpbuf);
    
                strftime(buf0, sizeof(buf0), "%Y-%m-%dT%H:%M:%S", tm);
    
                if (!strftime(buf1, sizeof(buf1), "%z", tm) || buf1[1]<'0' ||buf1[1]>'2') {
                    int tz_min, dst = tm->tm_isdst;
                    tm = gmtime_r(&tt, &tmpbuf);
                    tm->tm_isdst = dst;
                    wrongsecs = mktime(tm);
                    tz_min = (abs(wrongsecs - tt) + 30) / 60;
                    snprintf(buf1, sizeof(buf1),
                             "%c%02d%02d",
                             wrongsecs <= tt ? '+' : '-',
                             tz_min / 60,
                             tz_min % 60);
                }
    
                avio_printf(out, "#EXT-X-PROGRAM-DATE-TIME:%s.%03d%s\n", buf0, milli, buf1);
                prog_date_time += en->duration;
            }
    
                avio_printf(out, "%s", hls->baseurl);
            avio_printf(out, "%s\n", en->filename);
    
        if (last && (hls->flags & HLS_OMIT_ENDLIST)==0)
    
            avio_printf(out, "#EXT-X-ENDLIST\n");
    
        if( hls->vtt_m3u8_name ) {
    
            if ((ret = s->io_open(s, &sub_out, hls->vtt_m3u8_name, AVIO_FLAG_WRITE, &options)) < 0)
    
                goto fail;
            avio_printf(sub_out, "#EXTM3U\n");
            avio_printf(sub_out, "#EXT-X-VERSION:%d\n", version);
            if (hls->allowcache == 0 || hls->allowcache == 1) {
                avio_printf(sub_out, "#EXT-X-ALLOW-CACHE:%s\n", hls->allowcache == 0 ? "NO" : "YES");
            }
            avio_printf(sub_out, "#EXT-X-TARGETDURATION:%d\n", target_duration);
            avio_printf(sub_out, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence);
    
            av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n",
                   sequence);
    
            for (en = hls->segments; en; en = en->next) {
                avio_printf(sub_out, "#EXTINF:%f,\n", en->duration);
    
                     avio_printf(sub_out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n",
                             en->size, en->pos);
                if (hls->baseurl)
                    avio_printf(sub_out, "%s", hls->baseurl);
                avio_printf(sub_out, "%s\n", en->sub_filename);
            }
    
            if (last)
                avio_printf(sub_out, "#EXT-X-ENDLIST\n");
    
        }
    
    
        ff_format_io_close(s, &out);
    
        ff_format_io_close(s, &sub_out);
    
        if (ret >= 0 && use_rename)
    
            ff_rename(temp_filename, s->filename, s);
    
    static HLSSegment *find_segment_by_filename(HLSSegment *segment, const char *filename)
    {
        /* filename may contain rel/abs path, but segments store only basename */
        char *p = NULL, *dirname = NULL, *path = NULL;
        int path_size;
        HLSSegment *ret_segment = NULL;
        dirname = av_strdup(filename);
        if (!dirname)
            return NULL;
        p = (char *)av_basename(dirname); // av_dirname would return . in case of no dir
        *p = '\0'; // maybe empty
    
        while (segment) {
            path_size = strlen(dirname) + strlen(segment->filename) + 1;
            path = av_malloc(path_size);
            if (!path)
                goto end;
            av_strlcpy(path, dirname, path_size);
            av_strlcat(path, segment->filename, path_size);
            if (!strcmp(path,filename)) {
                ret_segment = segment;
                av_free(path);
                goto end;
            }
            av_free(path);
            segment = segment->next;
        }
    end:
        av_free(dirname);
        return ret_segment;
    }
    
    
    static int hls_start(AVFormatContext *s)
    {
        HLSContext *c = s->priv_data;
        AVFormatContext *oc = c->avf;
    
        AVFormatContext *vtt_oc = c->vtt_avf;
    
        AVDictionary *options = NULL;
        char *filename, iv_string[KEYSIZE*2 + 1];
    
        int err = 0;
    
    
        if (c->flags & HLS_SINGLE_FILE) {
    
            av_strlcpy(oc->filename, c->basename,
                       sizeof(oc->filename));
    
            if (c->vtt_basename)
                av_strlcpy(vtt_oc->filename, c->vtt_basename,
                      sizeof(vtt_oc->filename));
    
        } else if (c->max_seg_size > 0) {
            if (av_get_frame_filename2(oc->filename, sizeof(oc->filename),
                c->basename, c->wrap ? c->sequence % c->wrap : c->sequence,
    
                    av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s', you can try to use -use_localtime 1 with it\n", c->basename);
                    return AVERROR(EINVAL);
            }
    
            if (c->use_localtime) {
                time_t now0;
                struct tm *tm, tmpbuf;
                time(&now0);
                tm = localtime_r(&now0, &tmpbuf);
                if (!strftime(oc->filename, sizeof(oc->filename), c->basename, tm)) {
                    av_log(oc, AV_LOG_ERROR, "Could not get segment filename with use_localtime\n");
                    return AVERROR(EINVAL);
                }
    
                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 (av_get_frame_filename2(oc->filename, sizeof(oc->filename),
                        filename, c->wrap ? c->sequence % c->wrap : c->sequence,
                        AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) {
                        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 (find_segment_by_filename(c->segments, oc->filename)
                    || find_segment_by_filename(c->old_segments, oc->filename)) {
                    av_log(c, AV_LOG_WARNING, "Duplicated segment filename detected: %s\n", oc->filename);
                }
    
                if (c->use_localtime_mkdir) {
                    const char *dir;
                    char *fn_copy = av_strdup(oc->filename);
                    if (!fn_copy) {
                        return AVERROR(ENOMEM);
                    }
                    dir = av_dirname(fn_copy);
    
                    if (mkdir_p(dir) == -1 && errno != EEXIST) {
    
                        av_log(oc, AV_LOG_ERROR, "Could not create directory %s with use_localtime_mkdir\n", dir);
                        av_free(fn_copy);
                        return AVERROR(errno);
                    }
                    av_free(fn_copy);
                }
    
            } else if (av_get_frame_filename2(oc->filename, sizeof(oc->filename),
                                      c->basename, c->wrap ? c->sequence % c->wrap : c->sequence,
    
    Moritz Barsnick's avatar
    Moritz Barsnick committed
                av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s' you can try to use -use_localtime 1 with it\n", c->basename);
    
                return AVERROR(EINVAL);
            }
    
            if( c->vtt_basename) {
    
                if (av_get_frame_filename2(vtt_oc->filename, sizeof(vtt_oc->filename),
                                  c->vtt_basename, c->wrap ? c->sequence % c->wrap : c->sequence,
    
                    av_log(vtt_oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", c->vtt_basename);
                    return AVERROR(EINVAL);
                }
           }
        }
    
        if (c->key_info_file) {
            if ((err = hls_encryption_start(s)) < 0)
    
            if ((err = av_dict_set(&options, "encryption_key", c->key_string, 0))
                    < 0)
    
            err = av_strlcpy(iv_string, c->iv_string, sizeof(iv_string));
            if (!err)
                snprintf(iv_string, sizeof(iv_string), "%032"PRIx64, c->sequence);
            if ((err = av_dict_set(&options, "encryption_iv", iv_string, 0)) < 0)
    
    
            filename = av_asprintf("crypto:%s", oc->filename);
            if (!filename) {
    
                err = AVERROR(ENOMEM);
                goto fail;
    
            err = s->io_open(s, &oc->pb, filename, AVIO_FLAG_WRITE, &options);
    
            av_free(filename);
            av_dict_free(&options);
            if (err < 0)
                return err;
        } else
    
            if ((err = s->io_open(s, &oc->pb, oc->filename, AVIO_FLAG_WRITE, &options)) < 0)
    
        if (c->vtt_basename) {
    
            set_http_options(&options, c);
    
            if ((err = s->io_open(s, &vtt_oc->pb, vtt_oc->filename, AVIO_FLAG_WRITE, &options)) < 0)
    
        /* We only require one PAT/PMT per segment. */
        if (oc->oformat->priv_class && oc->priv_data) {
            char period[21];
    
            snprintf(period, sizeof(period), "%d", (INT_MAX / 2) - 1);
    
    
            av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0);
    
            av_opt_set(oc->priv_data, "sdt_period", period, 0);
            av_opt_set(oc->priv_data, "pat_period", period, 0);
        }
    
        if (c->vtt_basename) {
            err = avformat_write_header(vtt_oc,NULL);
            if (err < 0)
                return err;
        }
    
        return 0;
    
    fail:
        av_dict_free(&options);
    
        return err;
    
    static const char * get_default_pattern_localtime_fmt(void)
    {
        char b[21];
        time_t t = time(NULL);
        struct tm *p, tmbuf;
        p = localtime_r(&t, &tmbuf);
        // no %s support when strftime returned error or left format string unchanged
        return (!strftime(b, sizeof(b), "%s", p) || !strcmp(b, "%s")) ? "-%Y%m%d%H%I%S.ts" : "-%s.ts";
    }
    
    
    static int hls_write_header(AVFormatContext *s)
    {
        HLSContext *hls = s->priv_data;
        int ret, i;
        char *p;
        const char *pattern = "%d.ts";
    
        const char *pattern_localtime_fmt = get_default_pattern_localtime_fmt();
    
        const char *vtt_pattern = "%d.vtt";
    
    Steven Liu's avatar
    Steven Liu committed
        AVDictionary *options = NULL;
    
        int vtt_basename_size;
    
        hls->sequence       = hls->start_sequence;
    
        hls->recording_time = (hls->init_time ? hls->init_time : hls->time) * AV_TIME_BASE;
    
        hls->start_pts      = AV_NOPTS_VALUE;
    
    
        if (hls->flags & HLS_PROGRAM_DATE_TIME) {
            time_t now0;
            time(&now0);
            hls->initial_prog_date_time = now0;
        }
    
    
    Steven Liu's avatar
    Steven Liu committed
        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);
                goto fail;
            }
        }
    
    
        for (i = 0; i < s->nb_streams; i++) {
    
            hls->has_video +=
    
                s->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO;
    
            hls->has_subtitle +=
    
                s->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE;
    
    
        if (hls->has_video > 1)
            av_log(s, AV_LOG_WARNING,
                   "More than a single video stream present, "
                   "expect issues decoding it.\n");
    
        hls->oformat = av_guess_format("mpegts", NULL, NULL);
    
        if (!hls->oformat) {
            ret = AVERROR_MUXER_NOT_FOUND;
            goto fail;
        }
    
    
        if(hls->has_subtitle) {
            hls->vtt_oformat = av_guess_format("webvtt", NULL, NULL);
            if (!hls->oformat) {
                ret = AVERROR_MUXER_NOT_FOUND;
                goto fail;
            }
        }
    
    
        if (hls->segment_filename) {
            hls->basename = av_strdup(hls->segment_filename);
            if (!hls->basename) {
                ret = AVERROR(ENOMEM);
                goto fail;
            }
        } else {
            if (hls->flags & HLS_SINGLE_FILE)
                pattern = ".ts";
    
            if (hls->use_localtime) {
                basename_size = strlen(s->filename) + strlen(pattern_localtime_fmt) + 1;
            } else {
                basename_size = strlen(s->filename) + strlen(pattern) + 1;
            }
    
            hls->basename = av_malloc(basename_size);
            if (!hls->basename) {
                ret = AVERROR(ENOMEM);
                goto fail;
            }
    
            av_strlcpy(hls->basename, s->filename, basename_size);
    
            p = strrchr(hls->basename, '.');
            if (p)
                *p = '\0';
    
            if (hls->use_localtime) {
                av_strlcat(hls->basename, pattern_localtime_fmt, basename_size);
            } else {
                av_strlcat(hls->basename, pattern, basename_size);
            }
    
        if (!hls->use_localtime && (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);
            goto fail;
        }
    
        if(hls->has_subtitle) {
    
            if (hls->flags & HLS_SINGLE_FILE)
                vtt_pattern = ".vtt";
            vtt_basename_size = strlen(s->filename) + strlen(vtt_pattern) + 1;
            hls->vtt_basename = av_malloc(vtt_basename_size);
            if (!hls->vtt_basename) {
                ret = AVERROR(ENOMEM);
                goto fail;
            }
            hls->vtt_m3u8_name = av_malloc(vtt_basename_size);
            if (!hls->vtt_m3u8_name ) {
                ret = AVERROR(ENOMEM);
                goto fail;
            }
            av_strlcpy(hls->vtt_basename, s->filename, vtt_basename_size);
            p = strrchr(hls->vtt_basename, '.');
            if (p)
                *p = '\0';
    
            if( hls->subtitle_filename ) {
                strcpy(hls->vtt_m3u8_name, hls->subtitle_filename);
            } else {
                strcpy(hls->vtt_m3u8_name, hls->vtt_basename);
                av_strlcat(hls->vtt_m3u8_name, "_vtt.m3u8", vtt_basename_size);
            }
            av_strlcat(hls->vtt_basename, vtt_pattern, vtt_basename_size);
        }
    
    
        if ((ret = hls_mux_init(s)) < 0)
            goto fail;
    
    
        if (hls->flags & HLS_APPEND_LIST) {
            parse_playlist(s, s->filename);
    
            hls->discontinuity = 1;
    
            if (hls->init_time > 0) {
                av_log(s, AV_LOG_WARNING, "append_list mode does not support hls_init_time,"
                       " hls_init_time value will have no effect\n");
                hls->init_time = 0;
                hls->recording_time = hls->time * AV_TIME_BASE;
            }
    
        if ((ret = hls_start(s)) < 0)
            goto fail;
    
    
    Steven Liu's avatar
    Steven Liu committed
        av_dict_copy(&options, hls->format_options, 0);
        ret = avformat_write_header(hls->avf, &options);
        if (av_dict_count(options)) {
            av_log(s, AV_LOG_ERROR, "Some of provided format options in '%s' are not recognized\n", hls->format_options_str);
            ret = AVERROR(EINVAL);
    
        //av_assert0(s->nb_streams == hls->avf->nb_streams);
    
        for (i = 0; i < s->nb_streams; i++) {
    
            AVStream *inner_st;
    
            AVStream *outer_st = s->streams[i];
    
    
            if (hls->max_seg_size > 0) {
                if ((outer_st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) &&
                    (outer_st->codecpar->bit_rate > hls->max_seg_size)) {
                    av_log(s, AV_LOG_WARNING, "Your video bitrate is bigger than hls_segment_size, "
                           "(%"PRId64 " > %"PRId64 "), the result maybe not be what you want.",
                           outer_st->codecpar->bit_rate, hls->max_seg_size);
                }