History

avformat/hls demuxer: Add WebVTT subtitle support

This add support for WebVTT subtitles in HLS streams. Just like for separate audio streams, it supports all available WebVTT streams in all renditions. No new options are added, it just works and provides subtitles streams like any other demuxer. The code prevents downloading subtitle segments which are farther in the future than the main segments, to avoid loading hundreds of subtitle segments in advance.

softworkz committed 1 year ago

Commit fcc50c41 parent ec3f3451 file · +176 −37Browse files

Showing the changes to libavformat (1 of 1 file). Show all

Unified · Split

libavformat/hls.c+176−37
@@ -173,6 +173,7 @@ struct playlist { · 172 unmodified lines
173173 * playlist, if any. */
174174 int n_init_sections;
175175 struct segment **init_sections;
176+ int is_subtitle; /* Indicates if it's a subtitle playlist */
176177 };
177178
178179 /*
@@ -515,13 +516,6 @@ static struct rendition *new_rendition(HLSContext *c, struct rendition_info *inf · 336 unmodified lines
515516 return NULL;
516517 }
517518
518− /* TODO: handle subtitles (each segment has to parsed separately) */
519− if (c->ctx->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL)
520− if (type == AVMEDIA_TYPE_SUBTITLE) {
521− av_log(c->ctx, AV_LOG_WARNING, "Can't support the subtitle(uri: %s)\n", info->uri);
522− return NULL;
523− }
524−
525519 rend = av_mallocz(sizeof(struct rendition));
526520 if (!rend)
527521 return NULL;
@@ -536,9 +530,14 @@ static struct rendition *new_rendition(HLSContext *c, struct rendition_info *inf · 8 unmodified lines
536530 /* add the playlist if this is an external rendition */
537531 if (info->uri[0]) {
538532 rend->playlist = new_playlist(c, info->uri, url_base);
539− if (rend->playlist)
533+ if (rend->playlist) {
534+ if (type == AVMEDIA_TYPE_SUBTITLE) {
535+ rend->playlist->is_subtitle = 1;
536+ rend->playlist->is_id3_timestamped = 0;
537+ }
540538 dynarray_add(&rend->playlist->renditions,
541539 &rend->playlist->n_renditions, rend);
540+ }
542541 }
543542
544543 if (info->assoc_language[0]) {
@@ -761,6 +760,9 @@ static int test_segment(AVFormatContext *s, const AVInputFormat *in_fmt, struct · 216 unmodified lines
761760 } else if (!strcmp(in_fmt->name, "mpegts")) {
762761 matchF = av_match_ext( seg->url, "ts,m2t,m2ts,mts,mpg,m4s,mpeg,mpegts")
763762 + 2*(ff_match_url_ext(seg->url, "ts,m2t,m2ts,mts,mpg,m4s,mpeg,mpegts") > 0);
763+ } else if (!strcmp(in_fmt->name, "webvtt")) {
764+ matchF = av_match_ext( seg->url, "vtt,webvtt")
765+ + 2*(ff_match_url_ext(seg->url, "vtt,webvtt") > 0);
764766 }
765767
766768 if (!(matchA & matchF)) {
@@ -969,7 +971,7 @@ static int parse_playlist(HLSContext *c, const char *url, · 202 unmodified lines
969971 if (ptr)
970972 seg_offset = strtoll(ptr+1, NULL, 10);
971973 } else if (av_strstart(line, "#", NULL)) {
972− av_log(c->ctx, AV_LOG_INFO, "Skip ('%s')\n", line);
974+ av_log(c->ctx, AV_LOG_VERBOSE, "Skip ('%s')\n", line);
973975 continue;
974976 } else if (line[0]) {
975977 if (is_variant) {
@@ -1484,7 +1486,7 @@ static int playlist_needed(struct playlist *pls) · 508 unmodified lines
14841486 int first_st;
14851487
14861488 /* If there is no context or streams yet, the playlist is needed */
1487− if (!pls->ctx || !pls->n_main_streams)
1489+ if ((!pls->ctx || !pls->n_main_streams) && !pls->is_subtitle)
14881490 return 1;
14891491
14901492 /* check if any of the streams in the playlist are needed */
@@ -1522,17 +1524,13 @@ static int playlist_needed(struct playlist *pls) · 31 unmodified lines
15221524 return 0;
15231525 }
15241526
1525−static int read_data(void *opaque, uint8_t *buf, int buf_size)
1527+static int reload_playlist(struct playlist *v, HLSContext *c)
15261528 {
1527− struct playlist *v = opaque;
1528− HLSContext *c = v->parent->priv_data;
1529− int ret;
1530− int just_opened = 0;
1529+ int ret = 0;
15311530 int reload_count = 0;
1532− int segment_retries = 0;
1533− struct segment *seg;
15341531
1535−restart:
1532+ v->needed = playlist_needed(v);
1533+
15361534 if (!v->needed)
15371535 return AVERROR_EOF;
15381536
@@ -1588,7 +1586,7 @@ reload: · 49 unmodified lines
15881586 av_log(v->parent, AV_LOG_WARNING, "The m3u8 list sequence may have been wrapped.\n");
15891587 }
15901588 if (v->cur_seq_no >= v->start_seq_no + v->n_segments) {
1591− if (v->finished)
1589+ if (v->finished || v->is_subtitle)
15921590 return AVERROR_EOF;
15931591 while (av_gettime_relative() - v->last_load_time < reload_interval) {
15941592 if (ff_check_interrupt(c->interrupt_callback))
@@ -1599,9 +1597,35 @@ reload: · 4 unmodified lines
15991597 goto reload;
16001598 }
16011599
1602− v->input_read_done = 0;
1603− seg = current_segment(v);
1600+ }
1601+ return ret;
1602+}
16041603
1604+static int read_data_continuous(void *opaque, uint8_t *buf, int buf_size)
1605+{
1606+ struct playlist *v = opaque;
1607+ HLSContext *c = v->parent->priv_data;
1608+ int ret;
1609+ int just_opened = 0;
1610+ int segment_retries = 0;
1611+ struct segment *seg;
1612+
1613+ if (c->http_persistent && v->input_read_done) {
1614+ ret = reload_playlist(v, c);
1615+ if (ret < 0)
1616+ return ret;
1617+ }
1618+
1619+ v->input_read_done = 0;
1620+
1621+restart:
1622+ ret = reload_playlist(v, c);
1623+ if (ret < 0)
1624+ return ret;
1625+
1626+ seg = current_segment(v);
1627+
1628+ if (!v->input || (c->http_persistent && v->input_read_done)) {
16051629 /* load/update Media Initialization Section, if any */
16061630 ret = update_init_section(v, seg);
16071631 if (ret)
@@ -1630,7 +1654,7 @@ reload: · 22 unmodified lines
16301654 } else {
16311655 segment_retries++;
16321656 }
1633− goto reload;
1657+ goto restart;
16341658 }
16351659 segment_retries = 0;
16361660 just_opened = 1;
@@ -1692,6 +1716,110 @@ reload: · 55 unmodified lines
16921716 goto restart;
16931717 }
16941718
1719+static int read_data_subtitle_segment(void *opaque, uint8_t *buf, int buf_size)
1720+{
1721+ struct playlist *v = opaque;
1722+ HLSContext *c = v->parent->priv_data;
1723+ int ret;
1724+ struct segment *seg;
1725+
1726+ if (!v->needed || v->cur_seq_no - v->start_seq_no >= v->n_segments) {
1727+ return AVERROR_EOF;
1728+ } else {
1729+ seg = current_segment(v);
1730+ }
1731+
1732+ if (!v->input) {
1733+ ret = open_input(c, v, seg, &v->input);
1734+ if (ret < 0) {
1735+ if (ff_check_interrupt(c->interrupt_callback))
1736+ return AVERROR_EXIT;
1737+ av_log(v->parent, AV_LOG_WARNING, "Failed to open segment of playlist %d\n",
1738+ v->index);
1739+ return ret;
1740+ }
1741+ }
1742+
1743+ return read_from_url(v, seg, buf, buf_size);
1744+}
1745+
1746+static int nested_io_open(AVFormatContext *s, AVIOContext **pb, const char *url,
1747+ int flags, AVDictionary **opts)
1748+{
1749+ av_log(s, AV_LOG_ERROR,
1750+ "A HLS playlist item '%s' referred to an external file '%s'. "
1751+ "Opening this file was forbidden for security reasons\n",
1752+ s->url, url);
1753+ return AVERROR(EPERM);
1754+}
1755+
1756+static int init_subtitle_context(struct playlist *pls)
1757+{
1758+ HLSContext *c = pls->parent->priv_data;
1759+ const AVInputFormat *in_fmt;
1760+ AVDictionary *opts = NULL;
1761+ int ret;
1762+
1763+ if (!(pls->ctx = avformat_alloc_context()))
1764+ return AVERROR(ENOMEM);
1765+
1766+ pls->read_buffer = av_malloc(INITIAL_BUFFER_SIZE);
1767+ if (!pls->read_buffer) {
1768+ avformat_free_context(pls->ctx);
1769+ pls->ctx = NULL;
1770+ return AVERROR(ENOMEM);
1771+ }
1772+
1773+ ffio_init_context(&pls->pb, pls->read_buffer, INITIAL_BUFFER_SIZE, 0, pls,
1774+ read_data_subtitle_segment, NULL, NULL);
1775+ pls->pb.pub.seekable = 0;
1776+ pls->ctx->pb = &pls->pb.pub;
1777+ pls->ctx->io_open = nested_io_open;
1778+
1779+ ret = ff_copy_whiteblacklists(pls->ctx, pls->parent);
1780+ if (ret < 0)
1781+ return ret;
1782+
1783+ in_fmt = av_find_input_format("webvtt");
1784+ av_dict_copy(&opts, c->seg_format_opts, 0);
1785+ ret = avformat_open_input(&pls->ctx, current_segment(pls)->url, in_fmt, &opts);
1786+ av_dict_free(&opts);
1787+
1788+ return ret;
1789+}
1790+
1791+static int read_subtitle_packet(struct playlist *v, AVPacket *pkt)
1792+{
1793+ HLSContext *c = v->parent->priv_data;
1794+ int ret;
1795+
1796+restart:
1797+ ret = reload_playlist(v, c);
1798+ if (ret < 0)
1799+ return ret;
1800+
1801+ if (v->input && !v->ctx)
1802+ ff_format_io_close(v->parent, &v->input);
1803+
1804+ if (!v->input && !v->ctx) {
1805+ ret = init_subtitle_context(v);
1806+ if (ret < 0)
1807+ return ret;
1808+ }
1809+
1810+ ret = av_read_frame(v->ctx, v->pkt);
1811+ if (!ret) {
1812+ return ret;
1813+ }
1814+ ff_format_io_close(v->parent, &v->input);
1815+ v->cur_seq_no++;
1816+ c->cur_seq_no = v->cur_seq_no;
1817+
1818+ avformat_close_input(&v->ctx);
1819+
1820+ goto restart;
1821+}
1822+
16951823 static void add_renditions_to_variant(HLSContext *c, struct variant *var,
16961824 enum AVMediaType type, const char *group_id)
16971825 {
@@ -1853,16 +1981,6 @@ static int64_t select_cur_seq_no(HLSContext *c, struct playlist *pls) · 155 unmodified lines
18531981 return pls->start_seq_no;
18541982 }
18551983
1856−static int nested_io_open(AVFormatContext *s, AVIOContext **pb, const char *url,
1857− int flags, AVDictionary **opts)
1858−{
1859− av_log(s, AV_LOG_ERROR,
1860− "A HLS playlist item '%s' referred to an external file '%s'. "
1861− "Opening this file was forbidden for security reasons\n",
1862− s->url, url);
1863− return AVERROR(EPERM);
1864−}
1865−
18661984 static void add_stream_to_programs(AVFormatContext *s, struct playlist *pls, AVStream *stream)
18671985 {
18681986 HLSContext *c = s->priv_data;
@@ -2070,6 +2188,8 @@ static int hls_read_header(AVFormatContext *s) · 201 unmodified lines
20702188 highest_cur_seq_no = FFMAX(highest_cur_seq_no, pls->cur_seq_no);
20712189 }
20722190
2191+ av_dict_set(&c->seg_format_opts, "prefer_hls_mpegts_pts", "1", 0);
2192+
20732193 /* Open the demuxer for each playlist */
20742194 for (i = 0; i < c->n_playlists; i++) {
20752195 struct playlist *pls = c->playlists[i];
@@ -2107,8 +2227,12 @@ static int hls_read_header(AVFormatContext *s) · 31 unmodified lines
21072227 return AVERROR(ENOMEM);
21082228 }
21092229
2110− ffio_init_context(&pls->pb, pls->read_buffer, INITIAL_BUFFER_SIZE, 0, pls,
2111− read_data, NULL, NULL);
2230+ if (pls->is_subtitle)
2231+ ffio_init_context(&pls->pb, (unsigned char*)av_strdup("WEBVTT\n"), (int)strlen("WEBVTT\n"), 0, pls,
2232+ NULL, NULL, NULL);
2233+ else
2234+ ffio_init_context(&pls->pb, pls->read_buffer, INITIAL_BUFFER_SIZE, 0, pls,
2235+ read_data_continuous, NULL, NULL);
21122236
21132237 /*
21142238 * If encryption scheme is SAMPLE-AES, try to read ID3 tags of
@@ -2254,6 +2378,13 @@ static int hls_read_header(AVFormatContext *s) · 139 unmodified lines
22542378 if (pls->n_main_streams)
22552379 av_dict_copy(&pls->main_streams[0]->metadata, pls->ctx->metadata, 0);
22562380
2381+ if (pls->is_subtitle) {
2382+ avformat_free_context(pls->ctx);
2383+ pls->ctx = NULL;
2384+ pls->needed = 0;
2385+ pls->main_streams[0]->discard = AVDISCARD_ALL;
2386+ }
2387+
22572388 add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_AUDIO);
22582389 add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_VIDEO);
22592390 add_metadata_from_renditions(s, pls, AVMEDIA_TYPE_SUBTITLE);
@@ -2296,6 +2427,8 @@ static int recheck_discard_flags(AVFormatContext *s, int first) · 36 unmodified lines
22962427 pls->input_read_done = 0;
22972428 ff_format_io_close(pls->parent, &pls->input_next);
22982429 pls->input_next_requested = 0;
2430+ if (pls->is_subtitle)
2431+ avformat_close_input(&pls->ctx);
22992432 pls->needed = 0;
23002433 changed = 1;
23012434 av_log(s, AV_LOG_INFO, "No longer receiving playlist %d\n", i);
@@ -2363,7 +2496,10 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) · 61 unmodified lines
23632496 int64_t ts_diff;
23642497 AVRational tb;
23652498 struct segment *seg = NULL;
2366− ret = av_read_frame(pls->ctx, pls->pkt);
2499+ if (pls->is_subtitle)
2500+ ret = read_subtitle_packet(pls, pls->pkt);
2501+ else
2502+ ret = av_read_frame(pls->ctx, pls->pkt);
23672503 if (ret < 0) {
23682504 if (!avio_feof(&pls->pb.pub) && ret != AVERROR_EOF)
23692505 return ret;
@@ -2559,7 +2695,10 @@ static int hls_read_seek(AVFormatContext *s, int stream_index, · 189 unmodified lines
25592695 /* Reset the pos, to let the mpegts/mov demuxer know we've seeked. */
25602696 pb->pos = 0;
25612697 /* Flush the packet queue of the subdemuxer. */
2562− ff_read_frame_flush(pls->ctx);
2698+ if (pls->ctx)
2699+ ff_read_frame_flush(pls->ctx);
2700+ if (pls->is_subtitle)
2701+ avformat_close_input(&pls->ctx);
25632702
25642703 /* Reset the init segment so it's re-fetched and served appropiately */
25652704 pls->cur_init_section = NULL;
@@ -2628,7 +2767,7 @@ static const AVOption hls_options[] = { · 62 unmodified lines
26282767 OFFSET(prefer_x_start), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS},
26292768 {"allowed_extensions", "List of file extensions that hls is allowed to access",
26302769 OFFSET(allowed_extensions), AV_OPT_TYPE_STRING,
2631− {.str = "3gp,aac,avi,ac3,eac3,flac,mkv,m3u8,m4a,m4s,m4v,mpg,mov,mp2,mp3,mp4,mpeg,mpegts,ogg,ogv,oga,ts,vob,wav"},
2770+ {.str = "3gp,aac,avi,ac3,eac3,flac,mkv,m3u8,m4a,m4s,m4v,mpg,mov,mp2,mp3,mp4,mpeg,mpegts,ogg,ogv,oga,ts,vob,vtt,wav,webvtt"},
26322771 INT_MIN, INT_MAX, FLAGS},
26332772 {"extension_picky", "Be picky with all extensions matching",
26342773 OFFSET(extension_picky), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS},

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t