diff options
author | Michael Niedermayer <michaelni@gmx.at> | 2012-02-15 01:38:34 +0100 |
---|---|---|
committer | Michael Niedermayer <michaelni@gmx.at> | 2012-02-15 01:52:14 +0100 |
commit | c980be9e3aafad57ec769cbb16c9ad6e640479cb (patch) | |
tree | a804c9e2517a2e8ec9e5f22890862cbb226a66da /libavformat/hlsproto.c | |
parent | e7dbfa59f218ece7ec65f7ea0ff2950573dd2267 (diff) | |
parent | dc4e57489fa0f9cf4faf4c85cc405d6db77d84db (diff) | |
download | ffmpeg-c980be9e3aafad57ec769cbb16c9ad6e640479cb.tar.gz |
Merge remote-tracking branch 'qatar/master'
* qatar/master: (21 commits)
CDXL demuxer and decoder
hls: Re-add legacy applehttp name to preserve interface compatibility.
hlsproto: Rename the functions and context
hlsproto: Encourage users to try the hls demuxer instead of the proto
doc: Move the hls protocol section into the right place
libavformat: Rename the applehttp protocol to hls
hls: Rename the functions and context
libavformat: Rename the applehttp demuxer to hls
rtpdec: Support H263 in RFC 2190 format
rv30: check block type validity
ttadec: CRC checking
movenc: Support muxing VC1
avconv: Don't split out inline sequence headers when stream copying VC1
rv34: handle size changes during frame multithreading
rv40: prevent undefined signed overflow in rv40_loop_filter()
rv34: use AVERROR return values in ff_rv34_decode_frame()
rv34: use uint16_t for RV34DecContext.deblock_coefs
librtmp: Add "lib" prefix to librtmp URLProtocol declarations.
movenc: Use defines instead of hardcoded numbers for RTCP types
smjpegdec: implement seeking
...
Conflicts:
Changelog
doc/general.texi
libavcodec/avcodec.h
libavcodec/rv30.c
libavcodec/tta.c
libavcodec/version.h
libavformat/Makefile
libavformat/allformats.c
libavformat/version.h
libswscale/x86/swscale_mmx.c
Merged-by: Michael Niedermayer <michaelni@gmx.at>
Diffstat (limited to 'libavformat/hlsproto.c')
-rw-r--r-- | libavformat/hlsproto.c | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/libavformat/hlsproto.c b/libavformat/hlsproto.c new file mode 100644 index 0000000000..8c25689f91 --- /dev/null +++ b/libavformat/hlsproto.c @@ -0,0 +1,347 @@ +/* + * Apple HTTP Live Streaming Protocol Handler + * Copyright (c) 2010 Martin Storsjo + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * Apple HTTP Live Streaming Protocol Handler + * http://tools.ietf.org/html/draft-pantos-http-live-streaming + */ + +#include "libavutil/avstring.h" +#include "avformat.h" +#include "internal.h" +#include "url.h" +#include "version.h" +#include <unistd.h> + +/* + * An apple http stream consists of a playlist with media segment files, + * played sequentially. There may be several playlists with the same + * video content, in different bandwidth variants, that are played in + * parallel (preferrably only one bandwidth variant at a time). In this case, + * the user supplied the url to a main playlist that only lists the variant + * playlists. + * + * If the main playlist doesn't point at any variants, we still create + * one anonymous toplevel variant for this, to maintain the structure. + */ + +struct segment { + int duration; + char url[MAX_URL_SIZE]; +}; + +struct variant { + int bandwidth; + char url[MAX_URL_SIZE]; +}; + +typedef struct HLSContext { + char playlisturl[MAX_URL_SIZE]; + int target_duration; + int start_seq_no; + int finished; + int n_segments; + struct segment **segments; + int n_variants; + struct variant **variants; + int cur_seq_no; + URLContext *seg_hd; + int64_t last_load_time; +} HLSContext; + +static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) +{ + int len = ff_get_line(s, buf, maxlen); + while (len > 0 && isspace(buf[len - 1])) + buf[--len] = '\0'; + return len; +} + +static void free_segment_list(HLSContext *s) +{ + int i; + for (i = 0; i < s->n_segments; i++) + av_free(s->segments[i]); + av_freep(&s->segments); + s->n_segments = 0; +} + +static void free_variant_list(HLSContext *s) +{ + int i; + for (i = 0; i < s->n_variants; i++) + av_free(s->variants[i]); + av_freep(&s->variants); + s->n_variants = 0; +} + +struct variant_info { + char bandwidth[20]; +}; + +static void handle_variant_args(struct variant_info *info, const char *key, + int key_len, char **dest, int *dest_len) +{ + if (!strncmp(key, "BANDWIDTH=", key_len)) { + *dest = info->bandwidth; + *dest_len = sizeof(info->bandwidth); + } +} + +static int parse_playlist(URLContext *h, const char *url) +{ + HLSContext *s = h->priv_data; + AVIOContext *in; + int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0; + char line[1024]; + const char *ptr; + + if ((ret = avio_open2(&in, url, AVIO_FLAG_READ, + &h->interrupt_callback, NULL)) < 0) + return ret; + + read_chomp_line(in, line, sizeof(line)); + if (strcmp(line, "#EXTM3U")) + return AVERROR_INVALIDDATA; + + free_segment_list(s); + s->finished = 0; + while (!url_feof(in)) { + read_chomp_line(in, line, sizeof(line)); + if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) { + struct variant_info info = {{0}}; + is_variant = 1; + 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-TARGETDURATION:", &ptr)) { + s->target_duration = atoi(ptr); + } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { + s->start_seq_no = atoi(ptr); + } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) { + s->finished = 1; + } else if (av_strstart(line, "#EXTINF:", &ptr)) { + is_segment = 1; + duration = atoi(ptr); + } else if (av_strstart(line, "#", NULL)) { + continue; + } else if (line[0]) { + if (is_segment) { + struct segment *seg = av_malloc(sizeof(struct segment)); + if (!seg) { + ret = AVERROR(ENOMEM); + goto fail; + } + seg->duration = duration; + ff_make_absolute_url(seg->url, sizeof(seg->url), url, line); + dynarray_add(&s->segments, &s->n_segments, seg); + is_segment = 0; + } else if (is_variant) { + struct variant *var = av_malloc(sizeof(struct variant)); + if (!var) { + ret = AVERROR(ENOMEM); + goto fail; + } + var->bandwidth = bandwidth; + ff_make_absolute_url(var->url, sizeof(var->url), url, line); + dynarray_add(&s->variants, &s->n_variants, var); + is_variant = 0; + } + } + } + s->last_load_time = av_gettime(); + +fail: + avio_close(in); + return ret; +} + +static int hls_close(URLContext *h) +{ + HLSContext *s = h->priv_data; + + free_segment_list(s); + free_variant_list(s); + ffurl_close(s->seg_hd); + return 0; +} + +static int hls_open(URLContext *h, const char *uri, int flags) +{ + HLSContext *s = h->priv_data; + int ret, i; + const char *nested_url; + + if (flags & AVIO_FLAG_WRITE) + return AVERROR(ENOSYS); + + h->is_streamed = 1; + + if (av_strstart(uri, "hls+", &nested_url)) { + av_strlcpy(s->playlisturl, nested_url, sizeof(s->playlisturl)); + } else if (av_strstart(uri, "hls://", &nested_url)) { + av_log(h, AV_LOG_ERROR, + "No nested protocol specified. Specify e.g. hls+http://%s\n", + nested_url); + ret = AVERROR(EINVAL); + goto fail; +#if FF_API_APPLEHTTP_PROTO + } else if (av_strstart(uri, "applehttp+", &nested_url)) { + av_strlcpy(s->playlisturl, nested_url, sizeof(s->playlisturl)); + av_log(h, AV_LOG_WARNING, + "The applehttp protocol is deprecated, use hls+%s as url " + "instead.\n", nested_url); + } else if (av_strstart(uri, "applehttp://", &nested_url)) { + av_strlcpy(s->playlisturl, "http://", sizeof(s->playlisturl)); + av_strlcat(s->playlisturl, nested_url, sizeof(s->playlisturl)); + av_log(h, AV_LOG_WARNING, + "The applehttp protocol is deprecated, use hls+http://%s as url " + "instead.\n", nested_url); +#endif + } else { + av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri); + ret = AVERROR(EINVAL); + goto fail; + } + av_log(h, AV_LOG_WARNING, + "Using the hls protocol is discouraged, please try using the " + "hls demuxer instead. The hls demuxer should be more complete " + "and work as well as the protocol implementation. (If not, " + "please report it.) To use the demuxer, simply use %s as url.\n", + s->playlisturl); + + if ((ret = parse_playlist(h, s->playlisturl)) < 0) + goto fail; + + if (s->n_segments == 0 && s->n_variants > 0) { + int max_bandwidth = 0, maxvar = -1; + for (i = 0; i < s->n_variants; i++) { + if (s->variants[i]->bandwidth > max_bandwidth || i == 0) { + max_bandwidth = s->variants[i]->bandwidth; + maxvar = i; + } + } + av_strlcpy(s->playlisturl, s->variants[maxvar]->url, + sizeof(s->playlisturl)); + if ((ret = parse_playlist(h, s->playlisturl)) < 0) + goto fail; + } + + if (s->n_segments == 0) { + av_log(h, AV_LOG_WARNING, "Empty playlist\n"); + ret = AVERROR(EIO); + goto fail; + } + s->cur_seq_no = s->start_seq_no; + if (!s->finished && s->n_segments >= 3) + s->cur_seq_no = s->start_seq_no + s->n_segments - 3; + + return 0; + +fail: + hls_close(h); + return ret; +} + +static int hls_read(URLContext *h, uint8_t *buf, int size) +{ + HLSContext *s = h->priv_data; + const char *url; + int ret; + int64_t reload_interval; + +start: + if (s->seg_hd) { + ret = ffurl_read(s->seg_hd, buf, size); + if (ret > 0) + return ret; + } + if (s->seg_hd) { + ffurl_close(s->seg_hd); + s->seg_hd = NULL; + s->cur_seq_no++; + } + reload_interval = s->n_segments > 0 ? + s->segments[s->n_segments - 1]->duration : + s->target_duration; + reload_interval *= 1000000; +retry: + if (!s->finished) { + int64_t now = av_gettime(); + if (now - s->last_load_time >= reload_interval) { + if ((ret = parse_playlist(h, s->playlisturl)) < 0) + return ret; + /* If we need to reload the playlist again below (if + * there's still no more segments), switch to a reload + * interval of half the target duration. */ + reload_interval = s->target_duration * 500000; + } + } + if (s->cur_seq_no < s->start_seq_no) { + av_log(h, AV_LOG_WARNING, + "skipping %d segments ahead, expired from playlist\n", + s->start_seq_no - s->cur_seq_no); + s->cur_seq_no = s->start_seq_no; + } + if (s->cur_seq_no - s->start_seq_no >= s->n_segments) { + if (s->finished) + return AVERROR_EOF; + while (av_gettime() - s->last_load_time < reload_interval) { + if (ff_check_interrupt(&h->interrupt_callback)) + return AVERROR_EXIT; + usleep(100*1000); + } + goto retry; + } + url = s->segments[s->cur_seq_no - s->start_seq_no]->url, + av_log(h, AV_LOG_DEBUG, "opening %s\n", url); + ret = ffurl_open(&s->seg_hd, url, AVIO_FLAG_READ, + &h->interrupt_callback, NULL); + if (ret < 0) { + if (ff_check_interrupt(&h->interrupt_callback)) + return AVERROR_EXIT; + av_log(h, AV_LOG_WARNING, "Unable to open %s\n", url); + s->cur_seq_no++; + goto retry; + } + goto start; +} + +#if FF_API_APPLEHTTP_PROTO +URLProtocol ff_applehttp_protocol = { + .name = "applehttp", + .url_open = hls_open, + .url_read = hls_read, + .url_close = hls_close, + .flags = URL_PROTOCOL_FLAG_NESTED_SCHEME, + .priv_data_size = sizeof(HLSContext), +}; +#endif + +URLProtocol ff_hls_protocol = { + .name = "hls", + .url_open = hls_open, + .url_read = hls_read, + .url_close = hls_close, + .flags = URL_PROTOCOL_FLAG_NESTED_SCHEME, + .priv_data_size = sizeof(HLSContext), +}; |