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