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✌️0 5250k -c:v h264 -pix_fmt yuv420p -profile:v main -level 4.1 \
 -b🅰️0 256k \
 -c:s webvtt -c:a mp2 -ar 48000 -ac 2 -map 0:v -map 0🅰️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>
pull/336/head
Limin Wang 5 years ago committed by Steven Liu
parent 73dc87c4f0
commit cd8c5e89ba
  1. 15
      doc/muxers.texi
  2. 2
      libavformat/dashenc.c
  3. 26
      libavformat/hlsenc.c
  4. 17
      libavformat/hlsplaylist.c
  5. 4
      libavformat/hlsplaylist.h

@ -1062,6 +1062,21 @@ have and language is named ENG, the other audio language is named CHN.
By default, a single hls variant containing all the encoded streams is created. By default, a single hls variant containing all the encoded streams is created.
@example
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
@end example
This example adds @code{#EXT-X-MEDIA} tag with @code{TYPE=SUBTITLES} in
the master playlist with webvtt subtitle group name 'subtitle'. Please make sure
the input file has one text subtitle stream at least.
@item cc_stream_map @item cc_stream_map
Map string which specifies different closed captions groups and their Map string which specifies different closed captions groups and their
attributes. The closed captions stream groups are separated by space. attributes. The closed captions stream groups are separated by space.

@ -1311,7 +1311,7 @@ static int write_manifest(AVFormatContext *s, int final)
get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i); get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i);
ff_hls_write_stream_info(st, c->m3u8_out, stream_bitrate, ff_hls_write_stream_info(st, c->m3u8_out, stream_bitrate,
playlist_file, agroup, playlist_file, agroup,
codec_str_ptr, NULL); codec_str_ptr, NULL, NULL);
} }
dashenc_io_close(s, &c->m3u8_out, temp_filename); dashenc_io_close(s, &c->m3u8_out, temp_filename);
if (use_rename) if (use_rename)

@ -164,6 +164,7 @@ typedef struct VariantStream {
int is_default; /* default status of audio group */ int is_default; /* default status of audio group */
char *language; /* audio lauguage name */ char *language; /* audio lauguage name */
char *agroup; /* audio group name */ char *agroup; /* audio group name */
char *sgroup; /* subtitle group name */
char *ccgroup; /* closed caption group name */ char *ccgroup; /* closed caption group name */
char *baseurl; char *baseurl;
char *varname; // variant name char *varname; // variant name
@ -1285,7 +1286,9 @@ static int create_master_playlist(AVFormatContext *s,
unsigned int i, j; unsigned int i, j;
int ret, bandwidth; int ret, bandwidth;
const char *m3u8_rel_name = NULL; const char *m3u8_rel_name = NULL;
const char *vtt_m3u8_rel_name = NULL;
char *ccgroup; char *ccgroup;
char *sgroup = NULL;
ClosedCaptionsStream *ccs; ClosedCaptionsStream *ccs;
const char *proto = avio_find_protocol_name(hls->master_m3u8_url); const char *proto = avio_find_protocol_name(hls->master_m3u8_url);
int is_file_proto = proto && !strcmp(proto, "file"); int is_file_proto = proto && !strcmp(proto, "file");
@ -1408,13 +1411,24 @@ static int create_master_playlist(AVFormatContext *s,
vs->ccgroup); vs->ccgroup);
} }
if (vid_st && vs->sgroup) {
sgroup = vs->sgroup;
vtt_m3u8_rel_name = get_relative_url(hls->master_m3u8_url, vs->vtt_m3u8_name);
if (!vtt_m3u8_rel_name) {
av_log(s, AV_LOG_WARNING, "Unable to find relative subtitle URL\n");
break;
}
ff_hls_write_subtitle_rendition(hls->m3u8_out, sgroup, vtt_m3u8_rel_name, vs->language, i, hls->has_default_key ? vs->is_default : 1);
}
if (!hls->has_default_key || !hls->has_video_m3u8) { if (!hls->has_default_key || !hls->has_video_m3u8) {
ff_hls_write_stream_info(vid_st, hls->m3u8_out, bandwidth, m3u8_rel_name, ff_hls_write_stream_info(vid_st, hls->m3u8_out, bandwidth, m3u8_rel_name,
aud_st ? vs->agroup : NULL, vs->codec_attr, ccgroup); aud_st ? vs->agroup : NULL, vs->codec_attr, ccgroup, sgroup);
} else { } else {
if (vid_st) { if (vid_st) {
ff_hls_write_stream_info(vid_st, hls->m3u8_out, bandwidth, m3u8_rel_name, ff_hls_write_stream_info(vid_st, hls->m3u8_out, bandwidth, m3u8_rel_name,
aud_st ? vs->agroup : NULL, vs->codec_attr, ccgroup); aud_st ? vs->agroup : NULL, vs->codec_attr, ccgroup, sgroup);
} }
} }
} }
@ -1889,6 +1903,7 @@ static int parse_variant_stream_mapstring(AVFormatContext *s)
* practical usage) * practical usage)
* *
* agroup: is key to specify audio group. A string can be given as value. * agroup: is key to specify audio group. A string can be given as value.
* sgroup: is key to specify subtitle group. A string can be given as value.
*/ */
p = av_strdup(hls->var_stream_map); p = av_strdup(hls->var_stream_map);
if (!p) if (!p)
@ -1956,6 +1971,12 @@ static int parse_variant_stream_mapstring(AVFormatContext *s)
if (!vs->agroup) if (!vs->agroup)
return AVERROR(ENOMEM); return AVERROR(ENOMEM);
continue; continue;
} else if (av_strstart(keyval, "sgroup:", &val)) {
av_free(vs->sgroup);
vs->sgroup = av_strdup(val);
if (!vs->sgroup)
return AVERROR(ENOMEM);
continue;
} else if (av_strstart(keyval, "ccgroup:", &val)) { } else if (av_strstart(keyval, "ccgroup:", &val)) {
av_free(vs->ccgroup); av_free(vs->ccgroup);
vs->ccgroup = av_strdup(val); vs->ccgroup = av_strdup(val);
@ -2512,6 +2533,7 @@ static void hls_free_variant_streams(struct HLSContext *hls)
av_freep(&vs->m3u8_name); av_freep(&vs->m3u8_name);
av_freep(&vs->streams); av_freep(&vs->streams);
av_freep(&vs->agroup); av_freep(&vs->agroup);
av_freep(&vs->sgroup);
av_freep(&vs->language); av_freep(&vs->language);
av_freep(&vs->ccgroup); av_freep(&vs->ccgroup);
av_freep(&vs->baseurl); av_freep(&vs->baseurl);

@ -48,9 +48,22 @@ void ff_hls_write_audio_rendition(AVIOContext *out, char *agroup,
avio_printf(out, "URI=\"%s\"\n", filename); avio_printf(out, "URI=\"%s\"\n", filename);
} }
void ff_hls_write_subtitle_rendition(AVIOContext *out, char *sgroup,
const char *filename, char *language, int name_id, int is_default) {
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);
}
void ff_hls_write_stream_info(AVStream *st, AVIOContext *out, void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
int bandwidth, const char *filename, char *agroup, int bandwidth, const char *filename, char *agroup,
char *codecs, char *ccgroup) { char *codecs, char *ccgroup, char *sgroup) {
if (!out || !filename) if (!out || !filename)
return; return;
@ -71,6 +84,8 @@ void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
avio_printf(out, ",AUDIO=\"group_%s\"", agroup); avio_printf(out, ",AUDIO=\"group_%s\"", agroup);
if (ccgroup && ccgroup[0]) if (ccgroup && ccgroup[0])
avio_printf(out, ",CLOSED-CAPTIONS=\"%s\"", ccgroup); avio_printf(out, ",CLOSED-CAPTIONS=\"%s\"", ccgroup);
if (sgroup && sgroup[0])
avio_printf(out, ",SUBTITLES=\"%s\"", sgroup);
avio_printf(out, "\n%s\n\n", filename); avio_printf(out, "\n%s\n\n", filename);
} }

@ -39,9 +39,11 @@ typedef enum {
void ff_hls_write_playlist_version(AVIOContext *out, int version); void ff_hls_write_playlist_version(AVIOContext *out, int version);
void ff_hls_write_audio_rendition(AVIOContext *out, char *agroup, void ff_hls_write_audio_rendition(AVIOContext *out, char *agroup,
const char *filename, char *language, int name_id, int is_default); const char *filename, char *language, int name_id, int is_default);
void ff_hls_write_subtitle_rendition(AVIOContext *out, char *sgroup,
const char *filename, char *language, int name_id, int is_default);
void ff_hls_write_stream_info(AVStream *st, AVIOContext *out, void ff_hls_write_stream_info(AVStream *st, AVIOContext *out,
int bandwidth, const char *filename, char *agroup, int bandwidth, const char *filename, char *agroup,
char *codecs, char *ccgroup); char *codecs, char *ccgroup, char *sgroup);
void ff_hls_write_playlist_header(AVIOContext *out, int version, int allowcache, void ff_hls_write_playlist_header(AVIOContext *out, int version, int allowcache,
int target_duration, int64_t sequence, int target_duration, int64_t sequence,
uint32_t playlist_type, int iframe_mode); uint32_t playlist_type, int iframe_mode);

Loading…
Cancel
Save