mpegts: Unify section checks and add BAT parsing

* Avoid repeating code everywhere, and instead provide all parsing
  information in one go.
* Add BAT support
* Refine BAT/CAT identification (by adding PID checks)
This commit is contained in:
Edward Hervey 2013-07-08 08:42:18 +02:00
parent 2fb7b87140
commit ae4d6bb334
4 changed files with 256 additions and 206 deletions

View file

@ -133,7 +133,7 @@ G_DEFINE_BOXED_TYPE (GstMpegTsEIT, gst_mpegts_eit,
(GBoxedCopyFunc) _gst_mpegts_eit_copy, (GFreeFunc) _gst_mpegts_eit_free);
static GstMpegTsEIT *
static gpointer
_parse_eit (GstMpegTsSection * section)
{
GstMpegTsEIT *eit = NULL;
@ -141,13 +141,6 @@ _parse_eit (GstMpegTsSection * section)
guint8 *data, *end, *duration_ptr;
guint16 descriptors_loop_length;
/* fixed header + CRC == 16 */
if (section->section_length < 18) {
GST_WARNING ("PID %d invalid EIT size %d",
section->pid, section->section_length);
goto error;
}
eit = g_slice_new0 (GstMpegTsEIT);
data = section->data;
@ -218,11 +211,11 @@ _parse_eit (GstMpegTsSection * section)
goto error;
}
return eit;
return (gpointer) eit;
error:
if (eit)
gst_mpegts_section_unref (eit);
_gst_mpegts_eit_free (eit);
return NULL;
@ -243,32 +236,32 @@ gst_mpegts_section_get_eit (GstMpegTsSection * section)
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_EIT, NULL);
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
if (!section->cached_parsed) {
if (G_UNLIKELY (_calc_crc32 (section->data, section->section_length) != 0))
goto bad_crc;
section->cached_parsed = (gpointer) _parse_eit (section);
section->destroy_parsed = (GDestroyNotify) _gst_mpegts_eit_free;
if (section->cached_parsed == NULL)
goto parse_failure;
}
if (!section->cached_parsed)
section->cached_parsed = __common_desc_checks (section, 18, _parse_eit,
(GDestroyNotify) _gst_mpegts_eit_free);
return (const GstMpegTsEIT *) section->cached_parsed;
bad_crc:
{
GST_WARNING ("Bad CRC on section");
return NULL;
}
parse_failure:
{
GST_WARNING ("Failure to parse section");
return NULL;
}
}
/* Bouquet Association Table */
static GstMpegTsBATStream *
_gst_mpegts_bat_stream_copy (GstMpegTsBATStream * bat)
{
/* FIXME : IMPLEMENT */
return NULL;
}
static void
_gst_mpegts_bat_stream_free (GstMpegTsBATStream * bat)
{
g_array_unref (bat->descriptors);
g_slice_free (GstMpegTsBATStream, bat);
}
G_DEFINE_BOXED_TYPE (GstMpegTsBATStream, gst_mpegts_bat_stream,
(GBoxedCopyFunc) _gst_mpegts_bat_stream_copy,
(GFreeFunc) _gst_mpegts_bat_stream_free);
static GstMpegTsBAT *
_gst_mpegts_bat_copy (GstMpegTsBAT * bat)
{
@ -279,12 +272,143 @@ _gst_mpegts_bat_copy (GstMpegTsBAT * bat)
static void
_gst_mpegts_bat_free (GstMpegTsBAT * bat)
{
/* FIXME: IMPLEMENT */
g_array_unref (bat->descriptors);
g_ptr_array_unref (bat->streams);
g_slice_free (GstMpegTsBAT, bat);
}
G_DEFINE_BOXED_TYPE (GstMpegTsBAT, gst_mpegts_bat,
(GBoxedCopyFunc) _gst_mpegts_bat_copy, (GFreeFunc) _gst_mpegts_bat_free);
static gpointer
_parse_bat (GstMpegTsSection * section)
{
GstMpegTsBAT *bat = NULL;
guint i = 0, allocated_streams = 12;
guint8 *data, *end, *entry_begin;
guint16 descriptors_loop_length, transport_stream_loop_length;
GST_DEBUG ("BAT");
bat = g_slice_new0 (GstMpegTsBAT);
data = section->data;
end = data + section->section_length;
/* Skip already parsed data */
data += 8;
descriptors_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF;
data += 2;
/* see if the buffer is large enough */
if (descriptors_loop_length && (data + descriptors_loop_length > end - 4)) {
GST_WARNING ("PID %d invalid BAT descriptors loop length %d",
section->pid, descriptors_loop_length);
goto error;
}
bat->descriptors =
gst_mpegts_parse_descriptors (data, descriptors_loop_length);
if (bat->descriptors == NULL)
goto error;
data += descriptors_loop_length;
transport_stream_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF;
data += 2;
if (G_UNLIKELY (transport_stream_loop_length > (end - 4 - data))) {
GST_WARNING
("PID 0x%04x invalid BAT (transport_stream_loop_length too big)",
section->pid);
goto error;
}
bat->streams =
g_ptr_array_new_full (allocated_streams,
(GDestroyNotify) _gst_mpegts_bat_stream_free);
/* read up to the CRC */
while (transport_stream_loop_length - 4 > 0) {
GstMpegTsBATStream *stream = g_slice_new0 (GstMpegTsBATStream);
g_ptr_array_add (bat->streams, stream);
if (transport_stream_loop_length < 6) {
/* each entry must be at least 6 bytes (+ 4bytes CRC) */
GST_WARNING ("PID %d invalid BAT entry size %d",
section->pid, transport_stream_loop_length);
goto error;
}
entry_begin = data;
stream->transport_stream_id = GST_READ_UINT16_BE (data);
data += 2;
stream->original_network_id = GST_READ_UINT16_BE (data);
data += 2;
descriptors_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF;
data += 2;
GST_DEBUG ("descriptors_loop_length %d", descriptors_loop_length);
if (descriptors_loop_length && (data + descriptors_loop_length > end - 4)) {
GST_WARNING
("PID %d invalid BAT entry %d descriptors loop length %d (only have %"
G_GSIZE_FORMAT ")", section->pid, section->subtable_extension,
descriptors_loop_length, end - 4 - data);
goto error;
}
stream->descriptors =
gst_mpegts_parse_descriptors (data, descriptors_loop_length);
if (stream->descriptors == NULL)
goto error;
data += descriptors_loop_length;
i += 1;
transport_stream_loop_length -= data - entry_begin;
}
if (data != end - 4) {
GST_WARNING ("PID %d invalid BAT parsed %d length %d",
section->pid, (gint) (data - section->data), section->section_length);
goto error;
}
return (gpointer) bat;
error:
if (bat)
_gst_mpegts_bat_free (bat);
return NULL;
}
/**
* gst_mpegts_section_get_bat:
* @section: a #GstMpegTsSection of type %GST_MPEGTS_SECTION_BAT
*
* Returns the #GstMpegTsBAT contained in the @section.
*
* Returns: The #GstMpegTsBAT contained in the section, or %NULL if an error
* happened.
*/
const GstMpegTsBAT *
gst_mpegts_section_get_bat (GstMpegTsSection * section)
{
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_BAT, NULL);
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
if (!section->cached_parsed)
section->cached_parsed =
__common_desc_checks (section, 16, _parse_bat,
(GDestroyNotify) _gst_mpegts_bat_free);
return (const GstMpegTsBAT *) section->cached_parsed;
}
/* Network Information Table */
static GstMpegTsNITStream *
@ -324,7 +448,7 @@ G_DEFINE_BOXED_TYPE (GstMpegTsNIT, gst_mpegts_nit,
(GBoxedCopyFunc) _gst_mpegts_nit_copy, (GFreeFunc) _gst_mpegts_nit_free);
static GstMpegTsNIT *
static gpointer
_parse_nit (GstMpegTsSection * section)
{
GstMpegTsNIT *nit = NULL;
@ -334,13 +458,6 @@ _parse_nit (GstMpegTsSection * section)
GST_DEBUG ("NIT");
/* fixed header (no streams) + CRC == 16 */
if (section->section_length < 16) {
GST_WARNING ("PID %d invalid NIT size %d",
section->pid, section->section_length);
goto error;
}
nit = g_slice_new0 (GstMpegTsNIT);
data = section->data;
@ -429,11 +546,11 @@ _parse_nit (GstMpegTsSection * section)
goto error;
}
return nit;
return (gpointer) nit;
error:
if (nit)
gst_mpegts_section_unref (nit);
_gst_mpegts_nit_free (nit);
return NULL;
}
@ -453,29 +570,12 @@ gst_mpegts_section_get_nit (GstMpegTsSection * section)
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_NIT, NULL);
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
if (!section->cached_parsed) {
if (G_UNLIKELY (_calc_crc32 (section->data, section->section_length) != 0))
goto bad_crc;
section->cached_parsed = (gpointer) _parse_nit (section);
section->destroy_parsed = (GDestroyNotify) _gst_mpegts_nit_free;
if (section->cached_parsed == NULL)
goto parse_failure;
}
if (!section->cached_parsed)
section->cached_parsed =
__common_desc_checks (section, 16, _parse_nit,
(GDestroyNotify) _gst_mpegts_nit_free);
return (const GstMpegTsNIT *) section->cached_parsed;
bad_crc:
{
GST_WARNING ("Bad CRC on section");
return NULL;
}
parse_failure:
{
GST_WARNING ("Failure to parse section");
return NULL;
}
}
@ -517,7 +617,7 @@ G_DEFINE_BOXED_TYPE (GstMpegTsSDT, gst_mpegts_sdt,
(GBoxedCopyFunc) _gst_mpegts_sdt_copy, (GFreeFunc) _gst_mpegts_sdt_free);
static GstMpegTsSDT *
static gpointer
_parse_sdt (GstMpegTsSection * section)
{
GstMpegTsSDT *sdt = NULL;
@ -529,13 +629,6 @@ _parse_sdt (GstMpegTsSection * section)
GST_DEBUG ("SDT");
/* fixed header + CRC == 16 */
if (section->section_length < 14) {
GST_WARNING ("PID %d invalid SDT size %d",
section->pid, section->section_length);
goto error;
}
sdt = g_slice_new0 (GstMpegTsSDT);
data = section->data;
@ -564,7 +657,7 @@ _parse_sdt (GstMpegTsSection * section)
entry_begin = data;
if (sdt_info_length < 9) {
if (sdt_info_length + 5 < 4) {
/* each entry must be at least 5 bytes (+4 bytes for the CRC) */
GST_WARNING ("PID %d invalid SDT entry size %d",
section->pid, sdt_info_length);
@ -631,37 +724,19 @@ gst_mpegts_section_get_sdt (GstMpegTsSection * section)
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_SDT, NULL);
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
if (!section->cached_parsed) {
if (G_UNLIKELY (_calc_crc32 (section->data, section->section_length) != 0))
goto bad_crc;
section->cached_parsed = (gpointer) _parse_sdt (section);
section->destroy_parsed = (GDestroyNotify) _gst_mpegts_sdt_free;
if (section->cached_parsed == NULL)
goto parse_failure;
}
if (!section->cached_parsed)
section->cached_parsed =
__common_desc_checks (section, 15, _parse_sdt,
(GDestroyNotify) _gst_mpegts_sdt_free);
return (const GstMpegTsSDT *) section->cached_parsed;
bad_crc:
{
GST_WARNING ("Bad CRC on section");
return NULL;
}
parse_failure:
{
GST_WARNING ("Failure to parse section");
return NULL;
}
}
/* Time and Date Table (TDT) */
static GstDateTime *
static gpointer
_parse_tdt (GstMpegTsSection * section)
{
/* FIXME : Add length check */
return _parse_utc_time (section->data + 3);
return (gpointer) _parse_utc_time (section->data + 3);
}
/**
@ -679,20 +754,14 @@ gst_mpegts_section_get_tdt (GstMpegTsSection * section)
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_TDT, NULL);
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
if (!section->cached_parsed) {
section->cached_parsed = (gpointer) _parse_tdt (section);
section->destroy_parsed = (GDestroyNotify) gst_date_time_unref;
if (section->cached_parsed == NULL)
goto parse_failure;
}
if (!section->cached_parsed)
section->cached_parsed =
__common_desc_checks (section, 8, _parse_tdt,
(GDestroyNotify) gst_date_time_unref);
return gst_date_time_ref ((GstDateTime *) section->cached_parsed);
parse_failure:
{
GST_WARNING ("Failure to parse section");
return NULL;
}
if (section->cached_parsed)
return gst_date_time_ref ((GstDateTime *) section->cached_parsed);
return NULL;
}
@ -716,15 +785,13 @@ _gst_mpegts_tot_free (GstMpegTsTOT * tot)
G_DEFINE_BOXED_TYPE (GstMpegTsTOT, gst_mpegts_tot,
(GBoxedCopyFunc) _gst_mpegts_tot_copy, (GFreeFunc) _gst_mpegts_tot_free);
static GstMpegTsTOT *
static gpointer
_parse_tot (GstMpegTsSection * section)
{
guint8 *data;
GstMpegTsTOT *tot;
guint16 desc_len;
/* FIXME : Check minimum length */
GST_DEBUG ("TOT");
tot = g_slice_new0 (GstMpegTsTOT);
@ -738,7 +805,7 @@ _parse_tot (GstMpegTsSection * section)
data += 2;
tot->descriptors = gst_mpegts_parse_descriptors (data, desc_len);
return tot;
return (gpointer) tot;
}
/**
@ -756,27 +823,10 @@ gst_mpegts_section_get_tot (GstMpegTsSection * section)
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_TOT, NULL);
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
if (!section->cached_parsed) {
if (G_UNLIKELY (_calc_crc32 (section->data, section->section_length) != 0))
goto bad_crc;
section->cached_parsed = (gpointer) _parse_tot (section);
section->destroy_parsed = (GDestroyNotify) _gst_mpegts_tot_free;
if (section->cached_parsed == NULL)
goto parse_failure;
}
if (!section->cached_parsed)
section->cached_parsed =
__common_desc_checks (section, 14, _parse_tot,
(GDestroyNotify) _gst_mpegts_tot_free);
return (const GstMpegTsTOT *) section->cached_parsed;
bad_crc:
{
GST_WARNING ("Bad CRC on section");
return NULL;
}
parse_failure:
{
GST_WARNING ("Failure to parse section");
return NULL;
}
}

View file

@ -171,6 +171,7 @@ typedef struct _GstMpegTsBATStream GstMpegTsBATStream;
typedef struct _GstMpegTsBAT GstMpegTsBAT;
#define GST_TYPE_MPEGTS_BAT (gst_mpegts_bat_get_type())
#define GST_TYPE_MPEGTS_BAT_STREAM (gst_mpegts_bat_get_type())
struct _GstMpegTsBATStream
{
@ -195,6 +196,9 @@ struct _GstMpegTsBAT
};
GType gst_mpegts_bat_get_type (void);
GType gst_mpegts_bat_stream_get_type (void);
const GstMpegTsBAT *gst_mpegts_section_get_bat (GstMpegTsSection *section);
/* SDT */
#define GST_TYPE_MPEGTS_SDT (gst_mpegts_sdt_get_type())

View file

@ -31,4 +31,10 @@ G_GNUC_INTERNAL void __initialize_descriptors (void);
G_GNUC_INTERNAL guint32 _calc_crc32 (const guint8 *data, guint datalen);
G_GNUC_INTERNAL gchar *get_encoding_and_convert (const gchar *text, guint length);
typedef gpointer (*GstMpegTsParseFunc) (GstMpegTsSection *section);
G_GNUC_INTERNAL gpointer __common_desc_checks (GstMpegTsSection *section,
guint minsize,
GstMpegTsParseFunc parsefunc,
GDestroyNotify destroynotify);
#endif /* _GST_MPEGTS_PRIVATE_H_ */

View file

@ -149,6 +149,37 @@ _calc_crc32 (const guint8 * data, guint datalen)
return crc;
}
gpointer
__common_desc_checks (GstMpegTsSection * section, guint min_size,
GstMpegTsParseFunc parsefunc, GDestroyNotify destroynotify)
{
gpointer res;
/* Check section is big enough */
if (section->section_length < min_size) {
GST_WARNING
("PID:0x%04x table_id:0x%02x, section too small (Got %d, need at least %d)",
section->pid, section->table_id, section->section_length, min_size);
return NULL;
}
/* If section has a CRC, check it */
if (!section->short_section
&& (_calc_crc32 (section->data, section->section_length) != 0)) {
GST_WARNING ("PID:0x%04x table_id:0x%02x, Bad CRC on section", section->pid,
section->table_id);
return NULL;
}
/* Finally parse and set the destroy notify */
res = parsefunc (section);
if (res == NULL)
GST_WARNING ("PID:0x%04x table_id:0x%02x, Failed to parse section",
section->pid, section->table_id);
else
section->destroy_parsed = destroynotify;
return res;
}
/*
@ -287,7 +318,7 @@ gst_message_new_mpegts_section (GstObject * parent, GstMpegTsSection * section)
/* Program Association Table */
static GArray *
static gpointer
_parse_pat (GstMpegTsSection * section)
{
GArray *pat;
@ -326,7 +357,7 @@ _parse_pat (GstMpegTsSection * section)
return NULL;
}
return pat;
return (gpointer) pat;
}
/**
@ -350,29 +381,14 @@ gst_mpegts_section_get_pat (GstMpegTsSection * section)
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_PAT, NULL);
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
if (!section->cached_parsed) {
if (G_UNLIKELY (_calc_crc32 (section->data, section->section_length) != 0))
goto bad_crc;
if (!section->cached_parsed)
section->cached_parsed =
__common_desc_checks (section, 12, _parse_pat,
(GDestroyNotify) g_array_unref);
section->cached_parsed = (gpointer) _parse_pat (section);
section->destroy_parsed = (GDestroyNotify) g_array_unref;
if (section->cached_parsed == NULL)
goto parse_failure;
}
return g_array_ref ((GArray *) section->cached_parsed);
bad_crc:
{
GST_WARNING ("Bad CRC on section");
return NULL;
}
parse_failure:
{
GST_WARNING ("Failure to parse section");
return NULL;
}
if (section->cached_parsed)
return g_array_ref ((GArray *) section->cached_parsed);
return NULL;
}
@ -415,7 +431,7 @@ G_DEFINE_BOXED_TYPE (GstMpegTsPMT, gst_mpegts_pmt,
(GBoxedCopyFunc) _gst_mpegts_pmt_copy, (GFreeFunc) _gst_mpegts_pmt_free);
static GstMpegTsPMT *
static gpointer
_parse_pmt (GstMpegTsSection * section)
{
GstMpegTsPMT *pmt = NULL;
@ -424,13 +440,6 @@ _parse_pmt (GstMpegTsSection * section)
guint program_info_length;
guint stream_info_length;
/* fixed header + CRC == 16 */
if (section->section_length < 16) {
GST_WARNING ("PID %d invalid PMT size %d",
section->pid, section->section_length);
goto error;
}
pmt = g_slice_new0 (GstMpegTsPMT);
data = section->data;
@ -496,11 +505,11 @@ _parse_pmt (GstMpegTsSection * section)
g_assert (data == end - 4);
return pmt;
return (gpointer) pmt;
error:
if (pmt)
gst_mpegts_section_unref (pmt);
_gst_mpegts_pmt_free (pmt);
return NULL;
}
@ -520,34 +529,17 @@ gst_mpegts_section_get_pmt (GstMpegTsSection * section)
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_PMT, NULL);
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
if (!section->cached_parsed) {
if (G_UNLIKELY (_calc_crc32 (section->data, section->section_length) != 0))
goto bad_crc;
section->cached_parsed = (gpointer) _parse_pmt (section);
section->destroy_parsed = (GDestroyNotify) _gst_mpegts_pmt_free;
if (section->cached_parsed == NULL)
goto parse_failure;
}
if (!section->cached_parsed)
section->cached_parsed =
__common_desc_checks (section, 16, _parse_pmt,
(GDestroyNotify) _gst_mpegts_pmt_free);
return (const GstMpegTsPMT *) section->cached_parsed;
bad_crc:
{
GST_WARNING ("Bad CRC on section");
return NULL;
}
parse_failure:
{
GST_WARNING ("Failure to parse section");
return NULL;
}
}
/* Conditional Access Table */
static GArray *
static gpointer
_parse_cat (GstMpegTsSection * section)
{
guint8 *data;
@ -558,7 +550,7 @@ _parse_cat (GstMpegTsSection * section)
/* descriptors */
desc_len = section->section_length - 4 - 8;
return gst_mpegts_parse_descriptors (data, desc_len);
return (gpointer) gst_mpegts_parse_descriptors (data, desc_len);
}
/**
@ -578,20 +570,14 @@ gst_mpegts_section_get_cat (GstMpegTsSection * section)
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_CAT, NULL);
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
if (!section->cached_parsed) {
section->cached_parsed = (gpointer) _parse_cat (section);
section->destroy_parsed = (GDestroyNotify) g_array_unref;
if (section->cached_parsed == NULL)
goto parse_failure;
}
if (!section->cached_parsed)
section->cached_parsed =
__common_desc_checks (section, 12, _parse_cat,
(GDestroyNotify) g_array_unref);
return g_array_ref ((GArray *) section->cached_parsed);
parse_failure:
{
GST_WARNING ("Failure to parse section");
return NULL;
}
if (section->cached_parsed)
return g_array_ref ((GArray *) section->cached_parsed);
return NULL;
}
/* Transport Stream Description Table (TSDT) */
@ -662,11 +648,15 @@ _identify_section (guint16 pid, guint8 table_id)
return GST_MPEGTS_SECTION_PAT;
break;
case GST_MTS_TABLE_ID_CONDITIONAL_ACCESS:
return GST_MPEGTS_SECTION_CAT;
if (pid == 0x01)
return GST_MPEGTS_SECTION_CAT;
break;
case GST_MTS_TABLE_ID_TS_PROGRAM_MAP:
return GST_MPEGTS_SECTION_PMT;
case GST_MTS_TABLE_ID_BOUQUET_ASSOCIATION:
return GST_MPEGTS_SECTION_BAT;
if (pid == 0x0011)
return GST_MPEGTS_SECTION_BAT;
break;
case GST_MTS_TABLE_ID_NETWORK_INFORMATION_ACTUAL_NETWORK:
case GST_MTS_TABLE_ID_NETWORK_INFORMATION_OTHER_NETWORK:
if (pid == 0x0010)