2017-11-29 13:44:15 +02:00
|
|
|
/*
|
|
|
|
* 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>
|
2023-09-02 12:11:59 +02:00
|
|
|
#include <time.h>
|
2017-11-29 13:44:15 +02:00
|
|
|
|
|
|
|
#include "libavutil/time_internal.h"
|
|
|
|
|
|
|
|
#include "avformat.h"
|
|
|
|
#include "hlsplaylist.h"
|
|
|
|
|
2020-05-25 20:33:40 +02:00
|
|
|
void ff_hls_write_playlist_version(AVIOContext *out, int version)
|
|
|
|
{
|
2017-11-29 13:44:15 +02:00
|
|
|
if (!out)
|
|
|
|
return;
|
|
|
|
avio_printf(out, "#EXTM3U\n");
|
|
|
|
avio_printf(out, "#EXT-X-VERSION:%d\n", version);
|
|
|
|
}
|
|
|
|
|
2020-05-25 19:11:34 +02:00
|
|
|
void ff_hls_write_audio_rendition(AVIOContext *out, const char *agroup,
|
|
|
|
const char *filename, const char *language,
|
2023-10-27 03:43:59 +02:00
|
|
|
int name_id, int is_default, int nb_channels)
|
2020-05-25 19:11:34 +02:00
|
|
|
{
|
2017-12-29 07:45:31 +02:00
|
|
|
if (!out || !agroup || !filename)
|
|
|
|
return;
|
|
|
|
|
|
|
|
avio_printf(out, "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"group_%s\"", agroup);
|
avformat/hlsenc: add var_stream_map LANGUAGE field string parameter
use a:0,agroup:aud_low,default:Yes,language:CHN a:1,agroup:aud_low,language:ENG
a:2,agroup:aud_high,default:YesYes,language:CHN a:3,agroup:aud_high,language:ENG
v:0,agroup:aud_low v:1,agroup:aud_high
create master m3u8 list.
result:
EXTM3U
EXT-X-VERSION:3
EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="group_aud_low",NAME="audio_0",DEFAULT=YES,LANGUAGE="CHN",URI="out_0.m3u8"
EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="group_aud_low",NAME="audio_1",DEFAULT=NO,LANGUAGE="ENG",URI="out_1.m3u8"
EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="group_aud_high",NAME="audio_2",DEFAULT=YES,LANGUAGE="CHN",URI="out_2.m3u8"
EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="group_aud_high",NAME="audio_3",DEFAULT=NO,LANGUAGE="ENG",URI="out_3.m3u8"
EXT-X-STREAM-INF:BANDWIDTH=1170400,RESOLUTION=640x480,CODECS="avc1.64001e,mp4a.40.2",AUDIO="group_aud_low"
out_4.m3u8
EXT-X-STREAM-INF:BANDWIDTH=3440800,RESOLUTION=640x480,CODECS="avc1.64001e,mp4a.40.2",AUDIO="group_aud_high"
out_5.m3u8
Signed-off-by: Steven Liu <lq@chinaffmpeg.org>
2019-01-23 13:19:49 +02:00
|
|
|
avio_printf(out, ",NAME=\"audio_%d\",DEFAULT=%s,", name_id, is_default ? "YES" : "NO");
|
|
|
|
if (language) {
|
|
|
|
avio_printf(out, "LANGUAGE=\"%s\",", language);
|
|
|
|
}
|
2023-10-27 03:43:59 +02:00
|
|
|
if (nb_channels) {
|
|
|
|
avio_printf(out, "CHANNELS=\"%d\",", nb_channels);
|
|
|
|
}
|
avformat/hlsenc: add var_stream_map LANGUAGE field string parameter
use a:0,agroup:aud_low,default:Yes,language:CHN a:1,agroup:aud_low,language:ENG
a:2,agroup:aud_high,default:YesYes,language:CHN a:3,agroup:aud_high,language:ENG
v:0,agroup:aud_low v:1,agroup:aud_high
create master m3u8 list.
result:
EXTM3U
EXT-X-VERSION:3
EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="group_aud_low",NAME="audio_0",DEFAULT=YES,LANGUAGE="CHN",URI="out_0.m3u8"
EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="group_aud_low",NAME="audio_1",DEFAULT=NO,LANGUAGE="ENG",URI="out_1.m3u8"
EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="group_aud_high",NAME="audio_2",DEFAULT=YES,LANGUAGE="CHN",URI="out_2.m3u8"
EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="group_aud_high",NAME="audio_3",DEFAULT=NO,LANGUAGE="ENG",URI="out_3.m3u8"
EXT-X-STREAM-INF:BANDWIDTH=1170400,RESOLUTION=640x480,CODECS="avc1.64001e,mp4a.40.2",AUDIO="group_aud_low"
out_4.m3u8
EXT-X-STREAM-INF:BANDWIDTH=3440800,RESOLUTION=640x480,CODECS="avc1.64001e,mp4a.40.2",AUDIO="group_aud_high"
out_5.m3u8
Signed-off-by: Steven Liu <lq@chinaffmpeg.org>
2019-01-23 13:19:49 +02:00
|
|
|
avio_printf(out, "URI=\"%s\"\n", filename);
|
2017-12-29 07:45:31 +02:00
|
|
|
}
|
|
|
|
|
2020-05-25 19:11:34 +02:00
|
|
|
void ff_hls_write_subtitle_rendition(AVIOContext *out, const char *sgroup,
|
|
|
|
const char *filename, const char *language,
|
|
|
|
int name_id, int is_default)
|
|
|
|
{
|
avformat: add subtitle support in master playlist m3u8
Test with the following command for the webvtt subtitle:
$ ./ffmpeg -y -i input_with_subtitle.mkv \
-b:v:0 5250k -c:v h264 -pix_fmt yuv420p -profile:v main -level 4.1 \
-b:a:0 256k \
-c:s webvtt -c:a mp2 -ar 48000 -ac 2 -map 0:v -map 0:a:0 -map 0:s:0 \
-f hls -var_stream_map "v:0,a:0,s:0,sgroup:subtitle" \
-master_pl_name master.m3u8 -t 300 -hls_time 10 -hls_init_time 4 -hls_list_size \
10 -master_pl_publish_rate 10 -hls_flags \
delete_segments+discont_start+split_by_time ./tmp/video.m3u8
Check the master m3u8:
$ cat tmp/master.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subtitle",NAME="subtitle_0",DEFAULT=YES,URI="video_vtt.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=6056600,RESOLUTION=1280x720,CODECS="avc1.4d4829,mp4a.40.33",SUBTITLES="subtitle"
video.m3u8
Check the result by convert to mkv:
$ ./ffmpeg -strict experimental -i ./tmp/master.m3u8 -c:v copy -c:a mp2 -c:s srt ./test.mkv
Signed-off-by: Limin Wang <lance.lmwang@gmail.com>
2020-03-29 13:13:44 +02:00
|
|
|
if (!out || !filename)
|
|
|
|
return;
|
|
|
|
|
|
|
|
avio_printf(out, "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"%s\"", sgroup);
|
|
|
|
avio_printf(out, ",NAME=\"subtitle_%d\",DEFAULT=%s,", name_id, is_default ? "YES" : "NO");
|
|
|
|
if (language) {
|
|
|
|
avio_printf(out, "LANGUAGE=\"%s\",", language);
|
|
|
|
}
|
|
|
|
avio_printf(out, "URI=\"%s\"\n", filename);
|
|
|
|
}
|
|
|
|
|
2020-05-25 19:11:34 +02:00
|
|
|
void ff_hls_write_stream_info(AVStream *st, AVIOContext *out, int bandwidth,
|
hlsenc: Calculate the average and actual maximum bitrate of segments
Previously, the bitrate advertised in the master playlist would only
be based on the nominal values in either AVCodecParameters bit_rate,
or via AVCPBProperties max_bitrate. On top of this, a
fudge factor of 10% is added, to account for container overhead.
Neither of these bitrates may be known, and if the encoder is
running in VBR mode, there is no such value to be known. And
the container overhead may be more or less than the given
constant factor of 10%.
Instead, calculate the maximum bitrate per segment based on
what actually gets output from the muxer, and average bitrate
across all segments.
When muxing of the file finishes, update the master playlist
with these values, exposing both the maximum (which previously
was a guesstimate based on the nominal values) via
EXT-X-STREAM-INF BANDWIDTH, and the average via
EXT-X-STREAM-INF AVERAGE-BANDWIDTH.
This makes it possible to use the hlsenc muxer with VBR
encodes, for VOD style muxing.
Signed-off-by: Martin Storsjö <martin@martin.st>
2024-06-20 15:36:46 +02:00
|
|
|
int avg_bandwidth,
|
2020-05-25 19:11:34 +02:00
|
|
|
const char *filename, const char *agroup,
|
|
|
|
const char *codecs, const char *ccgroup,
|
|
|
|
const char *sgroup)
|
|
|
|
{
|
2017-11-29 13:44:15 +02:00
|
|
|
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);
|
hlsenc: Calculate the average and actual maximum bitrate of segments
Previously, the bitrate advertised in the master playlist would only
be based on the nominal values in either AVCodecParameters bit_rate,
or via AVCPBProperties max_bitrate. On top of this, a
fudge factor of 10% is added, to account for container overhead.
Neither of these bitrates may be known, and if the encoder is
running in VBR mode, there is no such value to be known. And
the container overhead may be more or less than the given
constant factor of 10%.
Instead, calculate the maximum bitrate per segment based on
what actually gets output from the muxer, and average bitrate
across all segments.
When muxing of the file finishes, update the master playlist
with these values, exposing both the maximum (which previously
was a guesstimate based on the nominal values) via
EXT-X-STREAM-INF BANDWIDTH, and the average via
EXT-X-STREAM-INF AVERAGE-BANDWIDTH.
This makes it possible to use the hlsenc muxer with VBR
encodes, for VOD style muxing.
Signed-off-by: Martin Storsjö <martin@martin.st>
2024-06-20 15:36:46 +02:00
|
|
|
if (avg_bandwidth)
|
|
|
|
avio_printf(out, ",AVERAGE-BANDWIDTH=%d", avg_bandwidth);
|
2017-11-29 13:44:15 +02:00
|
|
|
if (st && st->codecpar->width > 0 && st->codecpar->height > 0)
|
|
|
|
avio_printf(out, ",RESOLUTION=%dx%d", st->codecpar->width,
|
|
|
|
st->codecpar->height);
|
2020-03-26 15:56:58 +02:00
|
|
|
if (codecs && codecs[0])
|
2018-01-19 11:03:09 +02:00
|
|
|
avio_printf(out, ",CODECS=\"%s\"", codecs);
|
2020-03-26 15:56:58 +02:00
|
|
|
if (agroup && agroup[0])
|
2017-12-23 07:42:00 +02:00
|
|
|
avio_printf(out, ",AUDIO=\"group_%s\"", agroup);
|
2020-03-26 15:56:58 +02:00
|
|
|
if (ccgroup && ccgroup[0])
|
2018-01-24 05:42:57 +02:00
|
|
|
avio_printf(out, ",CLOSED-CAPTIONS=\"%s\"", ccgroup);
|
avformat: add subtitle support in master playlist m3u8
Test with the following command for the webvtt subtitle:
$ ./ffmpeg -y -i input_with_subtitle.mkv \
-b:v:0 5250k -c:v h264 -pix_fmt yuv420p -profile:v main -level 4.1 \
-b:a:0 256k \
-c:s webvtt -c:a mp2 -ar 48000 -ac 2 -map 0:v -map 0:a:0 -map 0:s:0 \
-f hls -var_stream_map "v:0,a:0,s:0,sgroup:subtitle" \
-master_pl_name master.m3u8 -t 300 -hls_time 10 -hls_init_time 4 -hls_list_size \
10 -master_pl_publish_rate 10 -hls_flags \
delete_segments+discont_start+split_by_time ./tmp/video.m3u8
Check the master m3u8:
$ cat tmp/master.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subtitle",NAME="subtitle_0",DEFAULT=YES,URI="video_vtt.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=6056600,RESOLUTION=1280x720,CODECS="avc1.4d4829,mp4a.40.33",SUBTITLES="subtitle"
video.m3u8
Check the result by convert to mkv:
$ ./ffmpeg -strict experimental -i ./tmp/master.m3u8 -c:v copy -c:a mp2 -c:s srt ./test.mkv
Signed-off-by: Limin Wang <lance.lmwang@gmail.com>
2020-03-29 13:13:44 +02:00
|
|
|
if (sgroup && sgroup[0])
|
|
|
|
avio_printf(out, ",SUBTITLES=\"%s\"", sgroup);
|
2017-11-29 13:44:15 +02:00
|
|
|
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,
|
2020-05-25 20:33:40 +02:00
|
|
|
uint32_t playlist_type, int iframe_mode)
|
|
|
|
{
|
2017-11-29 13:44:15 +02:00
|
|
|
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");
|
|
|
|
}
|
2019-06-02 16:03:17 +02:00
|
|
|
if (iframe_mode) {
|
|
|
|
avio_printf(out, "#EXT-X-I-FRAMES-ONLY\n");
|
|
|
|
}
|
2017-11-29 13:44:15 +02:00
|
|
|
}
|
|
|
|
|
2020-05-25 19:11:34 +02:00
|
|
|
void ff_hls_write_init_file(AVIOContext *out, const char *filename,
|
2020-05-25 20:33:40 +02:00
|
|
|
int byterange_mode, int64_t size, int64_t pos)
|
|
|
|
{
|
2017-11-29 13:44:15 +02:00
|
|
|
avio_printf(out, "#EXT-X-MAP:URI=\"%s\"", filename);
|
|
|
|
if (byterange_mode) {
|
|
|
|
avio_printf(out, ",BYTERANGE=\"%"PRId64"@%"PRId64"\"", size, pos);
|
|
|
|
}
|
|
|
|
avio_printf(out, "\n");
|
|
|
|
}
|
|
|
|
|
2017-12-04 06:03:37 +02:00
|
|
|
int ff_hls_write_file_entry(AVIOContext *out, int insert_discont,
|
2020-05-25 20:33:40 +02:00
|
|
|
int byterange_mode, double duration,
|
|
|
|
int round_duration, int64_t size,
|
|
|
|
int64_t pos /* Used only if HLS_SINGLE_FILE flag is set */,
|
2020-05-25 19:11:34 +02:00
|
|
|
const char *baseurl /* Ignored if NULL */,
|
|
|
|
const char *filename, double *prog_date_time,
|
2020-05-25 20:33:40 +02:00
|
|
|
int64_t video_keyframe_size, int64_t video_keyframe_pos,
|
|
|
|
int iframe_mode)
|
|
|
|
{
|
2017-11-29 13:44:15 +02:00
|
|
|
if (!out || !filename)
|
2017-12-04 06:03:37 +02:00
|
|
|
return AVERROR(EINVAL);
|
2017-11-29 13:44:15 +02:00
|
|
|
|
|
|
|
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)
|
2019-06-02 16:03:17 +02:00
|
|
|
avio_printf(out, "#EXT-X-BYTERANGE:%"PRId64"@%"PRId64"\n", iframe_mode ? video_keyframe_size : size,
|
|
|
|
iframe_mode ? video_keyframe_pos : pos);
|
2017-11-29 13:44:15 +02:00
|
|
|
|
|
|
|
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);
|
2017-12-04 06:05:04 +02:00
|
|
|
if (!strftime(buf0, sizeof(buf0), "%Y-%m-%dT%H:%M:%S", tm)) {
|
|
|
|
av_log(NULL, AV_LOG_DEBUG, "strftime error in ff_hls_write_file_entry\n");
|
|
|
|
return AVERROR_UNKNOWN;
|
|
|
|
}
|
2017-11-29 13:44:15 +02:00
|
|
|
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);
|
2017-12-04 06:03:37 +02:00
|
|
|
|
|
|
|
return 0;
|
2017-11-29 13:44:15 +02:00
|
|
|
}
|
|
|
|
|
2020-05-25 20:33:40 +02:00
|
|
|
void ff_hls_write_end_list(AVIOContext *out)
|
|
|
|
{
|
2017-11-29 13:44:15 +02:00
|
|
|
if (!out)
|
|
|
|
return;
|
|
|
|
avio_printf(out, "#EXT-X-ENDLIST\n");
|
|
|
|
}
|
|
|
|
|