/* * gst-scte-section.c - * Copyright (C) 2019 Centricular ltd * Author: Edward Hervey * * 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 #include #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 * * This contains the %GstMpegtsSection relevent to SCTE specifications. */ /* 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; }