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