X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fapplehttp.c;h=6c4d56d3056489b9293b61a9c1752e7486cff4c8;hb=4bf3c8f226252e18de8051fd0d417c1d39857b67;hp=815013d5e21f3770cb5342660f101d81b4ec9f0a;hpb=d3964da2c2d5e92b555a89e9650ad5e6c1d10010;p=ffmpeg diff --git a/libavformat/applehttp.c b/libavformat/applehttp.c index 815013d5e21..6c4d56d3056 100644 --- a/libavformat/applehttp.c +++ b/libavformat/applehttp.c @@ -25,11 +25,18 @@ * http://tools.ietf.org/html/draft-pantos-http-live-streaming */ -#define _XOPEN_SOURCE 600 #include "libavutil/avstring.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/mathematics.h" +#include "libavutil/opt.h" +#include "libavutil/dict.h" #include "avformat.h" #include "internal.h" #include +#include "avio_internal.h" +#include "url.h" + +#define INITIAL_BUFFER_SIZE 32768 /* * An apple http stream consists of a playlist with media segment files, @@ -43,9 +50,17 @@ * one anonymous toplevel variant for this, to maintain the structure. */ +enum KeyType { + KEY_NONE, + KEY_AES_128, +}; + struct segment { int duration; char url[MAX_URL_SIZE]; + char key[MAX_URL_SIZE]; + enum KeyType key_type; + uint8_t iv[16]; }; /* @@ -56,7 +71,11 @@ struct segment { struct variant { int bandwidth; char url[MAX_URL_SIZE]; - AVIOContext *pb; + AVIOContext pb; + uint8_t* read_buffer; + URLContext *input; + AVFormatContext *parent; + int index; AVFormatContext *ctx; AVPacket pkt; int stream_offset; @@ -66,16 +85,22 @@ struct variant { int start_seq_no; int n_segments; struct segment **segments; - int needed; + int needed, cur_needed; + int cur_seq_no; + int64_t last_load_time; + + char key_url[MAX_URL_SIZE]; + uint8_t key[16]; }; typedef struct AppleHTTPContext { int n_variants; struct variant **variants; int cur_seq_no; - int64_t last_load_time; - int64_t last_packet_dts; - int max_start_seq, min_end_seq; + int end_of_segment; + int first_packet; + int64_t first_timestamp; + AVIOInterruptCB *interrupt_callback; } AppleHTTPContext; static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) @@ -102,11 +127,12 @@ static void free_variant_list(AppleHTTPContext *c) struct variant *var = c->variants[i]; free_segment_list(var); av_free_packet(&var->pkt); - if (var->pb) - avio_close(var->pb); + av_free(var->pb.buffer); + if (var->input) + ffurl_close(var->input); if (var->ctx) { var->ctx->pb = NULL; - av_close_input_file(var->ctx); + avformat_close_input(&var->ctx); } av_free(var); } @@ -150,17 +176,43 @@ static void handle_variant_args(struct variant_info *info, const char *key, } } +struct key_info { + char uri[MAX_URL_SIZE]; + char method[10]; + char iv[35]; +}; + +static void handle_key_args(struct key_info *info, const char *key, + int key_len, char **dest, int *dest_len) +{ + if (!strncmp(key, "METHOD=", key_len)) { + *dest = info->method; + *dest_len = sizeof(info->method); + } else if (!strncmp(key, "URI=", key_len)) { + *dest = info->uri; + *dest_len = sizeof(info->uri); + } else if (!strncmp(key, "IV=", key_len)) { + *dest = info->iv; + *dest_len = sizeof(info->iv); + } +} + static int parse_playlist(AppleHTTPContext *c, const char *url, struct variant *var, AVIOContext *in) { int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0; + enum KeyType key_type = KEY_NONE; + uint8_t iv[16] = ""; + int has_iv = 0; + char key[MAX_URL_SIZE] = ""; char line[1024]; const char *ptr; int close_in = 0; if (!in) { close_in = 1; - if ((ret = avio_open(&in, url, URL_RDONLY)) < 0) + if ((ret = avio_open2(&in, url, AVIO_FLAG_READ, + c->interrupt_callback, NULL)) < 0) return ret; } @@ -182,6 +234,19 @@ static int parse_playlist(AppleHTTPContext *c, const char *url, ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args, &info); bandwidth = atoi(info.bandwidth); + } else if (av_strstart(line, "#EXT-X-KEY:", &ptr)) { + struct key_info info = {{0}}; + ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_key_args, + &info); + key_type = KEY_NONE; + has_iv = 0; + if (!strcmp(info.method, "AES-128")) + key_type = KEY_AES_128; + if (!strncmp(info.iv, "0x", 2) || !strncmp(info.iv, "0X", 2)) { + ff_hex_to_data(iv, info.iv + 2); + has_iv = 1; + } + av_strlcpy(key, info.uri, sizeof(key)); } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) { if (!var) { var = new_variant(c, 0, url, NULL); @@ -232,13 +297,23 @@ static int parse_playlist(AppleHTTPContext *c, const char *url, goto fail; } seg->duration = duration; + seg->key_type = key_type; + if (has_iv) { + memcpy(seg->iv, iv, sizeof(iv)); + } else { + int seq = var->start_seq_no + var->n_segments; + memset(seg->iv, 0, sizeof(seg->iv)); + AV_WB32(seg->iv + 12, seq); + } + ff_make_absolute_url(seg->key, sizeof(seg->key), url, key); ff_make_absolute_url(seg->url, sizeof(seg->url), url, line); dynarray_add(&var->segments, &var->n_segments, seg); is_segment = 0; } } } - c->last_load_time = av_gettime(); + if (var) + var->last_load_time = av_gettime(); fail: if (close_in) @@ -246,11 +321,126 @@ fail: return ret; } +static int open_input(struct variant *var) +{ + struct segment *seg = var->segments[var->cur_seq_no - var->start_seq_no]; + if (seg->key_type == KEY_NONE) { + return ffurl_open(&var->input, seg->url, AVIO_FLAG_READ, + &var->parent->interrupt_callback, NULL); + } else if (seg->key_type == KEY_AES_128) { + char iv[33], key[33], url[MAX_URL_SIZE]; + int ret; + if (strcmp(seg->key, var->key_url)) { + URLContext *uc; + if (ffurl_open(&uc, seg->key, AVIO_FLAG_READ, + &var->parent->interrupt_callback, NULL) == 0) { + if (ffurl_read_complete(uc, var->key, sizeof(var->key)) + != sizeof(var->key)) { + av_log(NULL, AV_LOG_ERROR, "Unable to read key file %s\n", + seg->key); + } + ffurl_close(uc); + } else { + av_log(NULL, AV_LOG_ERROR, "Unable to open key file %s\n", + seg->key); + } + av_strlcpy(var->key_url, seg->key, sizeof(var->key_url)); + } + ff_data_to_hex(iv, seg->iv, sizeof(seg->iv), 0); + ff_data_to_hex(key, var->key, sizeof(var->key), 0); + iv[32] = key[32] = '\0'; + if (strstr(seg->url, "://")) + snprintf(url, sizeof(url), "crypto+%s", seg->url); + else + snprintf(url, sizeof(url), "crypto:%s", seg->url); + if ((ret = ffurl_alloc(&var->input, url, AVIO_FLAG_READ, + &var->parent->interrupt_callback)) < 0) + return ret; + av_opt_set(var->input->priv_data, "key", key, 0); + av_opt_set(var->input->priv_data, "iv", iv, 0); + if ((ret = ffurl_connect(var->input, NULL)) < 0) { + ffurl_close(var->input); + var->input = NULL; + return ret; + } + return 0; + } + return AVERROR(ENOSYS); +} + +static int read_data(void *opaque, uint8_t *buf, int buf_size) +{ + struct variant *v = opaque; + AppleHTTPContext *c = v->parent->priv_data; + int ret, i; + +restart: + if (!v->input) { +reload: + /* If this is a live stream and target_duration has elapsed since + * the last playlist reload, reload the variant playlists now. */ + if (!v->finished && + av_gettime() - v->last_load_time >= v->target_duration*1000000 && + (ret = parse_playlist(c, v->url, v, NULL)) < 0) + return ret; + if (v->cur_seq_no < v->start_seq_no) { + av_log(NULL, AV_LOG_WARNING, + "skipping %d segments ahead, expired from playlists\n", + v->start_seq_no - v->cur_seq_no); + v->cur_seq_no = v->start_seq_no; + } + if (v->cur_seq_no >= v->start_seq_no + v->n_segments) { + if (v->finished) + return AVERROR_EOF; + while (av_gettime() - v->last_load_time < + v->target_duration*1000000) { + if (ff_check_interrupt(c->interrupt_callback)) + return AVERROR_EXIT; + usleep(100*1000); + } + /* Enough time has elapsed since the last reload */ + goto reload; + } + + ret = open_input(v); + if (ret < 0) + return ret; + } + ret = ffurl_read(v->input, buf, buf_size); + if (ret > 0) + return ret; + if (ret < 0 && ret != AVERROR_EOF) + return ret; + ffurl_close(v->input); + v->input = NULL; + v->cur_seq_no++; + + c->end_of_segment = 1; + c->cur_seq_no = v->cur_seq_no; + + if (v->ctx && v->ctx->nb_streams) { + v->needed = 0; + for (i = v->stream_offset; i < v->stream_offset + v->ctx->nb_streams; + i++) { + if (v->parent->streams[i]->discard < AVDISCARD_ALL) + v->needed = 1; + } + } + if (!v->needed) { + av_log(v->parent, AV_LOG_INFO, "No longer receiving variant %d\n", + v->index); + return AVERROR_EOF; + } + goto restart; +} + static int applehttp_read_header(AVFormatContext *s, AVFormatParameters *ap) { AppleHTTPContext *c = s->priv_data; int ret = 0, i, j, stream_offset = 0; + c->interrupt_callback = &s->interrupt_callback; + if ((ret = parse_playlist(c, s->filename, NULL, s->pb)) < 0) goto fail; @@ -284,39 +474,68 @@ static int applehttp_read_header(AVFormatContext *s, AVFormatParameters *ap) s->duration = duration * AV_TIME_BASE; } - c->min_end_seq = INT_MAX; /* Open the demuxer for each variant */ for (i = 0; i < c->n_variants; i++) { struct variant *v = c->variants[i]; + AVInputFormat *in_fmt = NULL; + char bitrate_str[20]; if (v->n_segments == 0) continue; - c->max_start_seq = FFMAX(c->max_start_seq, v->start_seq_no); - c->min_end_seq = FFMIN(c->min_end_seq, v->start_seq_no + - v->n_segments); - ret = av_open_input_file(&v->ctx, v->segments[0]->url, NULL, 0, NULL); + + if (!(v->ctx = avformat_alloc_context())) { + ret = AVERROR(ENOMEM); + goto fail; + } + + v->index = i; + v->needed = 1; + v->parent = s; + + /* If this is a live stream with more than 3 segments, start at the + * third last segment. */ + v->cur_seq_no = v->start_seq_no; + if (!v->finished && v->n_segments > 3) + v->cur_seq_no = v->start_seq_no + v->n_segments - 3; + + v->read_buffer = av_malloc(INITIAL_BUFFER_SIZE); + ffio_init_context(&v->pb, v->read_buffer, INITIAL_BUFFER_SIZE, 0, v, + read_data, NULL, NULL); + v->pb.seekable = 0; + ret = av_probe_input_buffer(&v->pb, &in_fmt, v->segments[0]->url, + NULL, 0, 0); + if (ret < 0) { + /* Free the ctx - it isn't initialized properly at this point, + * so avformat_close_input shouldn't be called. If + * avformat_open_input fails below, it frees and zeros the + * context, so it doesn't need any special treatment like this. */ + avformat_free_context(v->ctx); + v->ctx = NULL; + goto fail; + } + v->ctx->pb = &v->pb; + ret = avformat_open_input(&v->ctx, v->segments[0]->url, in_fmt, NULL); if (ret < 0) goto fail; - avio_close(v->ctx->pb); - v->ctx->pb = NULL; v->stream_offset = stream_offset; + snprintf(bitrate_str, sizeof(bitrate_str), "%d", v->bandwidth); /* Create new AVStreams for each stream in this variant */ for (j = 0; j < v->ctx->nb_streams; j++) { - AVStream *st = av_new_stream(s, i); + AVStream *st = avformat_new_stream(s, NULL); if (!st) { ret = AVERROR(ENOMEM); goto fail; } + st->id = i; avcodec_copy_context(st->codec, v->ctx->streams[j]->codec); + if (v->bandwidth) + av_dict_set(&st->metadata, "variant_bitrate", bitrate_str, + 0); } stream_offset += v->ctx->nb_streams; } - c->last_packet_dts = AV_NOPTS_VALUE; - c->cur_seq_no = c->max_start_seq; - /* If this is a live stream with more than 3 segments, start at the - * third last segment. */ - if (!c->variants[0]->finished && c->min_end_seq - c->max_start_seq > 3) - c->cur_seq_no = c->min_end_seq - 2; + c->first_packet = 1; + c->first_timestamp = AV_NOPTS_VALUE; return 0; fail: @@ -324,100 +543,66 @@ fail: return ret; } -static int open_variant(AppleHTTPContext *c, struct variant *var, int skip) +static int recheck_discard_flags(AVFormatContext *s, int first) { - int ret; + AppleHTTPContext *c = s->priv_data; + int i, changed = 0; - if (c->cur_seq_no < var->start_seq_no) { - av_log(NULL, AV_LOG_WARNING, - "seq %d not available in variant %s, skipping\n", - var->start_seq_no, var->url); - return 0; + /* Check if any new streams are needed */ + for (i = 0; i < c->n_variants; i++) + c->variants[i]->cur_needed = 0;; + + for (i = 0; i < s->nb_streams; i++) { + AVStream *st = s->streams[i]; + struct variant *var = c->variants[s->streams[i]->id]; + if (st->discard < AVDISCARD_ALL) + var->cur_needed = 1; } - if (c->cur_seq_no - var->start_seq_no >= var->n_segments) - return c->variants[0]->finished ? AVERROR_EOF : 0; - ret = avio_open(&var->pb, - var->segments[c->cur_seq_no - var->start_seq_no]->url, - URL_RDONLY); - if (ret < 0) - return ret; - var->ctx->pb = var->pb; - /* If this is a new segment in parallel with another one already opened, - * skip ahead so they're all at the same dts. */ - if (skip && c->last_packet_dts != AV_NOPTS_VALUE) { - while (1) { - ret = av_read_frame(var->ctx, &var->pkt); - if (ret < 0) { - if (ret == AVERROR_EOF) { - reset_packet(&var->pkt); - return 0; - } - return ret; - } - if (var->pkt.dts >= c->last_packet_dts) - break; - av_free_packet(&var->pkt); + for (i = 0; i < c->n_variants; i++) { + struct variant *v = c->variants[i]; + if (v->cur_needed && !v->needed) { + v->needed = 1; + changed = 1; + v->cur_seq_no = c->cur_seq_no; + v->pb.eof_reached = 0; + av_log(s, AV_LOG_INFO, "Now receiving variant %d\n", i); + } else if (first && !v->cur_needed && v->needed) { + if (v->input) + ffurl_close(v->input); + v->input = NULL; + v->needed = 0; + changed = 1; + av_log(s, AV_LOG_INFO, "No longer receiving variant %d\n", i); } } - return 0; + return changed; } static int applehttp_read_packet(AVFormatContext *s, AVPacket *pkt) { AppleHTTPContext *c = s->priv_data; - int ret, i, minvariant = -1, first = 1, needed = 0, changed = 0, - variants = 0; + int ret, i, minvariant = -1; - /* Recheck the discard flags - which streams are desired at the moment */ - for (i = 0; i < c->n_variants; i++) - c->variants[i]->needed = 0; - for (i = 0; i < s->nb_streams; i++) { - AVStream *st = s->streams[i]; - struct variant *var = c->variants[s->streams[i]->id]; - if (st->discard < AVDISCARD_ALL) { - var->needed = 1; - needed++; - } - /* Copy the discard flag to the chained demuxer, to indicate which - * streams are desired. */ - var->ctx->streams[i - var->stream_offset]->discard = st->discard; + if (c->first_packet) { + recheck_discard_flags(s, 1); + c->first_packet = 0; } - if (!needed) - return AVERROR_EOF; + start: + c->end_of_segment = 0; for (i = 0; i < c->n_variants; i++) { struct variant *var = c->variants[i]; - /* Close unneeded streams, open newly requested streams */ - if (var->pb && !var->needed) { - av_log(s, AV_LOG_DEBUG, - "Closing variant stream %d, no longer needed\n", i); - av_free_packet(&var->pkt); - reset_packet(&var->pkt); - avio_close(var->pb); - var->pb = NULL; - changed = 1; - } else if (!var->pb && var->needed) { - if (first) - av_log(s, AV_LOG_DEBUG, "Opening variant stream %d\n", i); - if (first && !var->finished) - if ((ret = parse_playlist(c, var->url, var, NULL)) < 0) - return ret; - ret = open_variant(c, var, first); - if (ret < 0) - return ret; - changed = 1; - } - /* Count the number of open variants */ - if (var->pb) - variants++; /* Make sure we've got one buffered packet from each open variant * stream */ - if (var->pb && !var->pkt.data) { + if (var->needed && !var->pkt.data) { ret = av_read_frame(var->ctx, &var->pkt); if (ret < 0) { - if (!var->pb->eof_reached) + if (!var->pb.eof_reached) return ret; reset_packet(&var->pkt); + } else { + if (c->first_timestamp == AV_NOPTS_VALUE) + c->first_timestamp = var->pkt.dts; } } /* Check if this stream has the packet with the lowest dts */ @@ -427,71 +612,18 @@ start: minvariant = i; } } - if (first && changed) - av_log(s, AV_LOG_INFO, "Receiving %d variant streams\n", variants); + if (c->end_of_segment) { + if (recheck_discard_flags(s, 0)) + goto start; + } /* If we got a packet, return it */ if (minvariant >= 0) { *pkt = c->variants[minvariant]->pkt; pkt->stream_index += c->variants[minvariant]->stream_offset; reset_packet(&c->variants[minvariant]->pkt); - c->last_packet_dts = pkt->dts; return 0; } - /* No more packets - eof reached in all variant streams, close the - * current segments. */ - for (i = 0; i < c->n_variants; i++) { - struct variant *var = c->variants[i]; - if (var->pb) { - avio_close(var->pb); - var->pb = NULL; - } - } - /* Indicate that we're opening the next segment, not opening a new - * variant stream in parallel, so we shouldn't try to skip ahead. */ - first = 0; - c->cur_seq_no++; -reload: - if (!c->variants[0]->finished) { - /* If this is a live stream and target_duration has elapsed since - * the last playlist reload, reload the variant playlists now. */ - int64_t now = av_gettime(); - if (now - c->last_load_time >= c->variants[0]->target_duration*1000000) { - c->max_start_seq = 0; - c->min_end_seq = INT_MAX; - for (i = 0; i < c->n_variants; i++) { - struct variant *var = c->variants[i]; - if (var->needed) { - if ((ret = parse_playlist(c, var->url, var, NULL)) < 0) - return ret; - c->max_start_seq = FFMAX(c->max_start_seq, - var->start_seq_no); - c->min_end_seq = FFMIN(c->min_end_seq, - var->start_seq_no + var->n_segments); - } - } - } - } - if (c->cur_seq_no < c->max_start_seq) { - av_log(NULL, AV_LOG_WARNING, - "skipping %d segments ahead, expired from playlists\n", - c->max_start_seq - c->cur_seq_no); - c->cur_seq_no = c->max_start_seq; - } - /* If more segments exist, open the next one */ - if (c->cur_seq_no < c->min_end_seq) - goto start; - /* We've reached the end of the playlists - return eof if this is a - * non-live stream, wait until the next playlist reload if it is live. */ - if (c->variants[0]->finished) - return AVERROR_EOF; - while (av_gettime() - c->last_load_time < - c->variants[0]->target_duration*1000000) { - if (url_interrupt_cb()) - return AVERROR_EXIT; - usleep(100*1000); - } - /* Enough time has elapsed since the last reload */ - goto reload; + return AVERROR_EOF; } static int applehttp_close(AVFormatContext *s) @@ -506,38 +638,43 @@ static int applehttp_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) { AppleHTTPContext *c = s->priv_data; - int64_t pos = 0; - int i; - struct variant *var = c->variants[0]; + int i, j, ret; if ((flags & AVSEEK_FLAG_BYTE) || !c->variants[0]->finished) return AVERROR(ENOSYS); - /* Reset the variants */ - c->last_packet_dts = AV_NOPTS_VALUE; + timestamp = av_rescale_rnd(timestamp, 1, stream_index >= 0 ? + s->streams[stream_index]->time_base.den : + AV_TIME_BASE, flags & AVSEEK_FLAG_BACKWARD ? + AV_ROUND_DOWN : AV_ROUND_UP); + ret = AVERROR(EIO); for (i = 0; i < c->n_variants; i++) { + /* Reset reading */ struct variant *var = c->variants[i]; - if (var->pb) { - avio_close(var->pb); - var->pb = NULL; + int64_t pos = c->first_timestamp == AV_NOPTS_VALUE ? 0 : + av_rescale_rnd(c->first_timestamp, 1, + stream_index >= 0 ? s->streams[stream_index]->time_base.den : AV_TIME_BASE, + flags & AVSEEK_FLAG_BACKWARD ? AV_ROUND_DOWN : AV_ROUND_UP); + if (var->input) { + ffurl_close(var->input); + var->input = NULL; } av_free_packet(&var->pkt); reset_packet(&var->pkt); - } - - timestamp = av_rescale_rnd(timestamp, 1, stream_index >= 0 ? - s->streams[stream_index]->time_base.den : - AV_TIME_BASE, flags & AVSEEK_FLAG_BACKWARD ? - AV_ROUND_DOWN : AV_ROUND_UP); - /* Locate the segment that contains the target timestamp */ - for (i = 0; i < var->n_segments; i++) { - if (timestamp >= pos && timestamp < pos + var->segments[i]->duration) { - c->cur_seq_no = var->start_seq_no + i; - return 0; + var->pb.eof_reached = 0; + + /* Locate the segment that contains the target timestamp */ + for (j = 0; j < var->n_segments; j++) { + if (timestamp >= pos && + timestamp < pos + var->segments[j]->duration) { + var->cur_seq_no = var->start_seq_no + j; + ret = 0; + break; + } + pos += var->segments[j]->duration; } - pos += var->segments[i]->duration; } - return AVERROR(EIO); + return ret; } static int applehttp_probe(AVProbeData *p) @@ -554,12 +691,12 @@ static int applehttp_probe(AVProbeData *p) } AVInputFormat ff_applehttp_demuxer = { - "applehttp", - NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming format"), - sizeof(AppleHTTPContext), - applehttp_probe, - applehttp_read_header, - applehttp_read_packet, - applehttp_close, - applehttp_read_seek, + .name = "applehttp", + .long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming format"), + .priv_data_size = sizeof(AppleHTTPContext), + .read_probe = applehttp_probe, + .read_header = applehttp_read_header, + .read_packet = applehttp_read_packet, + .read_close = applehttp_close, + .read_seek = applehttp_read_seek, };