gstreamer/gst-libs/gst/mpegts/gst-scte-section.c
Edward Hervey 6a9108884c mpegts: Add support for SCTE-35 sections
Not all commands are supported, but the most common ones are.
Both parsing and packetizing is supported
2019-10-31 12:31:27 +00:00

659 lines
19 KiB
C

/*
* gst-scte-section.c -
* Copyright (C) 2019 Centricular ltd
* Author: Edward Hervey <edward@centricular.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <stdlib.h>
#include "mpegts.h"
#include "gstmpegts-private.h"
#define MPEGTIME_TO_GSTTIME(t) ((t) * (guint64)100000 / 9)
/**
* SECTION:gst-scte-section
* @title: SCTE variants of MPEG-TS sections
* @short_description: Sections for the various SCTE specifications
* @include: gst/mpegts/mpegts.h
*
*/
/* Splice Information Table (SIT) */
static GstMpegtsSCTESpliceEvent *
_gst_mpegts_scte_splice_event_copy (GstMpegtsSCTESpliceEvent * event)
{
return g_slice_dup (GstMpegtsSCTESpliceEvent, event);
}
static void
_gst_mpegts_scte_splice_event_free (GstMpegtsSCTESpliceEvent * event)
{
g_slice_free (GstMpegtsSCTESpliceEvent, event);
}
G_DEFINE_BOXED_TYPE (GstMpegtsSCTESpliceEvent, gst_mpegts_scte_splice_event,
(GBoxedCopyFunc) _gst_mpegts_scte_splice_event_copy,
(GFreeFunc) _gst_mpegts_scte_splice_event_free);
static GstMpegtsSCTESpliceEvent *
_parse_slice_event (guint8 ** orig_data, guint8 * end, gboolean insert_event)
{
GstMpegtsSCTESpliceEvent *event = g_slice_new0 (GstMpegtsSCTESpliceEvent);
guint8 *data = *orig_data;
/* Note : +6 is because of the final descriptor_loop_length and CRC */
if (data + 5 + 6 > end)
goto error;
event->insert_event = insert_event;
event->splice_event_id = GST_READ_UINT32_BE (data);
GST_LOG ("splice_event_id: 0x%08x", event->splice_event_id);
data += 4;
event->splice_event_cancel_indicator = *data >> 7;
GST_LOG ("splice_event_cancel_indicator: %d",
event->splice_event_cancel_indicator);
data += 1;
GST_DEBUG ("data %p", data);
if (event->splice_event_cancel_indicator == 0) {
if (data + 5 + 6 > end)
goto error;
event->out_of_network_indicator = *data >> 7;
event->program_splice_flag = (*data >> 6) & 0x01;
event->duration_flag = (*data >> 5) & 0x01;
event->splice_immediate_flag = (*data >> 4) & 0x01;
GST_LOG ("out_of_network_indicator:%d", event->out_of_network_indicator);
GST_LOG ("program_splice_flag:%d", event->program_splice_flag);
GST_LOG ("duration_flag:%d", event->duration_flag);
GST_LOG ("splice_immediate_flag:%d", event->splice_immediate_flag);
data += 1;
if (event->program_splice_flag == 0) {
GST_ERROR ("Component splice flag not supported !");
goto error;
}
if (event->splice_immediate_flag == 0) {
event->program_splice_time_specified = *data >> 7;
if (event->program_splice_time_specified) {
event->program_splice_time = ((guint64) (*data & 0x01)) << 32;
data += 1;
event->program_splice_time += GST_READ_UINT32_BE (data);
data += 4;
GST_LOG ("program_splice_time %" G_GUINT64_FORMAT " (%" GST_TIME_FORMAT
")", event->program_splice_time,
GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (event->program_splice_time)));
} else
data += 1;
}
if (event->duration_flag) {
event->break_duration_auto_return = *data >> 7;
event->break_duration = ((guint64) (*data & 0x01)) << 32;
data += 1;
event->break_duration += GST_READ_UINT32_BE (data);
data += 4;
GST_LOG ("break_duration_auto_return:%d",
event->break_duration_auto_return);
GST_LOG ("break_duration %" G_GUINT64_FORMAT " (%" GST_TIME_FORMAT ")",
event->break_duration,
GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (event->break_duration)));
}
event->unique_program_id = GST_READ_UINT16_BE (data);
GST_LOG ("unique_program_id:%" G_GUINT16_FORMAT, event->unique_program_id);
data += 2;
event->avail_num = *data++;
event->avails_expected = *data++;
GST_LOG ("avail %d/%d", event->avail_num, event->avails_expected);
}
GST_DEBUG ("done");
*orig_data = data;
return event;
error:
{
if (event)
_gst_mpegts_scte_splice_event_free (event);
return NULL;
}
}
static GstMpegtsSCTESIT *
_gst_mpegts_scte_sit_copy (GstMpegtsSCTESIT * sit)
{
GstMpegtsSCTESIT *copy = g_slice_dup (GstMpegtsSCTESIT, sit);
copy->splices = g_ptr_array_ref (sit->splices);
copy->descriptors = g_ptr_array_ref (sit->descriptors);
return copy;
}
static void
_gst_mpegts_scte_sit_free (GstMpegtsSCTESIT * sit)
{
g_ptr_array_unref (sit->splices);
g_ptr_array_unref (sit->descriptors);
g_slice_free (GstMpegtsSCTESIT, sit);
}
G_DEFINE_BOXED_TYPE (GstMpegtsSCTESIT, gst_mpegts_scte_sit,
(GBoxedCopyFunc) _gst_mpegts_scte_sit_copy,
(GFreeFunc) _gst_mpegts_scte_sit_free);
static gpointer
_parse_sit (GstMpegtsSection * section)
{
GstMpegtsSCTESIT *sit = NULL;
guint8 *data, *end;
guint32 tmp;
GST_DEBUG ("SIT");
/* Even if the section is not a short one, it still uses CRC */
if (_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;
}
sit = g_slice_new0 (GstMpegtsSCTESIT);
data = section->data;
end = data + section->section_length;
GST_MEMDUMP ("section", data, section->section_length);
/* Skip already-checked fields */
data += 3;
/* Ensure protocol_version is 0 */
if (*data != 0) {
GST_WARNING ("Unsupported SCTE SIT protocol version (%d)", *data);
goto error;
}
data += 1;
/* encryption */
sit->encrypted_packet = (*data) >> 7;
sit->encryption_algorithm = (*data) & 0x3f;
sit->pts_adjustment = ((guint64) (*data & 0x01)) << 32;
data += 1;
sit->pts_adjustment += GST_READ_UINT32_BE (data);
data += 4;
sit->cw_index = *data;
data += 1;
tmp = GST_READ_UINT24_BE (data);
data += 3;
sit->tier = (tmp >> 12);
sit->splice_command_length = tmp & 0xfff;
/* 0xfff is for backwards compatibility when reading */
if (sit->splice_command_length == 0xfff)
sit->splice_command_length = 0;
GST_LOG ("command length %d", sit->splice_command_length);
sit->splice_command_type = *data;
data += 1;
if (sit->splice_command_length
&& (data + sit->splice_command_length > end - 4)) {
GST_WARNING ("PID %d invalid SCTE SIT splice command length %d",
section->pid, sit->splice_command_length);
goto error;
}
sit->splices = g_ptr_array_new_with_free_func ((GDestroyNotify)
_gst_mpegts_scte_splice_event_free);
switch (sit->splice_command_type) {
case GST_MTS_SCTE_SPLICE_COMMAND_NULL:
case GST_MTS_SCTE_SPLICE_COMMAND_BANDWIDTH:
/* These commands have no payload */
break;
case GST_MTS_SCTE_SPLICE_COMMAND_TIME:
{
sit->splice_time_specified = (*data >> 7);
if (sit->splice_time_specified) {
sit->splice_time = ((guint64) (*data & 0x01)) << 32;
data += 1;
sit->splice_time += GST_READ_UINT32_BE (data);
data += 4;
} else
data += 1;
}
break;
case GST_MTS_SCTE_SPLICE_COMMAND_INSERT:
{
GstMpegtsSCTESpliceEvent *event = _parse_slice_event (&data, end, TRUE);
if (event == NULL)
goto error;
g_ptr_array_add (sit->splices, event);
}
break;
case GST_MTS_SCTE_SPLICE_COMMAND_PRIVATE:
{
GST_FIXME ("Implement SCTE-35 private commands");
data += sit->splice_command_length;
}
break;
default:
GST_ERROR ("Unknown SCTE splice command type (0x%02x) !",
sit->splice_command_type);
goto error;
}
/* descriptors */
tmp = GST_READ_UINT16_BE (data);
data += 2;
GST_MEMDUMP ("desc ?", data, tmp);
sit->descriptors = gst_mpegts_parse_descriptors (data, tmp);
if (!sit->descriptors) {
GST_DEBUG ("no descriptors %d", tmp);
goto error;
}
data += tmp;
GST_DEBUG ("%p - %p", data, end);
if (data != end - 4) {
GST_WARNING ("PID %d invalid SIT parsed %d length %d",
section->pid, (gint) (data - section->data), section->section_length);
goto error;
}
return sit;
error:
if (sit)
_gst_mpegts_scte_sit_free (sit);
return NULL;
}
/**
* gst_mpegts_section_get_scte_sit:
* @section: a #GstMpegtsSection of type %GST_MPEGTS_SECTION_SCTE_SIT
*
* Returns the #GstMpegtsSCTESIT contained in the @section.
*
* Returns: The #GstMpegtsSCTESIT contained in the section, or %NULL if an error
* happened.
*/
const GstMpegtsSCTESIT *
gst_mpegts_section_get_scte_sit (GstMpegtsSection * section)
{
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_SCTE_SIT,
NULL);
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
if (!section->cached_parsed)
section->cached_parsed =
__common_section_checks (section, 18, _parse_sit,
(GDestroyNotify) _gst_mpegts_scte_sit_free);
return (const GstMpegtsSCTESIT *) section->cached_parsed;
}
/**
* gst_mpegts_scte_sit_new:
*
* Allocates and initializes a #GstMpegtsSCTESIT.
*
* Returns: (transfer full): A newly allocated #GstMpegtsSCTESIT
*/
GstMpegtsSCTESIT *
gst_mpegts_scte_sit_new (void)
{
GstMpegtsSCTESIT *sit;
sit = g_slice_new0 (GstMpegtsSCTESIT);
/* Set all default values (which aren't already 0/NULL) */
sit->tier = 0xfff;
sit->splices = g_ptr_array_new_with_free_func ((GDestroyNotify)
_gst_mpegts_scte_splice_event_free);
sit->descriptors = g_ptr_array_new_with_free_func ((GDestroyNotify)
gst_mpegts_descriptor_free);
return sit;
}
/**
* gst_mpegts_scte_null_new:
*
* Allocates and initializes a NULL command #GstMpegtsSCTESIT.
*
* Returns: (transfer full): A newly allocated #GstMpegtsSCTESIT
*/
GstMpegtsSCTESIT *
gst_mpegts_scte_null_new (void)
{
GstMpegtsSCTESIT *sit = gst_mpegts_scte_sit_new ();
sit->splice_command_type = GST_MTS_SCTE_SPLICE_COMMAND_NULL;
return sit;
}
/**
* gst_mpegts_scte_cancel_new:
* @event_id: The event ID to cancel.
*
* Allocates and initializes a new INSERT command #GstMpegtsSCTESIT
* setup to cancel the specified @event_id.
*
* Returns: (transfer full): A newly allocated #GstMpegtsSCTESIT
*/
GstMpegtsSCTESIT *
gst_mpegts_scte_cancel_new (guint32 event_id)
{
GstMpegtsSCTESIT *sit = gst_mpegts_scte_sit_new ();
GstMpegtsSCTESpliceEvent *event = gst_mpegts_scte_splice_event_new ();
sit->splice_command_type = GST_MTS_SCTE_SPLICE_COMMAND_INSERT;
event->splice_event_id = event_id;
event->splice_event_cancel_indicator = TRUE;
g_ptr_array_add (sit->splices, event);
return sit;
}
/**
* gst_mpegts_scte_splice_in_new:
* @event_id: The event ID.
* @splice_time: The PCR time for the splice event
*
* Allocates and initializes a new "Splice In" INSERT command
* #GstMpegtsSCTESIT for the given @event_id and @splice_time.
*
* If the @splice_time is #G_MAXUINT64 then the event will be
* immediate as opposed to for the target @splice_time.
*
* Returns: (transfer full): A newly allocated #GstMpegtsSCTESIT
*/
GstMpegtsSCTESIT *
gst_mpegts_scte_splice_in_new (guint32 event_id, guint64 splice_time)
{
GstMpegtsSCTESIT *sit = gst_mpegts_scte_sit_new ();
GstMpegtsSCTESpliceEvent *event = gst_mpegts_scte_splice_event_new ();
sit->splice_command_type = GST_MTS_SCTE_SPLICE_COMMAND_INSERT;
event->splice_event_id = event_id;
if (splice_time == G_MAXUINT64) {
event->splice_immediate_flag = TRUE;
} else {
event->program_splice_time_specified = TRUE;
event->program_splice_time = splice_time;
}
g_ptr_array_add (sit->splices, event);
return sit;
}
/**
* gst_mpegts_scte_splice_out_new:
* @event_id: The event ID.
* @splice_time: The PCR time for the splice event
* @duration: The optional duration.
*
* Allocates and initializes a new "Splice Out" INSERT command
* #GstMpegtsSCTESIT for the given @event_id, @splice_time and
* duration.
*
* If the @splice_time is #G_MAXUINT64 then the event will be
* immediate as opposed to for the target @splice_time.
*
* If the @duration is 0 it won't be specified in the event.
*
* Returns: (transfer full): A newly allocated #GstMpegtsSCTESIT
*/
GstMpegtsSCTESIT *
gst_mpegts_scte_splice_out_new (guint32 event_id, guint64 splice_time,
guint64 duration)
{
GstMpegtsSCTESIT *sit = gst_mpegts_scte_sit_new ();
GstMpegtsSCTESpliceEvent *event = gst_mpegts_scte_splice_event_new ();
sit->splice_command_type = GST_MTS_SCTE_SPLICE_COMMAND_INSERT;
event->splice_event_id = event_id;
event->out_of_network_indicator = TRUE;
if (splice_time == G_MAXUINT64) {
event->splice_immediate_flag = TRUE;
} else {
event->program_splice_time_specified = TRUE;
event->program_splice_time = splice_time;
}
if (duration != 0) {
event->duration_flag = TRUE;
event->break_duration = duration;
}
g_ptr_array_add (sit->splices, event);
return sit;
}
/**
* gst_mpegts_scte_splice_event_new:
*
* Allocates and initializes a #GstMpegtsSCTESpliceEvent.
*
* Returns: (transfer full): A newly allocated #GstMpegtsSCTESpliceEvent
*/
GstMpegtsSCTESpliceEvent *
gst_mpegts_scte_splice_event_new (void)
{
GstMpegtsSCTESpliceEvent *event = g_slice_new0 (GstMpegtsSCTESpliceEvent);
/* Non-0 Default values */
event->program_splice_flag = TRUE;
return event;
}
static gboolean
_packetize_sit (GstMpegtsSection * section)
{
gsize length, command_length, descriptor_length;
const GstMpegtsSCTESIT *sit;
GstMpegtsDescriptor *descriptor;
guint32 tmp32;
guint i;
guint8 *data;
sit = gst_mpegts_section_get_scte_sit (section);
if (sit == NULL)
return FALSE;
/* Skip cases we don't handle for now */
if (sit->encrypted_packet) {
GST_WARNING ("SCTE encrypted packet is not supported");
return FALSE;
}
switch (sit->splice_command_type) {
case GST_MTS_SCTE_SPLICE_COMMAND_SCHEDULE:
case GST_MTS_SCTE_SPLICE_COMMAND_TIME:
case GST_MTS_SCTE_SPLICE_COMMAND_PRIVATE:
GST_WARNING ("SCTE command not supported");
return FALSE;
default:
break;
}
/* Smallest splice section are the NULL and bandwith command:
* 14 bytes for the header
* 0 bytes for the command
* 2 bytes for the empty descriptor loop length
* 4 bytes for the CRC */
length = 20;
command_length = 0;
/* Add the size of splices */
for (i = 0; i < sit->splices->len; i++) {
GstMpegtsSCTESpliceEvent *event = g_ptr_array_index (sit->splices, i);
/* There is at least 5 bytes */
command_length += 5;
if (!event->splice_event_cancel_indicator) {
if (!event->program_splice_flag) {
GST_WARNING ("Only SCTE program splices are supported");
return FALSE;
}
/* Add at least 5 bytes for common fields */
command_length += 5;
if (!event->splice_immediate_flag) {
if (event->program_splice_time_specified)
command_length += 5;
else
command_length += 1;
}
if (event->duration_flag)
command_length += 5;
}
}
length += command_length;
/* Calculate size of descriptors */
descriptor_length = 0;
for (i = 0; i < sit->descriptors->len; i++) {
descriptor = g_ptr_array_index (sit->descriptors, i);
descriptor_length += descriptor->length + 2;
}
length += descriptor_length;
/* Max length of SIT section is 4093 bytes */
g_return_val_if_fail (length <= 4093, FALSE);
_packetize_common_section (section, length);
data = section->data + 3;
/* Protocol version (default 0) */
*data++ = 0;
/* 7bits for encryption (not supported : 0) */
/* 33bits for pts_adjustment */
*data++ = (sit->pts_adjustment) >> 32 & 0x01;
GST_WRITE_UINT32_BE (data, sit->pts_adjustment & 0xffffffff);
data += 4;
/* cw_index : 8 bit */
*data++ = sit->cw_index;
/* tier : 12bit
* splice_command_length : 12bit
* splice_command_type : 8 bit */
tmp32 = (sit->tier & 0xfff) << 20;
tmp32 |= (command_length & 0xfff) << 8;
tmp32 |= sit->splice_command_type;
GST_WRITE_UINT32_BE (data, tmp32);
data += 4;
/* Write the events */
for (i = 0; i < sit->splices->len; i++) {
GstMpegtsSCTESpliceEvent *event = g_ptr_array_index (sit->splices, i);
/* splice_event_id : 32bit */
GST_WRITE_UINT32_BE (data, event->splice_event_id);
data += 4;
/* splice_event_cancel_indicator : 1bit
* reserved ; 7bit */
*data++ = event->splice_event_cancel_indicator ? 0xff : 0x7f;
if (!event->splice_event_cancel_indicator) {
/* out_of_network_indicator : 1bit
* program_splice_flag : 1bit
* duration_flag : 1bit
* splice_immediate_flag : 1bit
* reserved : 4bit */
*data++ = (event->out_of_network_indicator << 7) |
(event->program_splice_flag << 6) |
(event->duration_flag << 5) |
(event->splice_immediate_flag << 4) | 0x0f;
if (!event->splice_immediate_flag) {
/* program_splice_time_specified : 1bit
* reserved : 6/7 bit */
if (!event->program_splice_time_specified)
*data++ = 0x7f;
else {
/* time : 33bit */
*data++ = 0xf2 | ((event->program_splice_time >> 32) & 0x1);
GST_WRITE_UINT32_BE (data, event->program_splice_time & 0xffffffff);
data += 4;
}
}
if (event->duration_flag) {
*data = event->break_duration_auto_return ? 0xfe : 0x7e;
*data++ |= (event->break_duration >> 32) & 0x1;
GST_WRITE_UINT32_BE (data, event->break_duration & 0xffffffff);
data += 4;
}
/* unique_program_id : 16bit */
GST_WRITE_UINT16_BE (data, event->unique_program_id);
data += 2;
/* avail_num : 8bit */
*data++ = event->avail_num;
/* avails_expected : 8bit */
*data++ = event->avails_expected;
}
}
/* Descriptors */
GST_WRITE_UINT16_BE (data, descriptor_length);
data += 2;
_packetize_descriptor_array (sit->descriptors, &data);
/* CALCULATE AND WRITE THE CRC ! */
GST_WRITE_UINT32_BE (data, _calc_crc32 (section->data, data - section->data));
return TRUE;
}
/**
* gst_mpegts_section_from_scte_sit:
* @sit: (transfer full): a #GstMpegtsSCTESIT to create the #GstMpegtsSection from
*
* Ownership of @sit is taken. The data in @sit is managed by the #GstMpegtsSection
*
* Returns: (transfer full): the #GstMpegtsSection
*/
GstMpegtsSection *
gst_mpegts_section_from_scte_sit (GstMpegtsSCTESIT * sit, guint16 pid)
{
GstMpegtsSection *section;
g_return_val_if_fail (sit != NULL, NULL);
section = _gst_mpegts_section_init (pid, GST_MTS_TABLE_ID_SCTE_SPLICE);
section->short_section = TRUE;
section->cached_parsed = (gpointer) sit;
section->packetizer = _packetize_sit;
section->destroy_parsed = (GDestroyNotify) _gst_mpegts_scte_sit_free;
section->short_section = TRUE;
return section;
}