From 1d4eb71a05c27fa66e53b949f9cce836d41c8b87 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Tue, 12 Jul 2016 23:05:55 +1000 Subject: [PATCH] hlsdemux: Add support for ID3 tag parsing Parse start PTS out of the ID3 tags in audio fragments. Informational only for now. --- ext/hls/gsthlsdemux-util.c | 123 +++++++++++++++++++++++++++++++++++-- ext/hls/gsthlsdemux.c | 43 +++++++++---- ext/hls/gsthlsdemux.h | 16 ++++- 3 files changed, 164 insertions(+), 18 deletions(-) diff --git a/ext/hls/gsthlsdemux-util.c b/ext/hls/gsthlsdemux-util.c index 39bb75f417..e212f5467f 100644 --- a/ext/hls/gsthlsdemux-util.c +++ b/ext/hls/gsthlsdemux-util.c @@ -1,4 +1,5 @@ #include +#include #include #include "gsthlsdemux.h" @@ -16,6 +17,9 @@ GST_DEBUG_CATEGORY_EXTERN (gst_hls_demux_debug); ((data[3] & 0x30) != 0x00 || \ ((data[3] & 0x30) == 0x00 && (data[1] & 0x1f) == 0x1f && (data[2] & 0xff) == 0xff))) +#define PCRTIME_TO_GSTTIME(t) (((t) * (guint64)1000) / 27) +#define MPEGTIME_TO_GSTTIME(t) (((t) * (guint64)100000) / 9) + static gboolean have_ts_sync (const guint8 * data, guint size, guint packet_size, guint num) { @@ -72,7 +76,6 @@ handle_pcr (GstHLSTSReader * r, const guint8 * data, guint size) pcr_base = (GST_READ_UINT64_BE (data) >> 16) >> (6 + 9); pcr_ext = (GST_READ_UINT64_BE (data) >> 16) & 0x1ff; pcr = pcr_base * 300 + pcr_ext; -#define PCRTIME_TO_GSTTIME(t) (((t) * (guint64)1000) / 27) ts = PCRTIME_TO_GSTTIME (pcr); GST_LOG ("have PCR! %" G_GUINT64_FORMAT "\t%" GST_TIME_FORMAT, pcr, GST_TIME_ARGS (ts)); @@ -156,28 +159,50 @@ handle_pat (GstHLSTSReader * r, const guint8 * data, guint size) void gst_hlsdemux_tsreader_init (GstHLSTSReader * r) { + r->rtype = GST_HLS_TSREADER_NONE; r->packet_size = 188; r->pmt_pid = r->pcr_pid = -1; r->first_pcr = GST_CLOCK_TIME_NONE; r->last_pcr = GST_CLOCK_TIME_NONE; } -gboolean -gst_hlsdemux_tsreader_find_pcrs (GstHLSTSReader * r, - const guint8 * data, guint size, GstClockTime * first_pcr, - GstClockTime * last_pcr) +void +gst_hlsdemux_tsreader_set_type (GstHLSTSReader * r, GstHLSTSReaderType rtype) { + r->rtype = rtype; + r->have_id3 = FALSE; +} + +static gboolean +gst_hlsdemux_tsreader_find_pcrs_mpegts (GstHLSTSReader * r, + GstBuffer * buffer, GstClockTime * first_pcr, GstClockTime * last_pcr) +{ + GstMapInfo info; gint offset; const guint8 *p; + const guint8 *data; + gsize size; + + if (!gst_buffer_map (buffer, &info, GST_MAP_READ)) + return FALSE; + + data = info.data; + size = info.size; *first_pcr = *last_pcr = GST_CLOCK_TIME_NONE; offset = find_offset (r, data, size); - if (offset < 0) + if (offset < 0) { + gst_buffer_unmap (buffer, &info); return FALSE; + } GST_LOG ("TS packet start offset: %d", offset); + /* We don't store a partial packet at the end, + * and just assume that the final PCR is + * going to be completely inside the last data + * segment passed to us */ data += offset; size -= offset; @@ -204,9 +229,95 @@ gst_hlsdemux_tsreader_find_pcrs (GstHLSTSReader * r, } } + gst_buffer_unmap (buffer, &info); + *first_pcr = r->first_pcr; *last_pcr = r->last_pcr; /* Return TRUE if this piece was big enough to get a PCR from */ return (r->first_pcr != GST_CLOCK_TIME_NONE); } + +static gboolean +gst_hlsdemux_tsreader_find_pcrs_id3 (GstHLSTSReader * r, + GstBuffer * buffer, GstClockTime * first_pcr, GstClockTime * last_pcr) +{ + GstMapInfo info; + guint32 tag_size; + gsize size; + GstTagList *taglist; + GstSample *priv_data = NULL; + GstBuffer *tag_buf; + guint64 pts; + + *first_pcr = r->first_pcr; + *last_pcr = r->last_pcr; + + if (r->have_id3) + return TRUE; + + /* We need at least 10 bytes, starting with "ID3" for the header */ + size = gst_buffer_get_size (buffer); + if (size < 10) + return FALSE; + + /* Read the tag size */ + tag_size = gst_tag_get_id3v2_tag_size (buffer); + + /* Check we've collected that much */ + if (size < tag_size) + return FALSE; + + /* From here, whether the tag is valid or not we'll + * not try and read again */ + r->have_id3 = TRUE; + + /* Parse the tag */ + taglist = gst_tag_list_from_id3v2_tag (buffer); + if (taglist == NULL) + return TRUE; /* Invalid tag, stop trying */ + + /* Extract the timestamps */ + if (!gst_tag_list_get_sample (taglist, GST_TAG_PRIVATE_DATA, &priv_data)) + goto out; + + if (!g_str_equal ("com.apple.streaming.transportStreamTimestamp", + gst_structure_get_string (gst_sample_get_info (priv_data), "owner"))) + goto out; + + /* OK, now as per section 3, the tag contains a 33-bit PCR inside a 64-bit + * BE-word */ + tag_buf = gst_sample_get_buffer (priv_data); + if (tag_buf == NULL) + goto out; + + if (!gst_buffer_map (tag_buf, &info, GST_MAP_READ)) + goto out; + + pts = GST_READ_UINT64_BE (info.data); + *first_pcr = r->first_pcr = MPEGTIME_TO_GSTTIME (pts); + + GST_LOG ("Got AAC TS PTS %" G_GUINT64_FORMAT " (%" G_GUINT64_FORMAT ")", + pts, r->first_pcr); + + gst_buffer_unmap (tag_buf, &info); + +out: + if (priv_data) + gst_sample_unref (priv_data); + + gst_tag_list_unref (taglist); + + return TRUE; +} + +gboolean +gst_hlsdemux_tsreader_find_pcrs (GstHLSTSReader * r, + GstBuffer * buffer, GstClockTime * first_pcr, GstClockTime * last_pcr) +{ + if (r->rtype == GST_HLS_TSREADER_MPEGTS) + return gst_hlsdemux_tsreader_find_pcrs_mpegts (r, buffer, first_pcr, + last_pcr); + + return gst_hlsdemux_tsreader_find_pcrs_id3 (r, buffer, first_pcr, last_pcr); +} diff --git a/ext/hls/gsthlsdemux.c b/ext/hls/gsthlsdemux.c index ed52b8e25a..8f10357038 100644 --- a/ext/hls/gsthlsdemux.c +++ b/ext/hls/gsthlsdemux.c @@ -447,6 +447,9 @@ create_stream_for_playlist (GstAdaptiveDemux * demux, GstM3U8 * playlist, gst_hls_demux_create_pad (hlsdemux)); hlsdemux_stream = GST_HLS_DEMUX_STREAM_CAST (stream); + + hlsdemux_stream->stream_type = GST_HLS_TSREADER_NONE; + hlsdemux_stream->playlist = gst_m3u8_ref (playlist); hlsdemux_stream->is_primary_playlist = is_primary_playlist; @@ -696,8 +699,11 @@ gst_hls_demux_start_fragment (GstAdaptiveDemux * demux, gst_hls_demux_stream_clear_pending_data (hls_stream); - /* Init the MPEG-TS reader for this fragment */ + /* Init the timestamp reader for this fragment */ gst_hlsdemux_tsreader_init (&hls_stream->tsreader); + /* Reset the stream type if we already know it */ + gst_hlsdemux_tsreader_set_type (&hls_stream->tsreader, + hls_stream->stream_type); /* If no decryption is needed, there's nothing to be done here */ if (hls_stream->current_key == NULL) @@ -725,6 +731,19 @@ key_failed: } } +static GstHLSTSReaderType +caps_to_reader (const GstCaps * caps) +{ + const GstStructure *s = gst_caps_get_structure (caps, 0); + + if (gst_structure_has_name (s, "video/mpegts")) + return GST_HLS_TSREADER_MPEGTS; + if (gst_structure_has_name (s, "application/x-id3")) + return GST_HLS_TSREADER_ID3; + + return GST_HLS_TSREADER_NONE; +} + static GstFlowReturn gst_hls_demux_handle_buffer (GstAdaptiveDemux * demux, GstAdaptiveDemuxStream * stream, GstBuffer * buffer, gboolean at_eos) @@ -780,30 +799,32 @@ gst_hls_demux_handle_buffer (GstAdaptiveDemux * demux, GST_DEBUG_OBJECT (hlsdemux, "Typefind result: %" GST_PTR_FORMAT " prob:%d", caps, prob); + hls_stream->stream_type = caps_to_reader (caps); + gst_hlsdemux_tsreader_set_type (&hls_stream->tsreader, + hls_stream->stream_type); + gst_adaptive_demux_stream_set_caps (stream, caps); + hls_stream->do_typefind = FALSE; } g_assert (hls_stream->pending_typefind_buffer == NULL); + gst_buffer_unmap (buffer, &info); + // Accumulate this buffer if (hls_stream->pending_pcr_buffer) { - gst_buffer_unmap (buffer, &info); buffer = gst_buffer_append (hls_stream->pending_pcr_buffer, buffer); hls_stream->pending_pcr_buffer = NULL; - gst_buffer_map (buffer, &info, GST_MAP_READ); } - if (!gst_hlsdemux_tsreader_find_pcrs (&hls_stream->tsreader, info.data, - info.size, &first_pcr, &last_pcr) + if (!gst_hlsdemux_tsreader_find_pcrs (&hls_stream->tsreader, buffer, + &first_pcr, &last_pcr) && !at_eos) { - gst_buffer_unmap (buffer, &info); // Store this buffer for later hls_stream->pending_pcr_buffer = buffer; return GST_FLOW_OK; } - gst_buffer_unmap (buffer, &info); - if (buffer) { buffer = gst_buffer_make_writable (buffer); GST_BUFFER_OFFSET (buffer) = hls_stream->current_offset; @@ -1020,7 +1041,7 @@ gst_hls_demux_update_fragment_info (GstAdaptiveDemuxStream * stream) g_free (stream->fragment.uri); stream->fragment.uri = g_strdup (file->uri); - GST_DEBUG_OBJECT (stream, "URI now %s", file->uri); + GST_DEBUG_OBJECT (hlsdemux, "Stream %p URI now %s", stream, file->uri); stream->fragment.range_start = file->offset; if (file->size != -1) @@ -1053,8 +1074,8 @@ gst_hls_demux_select_bitrate (GstAdaptiveDemuxStream * stream, guint64 bitrate) GST_M3U8_CLIENT_UNLOCK (hlsdemux->client); if (hls_stream->is_primary_playlist == FALSE) { - GST_LOG_OBJECT (stream, - "Not choosing new bitrate - not the primary stream"); + GST_LOG_OBJECT (hlsdemux, + "Stream %p Not choosing new bitrate - not the primary stream", stream); return FALSE; } diff --git a/ext/hls/gsthlsdemux.h b/ext/hls/gsthlsdemux.h index ee00f0e1f0..3b6a4c08e2 100644 --- a/ext/hls/gsthlsdemux.h +++ b/ext/hls/gsthlsdemux.h @@ -62,8 +62,17 @@ typedef struct _GstHLSTSReader GstHLSTSReader; #define GST_HLS_DEMUX_STREAM_CAST(stream) ((GstHLSDemuxStream *)(stream)) +typedef enum { + GST_HLS_TSREADER_NONE, + GST_HLS_TSREADER_MPEGTS, + GST_HLS_TSREADER_ID3 +} GstHLSTSReaderType; + struct _GstHLSTSReader { + GstHLSTSReaderType rtype; + gboolean have_id3; + gint packet_size; gint pmt_pid; gint pcr_pid; @@ -76,6 +85,8 @@ struct _GstHLSDemuxStream { GstAdaptiveDemux adaptive_demux_stream; + GstHLSTSReaderType stream_type; + GstM3U8 *playlist; gboolean is_primary_playlist; @@ -139,8 +150,11 @@ struct _GstHLSDemuxClass GstAdaptiveDemuxClass parent_class; }; + void gst_hlsdemux_tsreader_init (GstHLSTSReader *r); -gboolean gst_hlsdemux_tsreader_find_pcrs (GstHLSTSReader *r, const guint8 * data, guint size, +void gst_hlsdemux_tsreader_set_type (GstHLSTSReader *r, GstHLSTSReaderType rtype); + +gboolean gst_hlsdemux_tsreader_find_pcrs (GstHLSTSReader *r, GstBuffer *buffer, GstClockTime *first_pcr, GstClockTime *last_pcr); GType gst_hls_demux_get_type (void);