mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-18 14:26:43 +00:00
hlsdemux2: Add parsing of partial segments
Add partial segments to each media segment, and potentially create a trailing dummy segment if there are partial segments at the end of the playlist Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3883>
This commit is contained in:
parent
fac7177354
commit
07f51396af
2 changed files with 233 additions and 6 deletions
|
@ -112,10 +112,32 @@ gst_m3u8_media_segment_unref (GstM3U8MediaSegment * self)
|
|||
g_free (self->key);
|
||||
if (self->datetime)
|
||||
g_date_time_unref (self->datetime);
|
||||
if (self->partial_segments)
|
||||
g_ptr_array_free (self->partial_segments, TRUE);
|
||||
g_free (self);
|
||||
}
|
||||
}
|
||||
|
||||
GstM3U8PartialSegment *
|
||||
gst_m3u8_partial_segment_ref (GstM3U8PartialSegment * part)
|
||||
{
|
||||
g_assert (part != NULL && part->ref_count > 0);
|
||||
|
||||
g_atomic_int_add (&part->ref_count, 1);
|
||||
return part;
|
||||
}
|
||||
|
||||
void
|
||||
gst_m3u8_partial_segment_unref (GstM3U8PartialSegment * part)
|
||||
{
|
||||
g_return_if_fail (part != NULL && part->ref_count > 0);
|
||||
|
||||
if (g_atomic_int_dec_and_test (&part->ref_count)) {
|
||||
g_free (part->uri);
|
||||
g_free (part);
|
||||
}
|
||||
}
|
||||
|
||||
static GstM3U8InitFile *
|
||||
gst_m3u8_init_file_new (gchar * uri, gint64 size, gint64 offset)
|
||||
{
|
||||
|
@ -343,6 +365,7 @@ gst_hls_media_playlist_new (const gchar * uri, const gchar * base_uri)
|
|||
m3u8->version = 1;
|
||||
m3u8->type = GST_HLS_PLAYLIST_TYPE_UNDEFINED;
|
||||
m3u8->targetduration = GST_CLOCK_TIME_NONE;
|
||||
m3u8->partial_targetduration = GST_CLOCK_TIME_NONE;
|
||||
m3u8->media_sequence = 0;
|
||||
m3u8->discont_sequence = 0;
|
||||
m3u8->endlist = FALSE;
|
||||
|
@ -377,6 +400,8 @@ gst_hls_media_playlist_dump (GstHLSMediaPlaylist * self)
|
|||
|
||||
GST_DEBUG ("targetduration : %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (self->targetduration));
|
||||
GST_DEBUG ("partial segment targetduration : %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (self->partial_targetduration));
|
||||
GST_DEBUG ("media_sequence : %" G_GINT64_FORMAT, self->media_sequence);
|
||||
GST_DEBUG ("discont_sequence : %" G_GINT64_FORMAT, self->discont_sequence);
|
||||
|
||||
|
@ -398,6 +423,7 @@ gst_hls_media_playlist_dump (GstHLSMediaPlaylist * self)
|
|||
|
||||
GST_DEBUG (" sequence:%" G_GINT64_FORMAT " discont_sequence:%"
|
||||
G_GINT64_FORMAT, segment->sequence, segment->discont_sequence);
|
||||
GST_DEBUG (" partial only: %s", segment->partial_only ? "YES" : "NO");
|
||||
GST_DEBUG (" stream_time : %" GST_STIME_FORMAT,
|
||||
GST_STIME_ARGS (segment->stream_time));
|
||||
GST_DEBUG (" duration : %" GST_TIME_FORMAT,
|
||||
|
@ -412,6 +438,25 @@ gst_hls_media_playlist_dump (GstHLSMediaPlaylist * self)
|
|||
}
|
||||
GST_DEBUG (" uri : %s %" G_GUINT64_FORMAT " %" G_GINT64_FORMAT,
|
||||
segment->uri, segment->offset, segment->size);
|
||||
|
||||
GST_DEBUG (" is gap : %s", segment->is_gap ? "YES" : "NO");
|
||||
|
||||
if (segment->partial_segments != NULL) {
|
||||
guint part_idx;
|
||||
for (part_idx = 0; part_idx < segment->partial_segments->len; part_idx++) {
|
||||
GstM3U8PartialSegment *part =
|
||||
g_ptr_array_index (segment->partial_segments, part_idx);
|
||||
GST_DEBUG (" partial segment %u:", part_idx);
|
||||
GST_DEBUG (" uri : %s %" G_GUINT64_FORMAT " %"
|
||||
G_GINT64_FORMAT, part->uri, part->offset, part->size);
|
||||
GST_DEBUG (" stream_time : %" GST_STIME_FORMAT,
|
||||
GST_STIME_ARGS (part->stream_time));
|
||||
GST_DEBUG (" duration : %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (part->duration));
|
||||
GST_DEBUG (" is gap : %s", part->is_gap ? "YES" : "NO");
|
||||
GST_DEBUG (" independent : %s", part->independent ? "YES" : "NO");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -479,6 +524,62 @@ gst_hls_media_playlist_postprocess_pdt (GstHLSMediaPlaylist * self)
|
|||
}
|
||||
}
|
||||
|
||||
static GstM3U8PartialSegment *
|
||||
gst_m3u8_parse_partial_segment (gchar * data, const gchar * base_uri)
|
||||
{
|
||||
gchar *v, *a;
|
||||
gboolean have_duration = FALSE;
|
||||
GstM3U8PartialSegment *part = g_new0 (GstM3U8PartialSegment, 1);
|
||||
|
||||
part->ref_count = 1;
|
||||
part->stream_time = GST_CLOCK_STIME_NONE;
|
||||
part->size = -1;
|
||||
|
||||
while (data != NULL && parse_attributes (&data, &a, &v)) {
|
||||
if (strcmp (a, "URI") == 0) {
|
||||
g_free (part->uri);
|
||||
part->uri = uri_join (base_uri, v);
|
||||
} else if (strcmp (a, "DURATION") == 0) {
|
||||
if (!time_from_double_in_string (v, NULL, &part->duration)) {
|
||||
GST_WARNING ("Can't read EXT-X-PART duration");
|
||||
goto malformed_line;
|
||||
}
|
||||
have_duration = TRUE;
|
||||
} else if (strcmp (a, "INDEPENDENT") == 0) {
|
||||
part->independent = g_ascii_strcasecmp (v, "yes") == 0;
|
||||
} else if (strcmp (a, "GAP") == 0) {
|
||||
part->is_gap = g_ascii_strcasecmp (v, "yes") == 0;
|
||||
} else if (strcmp (a, "BYTERANGE") == 0) {
|
||||
if (int64_from_string (v, &v, &part->size)) {
|
||||
goto malformed_line;
|
||||
}
|
||||
if (*v == '@' && !int64_from_string (v + 1, &v, &part->offset)) {
|
||||
goto malformed_line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (part->uri == NULL || !have_duration) {
|
||||
goto required_attributes_missing;
|
||||
}
|
||||
|
||||
return part;
|
||||
|
||||
required_attributes_missing:
|
||||
{
|
||||
GST_WARNING
|
||||
("EXT-X-PART description is missing required URI or DURATION attributes");
|
||||
gst_m3u8_partial_segment_unref (part);
|
||||
return NULL;
|
||||
}
|
||||
malformed_line:
|
||||
{
|
||||
GST_WARNING ("Invalid EXT-X-PART entry in playlist");
|
||||
gst_m3u8_partial_segment_unref (part);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse and create a new GstHLSMediaPlaylist */
|
||||
GstHLSMediaPlaylist *
|
||||
gst_hls_media_playlist_parse (gchar * data, const gchar * uri,
|
||||
|
@ -487,7 +588,7 @@ gst_hls_media_playlist_parse (gchar * data, const gchar * uri,
|
|||
gchar *input_data = data;
|
||||
GstHLSMediaPlaylist *self;
|
||||
gint val;
|
||||
GstClockTime duration;
|
||||
GstClockTime duration, partial_duration;
|
||||
gchar *title, *end;
|
||||
gboolean discontinuity = FALSE;
|
||||
gchar *current_key = NULL;
|
||||
|
@ -499,6 +600,7 @@ gst_hls_media_playlist_parse (gchar * data, const gchar * uri,
|
|||
GDateTime *date_time = NULL;
|
||||
GstM3U8InitFile *last_init_file = NULL;
|
||||
GstM3U8MediaSegment *previous = NULL;
|
||||
GPtrArray *partial_segments = NULL;
|
||||
gboolean is_gap = FALSE;
|
||||
|
||||
GST_LOG ("uri: %s", uri);
|
||||
|
@ -523,6 +625,7 @@ gst_hls_media_playlist_parse (gchar * data, const gchar * uri,
|
|||
self->last_data = g_strdup (data);
|
||||
|
||||
duration = 0;
|
||||
partial_duration = 0;
|
||||
title = NULL;
|
||||
data += 7;
|
||||
while (TRUE) {
|
||||
|
@ -552,17 +655,22 @@ gst_hls_media_playlist_parse (gchar * data, const gchar * uri,
|
|||
data = NULL;
|
||||
date_time = NULL;
|
||||
duration = 0;
|
||||
partial_duration = 0;
|
||||
g_free (title);
|
||||
title = NULL;
|
||||
discontinuity = FALSE;
|
||||
size = offset = -1;
|
||||
is_gap = FALSE;
|
||||
if (partial_segments != NULL) {
|
||||
g_ptr_array_free (partial_segments, TRUE);
|
||||
partial_segments = NULL;
|
||||
}
|
||||
goto next_line;
|
||||
}
|
||||
if (data != NULL) {
|
||||
GstM3U8MediaSegment *file;
|
||||
/* We can finally create the segment */
|
||||
/* The disconinuity sequence number is only stored if the header has
|
||||
/* The discontinuity sequence number is only stored if the header has
|
||||
* EXT-X-DISCONTINUITY-SEQUENCE present. */
|
||||
file =
|
||||
gst_m3u8_media_segment_new (data, title, duration, mediasequence++,
|
||||
|
@ -593,8 +701,12 @@ gst_hls_media_playlist_parse (gchar * data, const gchar * uri,
|
|||
if (last_init_file)
|
||||
file->init_file = gst_m3u8_init_file_ref (last_init_file);
|
||||
|
||||
file->partial_segments = partial_segments;
|
||||
partial_segments = NULL;
|
||||
|
||||
date_time = NULL; /* Ownership was passed to the segment */
|
||||
duration = 0;
|
||||
partial_duration = 0;
|
||||
title = NULL; /* Ownership was passed to the segment */
|
||||
discontinuity = FALSE;
|
||||
size = offset = -1;
|
||||
|
@ -763,6 +875,36 @@ gst_hls_media_playlist_parse (gchar * data, const gchar * uri,
|
|||
}
|
||||
} else if (g_str_has_prefix (data_ext_x, "GAP:")) {
|
||||
is_gap = TRUE;
|
||||
} else if (g_str_has_prefix (data_ext_x, "PART:")) {
|
||||
GstM3U8PartialSegment *part = NULL;
|
||||
|
||||
part =
|
||||
gst_m3u8_parse_partial_segment (data + strlen ("#EXT-X-PART:"),
|
||||
self->base_uri ? self->base_uri : self->uri);
|
||||
if (part == NULL)
|
||||
goto next_line;
|
||||
|
||||
if (partial_segments == NULL) {
|
||||
partial_segments = g_ptr_array_new_full (2,
|
||||
(GDestroyNotify) gst_m3u8_partial_segment_unref);
|
||||
}
|
||||
g_ptr_array_add (partial_segments, part);
|
||||
partial_duration += part->duration;
|
||||
|
||||
} else if (g_str_has_prefix (data_ext_x, "PART-INF:")) {
|
||||
gchar *v, *a;
|
||||
|
||||
data += strlen ("#EXT-X-PART-INF:");
|
||||
|
||||
while (data != NULL && parse_attributes (&data, &a, &v)) {
|
||||
if (strcmp (a, "PART-TARGET") == 0) {
|
||||
if (!time_from_double_in_string (v, NULL,
|
||||
&self->partial_targetduration)) {
|
||||
GST_WARNING ("Invalid PART-TARGET");
|
||||
goto next_line;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GST_LOG ("Ignored line: %s", data);
|
||||
}
|
||||
|
@ -777,6 +919,59 @@ gst_hls_media_playlist_parse (gchar * data, const gchar * uri,
|
|||
data = g_utf8_next_char (end); /* skip \n */
|
||||
}
|
||||
|
||||
/* If there are trailing partial segments at the end,
|
||||
* create a dummy segment to hold them */
|
||||
if (partial_segments != NULL) {
|
||||
GstM3U8MediaSegment *file;
|
||||
GST_DEBUG ("Creating dummy segment for trailing partial segments");
|
||||
|
||||
/* The discontinuity sequence number is only stored if the header has
|
||||
* EXT-X-DISCONTINUITY-SEQUENCE present. */
|
||||
file =
|
||||
gst_m3u8_media_segment_new (NULL, title, partial_duration,
|
||||
mediasequence++, dsn, size, offset);
|
||||
|
||||
file->partial_only = TRUE;
|
||||
|
||||
self->duration += partial_duration;
|
||||
|
||||
file->is_gap = is_gap;
|
||||
|
||||
/* set encryption params */
|
||||
if (current_key != NULL) {
|
||||
file->key = g_strdup (current_key);
|
||||
if (have_iv) {
|
||||
memcpy (file->iv, iv, sizeof (iv));
|
||||
} else {
|
||||
/* An EXT-X-KEY tag with a KEYFORMAT of "identity" that does
|
||||
* not have an IV attribute indicates that the Media Sequence
|
||||
* Number is to be used as the IV when decrypting a Media
|
||||
* Segment, by putting its big-endian binary representation
|
||||
* into a 16-octet (128-bit) buffer and padding (on the left)
|
||||
* with zeros. */
|
||||
guint8 *iv = file->iv + 12;
|
||||
GST_WRITE_UINT32_BE (iv, file->sequence);
|
||||
}
|
||||
}
|
||||
|
||||
file->datetime = date_time;
|
||||
file->discont = discontinuity;
|
||||
if (last_init_file)
|
||||
file->init_file = gst_m3u8_init_file_ref (last_init_file);
|
||||
|
||||
file->partial_segments = partial_segments;
|
||||
partial_segments = NULL;
|
||||
|
||||
date_time = NULL; /* Ownership was passed to the partial segment */
|
||||
duration = 0;
|
||||
partial_duration = 0;
|
||||
title = NULL; /* Ownership was passed to the partial segment */
|
||||
discontinuity = FALSE;
|
||||
size = offset = -1;
|
||||
g_ptr_array_add (self->segments, file);
|
||||
previous = file;
|
||||
}
|
||||
|
||||
/* Clean up date that wasn't freed / handed to a segment */
|
||||
g_free (current_key);
|
||||
current_key = NULL;
|
||||
|
@ -1445,7 +1640,7 @@ gst_hls_media_playlist_is_live (GstHLSMediaPlaylist * m3u8)
|
|||
return is_live;
|
||||
}
|
||||
|
||||
gchar *
|
||||
static gchar *
|
||||
uri_join (const gchar * uri1, const gchar * uri2)
|
||||
{
|
||||
gchar *uri_copy, *tmp, *ret = NULL;
|
||||
|
@ -1617,7 +1812,7 @@ gst_hls_rendition_stream_type_get_name (GstHLSRenditionStreamType mtype)
|
|||
return nicks[mtype];
|
||||
}
|
||||
|
||||
/* returns unquoted copy of string */
|
||||
/* returns copy of string with surrounding quotation marks removed */
|
||||
static gchar *
|
||||
gst_m3u8_unquote (const gchar * str)
|
||||
{
|
||||
|
|
|
@ -35,6 +35,7 @@ G_BEGIN_DECLS
|
|||
typedef struct _GstHLSMediaPlaylist GstHLSMediaPlaylist;
|
||||
typedef struct _GstHLSTimeMap GstHLSTimeMap;
|
||||
typedef struct _GstM3U8MediaSegment GstM3U8MediaSegment;
|
||||
typedef struct _GstM3U8PartialSegment GstM3U8PartialSegment;
|
||||
typedef struct _GstM3U8InitFile GstM3U8InitFile;
|
||||
typedef struct _GstHLSRenditionStream GstHLSRenditionStream;
|
||||
typedef struct _GstM3U8Client GstM3U8Client;
|
||||
|
@ -43,6 +44,7 @@ typedef struct _GstHLSMasterPlaylist GstHLSMasterPlaylist;
|
|||
|
||||
#define GST_HLS_MEDIA_PLAYLIST(m) ((GstHLSMediaPlaylist*)m)
|
||||
#define GST_M3U8_MEDIA_SEGMENT(f) ((GstM3U8MediaSegment*)f)
|
||||
#define GST_M3U8_PARTIAL_SEGMENT(p) ((GstM3U8PartialSegment*)p)
|
||||
|
||||
#define GST_HLS_MEDIA_PLAYLIST_LOCK(m) g_mutex_lock (&m->lock);
|
||||
#define GST_HLS_MEDIA_PLAYLIST_UNLOCK(m) g_mutex_unlock (&m->lock);
|
||||
|
@ -82,6 +84,8 @@ struct _GstHLSMediaPlaylist
|
|||
|
||||
/* Media Playlist Tags */
|
||||
GstClockTime targetduration; /* EXT-X-TARGETDURATION, default GST_CLOCK_TIME_NONE */
|
||||
GstClockTime partial_targetduration; /* EXT-X-PART-INF, default GST_CLOCK_TIME_NONE */
|
||||
|
||||
gint64 media_sequence; /* EXT-X-MEDIA-SEQUENCE, MSN of the first Media
|
||||
Segment in the playlist. */
|
||||
gint64 discont_sequence; /* EXT-X-DISCONTINUITY-SEQUENCE. Default : 0 */
|
||||
|
@ -127,6 +131,31 @@ GstHLSMediaPlaylist * gst_hls_media_playlist_ref (GstHLSMediaPlaylist * m3u8);
|
|||
|
||||
void gst_hls_media_playlist_unref (GstHLSMediaPlaylist * m3u8);
|
||||
|
||||
/**
|
||||
* GstM3U8PartialSegment:
|
||||
*
|
||||
* Official term in RFC : "Partial Segment"
|
||||
*
|
||||
*/
|
||||
struct _GstM3U8PartialSegment
|
||||
{
|
||||
gboolean is_gap; /* TRUE if this part is a gap */
|
||||
gboolean independent; /* TRUE if there is an I-frame in the partial segment */
|
||||
gchar *uri;
|
||||
gint64 offset, size;
|
||||
|
||||
GstClockTimeDiff stream_time; /* Computed stream time */
|
||||
GstClockTime duration;
|
||||
|
||||
gint ref_count; /* ATOMIC */
|
||||
};
|
||||
|
||||
GstM3U8PartialSegment *
|
||||
gst_m3u8_partial_segment_ref (GstM3U8PartialSegment *part);
|
||||
|
||||
void
|
||||
gst_m3u8_partial_segment_unref (GstM3U8PartialSegment *part);
|
||||
|
||||
/**
|
||||
* GstM3U8MediaSegment:
|
||||
*
|
||||
|
@ -137,6 +166,7 @@ void gst_hls_media_playlist_unref (GstHLSMediaPlaylist * m3u8);
|
|||
struct _GstM3U8MediaSegment
|
||||
{
|
||||
gboolean is_gap; /* TRUE if EXT-X-GAP was present for this segment */
|
||||
gboolean partial_only; /* TRUE if this is the last segment in a playlist consisting of only EXT-X-PART and no full URL */
|
||||
|
||||
gchar *title;
|
||||
GstClockTimeDiff stream_time; /* Computed stream time */
|
||||
|
@ -148,9 +178,12 @@ struct _GstM3U8MediaSegment
|
|||
gchar *key;
|
||||
guint8 iv[16];
|
||||
gint64 offset, size;
|
||||
gint ref_count; /* ATOMIC */
|
||||
GstM3U8InitFile *init_file; /* Media Initialization (hold ref) */
|
||||
GDateTime *datetime; /* EXT-X-PROGRAM-DATE-TIME */
|
||||
|
||||
GPtrArray *partial_segments; /* If there are Partial Segments for this Media Segment */
|
||||
|
||||
gint ref_count; /* ATOMIC */
|
||||
};
|
||||
|
||||
struct _GstM3U8InitFile
|
||||
|
@ -170,7 +203,6 @@ gst_m3u8_media_segment_ref (GstM3U8MediaSegment * mfile);
|
|||
void
|
||||
gst_m3u8_media_segment_unref (GstM3U8MediaSegment * mfile);
|
||||
|
||||
|
||||
gboolean
|
||||
gst_hls_media_playlist_has_same_data (GstHLSMediaPlaylist * m3u8,
|
||||
gchar * playlist_data);
|
||||
|
|
Loading…
Reference in a new issue