1/* 2 * Apple HTTP Live Streaming Protocol Handler 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 Protocol Handler 25 * http://tools.ietf.org/html/draft-pantos-http-live-streaming 26 */ 27 28#include "libavutil/avstring.h" 29#include "avformat.h" 30#include "internal.h" 31#include "url.h" 32#include <unistd.h> 33 34/* 35 * An apple http stream consists of a playlist with media segment files, 36 * played sequentially. There may be several playlists with the same 37 * video content, in different bandwidth variants, that are played in 38 * parallel (preferrably only one bandwidth variant at a time). In this case, 39 * the user supplied the url to a main playlist that only lists the variant 40 * playlists. 41 * 42 * If the main playlist doesn't point at any variants, we still create 43 * one anonymous toplevel variant for this, to maintain the structure. 44 */ 45 46struct segment { 47 int duration; 48 char url[MAX_URL_SIZE]; 49}; 50 51struct variant { 52 int bandwidth; 53 char url[MAX_URL_SIZE]; 54}; 55 56typedef struct AppleHTTPContext { 57 char playlisturl[MAX_URL_SIZE]; 58 int target_duration; 59 int start_seq_no; 60 int finished; 61 int n_segments; 62 struct segment **segments; 63 int n_variants; 64 struct variant **variants; 65 int cur_seq_no; 66 URLContext *seg_hd; 67 int64_t last_load_time; 68} AppleHTTPContext; 69 70static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) 71{ 72 int len = ff_get_line(s, buf, maxlen); 73 while (len > 0 && isspace(buf[len - 1])) 74 buf[--len] = '\0'; 75 return len; 76} 77 78static void free_segment_list(AppleHTTPContext *s) 79{ 80 int i; 81 for (i = 0; i < s->n_segments; i++) 82 av_free(s->segments[i]); 83 av_freep(&s->segments); 84 s->n_segments = 0; 85} 86 87static void free_variant_list(AppleHTTPContext *s) 88{ 89 int i; 90 for (i = 0; i < s->n_variants; i++) 91 av_free(s->variants[i]); 92 av_freep(&s->variants); 93 s->n_variants = 0; 94} 95 96struct variant_info { 97 char bandwidth[20]; 98}; 99 100static void handle_variant_args(struct variant_info *info, const char *key, 101 int key_len, char **dest, int *dest_len) 102{ 103 if (!strncmp(key, "BANDWIDTH=", key_len)) { 104 *dest = info->bandwidth; 105 *dest_len = sizeof(info->bandwidth); 106 } 107} 108 109static int parse_playlist(URLContext *h, const char *url) 110{ 111 AppleHTTPContext *s = h->priv_data; 112 AVIOContext *in; 113 int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0; 114 char line[1024]; 115 const char *ptr; 116 117 if ((ret = avio_open2(&in, url, AVIO_FLAG_READ, 118 &h->interrupt_callback, NULL)) < 0) 119 return ret; 120 121 read_chomp_line(in, line, sizeof(line)); 122 if (strcmp(line, "#EXTM3U")) 123 return AVERROR_INVALIDDATA; 124 125 free_segment_list(s); 126 s->finished = 0; 127 while (!in->eof_reached) { 128 read_chomp_line(in, line, sizeof(line)); 129 if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) { 130 struct variant_info info = {{0}}; 131 is_variant = 1; 132 ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args, 133 &info); 134 bandwidth = atoi(info.bandwidth); 135 } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) { 136 s->target_duration = atoi(ptr); 137 } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { 138 s->start_seq_no = atoi(ptr); 139 } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) { 140 s->finished = 1; 141 } else if (av_strstart(line, "#EXTINF:", &ptr)) { 142 is_segment = 1; 143 duration = atoi(ptr); 144 } else if (av_strstart(line, "#", NULL)) { 145 continue; 146 } else if (line[0]) { 147 if (is_segment) { 148 struct segment *seg = av_malloc(sizeof(struct segment)); 149 if (!seg) { 150 ret = AVERROR(ENOMEM); 151 goto fail; 152 } 153 seg->duration = duration; 154 ff_make_absolute_url(seg->url, sizeof(seg->url), url, line); 155 dynarray_add(&s->segments, &s->n_segments, seg); 156 is_segment = 0; 157 } else if (is_variant) { 158 struct variant *var = av_malloc(sizeof(struct variant)); 159 if (!var) { 160 ret = AVERROR(ENOMEM); 161 goto fail; 162 } 163 var->bandwidth = bandwidth; 164 ff_make_absolute_url(var->url, sizeof(var->url), url, line); 165 dynarray_add(&s->variants, &s->n_variants, var); 166 is_variant = 0; 167 } 168 } 169 } 170 s->last_load_time = av_gettime(); 171 172fail: 173 avio_close(in); 174 return ret; 175} 176 177static int applehttp_close(URLContext *h) 178{ 179 AppleHTTPContext *s = h->priv_data; 180 181 free_segment_list(s); 182 free_variant_list(s); 183 ffurl_close(s->seg_hd); 184 return 0; 185} 186 187static int applehttp_open(URLContext *h, const char *uri, int flags) 188{ 189 AppleHTTPContext *s = h->priv_data; 190 int ret, i; 191 const char *nested_url; 192 193 if (flags & AVIO_FLAG_WRITE) 194 return AVERROR(ENOSYS); 195 196 h->is_streamed = 1; 197 198 if (av_strstart(uri, "applehttp+", &nested_url)) { 199 av_strlcpy(s->playlisturl, nested_url, sizeof(s->playlisturl)); 200 } else if (av_strstart(uri, "applehttp://", &nested_url)) { 201 av_strlcpy(s->playlisturl, "http://", sizeof(s->playlisturl)); 202 av_strlcat(s->playlisturl, nested_url, sizeof(s->playlisturl)); 203 } else { 204 av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri); 205 ret = AVERROR(EINVAL); 206 goto fail; 207 } 208 209 if ((ret = parse_playlist(h, s->playlisturl)) < 0) 210 goto fail; 211 212 if (s->n_segments == 0 && s->n_variants > 0) { 213 int max_bandwidth = 0, maxvar = -1; 214 for (i = 0; i < s->n_variants; i++) { 215 if (s->variants[i]->bandwidth > max_bandwidth || i == 0) { 216 max_bandwidth = s->variants[i]->bandwidth; 217 maxvar = i; 218 } 219 } 220 av_strlcpy(s->playlisturl, s->variants[maxvar]->url, 221 sizeof(s->playlisturl)); 222 if ((ret = parse_playlist(h, s->playlisturl)) < 0) 223 goto fail; 224 } 225 226 if (s->n_segments == 0) { 227 av_log(h, AV_LOG_WARNING, "Empty playlist\n"); 228 ret = AVERROR(EIO); 229 goto fail; 230 } 231 s->cur_seq_no = s->start_seq_no; 232 if (!s->finished && s->n_segments >= 3) 233 s->cur_seq_no = s->start_seq_no + s->n_segments - 3; 234 235 return 0; 236 237fail: 238 applehttp_close(h); 239 return ret; 240} 241 242static int applehttp_read(URLContext *h, uint8_t *buf, int size) 243{ 244 AppleHTTPContext *s = h->priv_data; 245 const char *url; 246 int ret; 247 int64_t reload_interval; 248 249start: 250 if (s->seg_hd) { 251 ret = ffurl_read(s->seg_hd, buf, size); 252 if (ret > 0) 253 return ret; 254 } 255 if (s->seg_hd) { 256 ffurl_close(s->seg_hd); 257 s->seg_hd = NULL; 258 s->cur_seq_no++; 259 } 260 reload_interval = s->n_segments > 0 ? 261 s->segments[s->n_segments - 1]->duration : 262 s->target_duration; 263 reload_interval *= 1000000; 264retry: 265 if (!s->finished) { 266 int64_t now = av_gettime(); 267 if (now - s->last_load_time >= reload_interval) { 268 if ((ret = parse_playlist(h, s->playlisturl)) < 0) 269 return ret; 270 /* If we need to reload the playlist again below (if 271 * there's still no more segments), switch to a reload 272 * interval of half the target duration. */ 273 reload_interval = s->target_duration * 500000; 274 } 275 } 276 if (s->cur_seq_no < s->start_seq_no) { 277 av_log(h, AV_LOG_WARNING, 278 "skipping %d segments ahead, expired from playlist\n", 279 s->start_seq_no - s->cur_seq_no); 280 s->cur_seq_no = s->start_seq_no; 281 } 282 if (s->cur_seq_no - s->start_seq_no >= s->n_segments) { 283 if (s->finished) 284 return AVERROR_EOF; 285 while (av_gettime() - s->last_load_time < reload_interval) { 286 if (ff_check_interrupt(&h->interrupt_callback)) 287 return AVERROR_EXIT; 288 usleep(100*1000); 289 } 290 goto retry; 291 } 292 url = s->segments[s->cur_seq_no - s->start_seq_no]->url, 293 av_log(h, AV_LOG_DEBUG, "opening %s\n", url); 294 ret = ffurl_open(&s->seg_hd, url, AVIO_FLAG_READ, 295 &h->interrupt_callback, NULL); 296 if (ret < 0) { 297 if (ff_check_interrupt(&h->interrupt_callback)) 298 return AVERROR_EXIT; 299 av_log(h, AV_LOG_WARNING, "Unable to open %s\n", url); 300 s->cur_seq_no++; 301 goto retry; 302 } 303 goto start; 304} 305 306URLProtocol ff_applehttp_protocol = { 307 .name = "applehttp", 308 .url_open = applehttp_open, 309 .url_read = applehttp_read, 310 .url_close = applehttp_close, 311 .flags = URL_PROTOCOL_FLAG_NESTED_SCHEME, 312 .priv_data_size = sizeof(AppleHTTPContext), 313}; 314