FFmpeg
hlsproto.c
Go to the documentation of this file.
1 /*
2  * Apple HTTP Live Streaming Protocol Handler
3  * Copyright (c) 2010 Martin Storsjo
4  *
5  * This file is part of FFmpeg.
6  *
7  * FFmpeg is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * FFmpeg is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with FFmpeg; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  */
21 
22 /**
23  * @file
24  * Apple HTTP Live Streaming Protocol Handler
25  * https://www.rfc-editor.org/rfc/rfc8216.txt
26  */
27 
28 #include "libavutil/avstring.h"
29 #include "libavutil/time.h"
30 #include "avio_internal.h"
31 #include "internal.h"
32 #include "url.h"
33 
34 /*
35  * An apple http stream consists of a playlist with media segment files,
36  * played sequentially. There may be several playlists with the same
37  * video content, in different bandwidth variants, that are played in
38  * parallel (preferably only one bandwidth variant at a time). In this case,
39  * the user supplied the url to a main playlist that only lists the variant
40  * playlists.
41  *
42  * If the main playlist doesn't point at any variants, we still create
43  * one anonymous toplevel variant for this, to maintain the structure.
44  */
45 
46 struct segment {
47  int64_t duration;
49 };
50 
51 struct variant {
52  int bandwidth;
54 };
55 
56 typedef struct HLSContext {
58  int64_t target_duration;
60  int finished;
62  struct segment **segments;
63  int n_variants;
64  struct variant **variants;
67  int64_t last_load_time;
68 } HLSContext;
69 
71 {
72  int i;
73  for (i = 0; i < s->n_segments; i++)
74  av_freep(&s->segments[i]);
75  av_freep(&s->segments);
76  s->n_segments = 0;
77 }
78 
80 {
81  int i;
82  for (i = 0; i < s->n_variants; i++)
83  av_freep(&s->variants[i]);
84  av_freep(&s->variants);
85  s->n_variants = 0;
86 }
87 
88 struct variant_info {
89  char bandwidth[20];
90 };
91 
92 static void handle_variant_args(struct variant_info *info, const char *key,
93  int key_len, char **dest, int *dest_len)
94 {
95  if (!strncmp(key, "BANDWIDTH=", key_len)) {
96  *dest = info->bandwidth;
97  *dest_len = sizeof(info->bandwidth);
98  }
99 }
100 
101 static int parse_playlist(URLContext *h, const char *url)
102 {
103  HLSContext *s = h->priv_data;
104  AVIOContext *in;
105  int ret = 0, is_segment = 0, is_variant = 0, bandwidth = 0;
106  int64_t duration = 0;
107  char line[1024];
108  const char *ptr;
109 
110  if ((ret = ffio_open_whitelist(&in, url, AVIO_FLAG_READ,
111  &h->interrupt_callback, NULL,
112  h->protocol_whitelist, h->protocol_blacklist)) < 0)
113  return ret;
114 
115  ff_get_chomp_line(in, line, sizeof(line));
116  if (strcmp(line, "#EXTM3U")) {
118  goto fail;
119  }
120 
122  s->finished = 0;
123  while (!avio_feof(in)) {
124  ff_get_chomp_line(in, line, sizeof(line));
125  if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) {
126  struct variant_info info = {{0}};
127  is_variant = 1;
129  &info);
130  bandwidth = atoi(info.bandwidth);
131  } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) {
132  s->target_duration = atoi(ptr) * AV_TIME_BASE;
133  } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) {
134  s->start_seq_no = atoi(ptr);
135  } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) {
136  s->finished = 1;
137  } else if (av_strstart(line, "#EXTINF:", &ptr)) {
138  is_segment = 1;
139  duration = atof(ptr) * AV_TIME_BASE;
140  } else if (av_strstart(line, "#", NULL)) {
141  continue;
142  } else if (line[0]) {
143  if (is_segment) {
144  struct segment *seg = av_malloc(sizeof(struct segment));
145  if (!seg) {
146  ret = AVERROR(ENOMEM);
147  goto fail;
148  }
149  seg->duration = duration;
150  ff_make_absolute_url(seg->url, sizeof(seg->url), url, line);
151  dynarray_add(&s->segments, &s->n_segments, seg);
152  is_segment = 0;
153  } else if (is_variant) {
154  struct variant *var = av_malloc(sizeof(struct variant));
155  if (!var) {
156  ret = AVERROR(ENOMEM);
157  goto fail;
158  }
159  var->bandwidth = bandwidth;
160  ff_make_absolute_url(var->url, sizeof(var->url), url, line);
161  dynarray_add(&s->variants, &s->n_variants, var);
162  is_variant = 0;
163  }
164  }
165  }
166  s->last_load_time = av_gettime_relative();
167 
168 fail:
169  avio_close(in);
170  return ret;
171 }
172 
173 static int hls_close(URLContext *h)
174 {
175  HLSContext *s = h->priv_data;
176 
179  ffurl_closep(&s->seg_hd);
180  return 0;
181 }
182 
183 static int hls_open(URLContext *h, const char *uri, int flags)
184 {
185  HLSContext *s = h->priv_data;
186  int ret, i;
187  const char *nested_url;
188 
189  if (flags & AVIO_FLAG_WRITE)
190  return AVERROR(ENOSYS);
191 
192  h->is_streamed = 1;
193 
194  if (av_strstart(uri, "hls+", &nested_url)) {
195  av_strlcpy(s->playlisturl, nested_url, sizeof(s->playlisturl));
196  } else if (av_strstart(uri, "hls://", &nested_url)) {
198  "No nested protocol specified. Specify e.g. hls+http://%s\n",
199  nested_url);
200  ret = AVERROR(EINVAL);
201  goto fail;
202  } else {
203  av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri);
204  ret = AVERROR(EINVAL);
205  goto fail;
206  }
208  "Using the hls protocol is discouraged, please try using the "
209  "hls demuxer instead. The hls demuxer should be more complete "
210  "and work as well as the protocol implementation. (If not, "
211  "please report it.) To use the demuxer, simply use %s as url.\n",
212  s->playlisturl);
213 
214  if ((ret = parse_playlist(h, s->playlisturl)) < 0)
215  goto fail;
216 
217  if (s->n_segments == 0 && s->n_variants > 0) {
218  int max_bandwidth = 0, maxvar = -1;
219  for (i = 0; i < s->n_variants; i++) {
220  if (s->variants[i]->bandwidth > max_bandwidth || i == 0) {
221  max_bandwidth = s->variants[i]->bandwidth;
222  maxvar = i;
223  }
224  }
225  av_strlcpy(s->playlisturl, s->variants[maxvar]->url,
226  sizeof(s->playlisturl));
227  if ((ret = parse_playlist(h, s->playlisturl)) < 0)
228  goto fail;
229  }
230 
231  if (s->n_segments == 0) {
232  av_log(h, AV_LOG_WARNING, "Empty playlist\n");
233  ret = AVERROR(EIO);
234  goto fail;
235  }
236  s->cur_seq_no = s->start_seq_no;
237  if (!s->finished && s->n_segments >= 3)
238  s->cur_seq_no = s->start_seq_no + s->n_segments - 3;
239 
240  return 0;
241 
242 fail:
243  hls_close(h);
244  return ret;
245 }
246 
247 static int hls_read(URLContext *h, uint8_t *buf, int size)
248 {
249  HLSContext *s = h->priv_data;
250  const char *url;
251  int ret;
252  int64_t reload_interval;
253 
254 start:
255  if (s->seg_hd) {
256  ret = ffurl_read(s->seg_hd, buf, size);
257  if (ret > 0)
258  return ret;
259  }
260  if (s->seg_hd) {
261  ffurl_closep(&s->seg_hd);
262  s->cur_seq_no++;
263  }
264  reload_interval = s->n_segments > 0 ?
265  s->segments[s->n_segments - 1]->duration :
266  s->target_duration;
267 retry:
268  if (!s->finished) {
269  int64_t now = av_gettime_relative();
270  if (now - s->last_load_time >= reload_interval) {
271  if ((ret = parse_playlist(h, s->playlisturl)) < 0)
272  return ret;
273  /* If we need to reload the playlist again below (if
274  * there's still no more segments), switch to a reload
275  * interval of half the target duration. */
276  reload_interval = s->target_duration / 2;
277  }
278  }
279  if (s->cur_seq_no < s->start_seq_no) {
281  "skipping %d segments ahead, expired from playlist\n",
282  s->start_seq_no - s->cur_seq_no);
283  s->cur_seq_no = s->start_seq_no;
284  }
285  if (s->cur_seq_no - s->start_seq_no >= s->n_segments) {
286  if (s->finished)
287  return AVERROR_EOF;
288  while (av_gettime_relative() - s->last_load_time < reload_interval) {
289  if (ff_check_interrupt(&h->interrupt_callback))
290  return AVERROR_EXIT;
291  av_usleep(100*1000);
292  }
293  goto retry;
294  }
295  url = s->segments[s->cur_seq_no - s->start_seq_no]->url;
296  av_log(h, AV_LOG_DEBUG, "opening %s\n", url);
298  &h->interrupt_callback, NULL,
299  h->protocol_whitelist, h->protocol_blacklist, h);
300  if (ret < 0) {
301  if (ff_check_interrupt(&h->interrupt_callback))
302  return AVERROR_EXIT;
303  av_log(h, AV_LOG_WARNING, "Unable to open %s\n", url);
304  s->cur_seq_no++;
305  goto retry;
306  }
307  goto start;
308 }
309 
311  .name = "hls",
312  .url_open = hls_open,
313  .url_read = hls_read,
314  .url_close = hls_close,
316  .priv_data_size = sizeof(HLSContext),
317 };
hls_open
static int hls_open(URLContext *h, const char *uri, int flags)
Definition: hlsproto.c:183
ff_get_chomp_line
int ff_get_chomp_line(AVIOContext *s, char *buf, int maxlen)
Same as ff_get_line but strip the white-space characters in the text tail.
Definition: aviobuf.c:833
av_gettime_relative
int64_t av_gettime_relative(void)
Get the current time in microseconds since some unspecified starting point.
Definition: time.c:56
AV_LOG_WARNING
#define AV_LOG_WARNING
Something somehow does not look correct.
Definition: log.h:186
HLSContext::cur_seq_no
int cur_seq_no
Definition: hlsproto.c:65
AVERROR
Filter the word “frame” indicates either a video frame or a group of audio as stored in an AVFrame structure Format for each input and each output the list of supported formats For video that means pixel format For audio that means channel sample they are references to shared objects When the negotiation mechanism computes the intersection of the formats supported at each end of a all references to both lists are replaced with a reference to the intersection And when a single format is eventually chosen for a link amongst the remaining all references to the list are updated That means that if a filter requires that its input and output have the same format amongst a supported all it has to do is use a reference to the same list of formats query_formats can leave some formats unset and return AVERROR(EAGAIN) to cause the negotiation mechanism toagain later. That can be used by filters with complex requirements to use the format negotiated on one link to set the formats supported on another. Frame references ownership and permissions
HLSContext::n_segments
int n_segments
Definition: hlsproto.c:61
HLSContext::n_variants
int n_variants
Definition: hls.c:207
avio_close
int avio_close(AVIOContext *s)
Close the resource accessed by the AVIOContext s and free it.
Definition: aviobuf.c:1335
AVERROR_EOF
#define AVERROR_EOF
End of file.
Definition: error.h:57
ffio_open_whitelist
int ffio_open_whitelist(AVIOContext **s, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist, const char *blacklist)
Definition: aviobuf.c:1308
URLProtocol
Definition: url.h:53
av_malloc
#define av_malloc(s)
Definition: tableprint_vlc.h:30
segment::duration
int64_t duration
Definition: hls.c:77
fail
#define fail()
Definition: checkasm.h:179
variant
Definition: hls.c:192
dynarray_add
#define dynarray_add(tab, nb_ptr, elem)
Definition: internal.h:454
ff_check_interrupt
int ff_check_interrupt(AVIOInterruptCB *cb)
Check if the user has requested to interrupt a blocking function associated with cb.
Definition: avio.c:667
AV_LOG_ERROR
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition: log.h:180
ff_hls_protocol
const URLProtocol ff_hls_protocol
Definition: hlsproto.c:310
duration
int64_t duration
Definition: movenc.c:64
ffurl_open_whitelist
int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options, const char *whitelist, const char *blacklist, URLContext *parent)
Create an URLContext for accessing to the resource indicated by url, and open it.
Definition: avio.c:300
variant_info
Definition: hls.c:339
hls_close
static int hls_close(URLContext *h)
Definition: hlsproto.c:173
s
#define s(width, name)
Definition: cbs_vp9.c:198
info
MIPS optimizations info
Definition: mips.txt:2
AVIO_FLAG_WRITE
#define AVIO_FLAG_WRITE
write-only
Definition: avio.h:637
handle_variant_args
static void handle_variant_args(struct variant_info *info, const char *key, int key_len, char **dest, int *dest_len)
Definition: hlsproto.c:92
AV_LOG_DEBUG
#define AV_LOG_DEBUG
Stuff which is only useful for libav* developers.
Definition: log.h:201
HLSContext::last_load_time
int64_t last_load_time
Definition: hlsproto.c:67
free_segment_list
static void free_segment_list(HLSContext *s)
Definition: hlsproto.c:70
hls_read
static int hls_read(URLContext *h, uint8_t *buf, int size)
Definition: hlsproto.c:247
key
const char * key
Definition: hwcontext_opencl.c:174
av_usleep
int av_usleep(unsigned usec)
Sleep for a period of time.
Definition: time.c:84
internal.h
variant::url
char url[MAX_URL_SIZE]
Definition: hlsproto.c:53
NULL
#define NULL
Definition: coverity.c:32
HLSContext::variants
struct variant ** variants
Definition: hls.c:208
parse_playlist
static int parse_playlist(URLContext *h, const char *url)
Definition: hlsproto.c:101
time.h
HLSContext::segments
struct segment ** segments
Definition: hlsproto.c:62
HLSContext::finished
int finished
Definition: hlsproto.c:60
AVIOContext
Bytestream IO Context.
Definition: avio.h:166
HLSContext::seg_hd
URLContext * seg_hd
Definition: hlsproto.c:66
HLSContext::playlisturl
char playlisturl[MAX_URL_SIZE]
Definition: hlsproto.c:57
URL_PROTOCOL_FLAG_NESTED_SCHEME
#define URL_PROTOCOL_FLAG_NESTED_SCHEME
Definition: url.h:32
size
int size
Definition: twinvq_data.h:10344
URLProtocol::name
const char * name
Definition: url.h:54
line
Definition: graph2dot.c:48
av_strstart
int av_strstart(const char *str, const char *pfx, const char **ptr)
Return non-zero if pfx is a prefix of str.
Definition: avstring.c:36
URLContext
Definition: url.h:37
i
#define i(width, name, range_min, range_max)
Definition: cbs_h2645.c:255
avio_internal.h
AV_TIME_BASE
#define AV_TIME_BASE
Internal time base represented as integer.
Definition: avutil.h:254
HLSContext
Definition: hls.c:204
url.h
ffurl_closep
int ffurl_closep(URLContext **hh)
Close the resource accessed by the URLContext h, and free the memory used by it.
Definition: avio.c:439
ret
ret
Definition: filter_design.txt:187
MAX_URL_SIZE
#define MAX_URL_SIZE
Definition: internal.h:30
HLSContext::start_seq_no
int start_seq_no
Definition: hlsproto.c:59
ff_parse_key_val_cb
void(* ff_parse_key_val_cb)(void *context, const char *key, int key_len, char **dest, int *dest_len)
Callback function type for ff_parse_key_value.
Definition: internal.h:582
variant::bandwidth
int bandwidth
Definition: hls.c:193
HLSContext::target_duration
int64_t target_duration
Definition: hlsproto.c:58
segment
Definition: hls.c:76
AVIO_FLAG_READ
#define AVIO_FLAG_READ
read-only
Definition: avio.h:636
free_variant_list
static void free_variant_list(HLSContext *s)
Definition: hlsproto.c:79
ff_make_absolute_url
int ff_make_absolute_url(char *buf, int size, const char *base, const char *rel)
Convert a relative url into an absolute url, given a base url.
Definition: url.c:321
variant_info::bandwidth
char bandwidth[20]
Definition: hls.c:340
segment::url
char * url
Definition: hls.c:80
av_freep
#define av_freep(p)
Definition: tableprint_vlc.h:34
flags
#define flags(name, subs,...)
Definition: cbs_av1.c:474
av_strlcpy
size_t av_strlcpy(char *dst, const char *src, size_t size)
Copy the string src to dst, but no more than size - 1 bytes, and null-terminate dst.
Definition: avstring.c:85
av_log
#define av_log(a,...)
Definition: tableprint_vlc.h:27
AVERROR_INVALIDDATA
#define AVERROR_INVALIDDATA
Invalid data found when processing input.
Definition: error.h:61
h
h
Definition: vp9dsp_template.c:2038
AVERROR_EXIT
#define AVERROR_EXIT
Immediate exit was requested; the called function should not be restarted.
Definition: error.h:58
avstring.h
ff_parse_key_value
void ff_parse_key_value(const char *str, ff_parse_key_val_cb callback_get_buf, void *context)
Parse a string with comma-separated key=value pairs.
Definition: utils.c:504
avio_feof
int avio_feof(AVIOContext *s)
Similar to feof() but also returns nonzero on read errors.
Definition: aviobuf.c:393
ffurl_read
static int ffurl_read(URLContext *h, uint8_t *buf, int size)
Read up to size bytes from the resource accessed by h, and store the read bytes in buf.
Definition: url.h:183