PageRenderTime 45ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/Main/GStreamer/Source/gst-plugins-bad/gst/hls/m3u8.c

http://ossbuild.googlecode.com/
C | 508 lines | 391 code | 86 blank | 31 comment | 131 complexity | caaf164fe6a932e620faa433a6a0e6c1 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, GPL-2.0, AGPL-1.0, LGPL-2.1, LGPL-2.0, GPL-3.0, LGPL-3.0, CC-BY-SA-3.0
  1. /* GStreamer
  2. * Copyright (C) 2010 Marc-Andre Lureau <marcandre.lureau@gmail.com>
  3. *
  4. * m3u8.c:
  5. *
  6. * This library is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Library General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 2 of the License, or (at your option) any later version.
  10. *
  11. * This library is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Library General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Library General Public
  17. * License along with this library; if not, write to the
  18. * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  19. * Boston, MA 02111-1307, USA.
  20. */
  21. #include <stdlib.h>
  22. #include <errno.h>
  23. #include <glib.h>
  24. #include "gstfragmented.h"
  25. #include "m3u8.h"
  26. #define GST_CAT_DEFAULT fragmented_debug
  27. static GstM3U8 *gst_m3u8_new (void);
  28. static void gst_m3u8_free (GstM3U8 * m3u8);
  29. static gboolean gst_m3u8_update (GstM3U8 * m3u8, gchar * data,
  30. gboolean * updated);
  31. static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri,
  32. gchar * title, gint duration, guint sequence);
  33. static void gst_m3u8_media_file_free (GstM3U8MediaFile * self);
  34. static GstM3U8 *
  35. gst_m3u8_new (void)
  36. {
  37. GstM3U8 *m3u8;
  38. m3u8 = g_new0 (GstM3U8, 1);
  39. return m3u8;
  40. }
  41. static void
  42. gst_m3u8_set_uri (GstM3U8 * self, gchar * uri)
  43. {
  44. g_return_if_fail (self != NULL);
  45. if (self->uri)
  46. g_free (self->uri);
  47. self->uri = uri;
  48. }
  49. static void
  50. gst_m3u8_free (GstM3U8 * self)
  51. {
  52. g_return_if_fail (self != NULL);
  53. g_free (self->uri);
  54. g_free (self->allowcache);
  55. g_free (self->codecs);
  56. g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
  57. g_list_free (self->files);
  58. g_free (self->last_data);
  59. g_list_foreach (self->lists, (GFunc) gst_m3u8_free, NULL);
  60. g_list_free (self->lists);
  61. g_free (self);
  62. }
  63. static GstM3U8MediaFile *
  64. gst_m3u8_media_file_new (gchar * uri, gchar * title, gint duration,
  65. guint sequence)
  66. {
  67. GstM3U8MediaFile *file;
  68. file = g_new0 (GstM3U8MediaFile, 1);
  69. file->uri = uri;
  70. file->title = title;
  71. file->duration = duration;
  72. file->sequence = sequence;
  73. return file;
  74. }
  75. static void
  76. gst_m3u8_media_file_free (GstM3U8MediaFile * self)
  77. {
  78. g_return_if_fail (self != NULL);
  79. g_free (self->title);
  80. g_free (self->uri);
  81. g_free (self);
  82. }
  83. static gboolean
  84. int_from_string (gchar * ptr, gchar ** endptr, gint * val)
  85. {
  86. gchar *end;
  87. g_return_val_if_fail (ptr != NULL, FALSE);
  88. g_return_val_if_fail (val != NULL, FALSE);
  89. errno = 0;
  90. *val = strtol (ptr, &end, 10);
  91. if ((errno == ERANGE && (*val == LONG_MAX || *val == LONG_MIN))
  92. || (errno != 0 && *val == 0)) {
  93. GST_WARNING ("%s", g_strerror (errno));
  94. return FALSE;
  95. }
  96. if (endptr)
  97. *endptr = end;
  98. return end != ptr;
  99. }
  100. static gboolean
  101. parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
  102. {
  103. gchar *end, *p;
  104. g_return_val_if_fail (ptr != NULL, FALSE);
  105. g_return_val_if_fail (*ptr != NULL, FALSE);
  106. g_return_val_if_fail (a != NULL, FALSE);
  107. g_return_val_if_fail (v != NULL, FALSE);
  108. /* [attribute=value,]* */
  109. *a = *ptr;
  110. end = p = g_utf8_strchr (*ptr, -1, ',');
  111. if (end) {
  112. do {
  113. end = g_utf8_next_char (end);
  114. } while (end && *end == ' ');
  115. *p = '\0';
  116. }
  117. *v = p = g_utf8_strchr (*ptr, -1, '=');
  118. if (*v) {
  119. *v = g_utf8_next_char (*v);
  120. *p = '\0';
  121. } else {
  122. GST_WARNING ("missing = after attribute");
  123. return FALSE;
  124. }
  125. *ptr = end;
  126. return TRUE;
  127. }
  128. static gint
  129. _m3u8_compare_uri (GstM3U8 * a, gchar * uri)
  130. {
  131. g_return_val_if_fail (a != NULL, 0);
  132. g_return_val_if_fail (uri != NULL, 0);
  133. return g_strcmp0 (a->uri, uri);
  134. }
  135. static gint
  136. gst_m3u8_compare_playlist_by_bitrate (gconstpointer a, gconstpointer b)
  137. {
  138. return ((GstM3U8 *) (a))->bandwidth - ((GstM3U8 *) (b))->bandwidth;
  139. }
  140. /*
  141. * @data: a m3u8 playlist text data, taking ownership
  142. */
  143. static gboolean
  144. gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated)
  145. {
  146. gint val, duration;
  147. gchar *title, *end;
  148. // gboolean discontinuity;
  149. GstM3U8 *list;
  150. g_return_val_if_fail (self != NULL, FALSE);
  151. g_return_val_if_fail (data != NULL, FALSE);
  152. g_return_val_if_fail (updated != NULL, FALSE);
  153. *updated = TRUE;
  154. /* check if the data changed since last update */
  155. if (self->last_data && g_str_equal (self->last_data, data)) {
  156. GST_DEBUG ("Playlist is the same as previous one");
  157. *updated = FALSE;
  158. g_free (data);
  159. return TRUE;
  160. }
  161. if (!g_str_has_prefix (data, "#EXTM3U")) {
  162. GST_WARNING ("Data doesn't start with #EXTM3U");
  163. g_free (data);
  164. return FALSE;
  165. }
  166. g_free (self->last_data);
  167. self->last_data = data;
  168. if (self->files) {
  169. g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
  170. g_list_free (self->files);
  171. self->files = NULL;
  172. }
  173. list = NULL;
  174. duration = -1;
  175. title = NULL;
  176. data += 7;
  177. while (TRUE) {
  178. end = g_utf8_strchr (data, -1, '\n'); /* FIXME: support \r\n */
  179. if (end)
  180. *end = '\0';
  181. if (data[0] != '#') {
  182. if (duration < 0 && list == NULL) {
  183. GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data);
  184. goto next_line;
  185. }
  186. if (!gst_uri_is_valid (data)) {
  187. gchar *slash;
  188. if (!self->uri) {
  189. GST_WARNING ("uri not set, can't build a valid uri");
  190. goto next_line;
  191. }
  192. slash = g_utf8_strrchr (self->uri, -1, '/');
  193. if (!slash) {
  194. GST_WARNING ("Can't build a valid uri");
  195. goto next_line;
  196. }
  197. *slash = '\0';
  198. data = g_strdup_printf ("%s/%s", self->uri, data);
  199. *slash = '/';
  200. } else
  201. data = g_strdup (data);
  202. if (list != NULL) {
  203. if (g_list_find_custom (self->lists, data,
  204. (GCompareFunc) _m3u8_compare_uri)) {
  205. GST_DEBUG ("Already have a list with this URI");
  206. gst_m3u8_free (list);
  207. g_free (data);
  208. } else {
  209. gst_m3u8_set_uri (list, data);
  210. self->lists = g_list_append (self->lists, list);
  211. }
  212. list = NULL;
  213. } else {
  214. GstM3U8MediaFile *file;
  215. file =
  216. gst_m3u8_media_file_new (data, title, duration,
  217. self->mediasequence++);
  218. duration = -1;
  219. title = NULL;
  220. self->files = g_list_append (self->files, file);
  221. }
  222. } else if (g_str_has_prefix (data, "#EXT-X-ENDLIST")) {
  223. self->endlist = TRUE;
  224. } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) {
  225. if (int_from_string (data + 15, &data, &val))
  226. self->version = val;
  227. } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:")) {
  228. gchar *v, *a;
  229. if (list != NULL) {
  230. GST_WARNING ("Found a list without a uri..., dropping");
  231. gst_m3u8_free (list);
  232. }
  233. list = gst_m3u8_new ();
  234. data = data + 18;
  235. while (data && parse_attributes (&data, &a, &v)) {
  236. if (g_str_equal (a, "BANDWIDTH")) {
  237. if (!int_from_string (v, NULL, &list->bandwidth))
  238. GST_WARNING ("Error while reading BANDWIDTH");
  239. } else if (g_str_equal (a, "PROGRAM-ID")) {
  240. if (!int_from_string (v, NULL, &list->program_id))
  241. GST_WARNING ("Error while reading PROGRAM-ID");
  242. } else if (g_str_equal (a, "CODECS")) {
  243. g_free (list->codecs);
  244. list->codecs = g_strdup (v);
  245. } else if (g_str_equal (a, "RESOLUTION")) {
  246. if (!int_from_string (v, &v, &list->width))
  247. GST_WARNING ("Error while reading RESOLUTION width");
  248. if (!v || *v != '=') {
  249. GST_WARNING ("Missing height");
  250. } else {
  251. v = g_utf8_next_char (v);
  252. if (!int_from_string (v, NULL, &list->height))
  253. GST_WARNING ("Error while reading RESOLUTION height");
  254. }
  255. }
  256. }
  257. } else if (g_str_has_prefix (data, "#EXT-X-TARGETDURATION:")) {
  258. if (int_from_string (data + 22, &data, &val))
  259. self->targetduration = val;
  260. } else if (g_str_has_prefix (data, "#EXT-X-MEDIA-SEQUENCE:")) {
  261. if (int_from_string (data + 22, &data, &val))
  262. self->mediasequence = val;
  263. } else if (g_str_has_prefix (data, "#EXT-X-DISCONTINUITY")) {
  264. /* discontinuity = TRUE; */
  265. } else if (g_str_has_prefix (data, "#EXT-X-PROGRAM-DATE-TIME:")) {
  266. /* <YYYY-MM-DDThh:mm:ssZ> */
  267. GST_DEBUG ("FIXME parse date");
  268. } else if (g_str_has_prefix (data, "#EXT-X-ALLOW-CACHE:")) {
  269. g_free (self->allowcache);
  270. self->allowcache = g_strdup (data + 19);
  271. } else if (g_str_has_prefix (data, "#EXTINF:")) {
  272. if (!int_from_string (data + 8, &data, &val)) {
  273. GST_WARNING ("Can't read EXTINF duration");
  274. goto next_line;
  275. }
  276. duration = val;
  277. if (duration > self->targetduration)
  278. GST_WARNING ("EXTINF duration > TARGETDURATION");
  279. if (!data || *data != ',')
  280. goto next_line;
  281. data = g_utf8_next_char (data);
  282. if (data != end) {
  283. g_free (title);
  284. title = g_strdup (data);
  285. }
  286. } else {
  287. GST_LOG ("Ignored line: %s", data);
  288. }
  289. next_line:
  290. if (!end)
  291. break;
  292. data = g_utf8_next_char (end); /* skip \n */
  293. }
  294. /* redorder playlists by bitrate */
  295. if (self->lists)
  296. self->lists =
  297. g_list_sort (self->lists,
  298. (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate);
  299. return TRUE;
  300. }
  301. GstM3U8Client *
  302. gst_m3u8_client_new (const gchar * uri)
  303. {
  304. GstM3U8Client *client;
  305. g_return_val_if_fail (uri != NULL, NULL);
  306. client = g_new0 (GstM3U8Client, 1);
  307. client->main = gst_m3u8_new ();
  308. client->current = NULL;
  309. client->sequence = -1;
  310. client->update_failed_count = 0;
  311. gst_m3u8_set_uri (client->main, g_strdup (uri));
  312. return client;
  313. }
  314. void
  315. gst_m3u8_client_free (GstM3U8Client * self)
  316. {
  317. g_return_if_fail (self != NULL);
  318. gst_m3u8_free (self->main);
  319. g_free (self);
  320. }
  321. void
  322. gst_m3u8_client_set_current (GstM3U8Client * self, GstM3U8 * m3u8)
  323. {
  324. g_return_if_fail (self != NULL);
  325. if (m3u8 != self->current) {
  326. self->current = m3u8;
  327. self->update_failed_count = 0;
  328. }
  329. }
  330. gboolean
  331. gst_m3u8_client_update (GstM3U8Client * self, gchar * data)
  332. {
  333. GstM3U8 *m3u8;
  334. gboolean updated = FALSE;
  335. g_return_val_if_fail (self != NULL, FALSE);
  336. m3u8 = self->current ? self->current : self->main;
  337. if (!gst_m3u8_update (m3u8, data, &updated))
  338. return FALSE;
  339. if (!updated) {
  340. self->update_failed_count++;
  341. return FALSE;
  342. }
  343. /* select the first playlist, for now */
  344. if (!self->current) {
  345. if (self->main->lists) {
  346. self->current = g_list_first (self->main->lists)->data;
  347. } else {
  348. self->current = self->main;
  349. }
  350. }
  351. if (m3u8->files && self->sequence == -1) {
  352. self->sequence =
  353. GST_M3U8_MEDIA_FILE (g_list_first (m3u8->files)->data)->sequence;
  354. GST_DEBUG ("Setting first sequence at %d", self->sequence);
  355. }
  356. return TRUE;
  357. }
  358. static gboolean
  359. _find_next (GstM3U8MediaFile * file, GstM3U8Client * client)
  360. {
  361. GST_DEBUG ("Found fragment %d", file->sequence);
  362. if (file->sequence >= client->sequence)
  363. return FALSE;
  364. return TRUE;
  365. }
  366. gboolean
  367. gst_m3u8_client_get_next_fragment (GstM3U8Client * client,
  368. gboolean * discontinuity, const gchar ** uri, GstClockTime * duration)
  369. {
  370. GList *l;
  371. GstM3U8MediaFile *file;
  372. g_return_val_if_fail (client != NULL, FALSE);
  373. g_return_val_if_fail (client->current != NULL, FALSE);
  374. g_return_val_if_fail (discontinuity != NULL, FALSE);
  375. GST_DEBUG ("Looking for fragment %d", client->sequence);
  376. l = g_list_find_custom (client->current->files, client,
  377. (GCompareFunc) _find_next);
  378. if (l == NULL)
  379. return FALSE;
  380. file = GST_M3U8_MEDIA_FILE (l->data);
  381. *discontinuity = client->sequence != file->sequence;
  382. client->sequence = file->sequence + 1;
  383. *uri = file->uri;
  384. *duration = file->duration * GST_SECOND;
  385. return TRUE;
  386. }
  387. static void
  388. _sum_duration (GstM3U8MediaFile * self, GstClockTime * duration)
  389. {
  390. *duration += self->duration;
  391. }
  392. GstClockTime
  393. gst_m3u8_client_get_duration (GstM3U8Client * client)
  394. {
  395. GstClockTime duration = 0;
  396. g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
  397. /* We can only get the duration for on-demand streams */
  398. if (!client->current->endlist)
  399. return GST_CLOCK_TIME_NONE;
  400. g_list_foreach (client->current->files, (GFunc) _sum_duration, &duration);
  401. return duration * GST_SECOND;
  402. }
  403. const gchar *
  404. gst_m3u8_client_get_uri (GstM3U8Client * client)
  405. {
  406. g_return_val_if_fail (client != NULL, NULL);
  407. return client->main->uri;
  408. }
  409. gboolean
  410. gst_m3u8_client_has_variant_playlist (GstM3U8Client * client)
  411. {
  412. g_return_val_if_fail (client != NULL, FALSE);
  413. return client->main->lists != NULL;
  414. }
  415. gboolean
  416. gst_m3u8_client_is_live (GstM3U8Client * client)
  417. {
  418. g_return_val_if_fail (client != NULL, FALSE);
  419. if (!client->current || client->current->endlist)
  420. return FALSE;
  421. return TRUE;
  422. }