| @@ -215,7 +215,7 @@ OBJS-$(CONFIG_HDS_MUXER) += hdsenc.o | |||
| OBJS-$(CONFIG_HEVC_DEMUXER) += hevcdec.o rawdec.o | |||
| OBJS-$(CONFIG_HEVC_MUXER) += rawenc.o | |||
| OBJS-$(CONFIG_HLS_DEMUXER) += hls.o | |||
| OBJS-$(CONFIG_HLS_MUXER) += hlsenc.o | |||
| OBJS-$(CONFIG_HLS_MUXER) += hlsenc.o hlsplaylist.o | |||
| OBJS-$(CONFIG_HNM_DEMUXER) += hnm.o | |||
| OBJS-$(CONFIG_ICO_DEMUXER) += icodec.o | |||
| OBJS-$(CONFIG_ICO_MUXER) += icoenc.o | |||
| @@ -46,6 +46,7 @@ | |||
| #include "avformat.h" | |||
| #include "avio_internal.h" | |||
| #include "http.h" | |||
| #include "hlsplaylist.h" | |||
| #include "internal.h" | |||
| #include "os_support.h" | |||
| @@ -97,13 +98,6 @@ typedef enum { | |||
| SEGMENT_TYPE_FMP4, | |||
| } SegmentType; | |||
| typedef enum { | |||
| PLAYLIST_TYPE_NONE, | |||
| PLAYLIST_TYPE_EVENT, | |||
| PLAYLIST_TYPE_VOD, | |||
| PLAYLIST_TYPE_NB, | |||
| } PlaylistType; | |||
| typedef struct VariantStream { | |||
| unsigned number; | |||
| int64_t sequence; | |||
| @@ -1055,19 +1049,6 @@ static void hls_free_segments(HLSSegment *p) | |||
| } | |||
| } | |||
| static void write_m3u8_head_block(HLSContext *hls, AVIOContext *out, int version, | |||
| int target_duration, int64_t sequence) | |||
| { | |||
| 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); | |||
| av_log(hls, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence); | |||
| } | |||
| static void hls_rename_temp_file(AVFormatContext *s, AVFormatContext *oc) | |||
| { | |||
| size_t len = strlen(oc->filename); | |||
| @@ -1133,8 +1114,7 @@ static int create_master_playlist(AVFormatContext *s, | |||
| goto fail; | |||
| } | |||
| avio_printf(master_pb, "#EXTM3U\n"); | |||
| avio_printf(master_pb, "#EXT-X-VERSION:%d\n", hls->version); | |||
| ff_hls_write_playlist_version(master_pb, hls->version); | |||
| /* For variant streams with video add #EXT-X-STREAM-INF tag with attributes*/ | |||
| for (i = 0; i < hls->nb_varstreams; i++) { | |||
| @@ -1175,18 +1155,7 @@ static int create_master_playlist(AVFormatContext *s, | |||
| bandwidth += aud_st->codecpar->bit_rate; | |||
| bandwidth += bandwidth / 10; | |||
| if (!bandwidth) { | |||
| av_log(NULL, AV_LOG_WARNING, | |||
| "Bandwidth info not available, set audio and video bitrates\n"); | |||
| av_freep(&m3u8_rel_name); | |||
| continue; | |||
| } | |||
| avio_printf(master_pb, "#EXT-X-STREAM-INF:BANDWIDTH=%d", bandwidth); | |||
| if (vid_st && vid_st->codecpar->width > 0 && vid_st->codecpar->height > 0) | |||
| avio_printf(master_pb, ",RESOLUTION=%dx%d", vid_st->codecpar->width, | |||
| vid_st->codecpar->height); | |||
| avio_printf(master_pb, "\n%s\n\n", m3u8_rel_name); | |||
| ff_hls_write_stream_info(vid_st, master_pb, bandwidth, m3u8_rel_name); | |||
| av_freep(&m3u8_rel_name); | |||
| } | |||
| @@ -1215,6 +1184,7 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs) | |||
| char *iv_string = NULL; | |||
| AVDictionary *options = NULL; | |||
| double prog_date_time = vs->initial_prog_date_time; | |||
| double *prog_date_time_p = (hls->flags & HLS_PROGRAM_DATE_TIME) ? &prog_date_time : NULL; | |||
| int byterange_mode = (hls->flags & HLS_SINGLE_FILE) || (hls->max_seg_size > 0); | |||
| hls->version = 3; | |||
| @@ -1245,12 +1215,8 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs) | |||
| } | |||
| vs->discontinuity_set = 0; | |||
| write_m3u8_head_block(hls, out, hls->version, target_duration, 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"); | |||
| } | |||
| ff_hls_write_playlist_header(out, hls->version, hls->allowcache, | |||
| target_duration, sequence, hls->pl_type); | |||
| if((hls->flags & HLS_DISCONT_START) && sequence==hls->start_sequence && vs->discontinuity_set==0 ){ | |||
| avio_printf(out, "#EXT-X-DISCONTINUITY\n"); | |||
| @@ -1270,74 +1236,35 @@ static int hls_window(AVFormatContext *s, int last, VariantStream *vs) | |||
| iv_string = en->iv_string; | |||
| } | |||
| if (en->discont) { | |||
| avio_printf(out, "#EXT-X-DISCONTINUITY\n"); | |||
| } | |||
| if ((hls->segment_type == SEGMENT_TYPE_FMP4) && (en == vs->segments)) { | |||
| avio_printf(out, "#EXT-X-MAP:URI=\"%s\"", vs->fmp4_init_filename); | |||
| if (hls->flags & HLS_SINGLE_FILE) { | |||
| avio_printf(out, ",BYTERANGE=\"%"PRId64"@%"PRId64"\"", en->size, en->pos); | |||
| } | |||
| avio_printf(out, "\n"); | |||
| ff_hls_write_init_file(out, vs->fmp4_init_filename, | |||
| hls->flags & HLS_SINGLE_FILE, en->size, en->pos); | |||
| } | |||
| if (hls->flags & HLS_ROUND_DURATIONS) | |||
| avio_printf(out, "#EXTINF:%ld,\n", lrint(en->duration)); | |||
| else | |||
| avio_printf(out, "#EXTINF:%f,\n", en->duration); | |||
| if (byterange_mode) | |||
| avio_printf(out, "#EXT-X-BYTERANGE:%"PRId64"@%"PRId64"\n", | |||
| en->size, en->pos); | |||
| if (hls->flags & HLS_PROGRAM_DATE_TIME) { | |||
| time_t tt, wrongsecs; | |||
| 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 = (FFABS(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; | |||
| } | |||
| if (vs->baseurl) | |||
| avio_printf(out, "%s", vs->baseurl); | |||
| avio_printf(out, "%s\n", en->filename); | |||
| ff_hls_write_file_entry(out, en->discont, byterange_mode, | |||
| en->duration, hls->flags & HLS_ROUND_DURATIONS, | |||
| en->size, en->pos, vs->baseurl, | |||
| en->filename, prog_date_time_p); | |||
| } | |||
| if (last && (hls->flags & HLS_OMIT_ENDLIST)==0) | |||
| avio_printf(out, "#EXT-X-ENDLIST\n"); | |||
| ff_hls_write_end_list(out); | |||
| if( vs->vtt_m3u8_name ) { | |||
| if ((ret = s->io_open(s, &sub_out, vs->vtt_m3u8_name, AVIO_FLAG_WRITE, &options)) < 0) | |||
| goto fail; | |||
| write_m3u8_head_block(hls, sub_out, hls->version, target_duration, sequence); | |||
| ff_hls_write_playlist_header(sub_out, hls->version, hls->allowcache, | |||
| target_duration, sequence, PLAYLIST_TYPE_NONE); | |||
| for (en = vs->segments; en; en = en->next) { | |||
| avio_printf(sub_out, "#EXTINF:%f,\n", en->duration); | |||
| if (byterange_mode) | |||
| avio_printf(sub_out, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n", | |||
| en->size, en->pos); | |||
| if (vs->baseurl) | |||
| avio_printf(sub_out, "%s", vs->baseurl); | |||
| avio_printf(sub_out, "%s\n", en->sub_filename); | |||
| ff_hls_write_file_entry(sub_out, 0, byterange_mode, | |||
| en->duration, 0, en->size, en->pos, | |||
| vs->baseurl, en->sub_filename, NULL); | |||
| } | |||
| if (last) | |||
| avio_printf(sub_out, "#EXT-X-ENDLIST\n"); | |||
| ff_hls_write_end_list(sub_out); | |||
| } | |||
| @@ -0,0 +1,138 @@ | |||
| /* | |||
| * 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 <stdint.h> | |||
| #include "libavutil/time_internal.h" | |||
| #include "avformat.h" | |||
| #include "hlsplaylist.h" | |||
| void ff_hls_write_playlist_version(AVIOContext *out, int version) { | |||
| if (!out) | |||
| return; | |||
| avio_printf(out, "#EXTM3U\n"); | |||
| avio_printf(out, "#EXT-X-VERSION:%d\n", version); | |||
| } | |||
| void ff_hls_write_stream_info(AVStream *st, AVIOContext *out, | |||
| int bandwidth, char *filename) { | |||
| if (!out || !filename) | |||
| return; | |||
| if (!bandwidth) { | |||
| av_log(NULL, AV_LOG_WARNING, | |||
| "Bandwidth info not available, set audio and video bitrates\n"); | |||
| return; | |||
| } | |||
| avio_printf(out, "#EXT-X-STREAM-INF:BANDWIDTH=%d", bandwidth); | |||
| if (st && st->codecpar->width > 0 && st->codecpar->height > 0) | |||
| avio_printf(out, ",RESOLUTION=%dx%d", st->codecpar->width, | |||
| st->codecpar->height); | |||
| avio_printf(out, "\n%s\n\n", filename); | |||
| } | |||
| void ff_hls_write_playlist_header(AVIOContext *out, int version, int allowcache, | |||
| int target_duration, int64_t sequence, | |||
| uint32_t playlist_type) { | |||
| if (!out) | |||
| return; | |||
| ff_hls_write_playlist_version(out, version); | |||
| if (allowcache == 0 || allowcache == 1) { | |||
| avio_printf(out, "#EXT-X-ALLOW-CACHE:%s\n", allowcache == 0 ? "NO" : "YES"); | |||
| } | |||
| avio_printf(out, "#EXT-X-TARGETDURATION:%d\n", target_duration); | |||
| avio_printf(out, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence); | |||
| av_log(NULL, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence); | |||
| if (playlist_type == PLAYLIST_TYPE_EVENT) { | |||
| avio_printf(out, "#EXT-X-PLAYLIST-TYPE:EVENT\n"); | |||
| } else if (playlist_type == PLAYLIST_TYPE_VOD) { | |||
| avio_printf(out, "#EXT-X-PLAYLIST-TYPE:VOD\n"); | |||
| } | |||
| } | |||
| void ff_hls_write_init_file(AVIOContext *out, char *filename, | |||
| int byterange_mode, int64_t size, int64_t pos) { | |||
| avio_printf(out, "#EXT-X-MAP:URI=\"%s\"", filename); | |||
| if (byterange_mode) { | |||
| avio_printf(out, ",BYTERANGE=\"%"PRId64"@%"PRId64"\"", size, pos); | |||
| } | |||
| avio_printf(out, "\n"); | |||
| } | |||
| void ff_hls_write_file_entry(AVIOContext *out, int insert_discont, | |||
| int byterange_mode, | |||
| double duration, int round_duration, | |||
| int64_t size, int64_t pos, //Used only if HLS_SINGLE_FILE flag is set | |||
| char *baseurl, //Ignored if NULL | |||
| char *filename, double *prog_date_time) { | |||
| if (!out || !filename) | |||
| return; | |||
| if (insert_discont) { | |||
| avio_printf(out, "#EXT-X-DISCONTINUITY\n"); | |||
| } | |||
| if (round_duration) | |||
| avio_printf(out, "#EXTINF:%ld,\n", lrint(duration)); | |||
| else | |||
| avio_printf(out, "#EXTINF:%f,\n", duration); | |||
| if (byterange_mode) | |||
| avio_printf(out, "#EXT-X-BYTERANGE:%"PRId64"@%"PRId64"\n", size, pos); | |||
| if (prog_date_time) { | |||
| time_t tt, wrongsecs; | |||
| 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 = (FFABS(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 += duration; | |||
| } | |||
| if (baseurl) | |||
| avio_printf(out, "%s", baseurl); | |||
| avio_printf(out, "%s\n", filename); | |||
| } | |||
| void ff_hls_write_end_list (AVIOContext *out) { | |||
| if (!out) | |||
| return; | |||
| avio_printf(out, "#EXT-X-ENDLIST\n"); | |||
| } | |||
| @@ -0,0 +1,51 @@ | |||
| /* | |||
| * 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 | |||
| */ | |||
| #ifndef AVFORMAT_HLSPLAYLIST_H_ | |||
| #define AVFORMAT_HLSPLAYLIST_H_ | |||
| #include "libavutil/common.h" | |||
| typedef enum { | |||
| PLAYLIST_TYPE_NONE, | |||
| PLAYLIST_TYPE_EVENT, | |||
| PLAYLIST_TYPE_VOD, | |||
| PLAYLIST_TYPE_NB, | |||
| } PlaylistType; | |||
| void ff_hls_write_playlist_version(AVIOContext *out, int version); | |||
| void ff_hls_write_stream_info(AVStream *st, AVIOContext *out, | |||
| int bandwidth, char *filename); | |||
| void ff_hls_write_playlist_header(AVIOContext *out, int version, int allowcache, | |||
| int target_duration, int64_t sequence, | |||
| uint32_t playlist_type); | |||
| void ff_hls_write_init_file(AVIOContext *out, char *filename, | |||
| int byterange_mode, int64_t size, int64_t pos); | |||
| void ff_hls_write_file_entry(AVIOContext *out, int insert_discont, | |||
| int byterange_mode, | |||
| double duration, int round_duration, | |||
| int64_t size, int64_t pos, //Used only if HLS_SINGLE_FILE flag is set | |||
| char *baseurl, //Ignored if NULL | |||
| char *filename, double *prog_date_time); | |||
| void ff_hls_write_end_list (AVIOContext *out); | |||
| #endif /* AVFORMAT_HLSPLAYLIST_H_ */ | |||