mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-20 00:31:13 +00:00
hlsdemux: Add support for ID3 tag parsing
Parse start PTS out of the ID3 tags in audio fragments. Informational only for now.
This commit is contained in:
parent
8cf50dbc44
commit
1d4eb71a05
3 changed files with 164 additions and 18 deletions
|
@ -1,4 +1,5 @@
|
||||||
#include <gst/gst.h>
|
#include <gst/gst.h>
|
||||||
|
#include <gst/tag/tag.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "gsthlsdemux.h"
|
#include "gsthlsdemux.h"
|
||||||
|
@ -16,6 +17,9 @@ GST_DEBUG_CATEGORY_EXTERN (gst_hls_demux_debug);
|
||||||
((data[3] & 0x30) != 0x00 || \
|
((data[3] & 0x30) != 0x00 || \
|
||||||
((data[3] & 0x30) == 0x00 && (data[1] & 0x1f) == 0x1f && (data[2] & 0xff) == 0xff)))
|
((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
|
static gboolean
|
||||||
have_ts_sync (const guint8 * data, guint size, guint packet_size, guint num)
|
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_base = (GST_READ_UINT64_BE (data) >> 16) >> (6 + 9);
|
||||||
pcr_ext = (GST_READ_UINT64_BE (data) >> 16) & 0x1ff;
|
pcr_ext = (GST_READ_UINT64_BE (data) >> 16) & 0x1ff;
|
||||||
pcr = pcr_base * 300 + pcr_ext;
|
pcr = pcr_base * 300 + pcr_ext;
|
||||||
#define PCRTIME_TO_GSTTIME(t) (((t) * (guint64)1000) / 27)
|
|
||||||
ts = PCRTIME_TO_GSTTIME (pcr);
|
ts = PCRTIME_TO_GSTTIME (pcr);
|
||||||
GST_LOG ("have PCR! %" G_GUINT64_FORMAT "\t%" GST_TIME_FORMAT,
|
GST_LOG ("have PCR! %" G_GUINT64_FORMAT "\t%" GST_TIME_FORMAT,
|
||||||
pcr, GST_TIME_ARGS (ts));
|
pcr, GST_TIME_ARGS (ts));
|
||||||
|
@ -156,28 +159,50 @@ handle_pat (GstHLSTSReader * r, const guint8 * data, guint size)
|
||||||
void
|
void
|
||||||
gst_hlsdemux_tsreader_init (GstHLSTSReader * r)
|
gst_hlsdemux_tsreader_init (GstHLSTSReader * r)
|
||||||
{
|
{
|
||||||
|
r->rtype = GST_HLS_TSREADER_NONE;
|
||||||
r->packet_size = 188;
|
r->packet_size = 188;
|
||||||
r->pmt_pid = r->pcr_pid = -1;
|
r->pmt_pid = r->pcr_pid = -1;
|
||||||
r->first_pcr = GST_CLOCK_TIME_NONE;
|
r->first_pcr = GST_CLOCK_TIME_NONE;
|
||||||
r->last_pcr = GST_CLOCK_TIME_NONE;
|
r->last_pcr = GST_CLOCK_TIME_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean
|
void
|
||||||
gst_hlsdemux_tsreader_find_pcrs (GstHLSTSReader * r,
|
gst_hlsdemux_tsreader_set_type (GstHLSTSReader * r, GstHLSTSReaderType rtype)
|
||||||
const guint8 * data, guint size, GstClockTime * first_pcr,
|
|
||||||
GstClockTime * last_pcr)
|
|
||||||
{
|
{
|
||||||
|
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;
|
gint offset;
|
||||||
const guint8 *p;
|
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;
|
*first_pcr = *last_pcr = GST_CLOCK_TIME_NONE;
|
||||||
|
|
||||||
offset = find_offset (r, data, size);
|
offset = find_offset (r, data, size);
|
||||||
if (offset < 0)
|
if (offset < 0) {
|
||||||
|
gst_buffer_unmap (buffer, &info);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
GST_LOG ("TS packet start offset: %d", offset);
|
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;
|
data += offset;
|
||||||
size -= offset;
|
size -= offset;
|
||||||
|
|
||||||
|
@ -204,9 +229,95 @@ gst_hlsdemux_tsreader_find_pcrs (GstHLSTSReader * r,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gst_buffer_unmap (buffer, &info);
|
||||||
|
|
||||||
*first_pcr = r->first_pcr;
|
*first_pcr = r->first_pcr;
|
||||||
*last_pcr = r->last_pcr;
|
*last_pcr = r->last_pcr;
|
||||||
|
|
||||||
/* Return TRUE if this piece was big enough to get a PCR from */
|
/* Return TRUE if this piece was big enough to get a PCR from */
|
||||||
return (r->first_pcr != GST_CLOCK_TIME_NONE);
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -447,6 +447,9 @@ create_stream_for_playlist (GstAdaptiveDemux * demux, GstM3U8 * playlist,
|
||||||
gst_hls_demux_create_pad (hlsdemux));
|
gst_hls_demux_create_pad (hlsdemux));
|
||||||
|
|
||||||
hlsdemux_stream = GST_HLS_DEMUX_STREAM_CAST (stream);
|
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->playlist = gst_m3u8_ref (playlist);
|
||||||
hlsdemux_stream->is_primary_playlist = is_primary_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);
|
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);
|
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 no decryption is needed, there's nothing to be done here */
|
||||||
if (hls_stream->current_key == NULL)
|
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
|
static GstFlowReturn
|
||||||
gst_hls_demux_handle_buffer (GstAdaptiveDemux * demux,
|
gst_hls_demux_handle_buffer (GstAdaptiveDemux * demux,
|
||||||
GstAdaptiveDemuxStream * stream, GstBuffer * buffer, gboolean at_eos)
|
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",
|
GST_DEBUG_OBJECT (hlsdemux, "Typefind result: %" GST_PTR_FORMAT " prob:%d",
|
||||||
caps, prob);
|
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);
|
gst_adaptive_demux_stream_set_caps (stream, caps);
|
||||||
|
|
||||||
hls_stream->do_typefind = FALSE;
|
hls_stream->do_typefind = FALSE;
|
||||||
}
|
}
|
||||||
g_assert (hls_stream->pending_typefind_buffer == NULL);
|
g_assert (hls_stream->pending_typefind_buffer == NULL);
|
||||||
|
|
||||||
|
gst_buffer_unmap (buffer, &info);
|
||||||
|
|
||||||
// Accumulate this buffer
|
// Accumulate this buffer
|
||||||
if (hls_stream->pending_pcr_buffer) {
|
if (hls_stream->pending_pcr_buffer) {
|
||||||
gst_buffer_unmap (buffer, &info);
|
|
||||||
buffer = gst_buffer_append (hls_stream->pending_pcr_buffer, buffer);
|
buffer = gst_buffer_append (hls_stream->pending_pcr_buffer, buffer);
|
||||||
hls_stream->pending_pcr_buffer = NULL;
|
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,
|
if (!gst_hlsdemux_tsreader_find_pcrs (&hls_stream->tsreader, buffer,
|
||||||
info.size, &first_pcr, &last_pcr)
|
&first_pcr, &last_pcr)
|
||||||
&& !at_eos) {
|
&& !at_eos) {
|
||||||
gst_buffer_unmap (buffer, &info);
|
|
||||||
// Store this buffer for later
|
// Store this buffer for later
|
||||||
hls_stream->pending_pcr_buffer = buffer;
|
hls_stream->pending_pcr_buffer = buffer;
|
||||||
return GST_FLOW_OK;
|
return GST_FLOW_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
gst_buffer_unmap (buffer, &info);
|
|
||||||
|
|
||||||
if (buffer) {
|
if (buffer) {
|
||||||
buffer = gst_buffer_make_writable (buffer);
|
buffer = gst_buffer_make_writable (buffer);
|
||||||
GST_BUFFER_OFFSET (buffer) = hls_stream->current_offset;
|
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);
|
g_free (stream->fragment.uri);
|
||||||
stream->fragment.uri = g_strdup (file->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;
|
stream->fragment.range_start = file->offset;
|
||||||
if (file->size != -1)
|
if (file->size != -1)
|
||||||
|
@ -1053,8 +1074,8 @@ gst_hls_demux_select_bitrate (GstAdaptiveDemuxStream * stream, guint64 bitrate)
|
||||||
GST_M3U8_CLIENT_UNLOCK (hlsdemux->client);
|
GST_M3U8_CLIENT_UNLOCK (hlsdemux->client);
|
||||||
|
|
||||||
if (hls_stream->is_primary_playlist == FALSE) {
|
if (hls_stream->is_primary_playlist == FALSE) {
|
||||||
GST_LOG_OBJECT (stream,
|
GST_LOG_OBJECT (hlsdemux,
|
||||||
"Not choosing new bitrate - not the primary stream");
|
"Stream %p Not choosing new bitrate - not the primary stream", stream);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,8 +62,17 @@ typedef struct _GstHLSTSReader GstHLSTSReader;
|
||||||
|
|
||||||
#define GST_HLS_DEMUX_STREAM_CAST(stream) ((GstHLSDemuxStream *)(stream))
|
#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
|
struct _GstHLSTSReader
|
||||||
{
|
{
|
||||||
|
GstHLSTSReaderType rtype;
|
||||||
|
gboolean have_id3;
|
||||||
|
|
||||||
gint packet_size;
|
gint packet_size;
|
||||||
gint pmt_pid;
|
gint pmt_pid;
|
||||||
gint pcr_pid;
|
gint pcr_pid;
|
||||||
|
@ -76,6 +85,8 @@ struct _GstHLSDemuxStream
|
||||||
{
|
{
|
||||||
GstAdaptiveDemux adaptive_demux_stream;
|
GstAdaptiveDemux adaptive_demux_stream;
|
||||||
|
|
||||||
|
GstHLSTSReaderType stream_type;
|
||||||
|
|
||||||
GstM3U8 *playlist;
|
GstM3U8 *playlist;
|
||||||
gboolean is_primary_playlist;
|
gboolean is_primary_playlist;
|
||||||
|
|
||||||
|
@ -139,8 +150,11 @@ struct _GstHLSDemuxClass
|
||||||
GstAdaptiveDemuxClass parent_class;
|
GstAdaptiveDemuxClass parent_class;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void gst_hlsdemux_tsreader_init (GstHLSTSReader *r);
|
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);
|
GstClockTime *first_pcr, GstClockTime *last_pcr);
|
||||||
|
|
||||||
GType gst_hls_demux_get_type (void);
|
GType gst_hls_demux_get_type (void);
|
||||||
|
|
Loading…
Reference in a new issue