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