~ubuntu-branches/ubuntu/quantal/gst-plugins-bad0.10/quantal-proposed

« back to all changes in this revision

Viewing changes to gst/hls/m3u8.c

  • Committer: Bazaar Package Importer
  • Author(s): Ken VanDine
  • Date: 2011-07-19 14:32:43 UTC
  • mfrom: (18.4.21 sid)
  • Revision ID: james.westby@ubuntu.com-20110719143243-p7pnkh45akfp0ihk
Tags: 0.10.22-2ubuntu1
* Rebased on debian unstable, remaining changes:
  - debian/gstreamer-plugins-bad.install
    * don't include dtmf, liveadder, rtpmux, autoconvert and shm, we include 
      them in -good

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
22
#include <stdlib.h>
 
23
#include <errno.h>
 
24
#include <glib.h>
 
25
 
 
26
#include "gstfragmented.h"
 
27
#include "m3u8.h"
 
28
 
 
29
#define GST_CAT_DEFAULT fragmented_debug
 
30
 
 
31
static GstM3U8 *gst_m3u8_new (void);
 
32
static void gst_m3u8_free (GstM3U8 * m3u8);
 
33
static gboolean gst_m3u8_update (GstM3U8 * m3u8, gchar * data,
 
34
    gboolean * updated);
 
35
static GstM3U8MediaFile *gst_m3u8_media_file_new (gchar * uri,
 
36
    gchar * title, gint duration, guint sequence);
 
37
static void gst_m3u8_media_file_free (GstM3U8MediaFile * self);
 
38
 
 
39
static GstM3U8 *
 
40
gst_m3u8_new (void)
 
41
{
 
42
  GstM3U8 *m3u8;
 
43
 
 
44
  m3u8 = g_new0 (GstM3U8, 1);
 
45
 
 
46
  return m3u8;
 
47
}
 
48
 
 
49
static void
 
50
gst_m3u8_set_uri (GstM3U8 * self, gchar * uri)
 
51
{
 
52
  g_return_if_fail (self != NULL);
 
53
 
 
54
  if (self->uri)
 
55
    g_free (self->uri);
 
56
  self->uri = uri;
 
57
}
 
58
 
 
59
static void
 
60
gst_m3u8_free (GstM3U8 * self)
 
61
{
 
62
  g_return_if_fail (self != NULL);
 
63
 
 
64
  g_free (self->uri);
 
65
  g_free (self->allowcache);
 
66
  g_free (self->codecs);
 
67
 
 
68
  g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
 
69
  g_list_free (self->files);
 
70
 
 
71
  g_free (self->last_data);
 
72
  g_list_foreach (self->lists, (GFunc) gst_m3u8_free, NULL);
 
73
  g_list_free (self->lists);
 
74
 
 
75
  g_free (self);
 
76
}
 
77
 
 
78
static GstM3U8MediaFile *
 
79
gst_m3u8_media_file_new (gchar * uri, gchar * title, gint duration,
 
80
    guint sequence)
 
81
{
 
82
  GstM3U8MediaFile *file;
 
83
 
 
84
  file = g_new0 (GstM3U8MediaFile, 1);
 
85
  file->uri = uri;
 
86
  file->title = title;
 
87
  file->duration = duration;
 
88
  file->sequence = sequence;
 
89
 
 
90
  return file;
 
91
}
 
92
 
 
93
static void
 
94
gst_m3u8_media_file_free (GstM3U8MediaFile * self)
 
95
{
 
96
  g_return_if_fail (self != NULL);
 
97
 
 
98
  g_free (self->title);
 
99
  g_free (self->uri);
 
100
  g_free (self);
 
101
}
 
102
 
 
103
static gboolean
 
104
int_from_string (gchar * ptr, gchar ** endptr, gint * val)
 
105
{
 
106
  gchar *end;
 
107
 
 
108
  g_return_val_if_fail (ptr != NULL, FALSE);
 
109
  g_return_val_if_fail (val != NULL, FALSE);
 
110
 
 
111
  errno = 0;
 
112
  *val = strtol (ptr, &end, 10);
 
113
  if ((errno == ERANGE && (*val == LONG_MAX || *val == LONG_MIN))
 
114
      || (errno != 0 && *val == 0)) {
 
115
    GST_WARNING ("%s", g_strerror (errno));
 
116
    return FALSE;
 
117
  }
 
118
 
 
119
  if (endptr)
 
120
    *endptr = end;
 
121
 
 
122
  return end != ptr;
 
123
}
 
124
 
 
125
static gboolean
 
126
parse_attributes (gchar ** ptr, gchar ** a, gchar ** v)
 
127
{
 
128
  gchar *end, *p;
 
129
 
 
130
  g_return_val_if_fail (ptr != NULL, FALSE);
 
131
  g_return_val_if_fail (*ptr != NULL, FALSE);
 
132
  g_return_val_if_fail (a != NULL, FALSE);
 
133
  g_return_val_if_fail (v != NULL, FALSE);
 
134
 
 
135
  /* [attribute=value,]* */
 
136
 
 
137
  *a = *ptr;
 
138
  end = p = g_utf8_strchr (*ptr, -1, ',');
 
139
  if (end) {
 
140
    do {
 
141
      end = g_utf8_next_char (end);
 
142
    } while (end && *end == ' ');
 
143
    *p = '\0';
 
144
  }
 
145
 
 
146
  *v = p = g_utf8_strchr (*ptr, -1, '=');
 
147
  if (*v) {
 
148
    *v = g_utf8_next_char (*v);
 
149
    *p = '\0';
 
150
  } else {
 
151
    GST_WARNING ("missing = after attribute");
 
152
    return FALSE;
 
153
  }
 
154
 
 
155
  *ptr = end;
 
156
  return TRUE;
 
157
}
 
158
 
 
159
static gint
 
160
_m3u8_compare_uri (GstM3U8 * a, gchar * uri)
 
161
{
 
162
  g_return_val_if_fail (a != NULL, 0);
 
163
  g_return_val_if_fail (uri != NULL, 0);
 
164
 
 
165
  return g_strcmp0 (a->uri, uri);
 
166
}
 
167
 
 
168
static gint
 
169
gst_m3u8_compare_playlist_by_bitrate (gconstpointer a, gconstpointer b)
 
170
{
 
171
  return ((GstM3U8 *) (a))->bandwidth - ((GstM3U8 *) (b))->bandwidth;
 
172
}
 
173
 
 
174
/*
 
175
 * @data: a m3u8 playlist text data, taking ownership
 
176
 */
 
177
static gboolean
 
178
gst_m3u8_update (GstM3U8 * self, gchar * data, gboolean * updated)
 
179
{
 
180
  gint val, duration;
 
181
  gchar *title, *end;
 
182
//  gboolean discontinuity;
 
183
  GstM3U8 *list;
 
184
 
 
185
  g_return_val_if_fail (self != NULL, FALSE);
 
186
  g_return_val_if_fail (data != NULL, FALSE);
 
187
  g_return_val_if_fail (updated != NULL, FALSE);
 
188
 
 
189
  *updated = TRUE;
 
190
 
 
191
  /* check if the data changed since last update */
 
192
  if (self->last_data && g_str_equal (self->last_data, data)) {
 
193
    GST_DEBUG ("Playlist is the same as previous one");
 
194
    *updated = FALSE;
 
195
    g_free (data);
 
196
    return TRUE;
 
197
  }
 
198
 
 
199
  if (!g_str_has_prefix (data, "#EXTM3U")) {
 
200
    GST_WARNING ("Data doesn't start with #EXTM3U");
 
201
    g_free (data);
 
202
    return FALSE;
 
203
  }
 
204
 
 
205
  g_free (self->last_data);
 
206
  self->last_data = data;
 
207
 
 
208
  if (self->files) {
 
209
    g_list_foreach (self->files, (GFunc) gst_m3u8_media_file_free, NULL);
 
210
    g_list_free (self->files);
 
211
    self->files = NULL;
 
212
  }
 
213
 
 
214
  list = NULL;
 
215
  duration = -1;
 
216
  title = NULL;
 
217
  data += 7;
 
218
  while (TRUE) {
 
219
    end = g_utf8_strchr (data, -1, '\n');       /* FIXME: support \r\n */
 
220
    if (end)
 
221
      *end = '\0';
 
222
 
 
223
    if (data[0] != '#') {
 
224
      if (duration < 0 && list == NULL) {
 
225
        GST_LOG ("%s: got line without EXTINF or EXTSTREAMINF, dropping", data);
 
226
        goto next_line;
 
227
      }
 
228
 
 
229
      if (!gst_uri_is_valid (data)) {
 
230
        gchar *slash;
 
231
        if (!self->uri) {
 
232
          GST_WARNING ("uri not set, can't build a valid uri");
 
233
          goto next_line;
 
234
        }
 
235
        slash = g_utf8_strrchr (self->uri, -1, '/');
 
236
        if (!slash) {
 
237
          GST_WARNING ("Can't build a valid uri");
 
238
          goto next_line;
 
239
        }
 
240
 
 
241
        *slash = '\0';
 
242
        data = g_strdup_printf ("%s/%s", self->uri, data);
 
243
        *slash = '/';
 
244
      } else
 
245
        data = g_strdup (data);
 
246
 
 
247
      if (list != NULL) {
 
248
        if (g_list_find_custom (self->lists, data,
 
249
                (GCompareFunc) _m3u8_compare_uri)) {
 
250
          GST_DEBUG ("Already have a list with this URI");
 
251
          gst_m3u8_free (list);
 
252
          g_free (data);
 
253
        } else {
 
254
          gst_m3u8_set_uri (list, data);
 
255
          self->lists = g_list_append (self->lists, list);
 
256
        }
 
257
        list = NULL;
 
258
      } else {
 
259
        GstM3U8MediaFile *file;
 
260
        file =
 
261
            gst_m3u8_media_file_new (data, title, duration,
 
262
            self->mediasequence++);
 
263
        duration = -1;
 
264
        title = NULL;
 
265
        self->files = g_list_append (self->files, file);
 
266
      }
 
267
 
 
268
    } else if (g_str_has_prefix (data, "#EXT-X-ENDLIST")) {
 
269
      self->endlist = TRUE;
 
270
    } else if (g_str_has_prefix (data, "#EXT-X-VERSION:")) {
 
271
      if (int_from_string (data + 15, &data, &val))
 
272
        self->version = val;
 
273
    } else if (g_str_has_prefix (data, "#EXT-X-STREAM-INF:")) {
 
274
      gchar *v, *a;
 
275
 
 
276
      if (list != NULL) {
 
277
        GST_WARNING ("Found a list without a uri..., dropping");
 
278
        gst_m3u8_free (list);
 
279
      }
 
280
 
 
281
      list = gst_m3u8_new ();
 
282
      data = data + 18;
 
283
      while (data && parse_attributes (&data, &a, &v)) {
 
284
        if (g_str_equal (a, "BANDWIDTH")) {
 
285
          if (!int_from_string (v, NULL, &list->bandwidth))
 
286
            GST_WARNING ("Error while reading BANDWIDTH");
 
287
        } else if (g_str_equal (a, "PROGRAM-ID")) {
 
288
          if (!int_from_string (v, NULL, &list->program_id))
 
289
            GST_WARNING ("Error while reading PROGRAM-ID");
 
290
        } else if (g_str_equal (a, "CODECS")) {
 
291
          g_free (list->codecs);
 
292
          list->codecs = g_strdup (v);
 
293
        } else if (g_str_equal (a, "RESOLUTION")) {
 
294
          if (!int_from_string (v, &v, &list->width))
 
295
            GST_WARNING ("Error while reading RESOLUTION width");
 
296
          if (!v || *v != '=') {
 
297
            GST_WARNING ("Missing height");
 
298
          } else {
 
299
            v = g_utf8_next_char (v);
 
300
            if (!int_from_string (v, NULL, &list->height))
 
301
              GST_WARNING ("Error while reading RESOLUTION height");
 
302
          }
 
303
        }
 
304
      }
 
305
    } else if (g_str_has_prefix (data, "#EXT-X-TARGETDURATION:")) {
 
306
      if (int_from_string (data + 22, &data, &val))
 
307
        self->targetduration = val;
 
308
    } else if (g_str_has_prefix (data, "#EXT-X-MEDIA-SEQUENCE:")) {
 
309
      if (int_from_string (data + 22, &data, &val))
 
310
        self->mediasequence = val;
 
311
    } else if (g_str_has_prefix (data, "#EXT-X-DISCONTINUITY")) {
 
312
      /* discontinuity = TRUE; */
 
313
    } else if (g_str_has_prefix (data, "#EXT-X-PROGRAM-DATE-TIME:")) {
 
314
      /* <YYYY-MM-DDThh:mm:ssZ> */
 
315
      GST_DEBUG ("FIXME parse date");
 
316
    } else if (g_str_has_prefix (data, "#EXT-X-ALLOW-CACHE:")) {
 
317
      g_free (self->allowcache);
 
318
      self->allowcache = g_strdup (data + 19);
 
319
    } else if (g_str_has_prefix (data, "#EXTINF:")) {
 
320
      if (!int_from_string (data + 8, &data, &val)) {
 
321
        GST_WARNING ("Can't read EXTINF duration");
 
322
        goto next_line;
 
323
      }
 
324
      duration = val;
 
325
      if (duration > self->targetduration)
 
326
        GST_WARNING ("EXTINF duration > TARGETDURATION");
 
327
      if (!data || *data != ',')
 
328
        goto next_line;
 
329
      data = g_utf8_next_char (data);
 
330
      if (data != end) {
 
331
        g_free (title);
 
332
        title = g_strdup (data);
 
333
      }
 
334
    } else {
 
335
      GST_LOG ("Ignored line: %s", data);
 
336
    }
 
337
 
 
338
  next_line:
 
339
    if (!end)
 
340
      break;
 
341
    data = g_utf8_next_char (end);      /* skip \n */
 
342
  }
 
343
 
 
344
  /* redorder playlists by bitrate */
 
345
  if (self->lists)
 
346
    self->lists =
 
347
        g_list_sort (self->lists,
 
348
        (GCompareFunc) gst_m3u8_compare_playlist_by_bitrate);
 
349
 
 
350
  return TRUE;
 
351
}
 
352
 
 
353
GstM3U8Client *
 
354
gst_m3u8_client_new (const gchar * uri)
 
355
{
 
356
  GstM3U8Client *client;
 
357
 
 
358
  g_return_val_if_fail (uri != NULL, NULL);
 
359
 
 
360
  client = g_new0 (GstM3U8Client, 1);
 
361
  client->main = gst_m3u8_new ();
 
362
  client->current = NULL;
 
363
  client->sequence = -1;
 
364
  client->update_failed_count = 0;
 
365
  gst_m3u8_set_uri (client->main, g_strdup (uri));
 
366
 
 
367
  return client;
 
368
}
 
369
 
 
370
void
 
371
gst_m3u8_client_free (GstM3U8Client * self)
 
372
{
 
373
  g_return_if_fail (self != NULL);
 
374
 
 
375
  gst_m3u8_free (self->main);
 
376
  g_free (self);
 
377
}
 
378
 
 
379
void
 
380
gst_m3u8_client_set_current (GstM3U8Client * self, GstM3U8 * m3u8)
 
381
{
 
382
  g_return_if_fail (self != NULL);
 
383
 
 
384
  if (m3u8 != self->current) {
 
385
    self->current = m3u8;
 
386
    self->update_failed_count = 0;
 
387
  }
 
388
}
 
389
 
 
390
gboolean
 
391
gst_m3u8_client_update (GstM3U8Client * self, gchar * data)
 
392
{
 
393
  GstM3U8 *m3u8;
 
394
  gboolean updated = FALSE;
 
395
 
 
396
  g_return_val_if_fail (self != NULL, FALSE);
 
397
 
 
398
  m3u8 = self->current ? self->current : self->main;
 
399
 
 
400
  if (!gst_m3u8_update (m3u8, data, &updated))
 
401
    return FALSE;
 
402
 
 
403
  if (!updated) {
 
404
    self->update_failed_count++;
 
405
    return FALSE;
 
406
  }
 
407
 
 
408
  /* select the first playlist, for now */
 
409
  if (!self->current) {
 
410
    if (self->main->lists) {
 
411
      self->current = g_list_first (self->main->lists)->data;
 
412
    } else {
 
413
      self->current = self->main;
 
414
    }
 
415
  }
 
416
 
 
417
  if (m3u8->files && self->sequence == -1) {
 
418
    self->sequence =
 
419
        GST_M3U8_MEDIA_FILE (g_list_first (m3u8->files)->data)->sequence;
 
420
    GST_DEBUG ("Setting first sequence at %d", self->sequence);
 
421
  }
 
422
 
 
423
  return TRUE;
 
424
}
 
425
 
 
426
static gboolean
 
427
_find_next (GstM3U8MediaFile * file, GstM3U8Client * client)
 
428
{
 
429
  GST_DEBUG ("Found fragment %d", file->sequence);
 
430
  if (file->sequence >= client->sequence)
 
431
    return FALSE;
 
432
  return TRUE;
 
433
}
 
434
 
 
435
gboolean
 
436
gst_m3u8_client_get_next_fragment (GstM3U8Client * client,
 
437
    gboolean * discontinuity, const gchar ** uri, GstClockTime * duration)
 
438
{
 
439
  GList *l;
 
440
  GstM3U8MediaFile *file;
 
441
 
 
442
  g_return_val_if_fail (client != NULL, FALSE);
 
443
  g_return_val_if_fail (client->current != NULL, FALSE);
 
444
  g_return_val_if_fail (discontinuity != NULL, FALSE);
 
445
 
 
446
  GST_DEBUG ("Looking for fragment %d", client->sequence);
 
447
  l = g_list_find_custom (client->current->files, client,
 
448
      (GCompareFunc) _find_next);
 
449
  if (l == NULL)
 
450
    return FALSE;
 
451
 
 
452
  file = GST_M3U8_MEDIA_FILE (l->data);
 
453
 
 
454
  *discontinuity = client->sequence != file->sequence;
 
455
  client->sequence = file->sequence + 1;
 
456
 
 
457
  *uri = file->uri;
 
458
  *duration = file->duration * GST_SECOND;
 
459
  return TRUE;
 
460
}
 
461
 
 
462
static void
 
463
_sum_duration (GstM3U8MediaFile * self, GstClockTime * duration)
 
464
{
 
465
  *duration += self->duration;
 
466
}
 
467
 
 
468
GstClockTime
 
469
gst_m3u8_client_get_duration (GstM3U8Client * client)
 
470
{
 
471
  GstClockTime duration = 0;
 
472
 
 
473
  g_return_val_if_fail (client != NULL, GST_CLOCK_TIME_NONE);
 
474
 
 
475
  /* We can only get the duration for on-demand streams */
 
476
  if (!client->current->endlist)
 
477
    return GST_CLOCK_TIME_NONE;
 
478
 
 
479
  g_list_foreach (client->current->files, (GFunc) _sum_duration, &duration);
 
480
  return duration * GST_SECOND;
 
481
}
 
482
 
 
483
const gchar *
 
484
gst_m3u8_client_get_uri (GstM3U8Client * client)
 
485
{
 
486
  g_return_val_if_fail (client != NULL, NULL);
 
487
 
 
488
  return client->main->uri;
 
489
}
 
490
 
 
491
gboolean
 
492
gst_m3u8_client_has_variant_playlist (GstM3U8Client * client)
 
493
{
 
494
  g_return_val_if_fail (client != NULL, FALSE);
 
495
 
 
496
  return client->main->lists != NULL;
 
497
}
 
498
 
 
499
gboolean
 
500
gst_m3u8_client_is_live (GstM3U8Client * client)
 
501
{
 
502
  g_return_val_if_fail (client != NULL, FALSE);
 
503
 
 
504
  if (!client->current || client->current->endlist)
 
505
    return FALSE;
 
506
 
 
507
  return TRUE;
 
508
}