ffmpeg / libavformat / applehttp.c @ f87b1b37
History | View | Annotate | Download (16.8 KB)
1 |
/*
|
---|---|
2 |
* Apple HTTP Live Streaming demuxer
|
3 |
* Copyright (c) 2010 Martin Storsjo
|
4 |
*
|
5 |
* This file is part of Libav.
|
6 |
*
|
7 |
* Libav 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 |
* Libav 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 Libav; 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 demuxer
|
25 |
* http://tools.ietf.org/html/draft-pantos-http-live-streaming
|
26 |
*/
|
27 |
|
28 |
#define _XOPEN_SOURCE 600 |
29 |
#include "libavutil/avstring.h" |
30 |
#include "avformat.h" |
31 |
#include "internal.h" |
32 |
#include <unistd.h> |
33 |
#include "avio_internal.h" |
34 |
|
35 |
#define INITIAL_BUFFER_SIZE 32768 |
36 |
|
37 |
/*
|
38 |
* An apple http stream consists of a playlist with media segment files,
|
39 |
* played sequentially. There may be several playlists with the same
|
40 |
* video content, in different bandwidth variants, that are played in
|
41 |
* parallel (preferrably only one bandwidth variant at a time). In this case,
|
42 |
* the user supplied the url to a main playlist that only lists the variant
|
43 |
* playlists.
|
44 |
*
|
45 |
* If the main playlist doesn't point at any variants, we still create
|
46 |
* one anonymous toplevel variant for this, to maintain the structure.
|
47 |
*/
|
48 |
|
49 |
struct segment {
|
50 |
int duration;
|
51 |
char url[MAX_URL_SIZE];
|
52 |
}; |
53 |
|
54 |
/*
|
55 |
* Each variant has its own demuxer. If it currently is active,
|
56 |
* it has an open AVIOContext too, and potentially an AVPacket
|
57 |
* containing the next packet from this stream.
|
58 |
*/
|
59 |
struct variant {
|
60 |
int bandwidth;
|
61 |
char url[MAX_URL_SIZE];
|
62 |
AVIOContext pb; |
63 |
uint8_t* read_buffer; |
64 |
URLContext *input; |
65 |
AVFormatContext *parent; |
66 |
int index;
|
67 |
AVFormatContext *ctx; |
68 |
AVPacket pkt; |
69 |
int stream_offset;
|
70 |
|
71 |
int finished;
|
72 |
int target_duration;
|
73 |
int start_seq_no;
|
74 |
int n_segments;
|
75 |
struct segment **segments;
|
76 |
int needed, cur_needed;
|
77 |
int cur_seq_no;
|
78 |
int64_t last_load_time; |
79 |
}; |
80 |
|
81 |
typedef struct AppleHTTPContext { |
82 |
int n_variants;
|
83 |
struct variant **variants;
|
84 |
int cur_seq_no;
|
85 |
int end_of_segment;
|
86 |
int first_packet;
|
87 |
} AppleHTTPContext; |
88 |
|
89 |
static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) |
90 |
{ |
91 |
int len = ff_get_line(s, buf, maxlen);
|
92 |
while (len > 0 && isspace(buf[len - 1])) |
93 |
buf[--len] = '\0';
|
94 |
return len;
|
95 |
} |
96 |
|
97 |
static void free_segment_list(struct variant *var) |
98 |
{ |
99 |
int i;
|
100 |
for (i = 0; i < var->n_segments; i++) |
101 |
av_free(var->segments[i]); |
102 |
av_freep(&var->segments); |
103 |
var->n_segments = 0;
|
104 |
} |
105 |
|
106 |
static void free_variant_list(AppleHTTPContext *c) |
107 |
{ |
108 |
int i;
|
109 |
for (i = 0; i < c->n_variants; i++) { |
110 |
struct variant *var = c->variants[i];
|
111 |
free_segment_list(var); |
112 |
av_free_packet(&var->pkt); |
113 |
av_free(var->pb.buffer); |
114 |
if (var->input)
|
115 |
url_close(var->input); |
116 |
if (var->ctx) {
|
117 |
var->ctx->pb = NULL;
|
118 |
av_close_input_file(var->ctx); |
119 |
} |
120 |
av_free(var); |
121 |
} |
122 |
av_freep(&c->variants); |
123 |
c->n_variants = 0;
|
124 |
} |
125 |
|
126 |
/*
|
127 |
* Used to reset a statically allocated AVPacket to a clean slate,
|
128 |
* containing no data.
|
129 |
*/
|
130 |
static void reset_packet(AVPacket *pkt) |
131 |
{ |
132 |
av_init_packet(pkt); |
133 |
pkt->data = NULL;
|
134 |
} |
135 |
|
136 |
static struct variant *new_variant(AppleHTTPContext *c, int bandwidth, |
137 |
const char *url, const char *base) |
138 |
{ |
139 |
struct variant *var = av_mallocz(sizeof(struct variant)); |
140 |
if (!var)
|
141 |
return NULL; |
142 |
reset_packet(&var->pkt); |
143 |
var->bandwidth = bandwidth; |
144 |
ff_make_absolute_url(var->url, sizeof(var->url), base, url);
|
145 |
dynarray_add(&c->variants, &c->n_variants, var); |
146 |
return var;
|
147 |
} |
148 |
|
149 |
struct variant_info {
|
150 |
char bandwidth[20]; |
151 |
}; |
152 |
|
153 |
static void handle_variant_args(struct variant_info *info, const char *key, |
154 |
int key_len, char **dest, int *dest_len) |
155 |
{ |
156 |
if (!strncmp(key, "BANDWIDTH=", key_len)) { |
157 |
*dest = info->bandwidth; |
158 |
*dest_len = sizeof(info->bandwidth);
|
159 |
} |
160 |
} |
161 |
|
162 |
static int parse_playlist(AppleHTTPContext *c, const char *url, |
163 |
struct variant *var, AVIOContext *in)
|
164 |
{ |
165 |
int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0; |
166 |
char line[1024]; |
167 |
const char *ptr; |
168 |
int close_in = 0; |
169 |
|
170 |
if (!in) {
|
171 |
close_in = 1;
|
172 |
if ((ret = avio_open(&in, url, AVIO_RDONLY)) < 0) |
173 |
return ret;
|
174 |
} |
175 |
|
176 |
read_chomp_line(in, line, sizeof(line));
|
177 |
if (strcmp(line, "#EXTM3U")) { |
178 |
ret = AVERROR_INVALIDDATA; |
179 |
goto fail;
|
180 |
} |
181 |
|
182 |
if (var) {
|
183 |
free_segment_list(var); |
184 |
var->finished = 0;
|
185 |
} |
186 |
while (!in->eof_reached) {
|
187 |
read_chomp_line(in, line, sizeof(line));
|
188 |
if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) { |
189 |
struct variant_info info = {{0}}; |
190 |
is_variant = 1;
|
191 |
ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args, |
192 |
&info); |
193 |
bandwidth = atoi(info.bandwidth); |
194 |
} else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) { |
195 |
if (!var) {
|
196 |
var = new_variant(c, 0, url, NULL); |
197 |
if (!var) {
|
198 |
ret = AVERROR(ENOMEM); |
199 |
goto fail;
|
200 |
} |
201 |
} |
202 |
var->target_duration = atoi(ptr); |
203 |
} else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { |
204 |
if (!var) {
|
205 |
var = new_variant(c, 0, url, NULL); |
206 |
if (!var) {
|
207 |
ret = AVERROR(ENOMEM); |
208 |
goto fail;
|
209 |
} |
210 |
} |
211 |
var->start_seq_no = atoi(ptr); |
212 |
} else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) { |
213 |
if (var)
|
214 |
var->finished = 1;
|
215 |
} else if (av_strstart(line, "#EXTINF:", &ptr)) { |
216 |
is_segment = 1;
|
217 |
duration = atoi(ptr); |
218 |
} else if (av_strstart(line, "#", NULL)) { |
219 |
continue;
|
220 |
} else if (line[0]) { |
221 |
if (is_variant) {
|
222 |
if (!new_variant(c, bandwidth, line, url)) {
|
223 |
ret = AVERROR(ENOMEM); |
224 |
goto fail;
|
225 |
} |
226 |
is_variant = 0;
|
227 |
bandwidth = 0;
|
228 |
} |
229 |
if (is_segment) {
|
230 |
struct segment *seg;
|
231 |
if (!var) {
|
232 |
var = new_variant(c, 0, url, NULL); |
233 |
if (!var) {
|
234 |
ret = AVERROR(ENOMEM); |
235 |
goto fail;
|
236 |
} |
237 |
} |
238 |
seg = av_malloc(sizeof(struct segment)); |
239 |
if (!seg) {
|
240 |
ret = AVERROR(ENOMEM); |
241 |
goto fail;
|
242 |
} |
243 |
seg->duration = duration; |
244 |
ff_make_absolute_url(seg->url, sizeof(seg->url), url, line);
|
245 |
dynarray_add(&var->segments, &var->n_segments, seg); |
246 |
is_segment = 0;
|
247 |
} |
248 |
} |
249 |
} |
250 |
if (var)
|
251 |
var->last_load_time = av_gettime(); |
252 |
|
253 |
fail:
|
254 |
if (close_in)
|
255 |
avio_close(in); |
256 |
return ret;
|
257 |
} |
258 |
|
259 |
static int read_data(void *opaque, uint8_t *buf, int buf_size) |
260 |
{ |
261 |
struct variant *v = opaque;
|
262 |
AppleHTTPContext *c = v->parent->priv_data; |
263 |
int ret, i;
|
264 |
|
265 |
restart:
|
266 |
if (!v->input) {
|
267 |
reload:
|
268 |
/* If this is a live stream and target_duration has elapsed since
|
269 |
* the last playlist reload, reload the variant playlists now. */
|
270 |
if (!v->finished &&
|
271 |
av_gettime() - v->last_load_time >= v->target_duration*1000000 &&
|
272 |
(ret = parse_playlist(c, v->url, v, NULL)) < 0) |
273 |
return ret;
|
274 |
if (v->cur_seq_no < v->start_seq_no) {
|
275 |
av_log(NULL, AV_LOG_WARNING,
|
276 |
"skipping %d segments ahead, expired from playlists\n",
|
277 |
v->start_seq_no - v->cur_seq_no); |
278 |
v->cur_seq_no = v->start_seq_no; |
279 |
} |
280 |
if (v->cur_seq_no >= v->start_seq_no + v->n_segments) {
|
281 |
if (v->finished)
|
282 |
return AVERROR_EOF;
|
283 |
while (av_gettime() - v->last_load_time <
|
284 |
v->target_duration*1000000) {
|
285 |
if (url_interrupt_cb())
|
286 |
return AVERROR_EXIT;
|
287 |
usleep(100*1000); |
288 |
} |
289 |
/* Enough time has elapsed since the last reload */
|
290 |
goto reload;
|
291 |
} |
292 |
|
293 |
ret = url_open(&v->input, |
294 |
v->segments[v->cur_seq_no - v->start_seq_no]->url, |
295 |
AVIO_RDONLY); |
296 |
if (ret < 0) |
297 |
return ret;
|
298 |
} |
299 |
ret = url_read(v->input, buf, buf_size); |
300 |
if (ret > 0) |
301 |
return ret;
|
302 |
if (ret < 0 && ret != AVERROR_EOF) |
303 |
return ret;
|
304 |
url_close(v->input); |
305 |
v->input = NULL;
|
306 |
v->cur_seq_no++; |
307 |
|
308 |
c->end_of_segment = 1;
|
309 |
c->cur_seq_no = v->cur_seq_no; |
310 |
|
311 |
v->needed = 0;
|
312 |
for (i = v->stream_offset; i < v->stream_offset + v->ctx->nb_streams; i++) {
|
313 |
if (v->parent->streams[i]->discard < AVDISCARD_ALL)
|
314 |
v->needed = 1;
|
315 |
} |
316 |
if (!v->needed) {
|
317 |
av_log(v->parent, AV_LOG_INFO, "No longer receiving variant %d\n",
|
318 |
v->index); |
319 |
return AVERROR_EOF;
|
320 |
} |
321 |
goto restart;
|
322 |
} |
323 |
|
324 |
static int applehttp_read_header(AVFormatContext *s, AVFormatParameters *ap) |
325 |
{ |
326 |
AppleHTTPContext *c = s->priv_data; |
327 |
int ret = 0, i, j, stream_offset = 0; |
328 |
|
329 |
if ((ret = parse_playlist(c, s->filename, NULL, s->pb)) < 0) |
330 |
goto fail;
|
331 |
|
332 |
if (c->n_variants == 0) { |
333 |
av_log(NULL, AV_LOG_WARNING, "Empty playlist\n"); |
334 |
ret = AVERROR_EOF; |
335 |
goto fail;
|
336 |
} |
337 |
/* If the playlist only contained variants, parse each individual
|
338 |
* variant playlist. */
|
339 |
if (c->n_variants > 1 || c->variants[0]->n_segments == 0) { |
340 |
for (i = 0; i < c->n_variants; i++) { |
341 |
struct variant *v = c->variants[i];
|
342 |
if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) |
343 |
goto fail;
|
344 |
} |
345 |
} |
346 |
|
347 |
if (c->variants[0]->n_segments == 0) { |
348 |
av_log(NULL, AV_LOG_WARNING, "Empty playlist\n"); |
349 |
ret = AVERROR_EOF; |
350 |
goto fail;
|
351 |
} |
352 |
|
353 |
/* If this isn't a live stream, calculate the total duration of the
|
354 |
* stream. */
|
355 |
if (c->variants[0]->finished) { |
356 |
int64_t duration = 0;
|
357 |
for (i = 0; i < c->variants[0]->n_segments; i++) |
358 |
duration += c->variants[0]->segments[i]->duration;
|
359 |
s->duration = duration * AV_TIME_BASE; |
360 |
} |
361 |
|
362 |
/* Open the demuxer for each variant */
|
363 |
for (i = 0; i < c->n_variants; i++) { |
364 |
struct variant *v = c->variants[i];
|
365 |
AVInputFormat *in_fmt = NULL;
|
366 |
if (v->n_segments == 0) |
367 |
continue;
|
368 |
|
369 |
v->index = i; |
370 |
v->needed = 1;
|
371 |
v->parent = s; |
372 |
|
373 |
/* If this is a live stream with more than 3 segments, start at the
|
374 |
* third last segment. */
|
375 |
v->cur_seq_no = v->start_seq_no; |
376 |
if (!v->finished && v->n_segments > 3) |
377 |
v->cur_seq_no = v->start_seq_no + v->n_segments - 3;
|
378 |
|
379 |
v->read_buffer = av_malloc(INITIAL_BUFFER_SIZE); |
380 |
ffio_init_context(&v->pb, v->read_buffer, INITIAL_BUFFER_SIZE, 0, v,
|
381 |
read_data, NULL, NULL); |
382 |
v->pb.seekable = 0;
|
383 |
ret = av_probe_input_buffer(&v->pb, &in_fmt, v->segments[0]->url,
|
384 |
NULL, 0, 0); |
385 |
if (ret < 0) |
386 |
goto fail;
|
387 |
ret = av_open_input_stream(&v->ctx, &v->pb, v->segments[0]->url,
|
388 |
in_fmt, NULL);
|
389 |
if (ret < 0) |
390 |
goto fail;
|
391 |
v->stream_offset = stream_offset; |
392 |
/* Create new AVStreams for each stream in this variant */
|
393 |
for (j = 0; j < v->ctx->nb_streams; j++) { |
394 |
AVStream *st = av_new_stream(s, i); |
395 |
if (!st) {
|
396 |
ret = AVERROR(ENOMEM); |
397 |
goto fail;
|
398 |
} |
399 |
avcodec_copy_context(st->codec, v->ctx->streams[j]->codec); |
400 |
} |
401 |
stream_offset += v->ctx->nb_streams; |
402 |
} |
403 |
|
404 |
c->first_packet = 1;
|
405 |
|
406 |
return 0; |
407 |
fail:
|
408 |
free_variant_list(c); |
409 |
return ret;
|
410 |
} |
411 |
|
412 |
static int recheck_discard_flags(AVFormatContext *s, int first) |
413 |
{ |
414 |
AppleHTTPContext *c = s->priv_data; |
415 |
int i, changed = 0; |
416 |
|
417 |
/* Check if any new streams are needed */
|
418 |
for (i = 0; i < c->n_variants; i++) |
419 |
c->variants[i]->cur_needed = 0;;
|
420 |
|
421 |
for (i = 0; i < s->nb_streams; i++) { |
422 |
AVStream *st = s->streams[i]; |
423 |
struct variant *var = c->variants[s->streams[i]->id];
|
424 |
if (st->discard < AVDISCARD_ALL)
|
425 |
var->cur_needed = 1;
|
426 |
} |
427 |
for (i = 0; i < c->n_variants; i++) { |
428 |
struct variant *v = c->variants[i];
|
429 |
if (v->cur_needed && !v->needed) {
|
430 |
v->needed = 1;
|
431 |
changed = 1;
|
432 |
v->cur_seq_no = c->cur_seq_no; |
433 |
v->pb.eof_reached = 0;
|
434 |
av_log(s, AV_LOG_INFO, "Now receiving variant %d\n", i);
|
435 |
} else if (first && !v->cur_needed && v->needed) { |
436 |
if (v->input)
|
437 |
url_close(v->input); |
438 |
v->input = NULL;
|
439 |
v->needed = 0;
|
440 |
changed = 1;
|
441 |
av_log(s, AV_LOG_INFO, "No longer receiving variant %d\n", i);
|
442 |
} |
443 |
} |
444 |
return changed;
|
445 |
} |
446 |
|
447 |
static int applehttp_read_packet(AVFormatContext *s, AVPacket *pkt) |
448 |
{ |
449 |
AppleHTTPContext *c = s->priv_data; |
450 |
int ret, i, minvariant = -1; |
451 |
|
452 |
if (c->first_packet) {
|
453 |
recheck_discard_flags(s, 1);
|
454 |
c->first_packet = 0;
|
455 |
} |
456 |
|
457 |
start:
|
458 |
c->end_of_segment = 0;
|
459 |
for (i = 0; i < c->n_variants; i++) { |
460 |
struct variant *var = c->variants[i];
|
461 |
/* Make sure we've got one buffered packet from each open variant
|
462 |
* stream */
|
463 |
if (var->needed && !var->pkt.data) {
|
464 |
ret = av_read_frame(var->ctx, &var->pkt); |
465 |
if (ret < 0) { |
466 |
if (!var->pb.eof_reached)
|
467 |
return ret;
|
468 |
reset_packet(&var->pkt); |
469 |
} |
470 |
} |
471 |
/* Check if this stream has the packet with the lowest dts */
|
472 |
if (var->pkt.data) {
|
473 |
if (minvariant < 0 || |
474 |
var->pkt.dts < c->variants[minvariant]->pkt.dts) |
475 |
minvariant = i; |
476 |
} |
477 |
} |
478 |
if (c->end_of_segment) {
|
479 |
if (recheck_discard_flags(s, 0)) |
480 |
goto start;
|
481 |
} |
482 |
/* If we got a packet, return it */
|
483 |
if (minvariant >= 0) { |
484 |
*pkt = c->variants[minvariant]->pkt; |
485 |
pkt->stream_index += c->variants[minvariant]->stream_offset; |
486 |
reset_packet(&c->variants[minvariant]->pkt); |
487 |
return 0; |
488 |
} |
489 |
return AVERROR_EOF;
|
490 |
} |
491 |
|
492 |
static int applehttp_close(AVFormatContext *s) |
493 |
{ |
494 |
AppleHTTPContext *c = s->priv_data; |
495 |
|
496 |
free_variant_list(c); |
497 |
return 0; |
498 |
} |
499 |
|
500 |
static int applehttp_read_seek(AVFormatContext *s, int stream_index, |
501 |
int64_t timestamp, int flags)
|
502 |
{ |
503 |
AppleHTTPContext *c = s->priv_data; |
504 |
int i, j, ret;
|
505 |
|
506 |
if ((flags & AVSEEK_FLAG_BYTE) || !c->variants[0]->finished) |
507 |
return AVERROR(ENOSYS);
|
508 |
|
509 |
timestamp = av_rescale_rnd(timestamp, 1, stream_index >= 0 ? |
510 |
s->streams[stream_index]->time_base.den : |
511 |
AV_TIME_BASE, flags & AVSEEK_FLAG_BACKWARD ? |
512 |
AV_ROUND_DOWN : AV_ROUND_UP); |
513 |
ret = AVERROR(EIO); |
514 |
for (i = 0; i < c->n_variants; i++) { |
515 |
/* Reset reading */
|
516 |
struct variant *var = c->variants[i];
|
517 |
int64_t pos = 0;
|
518 |
if (var->input) {
|
519 |
url_close(var->input); |
520 |
var->input = NULL;
|
521 |
} |
522 |
av_free_packet(&var->pkt); |
523 |
reset_packet(&var->pkt); |
524 |
var->pb.eof_reached = 0;
|
525 |
|
526 |
/* Locate the segment that contains the target timestamp */
|
527 |
for (j = 0; j < var->n_segments; j++) { |
528 |
if (timestamp >= pos &&
|
529 |
timestamp < pos + var->segments[j]->duration) { |
530 |
var->cur_seq_no = var->start_seq_no + j; |
531 |
ret = 0;
|
532 |
break;
|
533 |
} |
534 |
pos += var->segments[j]->duration; |
535 |
} |
536 |
} |
537 |
return ret;
|
538 |
} |
539 |
|
540 |
static int applehttp_probe(AVProbeData *p) |
541 |
{ |
542 |
/* Require #EXTM3U at the start, and either one of the ones below
|
543 |
* somewhere for a proper match. */
|
544 |
if (strncmp(p->buf, "#EXTM3U", 7)) |
545 |
return 0; |
546 |
if (strstr(p->buf, "#EXT-X-STREAM-INF:") || |
547 |
strstr(p->buf, "#EXT-X-TARGETDURATION:") ||
|
548 |
strstr(p->buf, "#EXT-X-MEDIA-SEQUENCE:"))
|
549 |
return AVPROBE_SCORE_MAX;
|
550 |
return 0; |
551 |
} |
552 |
|
553 |
AVInputFormat ff_applehttp_demuxer = { |
554 |
"applehttp",
|
555 |
NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming format"),
|
556 |
sizeof(AppleHTTPContext),
|
557 |
applehttp_probe, |
558 |
applehttp_read_header, |
559 |
applehttp_read_packet, |
560 |
applehttp_close, |
561 |
applehttp_read_seek, |
562 |
}; |