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