Browse code

Add Apple HTTP Live Streaming demuxer

Originally committed as revision 24834 to svn://svn.ffmpeg.org/ffmpeg/trunk

Martin Storsjö authored on 2010/08/19 23:54:37
Showing 5 changed files
... ...
@@ -30,6 +30,7 @@ version <next>:
30 30
 - RTP depacketization of MP4A-LATM
31 31
 - RTP packetization and depacketization of VP8
32 32
 - hflip filter
33
+- Apple HTTP Live Streaming demuxer
33 34
 
34 35
 
35 36
 version 0.6:
... ...
@@ -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
+};