Libav 0.7.1
|
00001 /* 00002 * Apple HTTP Live Streaming demuxer 00003 * Copyright (c) 2010 Martin Storsjo 00004 * 00005 * This file is part of Libav. 00006 * 00007 * Libav is free software; you can redistribute it and/or 00008 * modify it under the terms of the GNU Lesser General Public 00009 * License as published by the Free Software Foundation; either 00010 * version 2.1 of the License, or (at your option) any later version. 00011 * 00012 * Libav is distributed in the hope that it will be useful, 00013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 * Lesser General Public License for more details. 00016 * 00017 * You should have received a copy of the GNU Lesser General Public 00018 * License along with Libav; if not, write to the Free Software 00019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00020 */ 00021 00028 #include "libavutil/avstring.h" 00029 #include "libavutil/intreadwrite.h" 00030 #include "libavutil/opt.h" 00031 #include "libavutil/dict.h" 00032 #include "avformat.h" 00033 #include "internal.h" 00034 #include <unistd.h> 00035 #include "avio_internal.h" 00036 #include "url.h" 00037 00038 #define INITIAL_BUFFER_SIZE 32768 00039 00040 /* 00041 * An apple http stream consists of a playlist with media segment files, 00042 * played sequentially. There may be several playlists with the same 00043 * video content, in different bandwidth variants, that are played in 00044 * parallel (preferrably only one bandwidth variant at a time). In this case, 00045 * the user supplied the url to a main playlist that only lists the variant 00046 * playlists. 00047 * 00048 * If the main playlist doesn't point at any variants, we still create 00049 * one anonymous toplevel variant for this, to maintain the structure. 00050 */ 00051 00052 enum KeyType { 00053 KEY_NONE, 00054 KEY_AES_128, 00055 }; 00056 00057 struct segment { 00058 int duration; 00059 char url[MAX_URL_SIZE]; 00060 char key[MAX_URL_SIZE]; 00061 enum KeyType key_type; 00062 uint8_t iv[16]; 00063 }; 00064 00065 /* 00066 * Each variant has its own demuxer. If it currently is active, 00067 * it has an open AVIOContext too, and potentially an AVPacket 00068 * containing the next packet from this stream. 00069 */ 00070 struct variant { 00071 int bandwidth; 00072 char url[MAX_URL_SIZE]; 00073 AVIOContext pb; 00074 uint8_t* read_buffer; 00075 URLContext *input; 00076 AVFormatContext *parent; 00077 int index; 00078 AVFormatContext *ctx; 00079 AVPacket pkt; 00080 int stream_offset; 00081 00082 int finished; 00083 int target_duration; 00084 int start_seq_no; 00085 int n_segments; 00086 struct segment **segments; 00087 int needed, cur_needed; 00088 int cur_seq_no; 00089 int64_t last_load_time; 00090 00091 char key_url[MAX_URL_SIZE]; 00092 uint8_t key[16]; 00093 }; 00094 00095 typedef struct AppleHTTPContext { 00096 int n_variants; 00097 struct variant **variants; 00098 int cur_seq_no; 00099 int end_of_segment; 00100 int first_packet; 00101 } AppleHTTPContext; 00102 00103 static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) 00104 { 00105 int len = ff_get_line(s, buf, maxlen); 00106 while (len > 0 && isspace(buf[len - 1])) 00107 buf[--len] = '\0'; 00108 return len; 00109 } 00110 00111 static void free_segment_list(struct variant *var) 00112 { 00113 int i; 00114 for (i = 0; i < var->n_segments; i++) 00115 av_free(var->segments[i]); 00116 av_freep(&var->segments); 00117 var->n_segments = 0; 00118 } 00119 00120 static void free_variant_list(AppleHTTPContext *c) 00121 { 00122 int i; 00123 for (i = 0; i < c->n_variants; i++) { 00124 struct variant *var = c->variants[i]; 00125 free_segment_list(var); 00126 av_free_packet(&var->pkt); 00127 av_free(var->pb.buffer); 00128 if (var->input) 00129 ffurl_close(var->input); 00130 if (var->ctx) { 00131 var->ctx->pb = NULL; 00132 av_close_input_file(var->ctx); 00133 } 00134 av_free(var); 00135 } 00136 av_freep(&c->variants); 00137 c->n_variants = 0; 00138 } 00139 00140 /* 00141 * Used to reset a statically allocated AVPacket to a clean slate, 00142 * containing no data. 00143 */ 00144 static void reset_packet(AVPacket *pkt) 00145 { 00146 av_init_packet(pkt); 00147 pkt->data = NULL; 00148 } 00149 00150 static struct variant *new_variant(AppleHTTPContext *c, int bandwidth, 00151 const char *url, const char *base) 00152 { 00153 struct variant *var = av_mallocz(sizeof(struct variant)); 00154 if (!var) 00155 return NULL; 00156 reset_packet(&var->pkt); 00157 var->bandwidth = bandwidth; 00158 ff_make_absolute_url(var->url, sizeof(var->url), base, url); 00159 dynarray_add(&c->variants, &c->n_variants, var); 00160 return var; 00161 } 00162 00163 struct variant_info { 00164 char bandwidth[20]; 00165 }; 00166 00167 static void handle_variant_args(struct variant_info *info, const char *key, 00168 int key_len, char **dest, int *dest_len) 00169 { 00170 if (!strncmp(key, "BANDWIDTH=", key_len)) { 00171 *dest = info->bandwidth; 00172 *dest_len = sizeof(info->bandwidth); 00173 } 00174 } 00175 00176 struct key_info { 00177 char uri[MAX_URL_SIZE]; 00178 char method[10]; 00179 char iv[35]; 00180 }; 00181 00182 static void handle_key_args(struct key_info *info, const char *key, 00183 int key_len, char **dest, int *dest_len) 00184 { 00185 if (!strncmp(key, "METHOD=", key_len)) { 00186 *dest = info->method; 00187 *dest_len = sizeof(info->method); 00188 } else if (!strncmp(key, "URI=", key_len)) { 00189 *dest = info->uri; 00190 *dest_len = sizeof(info->uri); 00191 } else if (!strncmp(key, "IV=", key_len)) { 00192 *dest = info->iv; 00193 *dest_len = sizeof(info->iv); 00194 } 00195 } 00196 00197 static int parse_playlist(AppleHTTPContext *c, const char *url, 00198 struct variant *var, AVIOContext *in) 00199 { 00200 int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0; 00201 enum KeyType key_type = KEY_NONE; 00202 uint8_t iv[16] = ""; 00203 int has_iv = 0; 00204 char key[MAX_URL_SIZE]; 00205 char line[1024]; 00206 const char *ptr; 00207 int close_in = 0; 00208 00209 if (!in) { 00210 close_in = 1; 00211 if ((ret = avio_open(&in, url, AVIO_FLAG_READ)) < 0) 00212 return ret; 00213 } 00214 00215 read_chomp_line(in, line, sizeof(line)); 00216 if (strcmp(line, "#EXTM3U")) { 00217 ret = AVERROR_INVALIDDATA; 00218 goto fail; 00219 } 00220 00221 if (var) { 00222 free_segment_list(var); 00223 var->finished = 0; 00224 } 00225 while (!in->eof_reached) { 00226 read_chomp_line(in, line, sizeof(line)); 00227 if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) { 00228 struct variant_info info = {{0}}; 00229 is_variant = 1; 00230 ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args, 00231 &info); 00232 bandwidth = atoi(info.bandwidth); 00233 } else if (av_strstart(line, "#EXT-X-KEY:", &ptr)) { 00234 struct key_info info = {{0}}; 00235 ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_key_args, 00236 &info); 00237 key_type = KEY_NONE; 00238 has_iv = 0; 00239 if (!strcmp(info.method, "AES-128")) 00240 key_type = KEY_AES_128; 00241 if (!strncmp(info.iv, "0x", 2) || !strncmp(info.iv, "0X", 2)) { 00242 ff_hex_to_data(iv, info.iv + 2); 00243 has_iv = 1; 00244 } 00245 av_strlcpy(key, info.uri, sizeof(key)); 00246 } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) { 00247 if (!var) { 00248 var = new_variant(c, 0, url, NULL); 00249 if (!var) { 00250 ret = AVERROR(ENOMEM); 00251 goto fail; 00252 } 00253 } 00254 var->target_duration = atoi(ptr); 00255 } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { 00256 if (!var) { 00257 var = new_variant(c, 0, url, NULL); 00258 if (!var) { 00259 ret = AVERROR(ENOMEM); 00260 goto fail; 00261 } 00262 } 00263 var->start_seq_no = atoi(ptr); 00264 } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) { 00265 if (var) 00266 var->finished = 1; 00267 } else if (av_strstart(line, "#EXTINF:", &ptr)) { 00268 is_segment = 1; 00269 duration = atoi(ptr); 00270 } else if (av_strstart(line, "#", NULL)) { 00271 continue; 00272 } else if (line[0]) { 00273 if (is_variant) { 00274 if (!new_variant(c, bandwidth, line, url)) { 00275 ret = AVERROR(ENOMEM); 00276 goto fail; 00277 } 00278 is_variant = 0; 00279 bandwidth = 0; 00280 } 00281 if (is_segment) { 00282 struct segment *seg; 00283 if (!var) { 00284 var = new_variant(c, 0, url, NULL); 00285 if (!var) { 00286 ret = AVERROR(ENOMEM); 00287 goto fail; 00288 } 00289 } 00290 seg = av_malloc(sizeof(struct segment)); 00291 if (!seg) { 00292 ret = AVERROR(ENOMEM); 00293 goto fail; 00294 } 00295 seg->duration = duration; 00296 seg->key_type = key_type; 00297 if (has_iv) { 00298 memcpy(seg->iv, iv, sizeof(iv)); 00299 } else { 00300 int seq = var->start_seq_no + var->n_segments; 00301 memset(seg->iv, 0, sizeof(seg->iv)); 00302 AV_WB32(seg->iv + 12, seq); 00303 } 00304 ff_make_absolute_url(seg->key, sizeof(seg->key), url, key); 00305 ff_make_absolute_url(seg->url, sizeof(seg->url), url, line); 00306 dynarray_add(&var->segments, &var->n_segments, seg); 00307 is_segment = 0; 00308 } 00309 } 00310 } 00311 if (var) 00312 var->last_load_time = av_gettime(); 00313 00314 fail: 00315 if (close_in) 00316 avio_close(in); 00317 return ret; 00318 } 00319 00320 static int open_input(struct variant *var) 00321 { 00322 struct segment *seg = var->segments[var->cur_seq_no - var->start_seq_no]; 00323 if (seg->key_type == KEY_NONE) { 00324 return ffurl_open(&var->input, seg->url, AVIO_FLAG_READ); 00325 } else if (seg->key_type == KEY_AES_128) { 00326 char iv[33], key[33], url[MAX_URL_SIZE]; 00327 int ret; 00328 if (strcmp(seg->key, var->key_url)) { 00329 URLContext *uc; 00330 if (ffurl_open(&uc, seg->key, AVIO_FLAG_READ) == 0) { 00331 if (ffurl_read_complete(uc, var->key, sizeof(var->key)) 00332 != sizeof(var->key)) { 00333 av_log(NULL, AV_LOG_ERROR, "Unable to read key file %s\n", 00334 seg->key); 00335 } 00336 ffurl_close(uc); 00337 } else { 00338 av_log(NULL, AV_LOG_ERROR, "Unable to open key file %s\n", 00339 seg->key); 00340 } 00341 av_strlcpy(var->key_url, seg->key, sizeof(var->key_url)); 00342 } 00343 ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); 00344 ff_data_to_hex(key, var->key, sizeof(var->key), 0); 00345 iv[32] = key[32] = '\0'; 00346 if (strstr(seg->url, "://")) 00347 snprintf(url, sizeof(url), "crypto+%s", seg->url); 00348 else 00349 snprintf(url, sizeof(url), "crypto:%s", seg->url); 00350 if ((ret = ffurl_alloc(&var->input, url, AVIO_FLAG_READ)) < 0) 00351 return ret; 00352 av_set_string3(var->input->priv_data, "key", key, 0, NULL); 00353 av_set_string3(var->input->priv_data, "iv", iv, 0, NULL); 00354 if ((ret = ffurl_connect(var->input)) < 0) { 00355 ffurl_close(var->input); 00356 var->input = NULL; 00357 return ret; 00358 } 00359 return 0; 00360 } 00361 return AVERROR(ENOSYS); 00362 } 00363 00364 static int read_data(void *opaque, uint8_t *buf, int buf_size) 00365 { 00366 struct variant *v = opaque; 00367 AppleHTTPContext *c = v->parent->priv_data; 00368 int ret, i; 00369 00370 restart: 00371 if (!v->input) { 00372 reload: 00373 /* If this is a live stream and target_duration has elapsed since 00374 * the last playlist reload, reload the variant playlists now. */ 00375 if (!v->finished && 00376 av_gettime() - v->last_load_time >= v->target_duration*1000000 && 00377 (ret = parse_playlist(c, v->url, v, NULL)) < 0) 00378 return ret; 00379 if (v->cur_seq_no < v->start_seq_no) { 00380 av_log(NULL, AV_LOG_WARNING, 00381 "skipping %d segments ahead, expired from playlists\n", 00382 v->start_seq_no - v->cur_seq_no); 00383 v->cur_seq_no = v->start_seq_no; 00384 } 00385 if (v->cur_seq_no >= v->start_seq_no + v->n_segments) { 00386 if (v->finished) 00387 return AVERROR_EOF; 00388 while (av_gettime() - v->last_load_time < 00389 v->target_duration*1000000) { 00390 if (url_interrupt_cb()) 00391 return AVERROR_EXIT; 00392 usleep(100*1000); 00393 } 00394 /* Enough time has elapsed since the last reload */ 00395 goto reload; 00396 } 00397 00398 ret = open_input(v); 00399 if (ret < 0) 00400 return ret; 00401 } 00402 ret = ffurl_read(v->input, buf, buf_size); 00403 if (ret > 0) 00404 return ret; 00405 if (ret < 0 && ret != AVERROR_EOF) 00406 return ret; 00407 ffurl_close(v->input); 00408 v->input = NULL; 00409 v->cur_seq_no++; 00410 00411 c->end_of_segment = 1; 00412 c->cur_seq_no = v->cur_seq_no; 00413 00414 if (v->ctx) { 00415 v->needed = 0; 00416 for (i = v->stream_offset; i < v->stream_offset + v->ctx->nb_streams; 00417 i++) { 00418 if (v->parent->streams[i]->discard < AVDISCARD_ALL) 00419 v->needed = 1; 00420 } 00421 } 00422 if (!v->needed) { 00423 av_log(v->parent, AV_LOG_INFO, "No longer receiving variant %d\n", 00424 v->index); 00425 return AVERROR_EOF; 00426 } 00427 goto restart; 00428 } 00429 00430 static int applehttp_read_header(AVFormatContext *s, AVFormatParameters *ap) 00431 { 00432 AppleHTTPContext *c = s->priv_data; 00433 int ret = 0, i, j, stream_offset = 0; 00434 00435 if ((ret = parse_playlist(c, s->filename, NULL, s->pb)) < 0) 00436 goto fail; 00437 00438 if (c->n_variants == 0) { 00439 av_log(NULL, AV_LOG_WARNING, "Empty playlist\n"); 00440 ret = AVERROR_EOF; 00441 goto fail; 00442 } 00443 /* If the playlist only contained variants, parse each individual 00444 * variant playlist. */ 00445 if (c->n_variants > 1 || c->variants[0]->n_segments == 0) { 00446 for (i = 0; i < c->n_variants; i++) { 00447 struct variant *v = c->variants[i]; 00448 if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) 00449 goto fail; 00450 } 00451 } 00452 00453 if (c->variants[0]->n_segments == 0) { 00454 av_log(NULL, AV_LOG_WARNING, "Empty playlist\n"); 00455 ret = AVERROR_EOF; 00456 goto fail; 00457 } 00458 00459 /* If this isn't a live stream, calculate the total duration of the 00460 * stream. */ 00461 if (c->variants[0]->finished) { 00462 int64_t duration = 0; 00463 for (i = 0; i < c->variants[0]->n_segments; i++) 00464 duration += c->variants[0]->segments[i]->duration; 00465 s->duration = duration * AV_TIME_BASE; 00466 } 00467 00468 /* Open the demuxer for each variant */ 00469 for (i = 0; i < c->n_variants; i++) { 00470 struct variant *v = c->variants[i]; 00471 AVInputFormat *in_fmt = NULL; 00472 char bitrate_str[20]; 00473 if (v->n_segments == 0) 00474 continue; 00475 00476 if (!(v->ctx = avformat_alloc_context())) { 00477 ret = AVERROR(ENOMEM); 00478 goto fail; 00479 } 00480 00481 v->index = i; 00482 v->needed = 1; 00483 v->parent = s; 00484 00485 /* If this is a live stream with more than 3 segments, start at the 00486 * third last segment. */ 00487 v->cur_seq_no = v->start_seq_no; 00488 if (!v->finished && v->n_segments > 3) 00489 v->cur_seq_no = v->start_seq_no + v->n_segments - 3; 00490 00491 v->read_buffer = av_malloc(INITIAL_BUFFER_SIZE); 00492 ffio_init_context(&v->pb, v->read_buffer, INITIAL_BUFFER_SIZE, 0, v, 00493 read_data, NULL, NULL); 00494 v->pb.seekable = 0; 00495 ret = av_probe_input_buffer(&v->pb, &in_fmt, v->segments[0]->url, 00496 NULL, 0, 0); 00497 if (ret < 0) 00498 goto fail; 00499 v->ctx->pb = &v->pb; 00500 ret = avformat_open_input(&v->ctx, v->segments[0]->url, in_fmt, NULL); 00501 if (ret < 0) 00502 goto fail; 00503 v->stream_offset = stream_offset; 00504 snprintf(bitrate_str, sizeof(bitrate_str), "%d", v->bandwidth); 00505 /* Create new AVStreams for each stream in this variant */ 00506 for (j = 0; j < v->ctx->nb_streams; j++) { 00507 AVStream *st = av_new_stream(s, i); 00508 if (!st) { 00509 ret = AVERROR(ENOMEM); 00510 goto fail; 00511 } 00512 avcodec_copy_context(st->codec, v->ctx->streams[j]->codec); 00513 if (v->bandwidth) 00514 av_dict_set(&st->metadata, "variant_bitrate", bitrate_str, 00515 0); 00516 } 00517 stream_offset += v->ctx->nb_streams; 00518 } 00519 00520 c->first_packet = 1; 00521 00522 return 0; 00523 fail: 00524 free_variant_list(c); 00525 return ret; 00526 } 00527 00528 static int recheck_discard_flags(AVFormatContext *s, int first) 00529 { 00530 AppleHTTPContext *c = s->priv_data; 00531 int i, changed = 0; 00532 00533 /* Check if any new streams are needed */ 00534 for (i = 0; i < c->n_variants; i++) 00535 c->variants[i]->cur_needed = 0;; 00536 00537 for (i = 0; i < s->nb_streams; i++) { 00538 AVStream *st = s->streams[i]; 00539 struct variant *var = c->variants[s->streams[i]->id]; 00540 if (st->discard < AVDISCARD_ALL) 00541 var->cur_needed = 1; 00542 } 00543 for (i = 0; i < c->n_variants; i++) { 00544 struct variant *v = c->variants[i]; 00545 if (v->cur_needed && !v->needed) { 00546 v->needed = 1; 00547 changed = 1; 00548 v->cur_seq_no = c->cur_seq_no; 00549 v->pb.eof_reached = 0; 00550 av_log(s, AV_LOG_INFO, "Now receiving variant %d\n", i); 00551 } else if (first && !v->cur_needed && v->needed) { 00552 if (v->input) 00553 ffurl_close(v->input); 00554 v->input = NULL; 00555 v->needed = 0; 00556 changed = 1; 00557 av_log(s, AV_LOG_INFO, "No longer receiving variant %d\n", i); 00558 } 00559 } 00560 return changed; 00561 } 00562 00563 static int applehttp_read_packet(AVFormatContext *s, AVPacket *pkt) 00564 { 00565 AppleHTTPContext *c = s->priv_data; 00566 int ret, i, minvariant = -1; 00567 00568 if (c->first_packet) { 00569 recheck_discard_flags(s, 1); 00570 c->first_packet = 0; 00571 } 00572 00573 start: 00574 c->end_of_segment = 0; 00575 for (i = 0; i < c->n_variants; i++) { 00576 struct variant *var = c->variants[i]; 00577 /* Make sure we've got one buffered packet from each open variant 00578 * stream */ 00579 if (var->needed && !var->pkt.data) { 00580 ret = av_read_frame(var->ctx, &var->pkt); 00581 if (ret < 0) { 00582 if (!var->pb.eof_reached) 00583 return ret; 00584 reset_packet(&var->pkt); 00585 } 00586 } 00587 /* Check if this stream has the packet with the lowest dts */ 00588 if (var->pkt.data) { 00589 if (minvariant < 0 || 00590 var->pkt.dts < c->variants[minvariant]->pkt.dts) 00591 minvariant = i; 00592 } 00593 } 00594 if (c->end_of_segment) { 00595 if (recheck_discard_flags(s, 0)) 00596 goto start; 00597 } 00598 /* If we got a packet, return it */ 00599 if (minvariant >= 0) { 00600 *pkt = c->variants[minvariant]->pkt; 00601 pkt->stream_index += c->variants[minvariant]->stream_offset; 00602 reset_packet(&c->variants[minvariant]->pkt); 00603 return 0; 00604 } 00605 return AVERROR_EOF; 00606 } 00607 00608 static int applehttp_close(AVFormatContext *s) 00609 { 00610 AppleHTTPContext *c = s->priv_data; 00611 00612 free_variant_list(c); 00613 return 0; 00614 } 00615 00616 static int applehttp_read_seek(AVFormatContext *s, int stream_index, 00617 int64_t timestamp, int flags) 00618 { 00619 AppleHTTPContext *c = s->priv_data; 00620 int i, j, ret; 00621 00622 if ((flags & AVSEEK_FLAG_BYTE) || !c->variants[0]->finished) 00623 return AVERROR(ENOSYS); 00624 00625 timestamp = av_rescale_rnd(timestamp, 1, stream_index >= 0 ? 00626 s->streams[stream_index]->time_base.den : 00627 AV_TIME_BASE, flags & AVSEEK_FLAG_BACKWARD ? 00628 AV_ROUND_DOWN : AV_ROUND_UP); 00629 ret = AVERROR(EIO); 00630 for (i = 0; i < c->n_variants; i++) { 00631 /* Reset reading */ 00632 struct variant *var = c->variants[i]; 00633 int64_t pos = 0; 00634 if (var->input) { 00635 ffurl_close(var->input); 00636 var->input = NULL; 00637 } 00638 av_free_packet(&var->pkt); 00639 reset_packet(&var->pkt); 00640 var->pb.eof_reached = 0; 00641 00642 /* Locate the segment that contains the target timestamp */ 00643 for (j = 0; j < var->n_segments; j++) { 00644 if (timestamp >= pos && 00645 timestamp < pos + var->segments[j]->duration) { 00646 var->cur_seq_no = var->start_seq_no + j; 00647 ret = 0; 00648 break; 00649 } 00650 pos += var->segments[j]->duration; 00651 } 00652 } 00653 return ret; 00654 } 00655 00656 static int applehttp_probe(AVProbeData *p) 00657 { 00658 /* Require #EXTM3U at the start, and either one of the ones below 00659 * somewhere for a proper match. */ 00660 if (strncmp(p->buf, "#EXTM3U", 7)) 00661 return 0; 00662 if (strstr(p->buf, "#EXT-X-STREAM-INF:") || 00663 strstr(p->buf, "#EXT-X-TARGETDURATION:") || 00664 strstr(p->buf, "#EXT-X-MEDIA-SEQUENCE:")) 00665 return AVPROBE_SCORE_MAX; 00666 return 0; 00667 } 00668 00669 AVInputFormat ff_applehttp_demuxer = { 00670 "applehttp", 00671 NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming format"), 00672 sizeof(AppleHTTPContext), 00673 applehttp_probe, 00674 applehttp_read_header, 00675 applehttp_read_packet, 00676 applehttp_close, 00677 applehttp_read_seek, 00678 };