Libav 0.7.1
|
00001 /* 00002 * Apple HTTP Live Streaming Protocol Handler 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 "avformat.h" 00030 #include "internal.h" 00031 #include "url.h" 00032 #include <unistd.h> 00033 00034 /* 00035 * An apple http stream consists of a playlist with media segment files, 00036 * played sequentially. There may be several playlists with the same 00037 * video content, in different bandwidth variants, that are played in 00038 * parallel (preferrably only one bandwidth variant at a time). In this case, 00039 * the user supplied the url to a main playlist that only lists the variant 00040 * playlists. 00041 * 00042 * If the main playlist doesn't point at any variants, we still create 00043 * one anonymous toplevel variant for this, to maintain the structure. 00044 */ 00045 00046 struct segment { 00047 int duration; 00048 char url[MAX_URL_SIZE]; 00049 }; 00050 00051 struct variant { 00052 int bandwidth; 00053 char url[MAX_URL_SIZE]; 00054 }; 00055 00056 typedef struct AppleHTTPContext { 00057 char playlisturl[MAX_URL_SIZE]; 00058 int target_duration; 00059 int start_seq_no; 00060 int finished; 00061 int n_segments; 00062 struct segment **segments; 00063 int n_variants; 00064 struct variant **variants; 00065 int cur_seq_no; 00066 URLContext *seg_hd; 00067 int64_t last_load_time; 00068 } AppleHTTPContext; 00069 00070 static int read_chomp_line(AVIOContext *s, char *buf, int maxlen) 00071 { 00072 int len = ff_get_line(s, buf, maxlen); 00073 while (len > 0 && isspace(buf[len - 1])) 00074 buf[--len] = '\0'; 00075 return len; 00076 } 00077 00078 static void free_segment_list(AppleHTTPContext *s) 00079 { 00080 int i; 00081 for (i = 0; i < s->n_segments; i++) 00082 av_free(s->segments[i]); 00083 av_freep(&s->segments); 00084 s->n_segments = 0; 00085 } 00086 00087 static void free_variant_list(AppleHTTPContext *s) 00088 { 00089 int i; 00090 for (i = 0; i < s->n_variants; i++) 00091 av_free(s->variants[i]); 00092 av_freep(&s->variants); 00093 s->n_variants = 0; 00094 } 00095 00096 struct variant_info { 00097 char bandwidth[20]; 00098 }; 00099 00100 static void handle_variant_args(struct variant_info *info, const char *key, 00101 int key_len, char **dest, int *dest_len) 00102 { 00103 if (!strncmp(key, "BANDWIDTH=", key_len)) { 00104 *dest = info->bandwidth; 00105 *dest_len = sizeof(info->bandwidth); 00106 } 00107 } 00108 00109 static int parse_playlist(URLContext *h, const char *url) 00110 { 00111 AppleHTTPContext *s = h->priv_data; 00112 AVIOContext *in; 00113 int ret = 0, duration = 0, is_segment = 0, is_variant = 0, bandwidth = 0; 00114 char line[1024]; 00115 const char *ptr; 00116 00117 if ((ret = avio_open(&in, url, AVIO_FLAG_READ)) < 0) 00118 return ret; 00119 00120 read_chomp_line(in, line, sizeof(line)); 00121 if (strcmp(line, "#EXTM3U")) 00122 return AVERROR_INVALIDDATA; 00123 00124 free_segment_list(s); 00125 s->finished = 0; 00126 while (!in->eof_reached) { 00127 read_chomp_line(in, line, sizeof(line)); 00128 if (av_strstart(line, "#EXT-X-STREAM-INF:", &ptr)) { 00129 struct variant_info info = {{0}}; 00130 is_variant = 1; 00131 ff_parse_key_value(ptr, (ff_parse_key_val_cb) handle_variant_args, 00132 &info); 00133 bandwidth = atoi(info.bandwidth); 00134 } else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) { 00135 s->target_duration = atoi(ptr); 00136 } else if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { 00137 s->start_seq_no = atoi(ptr); 00138 } else if (av_strstart(line, "#EXT-X-ENDLIST", &ptr)) { 00139 s->finished = 1; 00140 } else if (av_strstart(line, "#EXTINF:", &ptr)) { 00141 is_segment = 1; 00142 duration = atoi(ptr); 00143 } else if (av_strstart(line, "#", NULL)) { 00144 continue; 00145 } else if (line[0]) { 00146 if (is_segment) { 00147 struct segment *seg = av_malloc(sizeof(struct segment)); 00148 if (!seg) { 00149 ret = AVERROR(ENOMEM); 00150 goto fail; 00151 } 00152 seg->duration = duration; 00153 ff_make_absolute_url(seg->url, sizeof(seg->url), url, line); 00154 dynarray_add(&s->segments, &s->n_segments, seg); 00155 is_segment = 0; 00156 } else if (is_variant) { 00157 struct variant *var = av_malloc(sizeof(struct variant)); 00158 if (!var) { 00159 ret = AVERROR(ENOMEM); 00160 goto fail; 00161 } 00162 var->bandwidth = bandwidth; 00163 ff_make_absolute_url(var->url, sizeof(var->url), url, line); 00164 dynarray_add(&s->variants, &s->n_variants, var); 00165 is_variant = 0; 00166 } 00167 } 00168 } 00169 s->last_load_time = av_gettime(); 00170 00171 fail: 00172 avio_close(in); 00173 return ret; 00174 } 00175 00176 static int applehttp_open(URLContext *h, const char *uri, int flags) 00177 { 00178 AppleHTTPContext *s; 00179 int ret, i; 00180 const char *nested_url; 00181 00182 if (flags & AVIO_FLAG_WRITE) 00183 return AVERROR(ENOSYS); 00184 00185 s = av_mallocz(sizeof(AppleHTTPContext)); 00186 if (!s) 00187 return AVERROR(ENOMEM); 00188 h->priv_data = s; 00189 h->is_streamed = 1; 00190 00191 if (av_strstart(uri, "applehttp+", &nested_url)) { 00192 av_strlcpy(s->playlisturl, nested_url, sizeof(s->playlisturl)); 00193 } else if (av_strstart(uri, "applehttp://", &nested_url)) { 00194 av_strlcpy(s->playlisturl, "http://", sizeof(s->playlisturl)); 00195 av_strlcat(s->playlisturl, nested_url, sizeof(s->playlisturl)); 00196 } else { 00197 av_log(h, AV_LOG_ERROR, "Unsupported url %s\n", uri); 00198 ret = AVERROR(EINVAL); 00199 goto fail; 00200 } 00201 00202 if ((ret = parse_playlist(h, s->playlisturl)) < 0) 00203 goto fail; 00204 00205 if (s->n_segments == 0 && s->n_variants > 0) { 00206 int max_bandwidth = 0, maxvar = -1; 00207 for (i = 0; i < s->n_variants; i++) { 00208 if (s->variants[i]->bandwidth > max_bandwidth || i == 0) { 00209 max_bandwidth = s->variants[i]->bandwidth; 00210 maxvar = i; 00211 } 00212 } 00213 av_strlcpy(s->playlisturl, s->variants[maxvar]->url, 00214 sizeof(s->playlisturl)); 00215 if ((ret = parse_playlist(h, s->playlisturl)) < 0) 00216 goto fail; 00217 } 00218 00219 if (s->n_segments == 0) { 00220 av_log(h, AV_LOG_WARNING, "Empty playlist\n"); 00221 ret = AVERROR(EIO); 00222 goto fail; 00223 } 00224 s->cur_seq_no = s->start_seq_no; 00225 if (!s->finished && s->n_segments >= 3) 00226 s->cur_seq_no = s->start_seq_no + s->n_segments - 3; 00227 00228 return 0; 00229 00230 fail: 00231 av_free(s); 00232 return ret; 00233 } 00234 00235 static int applehttp_read(URLContext *h, uint8_t *buf, int size) 00236 { 00237 AppleHTTPContext *s = h->priv_data; 00238 const char *url; 00239 int ret; 00240 00241 start: 00242 if (s->seg_hd) { 00243 ret = ffurl_read(s->seg_hd, buf, size); 00244 if (ret > 0) 00245 return ret; 00246 } 00247 if (s->seg_hd) { 00248 ffurl_close(s->seg_hd); 00249 s->seg_hd = NULL; 00250 s->cur_seq_no++; 00251 } 00252 retry: 00253 if (!s->finished) { 00254 int64_t now = av_gettime(); 00255 if (now - s->last_load_time >= s->target_duration*1000000) 00256 if ((ret = parse_playlist(h, s->playlisturl)) < 0) 00257 return ret; 00258 } 00259 if (s->cur_seq_no < s->start_seq_no) { 00260 av_log(h, AV_LOG_WARNING, 00261 "skipping %d segments ahead, expired from playlist\n", 00262 s->start_seq_no - s->cur_seq_no); 00263 s->cur_seq_no = s->start_seq_no; 00264 } 00265 if (s->cur_seq_no - s->start_seq_no >= s->n_segments) { 00266 if (s->finished) 00267 return AVERROR_EOF; 00268 while (av_gettime() - s->last_load_time < s->target_duration*1000000) { 00269 if (url_interrupt_cb()) 00270 return AVERROR_EXIT; 00271 usleep(100*1000); 00272 } 00273 goto retry; 00274 } 00275 url = s->segments[s->cur_seq_no - s->start_seq_no]->url, 00276 av_log(h, AV_LOG_DEBUG, "opening %s\n", url); 00277 ret = ffurl_open(&s->seg_hd, url, AVIO_FLAG_READ); 00278 if (ret < 0) { 00279 if (url_interrupt_cb()) 00280 return AVERROR_EXIT; 00281 av_log(h, AV_LOG_WARNING, "Unable to open %s\n", url); 00282 s->cur_seq_no++; 00283 goto retry; 00284 } 00285 goto start; 00286 } 00287 00288 static int applehttp_close(URLContext *h) 00289 { 00290 AppleHTTPContext *s = h->priv_data; 00291 00292 free_segment_list(s); 00293 free_variant_list(s); 00294 ffurl_close(s->seg_hd); 00295 av_free(s); 00296 return 0; 00297 } 00298 00299 URLProtocol ff_applehttp_protocol = { 00300 .name = "applehttp", 00301 .url_open = applehttp_open, 00302 .url_read = applehttp_read, 00303 .url_close = applehttp_close, 00304 .flags = URL_PROTOCOL_FLAG_NESTED_SCHEME, 00305 };