1/* 2 * Apple HTTP Live Streaming demuxer 3 * Copyright (c) 2010 Martin Storsjo 4 * 5 * This file is part of Libav. 6 * 7 * Libav 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 * Libav 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 Libav; 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#include "libavutil/avstring.h" 29#include "libavutil/intreadwrite.h" 30#include "libavutil/mathematics.h" 31#include "libavutil/opt.h" 32#include "libavutil/dict.h" 33#include "avformat.h" 34#include "internal.h" 35#include <unistd.h> 36#include "avio_internal.h" 37#include "url.h" 38 39#define INITIAL_BUFFER_SIZE 32768 40 41/* 42 * An apple http stream consists of a playlist with media segment files, 43 * played sequentially. There may be several playlists with the same 44 * video content, in different bandwidth variants, that are played in 45 * parallel (preferrably only one bandwidth variant at a time). In this case, 46 * the user supplied the url to a main playlist that only lists the variant 47 * playlists. 48 * 49 * If the main playlist doesn't point at any variants, we still create 50 * one anonymous toplevel variant for this, to maintain the structure. 51 */ 52 53enum KeyType { 54 KEY_NONE, 55 KEY_AES_128, 56}; 57 58struct segment { 59 int duration; 60 char url[MAX_URL_SIZE]; 61 char key[MAX_URL_SIZE]; 62 enum KeyType key_type; 63 uint8_t iv[16]; 64}; 65 66/* 67 * Each variant has its own demuxer. If it currently is active, 68 * it has an open AVIOContext too, and potentially an AVPacket 69 * containing the next packet from this stream. 70 */ 71struct variant { 72 int bandwidth; 73 char url[MAX_URL_SIZE]; 74 AVIOContext pb; 75 uint8_t* read_buffer; 76 URLContext *input; 77 AVFormatContext *parent; 78 int index; 79 AVFormatContext *ctx; 80 AVPacket pkt; 81 int stream_offset; 82 83 int finished; 84 int target_duration; 85 int start_seq_no; 86 int n_segments; 87 struct segment **segments; 88 int needed, cur_needed; 89 int cur_seq_no; 90 int64_t last_load_time; 91 92 char key_url[MAX_URL_SIZE]; 93 uint8_t key[16]; 94}; 95 96typedef struct AppleHTTPContext { 97 int n_variants; 98 struct variant **variants; 99 int cur_seq_no; 100 int end_of_segment; 101 int first_packet; 102 int64_t first_timestamp; 103 AVIOInterruptCB *interrupt_callback; 104} AppleHTTPContext; 105 106static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) 107{ 108 int len = ff_get_line(s, buf, maxlen); 109 while (len > 0 && isspace(buf[len - 1])) 110 buf[--len] = '\0'; 111 return len; 112} 113 114static 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 123static 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 av_free(var->pb.buffer); 131 if (var->input) 132 ffurl_close(var->input); 133 if (var->ctx) { 134 var->ctx->pb = NULL; 135 avformat_close_input(&var->ctx); 136 } 137 av_free(var); 138 } 139 av_freep(&c->variants); 140 c->n_variants = 0; 141} 142 143/* 144 * Used to reset a statically allocated AVPacket to a clean slate, 145 * containing no data. 146 */ 147static void reset_packet(AVPacket *pkt) 148{ 149 av_init_packet(pkt); 150 pkt->data = NULL; 151} 152 153static struct variant *new_variant(AppleHTTPContext *c, int bandwidth, 154 const char *url, const char *base) 155{ 156 struct variant *var = av_mallocz(sizeof(struct variant)); 157 if (!var) 158 return NULL; 159 reset_packet(&var->pkt); 160 var->bandwidth = bandwidth; 161 ff_make_absolute_url(var->url, sizeof(var->url), base, url); 162 dynarray_add(&c->variants, &c->n_variants, var); 163 return var; 164} 165 166struct variant_info { 167 char bandwidth[20]; 168}; 169 170static void handle_variant_args(struct variant_info *info, const char *key, 171 int key_len, char **dest, int *dest_len) 172{ 173 if (!strncmp(key, "BANDWIDTH=", key_len)) { 174 *dest = info->bandwidth; 175 *dest_len = sizeof(info->bandwidth); 176 } 177} 178 179struct key_info { 180 char uri[MAX_URL_SIZE]; 181 char method[10]; 182 char iv[35]; 183}; 184 185static void handle_key_args(struct key_info *info, const char *key, 186 int key_len, char **dest, int *dest_len) 187{ 188 if (!strncmp(key, "METHOD=", key_len)) { 189 *dest = info->method; 190 *dest_len = sizeof(info->method); 191 } else if (!strncmp(key, "URI=", key_len)) { 192 *dest = info->uri; 193 *dest_len = sizeof(info->uri); 194 } else if (!strncmp(key, "IV=", key_len)) { 195 *dest = info->iv; 196 *dest_len = sizeof(info->iv); 197 } 198} 199 200static int parse_playlist(AppleHTTPContext *c, const char *url, 201 struct variant *var, AVIOContext *in) 202{ 203 int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0; 204 enum KeyType key_type = KEY_NONE; 205 uint8_t iv[16] = ""; 206 int has_iv = 0; 207 char key[MAX_URL_SIZE] = ""; 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_open2(&in, url, AVIO_FLAG_READ, 215 c->interrupt_callback, NULL)) < 0) 216 return ret; 217 } 218 219 read_chomp_line(in, line, sizeof(line)); 220 if (strcmp(line, "#EXTM3U")) { 221 ret = AVERROR_INVALIDDATA; 222 goto fail; 223 } 224 225 if (var) { 226 free_segment_list(var); 227 var->finished = 0; 228 } 229 while (!in->eof_reached) { 230 read_chomp_line(in, line, sizeof(line)); 231 if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) { 232 struct variant_info info = {{0}}; 233 is_variant = 1; 234 ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args, 235 &info); 236 bandwidth = atoi(info.bandwidth); 237 } else if (av_strstart(line, "#EXT-X-KEY:", &ptr)) { 238 struct key_info info = {{0}}; 239 ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_key_args, 240 &info); 241 key_type = KEY_NONE; 242 has_iv = 0; 243 if (!strcmp(info.method, "AES-128")) 244 key_type = KEY_AES_128; 245 if (!strncmp(info.iv, "0x", 2) || !strncmp(info.iv, "0X", 2)) { 246 ff_hex_to_data(iv, info.iv + 2); 247 has_iv = 1; 248 } 249 av_strlcpy(key, info.uri, sizeof(key)); 250 } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) { 251 if (!var) { 252 var = new_variant(c, 0, url, NULL); 253 if (!var) { 254 ret = AVERROR(ENOMEM); 255 goto fail; 256 } 257 } 258 var->target_duration = atoi(ptr); 259 } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { 260 if (!var) { 261 var = new_variant(c, 0, url, NULL); 262 if (!var) { 263 ret = AVERROR(ENOMEM); 264 goto fail; 265 } 266 } 267 var->start_seq_no = atoi(ptr); 268 } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) { 269 if (var) 270 var->finished = 1; 271 } else if (av_strstart(line, "#EXTINF:", &ptr)) { 272 is_segment = 1; 273 duration = atoi(ptr); 274 } else if (av_strstart(line, "#", NULL)) { 275 continue; 276 } else if (line[0]) { 277 if (is_variant) { 278 if (!new_variant(c, bandwidth, line, url)) { 279 ret = AVERROR(ENOMEM); 280 goto fail; 281 } 282 is_variant = 0; 283 bandwidth = 0; 284 } 285 if (is_segment) { 286 struct segment *seg; 287 if (!var) { 288 var = new_variant(c, 0, url, NULL); 289 if (!var) { 290 ret = AVERROR(ENOMEM); 291 goto fail; 292 } 293 } 294 seg = av_malloc(sizeof(struct segment)); 295 if (!seg) { 296 ret = AVERROR(ENOMEM); 297 goto fail; 298 } 299 seg->duration = duration; 300 seg->key_type = key_type; 301 if (has_iv) { 302 memcpy(seg->iv, iv, sizeof(iv)); 303 } else { 304 int seq = var->start_seq_no + var->n_segments; 305 memset(seg->iv, 0, sizeof(seg->iv)); 306 AV_WB32(seg->iv + 12, seq); 307 } 308 ff_make_absolute_url(seg->key, sizeof(seg->key), url, key); 309 ff_make_absolute_url(seg->url, sizeof(seg->url), url, line); 310 dynarray_add(&var->segments, &var->n_segments, seg); 311 is_segment = 0; 312 } 313 } 314 } 315 if (var) 316 var->last_load_time = av_gettime(); 317 318fail: 319 if (close_in) 320 avio_close(in); 321 return ret; 322} 323 324static int open_input(struct variant *var) 325{ 326 struct segment *seg = var->segments[var->cur_seq_no - var->start_seq_no]; 327 if (seg->key_type == KEY_NONE) { 328 return ffurl_open(&var->input, seg->url, AVIO_FLAG_READ, 329 &var->parent->interrupt_callback, NULL); 330 } else if (seg->key_type == KEY_AES_128) { 331 char iv[33], key[33], url[MAX_URL_SIZE]; 332 int ret; 333 if (strcmp(seg->key, var->key_url)) { 334 URLContext *uc; 335 if (ffurl_open(&uc, seg->key, AVIO_FLAG_READ, 336 &var->parent->interrupt_callback, NULL) == 0) { 337 if (ffurl_read_complete(uc, var->key, sizeof(var->key)) 338 != sizeof(var->key)) { 339 av_log(NULL, AV_LOG_ERROR, "Unable to read key file %s\n", 340 seg->key); 341 } 342 ffurl_close(uc); 343 } else { 344 av_log(NULL, AV_LOG_ERROR, "Unable to open key file %s\n", 345 seg->key); 346 } 347 av_strlcpy(var->key_url, seg->key, sizeof(var->key_url)); 348 } 349 ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); 350 ff_data_to_hex(key, var->key, sizeof(var->key), 0); 351 iv[32] = key[32] = '\0'; 352 if (strstr(seg->url, "://")) 353 snprintf(url, sizeof(url), "crypto+%s", seg->url); 354 else 355 snprintf(url, sizeof(url), "crypto:%s", seg->url); 356 if ((ret = ffurl_alloc(&var->input, url, AVIO_FLAG_READ, 357 &var->parent->interrupt_callback)) < 0) 358 return ret; 359 av_opt_set(var->input->priv_data, "key", key, 0); 360 av_opt_set(var->input->priv_data, "iv", iv, 0); 361 if ((ret = ffurl_connect(var->input, NULL)) < 0) { 362 ffurl_close(var->input); 363 var->input = NULL; 364 return ret; 365 } 366 return 0; 367 } 368 return AVERROR(ENOSYS); 369} 370 371static int read_data(void *opaque, uint8_t *buf, int buf_size) 372{ 373 struct variant *v = opaque; 374 AppleHTTPContext *c = v->parent->priv_data; 375 int ret, i; 376 377restart: 378 if (!v->input) { 379 /* If this is a live stream and the reload interval has elapsed since 380 * the last playlist reload, reload the variant playlists now. */ 381 int64_t reload_interval = v->n_segments > 0 ? 382 v->segments[v->n_segments - 1]->duration : 383 v->target_duration; 384 reload_interval *= 1000000; 385 386reload: 387 if (!v->finished && 388 av_gettime() - v->last_load_time >= reload_interval) { 389 if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) 390 return ret; 391 /* If we need to reload the playlist again below (if 392 * there's still no more segments), switch to a reload 393 * interval of half the target duration. */ 394 reload_interval = v->target_duration * 500000; 395 } 396 if (v->cur_seq_no < v->start_seq_no) { 397 av_log(NULL, AV_LOG_WARNING, 398 "skipping %d segments ahead, expired from playlists\n", 399 v->start_seq_no - v->cur_seq_no); 400 v->cur_seq_no = v->start_seq_no; 401 } 402 if (v->cur_seq_no >= v->start_seq_no + v->n_segments) { 403 if (v->finished) 404 return AVERROR_EOF; 405 while (av_gettime() - v->last_load_time < reload_interval) { 406 if (ff_check_interrupt(c->interrupt_callback)) 407 return AVERROR_EXIT; 408 usleep(100*1000); 409 } 410 /* Enough time has elapsed since the last reload */ 411 goto reload; 412 } 413 414 ret = open_input(v); 415 if (ret < 0) 416 return ret; 417 } 418 ret = ffurl_read(v->input, buf, buf_size); 419 if (ret > 0) 420 return ret; 421 if (ret < 0 && ret != AVERROR_EOF) 422 return ret; 423 ffurl_close(v->input); 424 v->input = NULL; 425 v->cur_seq_no++; 426 427 c->end_of_segment = 1; 428 c->cur_seq_no = v->cur_seq_no; 429 430 if (v->ctx && v->ctx->nb_streams) { 431 v->needed = 0; 432 for (i = v->stream_offset; i < v->stream_offset + v->ctx->nb_streams; 433 i++) { 434 if (v->parent->streams[i]->discard < AVDISCARD_ALL) 435 v->needed = 1; 436 } 437 } 438 if (!v->needed) { 439 av_log(v->parent, AV_LOG_INFO, "No longer receiving variant %d\n", 440 v->index); 441 return AVERROR_EOF; 442 } 443 goto restart; 444} 445 446static int applehttp_read_header(AVFormatContext *s, AVFormatParameters *ap) 447{ 448 AppleHTTPContext *c = s->priv_data; 449 int ret = 0, i, j, stream_offset = 0; 450 451 c->interrupt_callback = &s->interrupt_callback; 452 453 if ((ret = parse_playlist(c, s->filename, NULL, s->pb)) < 0) 454 goto fail; 455 456 if (c->n_variants == 0) { 457 av_log(NULL, AV_LOG_WARNING, "Empty playlist\n"); 458 ret = AVERROR_EOF; 459 goto fail; 460 } 461 /* If the playlist only contained variants, parse each individual 462 * variant playlist. */ 463 if (c->n_variants > 1 || c->variants[0]->n_segments == 0) { 464 for (i = 0; i < c->n_variants; i++) { 465 struct variant *v = c->variants[i]; 466 if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) 467 goto fail; 468 } 469 } 470 471 if (c->variants[0]->n_segments == 0) { 472 av_log(NULL, AV_LOG_WARNING, "Empty playlist\n"); 473 ret = AVERROR_EOF; 474 goto fail; 475 } 476 477 /* If this isn't a live stream, calculate the total duration of the 478 * stream. */ 479 if (c->variants[0]->finished) { 480 int64_t duration = 0; 481 for (i = 0; i < c->variants[0]->n_segments; i++) 482 duration += c->variants[0]->segments[i]->duration; 483 s->duration = duration * AV_TIME_BASE; 484 } 485 486 /* Open the demuxer for each variant */ 487 for (i = 0; i < c->n_variants; i++) { 488 struct variant *v = c->variants[i]; 489 AVInputFormat *in_fmt = NULL; 490 char bitrate_str[20]; 491 if (v->n_segments == 0) 492 continue; 493 494 if (!(v->ctx = avformat_alloc_context())) { 495 ret = AVERROR(ENOMEM); 496 goto fail; 497 } 498 499 v->index = i; 500 v->needed = 1; 501 v->parent = s; 502 503 /* If this is a live stream with more than 3 segments, start at the 504 * third last segment. */ 505 v->cur_seq_no = v->start_seq_no; 506 if (!v->finished && v->n_segments > 3) 507 v->cur_seq_no = v->start_seq_no + v->n_segments - 3; 508 509 v->read_buffer = av_malloc(INITIAL_BUFFER_SIZE); 510 ffio_init_context(&v->pb, v->read_buffer, INITIAL_BUFFER_SIZE, 0, v, 511 read_data, NULL, NULL); 512 v->pb.seekable = 0; 513 ret = av_probe_input_buffer(&v->pb, &in_fmt, v->segments[0]->url, 514 NULL, 0, 0); 515 if (ret < 0) { 516 /* Free the ctx - it isn't initialized properly at this point, 517 * so avformat_close_input shouldn't be called. If 518 * avformat_open_input fails below, it frees and zeros the 519 * context, so it doesn't need any special treatment like this. */ 520 avformat_free_context(v->ctx); 521 v->ctx = NULL; 522 goto fail; 523 } 524 v->ctx->pb = &v->pb; 525 ret = avformat_open_input(&v->ctx, v->segments[0]->url, in_fmt, NULL); 526 if (ret < 0) 527 goto fail; 528 v->stream_offset = stream_offset; 529 snprintf(bitrate_str, sizeof(bitrate_str), "%d", v->bandwidth); 530 /* Create new AVStreams for each stream in this variant */ 531 for (j = 0; j < v->ctx->nb_streams; j++) { 532 AVStream *st = avformat_new_stream(s, NULL); 533 if (!st) { 534 ret = AVERROR(ENOMEM); 535 goto fail; 536 } 537 st->id = i; 538 avcodec_copy_context(st->codec, v->ctx->streams[j]->codec); 539 if (v->bandwidth) 540 av_dict_set(&st->metadata, "variant_bitrate", bitrate_str, 541 0); 542 } 543 stream_offset += v->ctx->nb_streams; 544 } 545 546 c->first_packet = 1; 547 c->first_timestamp = AV_NOPTS_VALUE; 548 549 return 0; 550fail: 551 free_variant_list(c); 552 return ret; 553} 554 555static int recheck_discard_flags(AVFormatContext *s, int first) 556{ 557 AppleHTTPContext *c = s->priv_data; 558 int i, changed = 0; 559 560 /* Check if any new streams are needed */ 561 for (i = 0; i < c->n_variants; i++) 562 c->variants[i]->cur_needed = 0;; 563 564 for (i = 0; i < s->nb_streams; i++) { 565 AVStream *st = s->streams[i]; 566 struct variant *var = c->variants[s->streams[i]->id]; 567 if (st->discard < AVDISCARD_ALL) 568 var->cur_needed = 1; 569 } 570 for (i = 0; i < c->n_variants; i++) { 571 struct variant *v = c->variants[i]; 572 if (v->cur_needed && !v->needed) { 573 v->needed = 1; 574 changed = 1; 575 v->cur_seq_no = c->cur_seq_no; 576 v->pb.eof_reached = 0; 577 av_log(s, AV_LOG_INFO, "Now receiving variant %d\n", i); 578 } else if (first && !v->cur_needed && v->needed) { 579 if (v->input) 580 ffurl_close(v->input); 581 v->input = NULL; 582 v->needed = 0; 583 changed = 1; 584 av_log(s, AV_LOG_INFO, "No longer receiving variant %d\n", i); 585 } 586 } 587 return changed; 588} 589 590static int applehttp_read_packet(AVFormatContext *s, AVPacket *pkt) 591{ 592 AppleHTTPContext *c = s->priv_data; 593 int ret, i, minvariant = -1; 594 595 if (c->first_packet) { 596 recheck_discard_flags(s, 1); 597 c->first_packet = 0; 598 } 599 600start: 601 c->end_of_segment = 0; 602 for (i = 0; i < c->n_variants; i++) { 603 struct variant *var = c->variants[i]; 604 /* Make sure we've got one buffered packet from each open variant 605 * stream */ 606 if (var->needed && !var->pkt.data) { 607 ret = av_read_frame(var->ctx, &var->pkt); 608 if (ret < 0) { 609 if (!var->pb.eof_reached) 610 return ret; 611 reset_packet(&var->pkt); 612 } else { 613 if (c->first_timestamp == AV_NOPTS_VALUE) 614 c->first_timestamp = var->pkt.dts; 615 } 616 } 617 /* Check if this stream has the packet with the lowest dts */ 618 if (var->pkt.data) { 619 if (minvariant < 0 || 620 var->pkt.dts < c->variants[minvariant]->pkt.dts) 621 minvariant = i; 622 } 623 } 624 if (c->end_of_segment) { 625 if (recheck_discard_flags(s, 0)) 626 goto start; 627 } 628 /* If we got a packet, return it */ 629 if (minvariant >= 0) { 630 *pkt = c->variants[minvariant]->pkt; 631 pkt->stream_index += c->variants[minvariant]->stream_offset; 632 reset_packet(&c->variants[minvariant]->pkt); 633 return 0; 634 } 635 return AVERROR_EOF; 636} 637 638static int applehttp_close(AVFormatContext *s) 639{ 640 AppleHTTPContext *c = s->priv_data; 641 642 free_variant_list(c); 643 return 0; 644} 645 646static int applehttp_read_seek(AVFormatContext *s, int stream_index, 647 int64_t timestamp, int flags) 648{ 649 AppleHTTPContext *c = s->priv_data; 650 int i, j, ret; 651 652 if ((flags & AVSEEK_FLAG_BYTE) || !c->variants[0]->finished) 653 return AVERROR(ENOSYS); 654 655 timestamp = av_rescale_rnd(timestamp, 1, stream_index >= 0 ? 656 s->streams[stream_index]->time_base.den : 657 AV_TIME_BASE, flags & AVSEEK_FLAG_BACKWARD ? 658 AV_ROUND_DOWN : AV_ROUND_UP); 659 ret = AVERROR(EIO); 660 for (i = 0; i < c->n_variants; i++) { 661 /* Reset reading */ 662 struct variant *var = c->variants[i]; 663 int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ? 0 : 664 av_rescale_rnd(c->first_timestamp, 1, 665 stream_index >= 0 ? s->streams[stream_index]->time_base.den : AV_TIME_BASE, 666 flags & AVSEEK_FLAG_BACKWARD ? AV_ROUND_DOWN : AV_ROUND_UP); 667 if (var->input) { 668 ffurl_close(var->input); 669 var->input = NULL; 670 } 671 av_free_packet(&var->pkt); 672 reset_packet(&var->pkt); 673 var->pb.eof_reached = 0; 674 675 /* Locate the segment that contains the target timestamp */ 676 for (j = 0; j < var->n_segments; j++) { 677 if (timestamp >= pos && 678 timestamp < pos + var->segments[j]->duration) { 679 var->cur_seq_no = var->start_seq_no + j; 680 ret = 0; 681 break; 682 } 683 pos += var->segments[j]->duration; 684 } 685 } 686 return ret; 687} 688 689static int applehttp_probe(AVProbeData *p) 690{ 691 /* Require #EXTM3U at the start, and either one of the ones below 692 * somewhere for a proper match. */ 693 if (strncmp(p->buf, "#EXTM3U", 7)) 694 return 0; 695 if (strstr(p->buf, "#EXT-X-STREAM-INF:") || 696 strstr(p->buf, "#EXT-X-TARGETDURATION:") || 697 strstr(p->buf, "#EXT-X-MEDIA-SEQUENCE:")) 698 return AVPROBE_SCORE_MAX; 699 return 0; 700} 701 702AVInputFormat ff_applehttp_demuxer = { 703 .name = "applehttp", 704 .long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming format"), 705 .priv_data_size = sizeof(AppleHTTPContext), 706 .read_probe = applehttp_probe, 707 .read_header = applehttp_read_header, 708 .read_packet = applehttp_read_packet, 709 .read_close = applehttp_close, 710 .read_seek = applehttp_read_seek, 711}; 712