mirror of https://git.ffmpeg.org/ffmpeg.git
avformat/hlsenc: configurable variant stream index position in filenames
This commit is contained in:
parent
26e1efb04f
commit
e872befdb5
|
@ -577,6 +577,17 @@ Should a relative path be specified, the path of the created segment
|
|||
files will be relative to the current working directory.
|
||||
When use_localtime_mkdir is set, the whole expanded value of @var{filename} will be written into the m3u8 segment list.
|
||||
|
||||
When @code{var_stream_map} is set with two or more variant streams, the
|
||||
@var{filename} pattern must contain the string "%v", this string specifies
|
||||
the position of variant stream index in the generated segment file names.
|
||||
@example
|
||||
ffmpeg -i in.ts -b:v:0 1000k -b:v:1 256k -b:a:0 64k -b:a:1 32k \
|
||||
-map 0:v -map 0:a -map 0:v -map 0:a -f hls -var_stream_map "v:0,a:0 v:1,a:1" \
|
||||
-hls_segment_filename 'file_%v_%03d.ts' out_%v.m3u8
|
||||
@end example
|
||||
This example will produce the playlists segment file sets:
|
||||
@file{file_0_000.ts}, @file{file_0_001.ts}, @file{file_0_002.ts}, etc. and
|
||||
@file{file_1_000.ts}, @file{file_1_001.ts}, @file{file_1_002.ts}, etc.
|
||||
|
||||
@item use_localtime
|
||||
Use strftime() on @var{filename} to expand the segment filename with localtime.
|
||||
|
@ -703,6 +714,10 @@ the fmp4 files is used in hls after version 7.
|
|||
@item hls_fmp4_init_filename @var{filename}
|
||||
set filename to the fragment files header file, default filename is @file{init.mp4}.
|
||||
|
||||
When @code{var_stream_map} is set with two or more variant streams, the
|
||||
@var{filename} pattern must contain the string "%v", this string specifies
|
||||
the position of variant stream index in the generated init file names.
|
||||
|
||||
@item hls_flags @var{flags}
|
||||
Possible values:
|
||||
|
||||
|
@ -816,32 +831,36 @@ Expected string format is like this "a:0,v:0 a:1,v:1 ....". Here a:, v:, s: are
|
|||
the keys to specify audio, video and subtitle streams respectively.
|
||||
Allowed values are 0 to 9 (limited just based on practical usage).
|
||||
|
||||
When there are two or more variant streams, the output filename pattern must
|
||||
contain the string "%v", this string specifies the position of variant stream
|
||||
index in the output media playlist filenames.
|
||||
|
||||
@example
|
||||
ffmpeg -re -i in.ts -b:v:0 1000k -b:v:1 256k -b:a:0 64k -b:a:1 32k \
|
||||
-map 0:v -map 0:a -map 0:v -map 0:a -f hls -var_stream_map "v:0,a:0 v:1,a:1" \
|
||||
http://example.com/live/out.m3u8
|
||||
http://example.com/live/out_%v.m3u8
|
||||
@end example
|
||||
This example creates two hls variant streams. The first variant stream will
|
||||
contain video stream of bitrate 1000k and audio stream of bitrate 64k and the
|
||||
second variant stream will contain video stream of bitrate 256k and audio
|
||||
stream of bitrate 32k. Here, two media playlist with file names out_1.m3u8 and
|
||||
out_2.m3u8 will be created.
|
||||
stream of bitrate 32k. Here, two media playlist with file names out_0.m3u8 and
|
||||
out_1.m3u8 will be created.
|
||||
@example
|
||||
ffmpeg -re -i in.ts -b:v:0 1000k -b:v:1 256k -b:a:0 64k \
|
||||
-map 0:v -map 0:a -map 0:v -f hls -var_stream_map "v:0 a:0 v:1" \
|
||||
http://example.com/live/out.m3u8
|
||||
http://example.com/live/out_%v.m3u8
|
||||
@end example
|
||||
This example creates three hls variant streams. The first variant stream will
|
||||
be a video only stream with video bitrate 1000k, the second variant stream will
|
||||
be an audio only stream with bitrate 64k and the third variant stream will be a
|
||||
video only stream with bitrate 256k. Here, three media playlist with file names
|
||||
out_1.m3u8, out_2.m3u8 and out_3.m3u8 will be created.
|
||||
out_0.m3u8, out_1.m3u8 and out_2.m3u8 will be created.
|
||||
@example
|
||||
ffmpeg -re -i in.ts -b:a:0 32k -b:a:1 64k -b:v:0 1000k -b:v:1 3000k \
|
||||
-map 0:a -map 0:a -map 0:v -map 0:v -f hls \
|
||||
-var_stream_map "a:0,agroup:aud_low a:1,agroup:aud_high v:0,agroup:aud_low v:1,agroup:aud_high" \
|
||||
-master_pl_name master.m3u8 \
|
||||
http://example.com/live/out.m3u8
|
||||
http://example.com/live/out_%v.m3u8
|
||||
@end example
|
||||
This example creates two audio only and two video only variant streams. In
|
||||
addition to the #EXT-X-STREAM-INF tag for each variant stream in the master
|
||||
|
|
|
@ -1528,7 +1528,7 @@ static const char * get_default_pattern_localtime_fmt(AVFormatContext *s)
|
|||
return (HAVE_LIBC_MSVCRT || !strftime(b, sizeof(b), "%s", p) || !strcmp(b, "%s")) ? "-%Y%m%d%H%M%S.ts" : "-%s.ts";
|
||||
}
|
||||
|
||||
static int format_name(char *name, int name_buf_len, int i)
|
||||
static int append_postfix(char *name, int name_buf_len, int i)
|
||||
{
|
||||
char *p;
|
||||
char extension[10] = {'\0'};
|
||||
|
@ -1547,6 +1547,53 @@ static int format_name(char *name, int name_buf_len, int i)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int validate_name(int nb_vs, const char *fn)
|
||||
{
|
||||
const char *filename;
|
||||
int ret = 0;
|
||||
|
||||
if (!fn) {
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
filename = av_basename(fn);
|
||||
|
||||
if (nb_vs > 1 && !av_stristr(filename, "%v")) {
|
||||
av_log(NULL, AV_LOG_ERROR, "More than 1 variant streams are present, %%v is expected in the filename %s\n",
|
||||
fn);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int format_name(char *buf, int buf_len, int index)
|
||||
{
|
||||
char *orig_buf_dup = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (!av_stristr(buf, "%v"))
|
||||
return ret;
|
||||
|
||||
orig_buf_dup = av_strdup(buf);
|
||||
if (!orig_buf_dup) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (replace_int_data_in_filename(buf, buf_len, orig_buf_dup, 'v', index) < 1) {
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fail:
|
||||
av_freep(&orig_buf_dup);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_nth_codec_stream_index(AVFormatContext *s,
|
||||
enum AVMediaType codec_type,
|
||||
int stream_id)
|
||||
|
@ -1690,7 +1737,7 @@ static int update_variant_stream_info(AVFormatContext *s) {
|
|||
static int update_master_pl_info(AVFormatContext *s) {
|
||||
HLSContext *hls = s->priv_data;
|
||||
const char *dir;
|
||||
char *fn;
|
||||
char *fn = NULL;
|
||||
int ret = 0;
|
||||
|
||||
fn = av_strdup(s->filename);
|
||||
|
@ -2052,7 +2099,7 @@ static int hls_init(AVFormatContext *s)
|
|||
const char *pattern_localtime_fmt = get_default_pattern_localtime_fmt(s);
|
||||
const char *vtt_pattern = "%d.vtt";
|
||||
char *p = NULL;
|
||||
int vtt_basename_size = 0, m3u8_name_size = 0;
|
||||
int vtt_basename_size = 0;
|
||||
int fmp4_init_filename_len = strlen(hls->fmp4_init_filename) + 1;
|
||||
|
||||
ret = update_variant_stream_info(s);
|
||||
|
@ -2068,6 +2115,28 @@ static int hls_init(AVFormatContext *s)
|
|||
goto fail;
|
||||
}
|
||||
|
||||
ret = validate_name(hls->nb_varstreams, s->filename);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
if (hls->segment_filename) {
|
||||
ret = validate_name(hls->nb_varstreams, hls->segment_filename);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (av_strcasecmp(hls->fmp4_init_filename, "init.mp4")) {
|
||||
ret = validate_name(hls->nb_varstreams, hls->fmp4_init_filename);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (hls->subtitle_filename) {
|
||||
ret = validate_name(hls->nb_varstreams, hls->subtitle_filename);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (hls->master_pl_name) {
|
||||
ret = update_master_pl_info(s);
|
||||
if (ret < 0) {
|
||||
|
@ -2100,6 +2169,16 @@ static int hls_init(AVFormatContext *s)
|
|||
hls->recording_time = (hls->init_time ? hls->init_time : hls->time) * AV_TIME_BASE;
|
||||
for (i = 0; i < hls->nb_varstreams; i++) {
|
||||
vs = &hls->var_streams[i];
|
||||
|
||||
vs->m3u8_name = av_strdup(s->filename);
|
||||
if (!vs->m3u8_name ) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
ret = format_name(vs->m3u8_name, strlen(s->filename) + 1, i);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
vs->sequence = hls->start_sequence;
|
||||
vs->start_pts = AV_NOPTS_VALUE;
|
||||
vs->end_pts = AV_NOPTS_VALUE;
|
||||
|
@ -2153,9 +2232,6 @@ static int hls_init(AVFormatContext *s)
|
|||
}
|
||||
if (hls->segment_filename) {
|
||||
basename_size = strlen(hls->segment_filename) + 1;
|
||||
if (hls->nb_varstreams > 1) {
|
||||
basename_size += strlen(POSTFIX_PATTERN);
|
||||
}
|
||||
vs->basename = av_malloc(basename_size);
|
||||
if (!vs->basename) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
|
@ -2163,6 +2239,9 @@ static int hls_init(AVFormatContext *s)
|
|||
}
|
||||
|
||||
av_strlcpy(vs->basename, hls->segment_filename, basename_size);
|
||||
ret = format_name(vs->basename, basename_size, i);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
} else {
|
||||
if (hls->flags & HLS_SINGLE_FILE) {
|
||||
if (hls->segment_type == SEGMENT_TYPE_FMP4) {
|
||||
|
@ -2173,13 +2252,9 @@ static int hls_init(AVFormatContext *s)
|
|||
}
|
||||
|
||||
if (hls->use_localtime) {
|
||||
basename_size = strlen(s->filename) + strlen(pattern_localtime_fmt) + 1;
|
||||
basename_size = strlen(vs->m3u8_name) + strlen(pattern_localtime_fmt) + 1;
|
||||
} else {
|
||||
basename_size = strlen(s->filename) + strlen(pattern) + 1;
|
||||
}
|
||||
|
||||
if (hls->nb_varstreams > 1) {
|
||||
basename_size += strlen(POSTFIX_PATTERN);
|
||||
basename_size = strlen(vs->m3u8_name) + strlen(pattern) + 1;
|
||||
}
|
||||
|
||||
vs->basename = av_malloc(basename_size);
|
||||
|
@ -2188,7 +2263,7 @@ static int hls_init(AVFormatContext *s)
|
|||
goto fail;
|
||||
}
|
||||
|
||||
av_strlcpy(vs->basename, s->filename, basename_size);
|
||||
av_strlcpy(vs->basename, vs->m3u8_name, basename_size);
|
||||
|
||||
p = strrchr(vs->basename, '.');
|
||||
if (p)
|
||||
|
@ -2200,28 +2275,6 @@ static int hls_init(AVFormatContext *s)
|
|||
}
|
||||
}
|
||||
|
||||
m3u8_name_size = strlen(s->filename) + 1;
|
||||
if (hls->nb_varstreams > 1) {
|
||||
m3u8_name_size += strlen(POSTFIX_PATTERN);
|
||||
}
|
||||
|
||||
vs->m3u8_name = av_malloc(m3u8_name_size);
|
||||
if (!vs->m3u8_name ) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
av_strlcpy(vs->m3u8_name, s->filename, m3u8_name_size);
|
||||
|
||||
if (hls->nb_varstreams > 1) {
|
||||
ret = format_name(vs->basename, basename_size, i);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
ret = format_name(vs->m3u8_name, m3u8_name_size, i);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (hls->segment_type == SEGMENT_TYPE_FMP4) {
|
||||
if (hls->nb_varstreams > 1)
|
||||
fmp4_init_filename_len += strlen(POSTFIX_PATTERN);
|
||||
|
@ -2232,13 +2285,12 @@ static int hls_init(AVFormatContext *s)
|
|||
}
|
||||
av_strlcpy(vs->fmp4_init_filename, hls->fmp4_init_filename,
|
||||
fmp4_init_filename_len);
|
||||
if (hls->nb_varstreams > 1) {
|
||||
|
||||
if (av_strcasecmp(hls->fmp4_init_filename, "init.mp4")) {
|
||||
ret = format_name(vs->fmp4_init_filename, fmp4_init_filename_len, i);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (av_strcasecmp(hls->fmp4_init_filename, "init.mp4")) {
|
||||
fmp4_init_filename_len = strlen(vs->fmp4_init_filename) + 1;
|
||||
vs->base_output_dirname = av_malloc(fmp4_init_filename_len);
|
||||
if (!vs->base_output_dirname) {
|
||||
|
@ -2248,6 +2300,12 @@ static int hls_init(AVFormatContext *s)
|
|||
av_strlcpy(vs->base_output_dirname, vs->fmp4_init_filename,
|
||||
fmp4_init_filename_len);
|
||||
} else {
|
||||
if (hls->nb_varstreams > 1) {
|
||||
ret = append_postfix(vs->fmp4_init_filename, fmp4_init_filename_len, i);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fmp4_init_filename_len = strlen(vs->m3u8_name) +
|
||||
strlen(vs->fmp4_init_filename) + 1;
|
||||
|
||||
|
@ -2286,10 +2344,7 @@ static int hls_init(AVFormatContext *s)
|
|||
|
||||
if (hls->flags & HLS_SINGLE_FILE)
|
||||
vtt_pattern = ".vtt";
|
||||
vtt_basename_size = strlen(s->filename) + strlen(vtt_pattern) + 1;
|
||||
if (hls->nb_varstreams > 1) {
|
||||
vtt_basename_size += strlen(POSTFIX_PATTERN);
|
||||
}
|
||||
vtt_basename_size = strlen(vs->m3u8_name) + strlen(vtt_pattern) + 1;
|
||||
|
||||
vs->vtt_basename = av_malloc(vtt_basename_size);
|
||||
if (!vs->vtt_basename) {
|
||||
|
@ -2301,27 +2356,21 @@ static int hls_init(AVFormatContext *s)
|
|||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
av_strlcpy(vs->vtt_basename, s->filename, vtt_basename_size);
|
||||
av_strlcpy(vs->vtt_basename, vs->m3u8_name, vtt_basename_size);
|
||||
p = strrchr(vs->vtt_basename, '.');
|
||||
if (p)
|
||||
*p = '\0';
|
||||
|
||||
if ( hls->subtitle_filename ) {
|
||||
strcpy(vs->vtt_m3u8_name, hls->subtitle_filename);
|
||||
ret = format_name(vs->vtt_m3u8_name, vtt_basename_size, i);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
} else {
|
||||
strcpy(vs->vtt_m3u8_name, vs->vtt_basename);
|
||||
av_strlcat(vs->vtt_m3u8_name, "_vtt.m3u8", vtt_basename_size);
|
||||
}
|
||||
av_strlcat(vs->vtt_basename, vtt_pattern, vtt_basename_size);
|
||||
|
||||
if (hls->nb_varstreams > 1) {
|
||||
ret= format_name(vs->vtt_basename, vtt_basename_size, i);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
ret = format_name(vs->vtt_m3u8_name, vtt_basename_size, i);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (hls->baseurl) {
|
||||
|
|
Loading…
Reference in New Issue