ffmpeg / libavformat / applehttp.c @ f87b1b37
History | View | Annotate | Download (16.8 KB)
1 | cd223e0b | Martin Storsjö | /*
|
---|---|---|---|
2 | * Apple HTTP Live Streaming demuxer
|
||
3 | * Copyright (c) 2010 Martin Storsjo
|
||
4 | *
|
||
5 | 2912e87a | Mans Rullgard | * This file is part of Libav.
|
6 | cd223e0b | Martin Storsjö | *
|
7 | 2912e87a | Mans Rullgard | * Libav is free software; you can redistribute it and/or
|
8 | cd223e0b | Martin Storsjö | * 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 | 2912e87a | Mans Rullgard | * Libav is distributed in the hope that it will be useful,
|
13 | cd223e0b | Martin Storsjö | * 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 | 2912e87a | Mans Rullgard | * License along with Libav; if not, write to the Free Software
|
19 | cd223e0b | Martin Storsjö | * 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 | b2125520 | Måns Rullgård | #define _XOPEN_SOURCE 600 |
29 | cd223e0b | Martin Storsjö | #include "libavutil/avstring.h" |
30 | #include "avformat.h" |
||
31 | #include "internal.h" |
||
32 | #include <unistd.h> |
||
33 | 6cc7f139 | Martin Storsjö | #include "avio_internal.h" |
34 | |||
35 | #define INITIAL_BUFFER_SIZE 32768 |
||
36 | cd223e0b | Martin Storsjö | |
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 | ae628ec1 | Anton Khirnov | * it has an open AVIOContext too, and potentially an AVPacket
|
57 | cd223e0b | Martin Storsjö | * containing the next packet from this stream.
|
58 | */
|
||
59 | struct variant {
|
||
60 | int bandwidth;
|
||
61 | char url[MAX_URL_SIZE];
|
||
62 | 6cc7f139 | Martin Storsjö | AVIOContext pb; |
63 | uint8_t* read_buffer; |
||
64 | URLContext *input; |
||
65 | AVFormatContext *parent; |
||
66 | int index;
|
||
67 | cd223e0b | Martin Storsjö | AVFormatContext *ctx; |
68 | AVPacket pkt; |
||
69 | int stream_offset;
|
||
70 | |||
71 | d3964da2 | Martin Storsjö | int finished;
|
72 | int target_duration;
|
||
73 | cd223e0b | Martin Storsjö | int start_seq_no;
|
74 | int n_segments;
|
||
75 | struct segment **segments;
|
||
76 | 6cc7f139 | Martin Storsjö | int needed, cur_needed;
|
77 | int cur_seq_no;
|
||
78 | int64_t last_load_time; |
||
79 | cd223e0b | Martin Storsjö | }; |
80 | |||
81 | typedef struct AppleHTTPContext { |
||
82 | int n_variants;
|
||
83 | struct variant **variants;
|
||
84 | int cur_seq_no;
|
||
85 | 6cc7f139 | Martin Storsjö | int end_of_segment;
|
86 | int first_packet;
|
||
87 | cd223e0b | Martin Storsjö | } AppleHTTPContext; |
88 | |||
89 | ae628ec1 | Anton Khirnov | static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) |
90 | cd223e0b | Martin Storsjö | { |
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 | 6cc7f139 | Martin Storsjö | av_free(var->pb.buffer); |
114 | if (var->input)
|
||
115 | url_close(var->input); |
||
116 | cd223e0b | Martin Storsjö | 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 | f1f60f52 | Martin Storsjö | ff_make_absolute_url(var->url, sizeof(var->url), base, url);
|
145 | cd223e0b | Martin Storsjö | 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 | 2b0decf6 | Martin Storsjö | if (!strncmp(key, "BANDWIDTH=", key_len)) { |
157 | cd223e0b | Martin Storsjö | *dest = info->bandwidth; |
158 | *dest_len = sizeof(info->bandwidth);
|
||
159 | } |
||
160 | } |
||
161 | |||
162 | static int parse_playlist(AppleHTTPContext *c, const char *url, |
||
163 | ae628ec1 | Anton Khirnov | struct variant *var, AVIOContext *in)
|
164 | cd223e0b | Martin Storsjö | { |
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 | f87b1b37 | Anton Khirnov | if ((ret = avio_open(&in, url, AVIO_RDONLY)) < 0) |
173 | cd223e0b | Martin Storsjö | 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 | d3964da2 | Martin Storsjö | if (var) {
|
183 | cd223e0b | Martin Storsjö | free_segment_list(var); |
184 | d3964da2 | Martin Storsjö | var->finished = 0;
|
185 | } |
||
186 | 66e5b1df | Anton Khirnov | while (!in->eof_reached) {
|
187 | cd223e0b | Martin Storsjö | 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 | d3964da2 | Martin Storsjö | 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 | cd223e0b | Martin Storsjö | } 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 | d3964da2 | Martin Storsjö | if (var)
|
214 | var->finished = 1;
|
||
215 | cd223e0b | Martin Storsjö | } 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 | f1f60f52 | Martin Storsjö | ff_make_absolute_url(seg->url, sizeof(seg->url), url, line);
|
245 | cd223e0b | Martin Storsjö | dynarray_add(&var->segments, &var->n_segments, seg); |
246 | is_segment = 0;
|
||
247 | } |
||
248 | } |
||
249 | } |
||
250 | 6cc7f139 | Martin Storsjö | if (var)
|
251 | var->last_load_time = av_gettime(); |
||
252 | cd223e0b | Martin Storsjö | |
253 | fail:
|
||
254 | if (close_in)
|
||
255 | 22a3212e | Anton Khirnov | avio_close(in); |
256 | cd223e0b | Martin Storsjö | return ret;
|
257 | } |
||
258 | |||
259 | 6cc7f139 | Martin Storsjö | 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 | f87b1b37 | Anton Khirnov | AVIO_RDONLY); |
296 | 6cc7f139 | Martin Storsjö | 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 | cd223e0b | Martin Storsjö | 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 | d3964da2 | Martin Storsjö | if (c->variants[0]->finished) { |
356 | b79c3df0 | John Wimer | int64_t duration = 0;
|
357 | cd223e0b | Martin Storsjö | 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 | 6cc7f139 | Martin Storsjö | AVInputFormat *in_fmt = NULL;
|
366 | cd223e0b | Martin Storsjö | if (v->n_segments == 0) |
367 | continue;
|
||
368 | 6cc7f139 | Martin Storsjö | |
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 | cd223e0b | Martin Storsjö | 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 | 6cc7f139 | Martin Storsjö | c->first_packet = 1;
|
405 | cd223e0b | Martin Storsjö | |
406 | return 0; |
||
407 | fail:
|
||
408 | free_variant_list(c); |
||
409 | return ret;
|
||
410 | } |
||
411 | |||
412 | 6cc7f139 | Martin Storsjö | static int recheck_discard_flags(AVFormatContext *s, int first) |
413 | cd223e0b | Martin Storsjö | { |
414 | 6cc7f139 | Martin Storsjö | AppleHTTPContext *c = s->priv_data; |
415 | int i, changed = 0; |
||
416 | cd223e0b | Martin Storsjö | |
417 | 6cc7f139 | Martin Storsjö | /* 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 | cd223e0b | Martin Storsjö | } |
427 | 6cc7f139 | Martin Storsjö | 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 | cd223e0b | Martin Storsjö | } |
443 | } |
||
444 | 6cc7f139 | Martin Storsjö | return changed;
|
445 | cd223e0b | Martin Storsjö | } |
446 | |||
447 | static int applehttp_read_packet(AVFormatContext *s, AVPacket *pkt) |
||
448 | { |
||
449 | AppleHTTPContext *c = s->priv_data; |
||
450 | 6cc7f139 | Martin Storsjö | int ret, i, minvariant = -1; |
451 | cd223e0b | Martin Storsjö | |
452 | 6cc7f139 | Martin Storsjö | if (c->first_packet) {
|
453 | recheck_discard_flags(s, 1);
|
||
454 | c->first_packet = 0;
|
||
455 | cd223e0b | Martin Storsjö | } |
456 | 6cc7f139 | Martin Storsjö | |
457 | cd223e0b | Martin Storsjö | start:
|
458 | 6cc7f139 | Martin Storsjö | c->end_of_segment = 0;
|
459 | cd223e0b | Martin Storsjö | 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 | 6cc7f139 | Martin Storsjö | if (var->needed && !var->pkt.data) {
|
464 | cd223e0b | Martin Storsjö | ret = av_read_frame(var->ctx, &var->pkt); |
465 | if (ret < 0) { |
||
466 | 6cc7f139 | Martin Storsjö | if (!var->pb.eof_reached)
|
467 | cd223e0b | Martin Storsjö | 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 | 6cc7f139 | Martin Storsjö | if (c->end_of_segment) {
|
479 | if (recheck_discard_flags(s, 0)) |
||
480 | goto start;
|
||
481 | } |
||
482 | cd223e0b | Martin Storsjö | /* 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 | 6cc7f139 | Martin Storsjö | return AVERROR_EOF;
|
490 | cd223e0b | Martin Storsjö | } |
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 | 6cc7f139 | Martin Storsjö | int i, j, ret;
|
505 | cd223e0b | Martin Storsjö | |
506 | d3964da2 | Martin Storsjö | if ((flags & AVSEEK_FLAG_BYTE) || !c->variants[0]->finished) |
507 | cd223e0b | Martin Storsjö | return AVERROR(ENOSYS);
|
508 | |||
509 | 0d8a33b1 | Martin Storsjö | 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 | cd223e0b | Martin Storsjö | for (i = 0; i < c->n_variants; i++) { |
515 | 0d8a33b1 | Martin Storsjö | /* Reset reading */
|
516 | cd223e0b | Martin Storsjö | struct variant *var = c->variants[i];
|
517 | 0d8a33b1 | Martin Storsjö | int64_t pos = 0;
|
518 | 6cc7f139 | Martin Storsjö | if (var->input) {
|
519 | url_close(var->input); |
||
520 | var->input = NULL;
|
||
521 | cd223e0b | Martin Storsjö | } |
522 | av_free_packet(&var->pkt); |
||
523 | reset_packet(&var->pkt); |
||
524 | 6cc7f139 | Martin Storsjö | var->pb.eof_reached = 0;
|
525 | cd223e0b | Martin Storsjö | |
526 | 6cc7f139 | Martin Storsjö | /* 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 | cd223e0b | Martin Storsjö | } |
536 | } |
||
537 | 6cc7f139 | Martin Storsjö | return ret;
|
538 | cd223e0b | Martin Storsjö | } |
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 | c6610a21 | Diego Elio Pettenò | AVInputFormat ff_applehttp_demuxer = { |
554 | cd223e0b | Martin Storsjö | "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 | }; |