You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

359 lines
10KB

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