diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gst-scte-section.c b/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gst-scte-section.c index ebc810aa05..4721b72119 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gst-scte-section.c +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gst-scte-section.c @@ -467,6 +467,8 @@ gst_mpegts_scte_sit_new (void) sit->descriptors = g_ptr_array_new_with_free_func ((GDestroyNotify) gst_mpegts_descriptor_free); + sit->is_running_time = TRUE; + return sit; } @@ -483,6 +485,9 @@ gst_mpegts_scte_null_new (void) GstMpegtsSCTESIT *sit = gst_mpegts_scte_sit_new (); sit->splice_command_type = GST_MTS_SCTE_SPLICE_COMMAND_NULL; + + sit->is_running_time = TRUE; + return sit; } @@ -506,6 +511,8 @@ gst_mpegts_scte_cancel_new (guint32 event_id) event->splice_event_cancel_indicator = TRUE; g_ptr_array_add (sit->splices, event); + sit->is_running_time = TRUE; + return sit; } @@ -539,6 +546,8 @@ gst_mpegts_scte_splice_in_new (guint32 event_id, GstClockTime splice_time) } g_ptr_array_add (sit->splices, event); + sit->is_running_time = TRUE; + return sit; } @@ -582,6 +591,8 @@ gst_mpegts_scte_splice_out_new (guint32 event_id, GstClockTime splice_time, } g_ptr_array_add (sit->splices, event); + sit->is_running_time = TRUE; + return sit; } diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gst-scte-section.h b/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gst-scte-section.h index 498df348b6..af10bece22 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gst-scte-section.h +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gst-scte-section.h @@ -185,10 +185,6 @@ struct _GstMpegtsSCTESIT guint16 splice_command_length; - /* When encrypted, or when encountering an unknown command type, - * we may still want to pass the sit through */ - gboolean fully_parsed; - GstMpegtsSCTESpliceCommandType splice_command_type; /* For time_signal commands */ @@ -198,6 +194,13 @@ struct _GstMpegtsSCTESIT GPtrArray *splices; GPtrArray *descriptors; + + /* When encrypted, or when encountering an unknown command type, + * we may still want to pass the sit through */ + gboolean fully_parsed; + /* When the SIT was constructed by the application, splice times + * are in running_time and must be translated before packetizing */ + gboolean is_running_time; }; GST_MPEGTS_API diff --git a/subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmux.c b/subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmux.c index c01ae16db4..d60afb0147 100644 --- a/subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmux.c +++ b/subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmux.c @@ -1420,6 +1420,82 @@ gst_base_ts_mux_release_pad (GstElement * element, GstPad * pad) GST_ELEMENT_CLASS (parent_class)->release_pad (element, pad); } +/* GstAggregator implementation */ + +static void +request_keyframe (GstBaseTsMux * mux, GstClockTime running_time) +{ + GList *l; + GST_OBJECT_LOCK (mux); + + for (l = GST_ELEMENT_CAST (mux)->sinkpads; l; l = l->next) { + gst_pad_push_event (GST_PAD (l->data), + gst_video_event_new_upstream_force_key_unit (running_time, TRUE, 0)); + } + + GST_OBJECT_UNLOCK (mux); +} + +static const guint32 crc_tab[256] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +static guint32 +_calc_crc32 (const guint8 * data, guint datalen) +{ + gint i; + guint32 crc = 0xffffffff; + + for (i = 0; i < datalen; i++) { + crc = (crc << 8) ^ crc_tab[((crc >> 24) ^ *data++) & 0xff]; + } + return crc; +} + +#define MPEGTIME_TO_GSTTIME(t) ((t) * (guint64)100000 / 9) + static GstMpegtsSCTESpliceEvent * copy_splice (GstMpegtsSCTESpliceEvent * splice) { @@ -1439,94 +1515,149 @@ deep_copy_sit (const GstMpegtsSCTESIT * sit) return sit_copy; } -/* GstAggregator implementation */ - +/* Takes ownership of @section. + * + * This function is a bit complex because the SCTE sections can + * have various origins: + * + * * Sections created by the application with the gst_mpegts_scte_*_new() + * API. The splice times / durations contained by these are expressed + * in the GStreamer running time domain, and must be translated to + * our local PES time domain. In this case, we will packetize the section + * ourselves. + * + * * Sections passed through from tsdemux: this case is complicated as + * splice times in the incoming stream may be encrypted, with pts_adjustment + * being the only timing field guaranteed *not* to be encrypted. In this + * case, the original binary data (section->data) will be reinjected as is + * in the output stream, with pts_adjustment adjusted. tsdemux provides us + * with the pts_offset it introduces, the difference between the original + * PES PTSs and the running times it outputs. + * + * Additionally, in either of these cases when the splice times aren't encrypted + * we want to make use of those to request keyframes. For the passthrough case, + * as the splice times are left untouched tsdemux provides us with the running + * times the section originally referred to. We cannot calculate it locally + * because we would need to have access to the information that the timestamps + * in the original PES domain have wrapped around, and how many times they have + * done so. While we could probably make educated guesses, tsdemux (more specifically + * mpegtspacketizer) already keeps track of that, and it seemed more logical to + * perform the calculation there and forward it alongside the downstream events. + * + * Finally, while we can't request keyframes at splice points in the encrypted + * case, if the input stream was compliant in that regard and no reencoding took + * place the splice times will still match with valid splice points, it is up + * to the application to ensure that that is the case. + */ static void -request_keyframe (GstBaseTsMux * mux, GstClockTime running_time) +handle_scte35_section (GstBaseTsMux * mux, GstMpegtsSection * section, + guint64 mpeg_pts_offset, GstStructure * rtime_map) { - GList *l; - GST_OBJECT_LOCK (mux); - - for (l = GST_ELEMENT_CAST (mux)->sinkpads; l; l = l->next) { - gst_pad_push_event (GST_PAD (l->data), - gst_video_event_new_upstream_force_key_unit (running_time, TRUE, 0)); - } - - GST_OBJECT_UNLOCK (mux); -} - -/* Takes ownership of @section */ -static void -handle_scte35_section (GstBaseTsMux * mux, GstMpegtsSection * section) -{ - const GstMpegtsSCTESIT *sit; - GstMpegtsSCTESIT *sit_copy; + GstMpegtsSCTESIT *sit; guint i; gboolean forward = TRUE; + guint64 pts_adjust; + guint8 *section_data; + guint8 *crc; + gboolean translate = FALSE; - sit = gst_mpegts_section_get_scte_sit (section); - sit_copy = deep_copy_sit (sit); + sit = (GstMpegtsSCTESIT *) gst_mpegts_section_get_scte_sit (section); - switch (sit_copy->splice_command_type) { + /* When the application injects manually constructed splice events, + * their time domain is the GStreamer running time, we receive them + * unpacketized and translate the fields in the SIT to local PTS. + * + * We make a copy of the SIT in order to make sure we can rewrite it. + */ + if (sit->is_running_time) { + sit = deep_copy_sit (sit); + translate = TRUE; + } + + switch (sit->splice_command_type) { case GST_MTS_SCTE_SPLICE_COMMAND_NULL: /* We implement heartbeating ourselves */ forward = FALSE; break; case GST_MTS_SCTE_SPLICE_COMMAND_SCHEDULE: - /* Only translate timestamps and forward, splice_insert + /* No need to request keyframes at this point, splice_insert * messages will precede the future splice points and we - * can request keyframes then. + * can request keyframes then. Only translate if needed. */ - for (i = 0; i < sit_copy->splices->len; i++) { - GstMpegtsSCTESpliceEvent *sevent = - g_ptr_array_index (sit_copy->splices, i); - if (sevent->program_splice_time_specified) { - sevent->program_splice_time = - GSTTIME_TO_MPEGTIME (sevent->program_splice_time) + - TS_MUX_CLOCK_BASE; - } - if (sevent->duration_flag) { - sevent->break_duration = GSTTIME_TO_MPEGTIME (sevent->break_duration); + if (translate) { + for (i = 0; i < sit->splices->len; i++) { + GstMpegtsSCTESpliceEvent *sevent = + g_ptr_array_index (sit->splices, i); + + if (sevent->program_splice_time_specified) + sevent->program_splice_time = + GSTTIME_TO_MPEGTIME (sevent->program_splice_time) + + TS_MUX_CLOCK_BASE; + + if (sevent->duration_flag) + sevent->break_duration = + GSTTIME_TO_MPEGTIME (sevent->break_duration); } } break; case GST_MTS_SCTE_SPLICE_COMMAND_INSERT: /* We want keyframes at splice points */ - for (i = 0; i < sit_copy->splices->len; i++) { - guint64 running_time = GST_CLOCK_TIME_NONE; + if (sit->fully_parsed && (rtime_map || translate)) { - GstMpegtsSCTESpliceEvent *sevent = - g_ptr_array_index (sit_copy->splices, i); - if (sevent->program_splice_time_specified) { - GST_DEBUG_OBJECT (mux, - "Requesting keyframe for splice point at %" GST_TIME_FORMAT, - GST_TIME_ARGS (sevent->program_splice_time)); - running_time = sevent->program_splice_time; - request_keyframe (mux, running_time); - sevent->program_splice_time = - GSTTIME_TO_MPEGTIME (sevent->program_splice_time) + - TS_MUX_CLOCK_BASE; - } else { - GST_DEBUG_OBJECT (mux, - "Requesting keyframe for immediate splice point"); - request_keyframe (mux, GST_CLOCK_TIME_NONE); - } + for (i = 0; i < sit->splices->len; i++) { + guint64 running_time = GST_CLOCK_TIME_NONE; - if (sevent->duration_flag) { - /* Even if auto_return is FALSE, when a break_duration is specified it - * is intended as a redundancy mechanism in case the follow-up - * splice insert goes missing. - * - * Schedule a keyframe at that point (if we can calculate its position - * accurately). - */ - if (GST_CLOCK_STIME_IS_VALID (running_time)) { + GstMpegtsSCTESpliceEvent *sevent = + g_ptr_array_index (sit->splices, i); + if (sevent->program_splice_time_specified) { + if (rtime_map) { + gchar *field_name = g_strdup_printf ("event-%u-splice-time", + sevent->splice_event_id); + if (gst_structure_get_uint64 (rtime_map, field_name, + &running_time)) { + GST_DEBUG_OBJECT (mux, + "Requesting keyframe for splice point at %" GST_TIME_FORMAT, + GST_TIME_ARGS (running_time)); + request_keyframe (mux, running_time); + } + g_free (field_name); + } else { + g_assert (translate == TRUE); + running_time = sevent->program_splice_time; + GST_DEBUG_OBJECT (mux, + "Requesting keyframe for splice point at %" GST_TIME_FORMAT, + GST_TIME_ARGS (running_time)); + request_keyframe (mux, running_time); + sevent->program_splice_time = + GSTTIME_TO_MPEGTIME (running_time) + TS_MUX_CLOCK_BASE; + } + } else { GST_DEBUG_OBJECT (mux, - "Requesting keyframe for end of break at %" GST_TIME_FORMAT, - GST_TIME_ARGS (running_time + sevent->break_duration)); - request_keyframe (mux, running_time + sevent->break_duration); + "Requesting keyframe for immediate splice point"); + request_keyframe (mux, GST_CLOCK_TIME_NONE); + } + + if (sevent->duration_flag) { + if (translate) { + sevent->break_duration = + GSTTIME_TO_MPEGTIME (sevent->break_duration); + } + + /* Even if auto_return is FALSE, when a break_duration is specified it + * is intended as a redundancy mechanism in case the follow-up + * splice insert goes missing. + * + * Schedule a keyframe at that point (if we can calculate its position + * accurately). + */ + if (GST_CLOCK_TIME_IS_VALID (running_time)) { + running_time += MPEGTIME_TO_GSTTIME (sevent->break_duration); + GST_DEBUG_OBJECT (mux, + "Requesting keyframe for end of break at %" GST_TIME_FORMAT, + GST_TIME_ARGS (running_time)); + request_keyframe (mux, running_time); + } } - sevent->break_duration = GSTTIME_TO_MPEGTIME (sevent->break_duration); } } break; @@ -1539,37 +1670,55 @@ handle_scte35_section (GstBaseTsMux * mux, GstMpegtsSection * section) * of the requirement in 10.3.4 that a keyframe should not be created * when the signal contains only a time_descriptor. */ - for (i = 0; i < sit_copy->descriptors->len; i++) { - GstMpegtsDescriptor *descriptor = - g_ptr_array_index (sit_copy->descriptors, i); + if (sit->fully_parsed && (rtime_map || translate)) { + for (i = 0; i < sit->descriptors->len; i++) { + GstMpegtsDescriptor *descriptor = + g_ptr_array_index (sit->descriptors, i); - switch (descriptor->tag) { - case GST_MTS_SCTE_DESC_AVAIL: - case GST_MTS_SCTE_DESC_DTMF: - case GST_MTS_SCTE_DESC_SEGMENTATION: - do_request_keyframes = TRUE; - break; - case GST_MTS_SCTE_DESC_TIME: - case GST_MTS_SCTE_DESC_AUDIO: + switch (descriptor->tag) { + case GST_MTS_SCTE_DESC_AVAIL: + case GST_MTS_SCTE_DESC_DTMF: + case GST_MTS_SCTE_DESC_SEGMENTATION: + do_request_keyframes = TRUE; + break; + case GST_MTS_SCTE_DESC_TIME: + case GST_MTS_SCTE_DESC_AUDIO: + break; + } + + if (do_request_keyframes) break; } - if (do_request_keyframes) - break; - } + if (sit->splice_time_specified) { + GstClockTime running_time = GST_CLOCK_TIME_NONE; - if (sit_copy->splice_time_specified) { - if (do_request_keyframes) { + if (rtime_map) { + if (do_request_keyframes + && gst_structure_get_uint64 (rtime_map, "splice-time", + &running_time)) { + GST_DEBUG_OBJECT (mux, + "Requesting keyframe for time signal at %" GST_TIME_FORMAT, + GST_TIME_ARGS (running_time)); + request_keyframe (mux, running_time); + } + } else { + g_assert (translate); + running_time = sit->splice_time; + sit->splice_time = + GSTTIME_TO_MPEGTIME (running_time) + TS_MUX_CLOCK_BASE; + if (do_request_keyframes) { + GST_DEBUG_OBJECT (mux, + "Requesting keyframe for time signal at %" GST_TIME_FORMAT, + GST_TIME_ARGS (running_time)); + request_keyframe (mux, running_time); + } + } + } else if (do_request_keyframes) { GST_DEBUG_OBJECT (mux, - "Requesting keyframe for time signal at %" GST_TIME_FORMAT, - GST_TIME_ARGS (sit_copy->splice_time)); - request_keyframe (mux, sit_copy->splice_time); + "Requesting keyframe for immediate time signal"); + request_keyframe (mux, GST_CLOCK_TIME_NONE); } - sit_copy->splice_time = - GSTTIME_TO_MPEGTIME (sit_copy->splice_time) + TS_MUX_CLOCK_BASE; - } else if (do_request_keyframes) { - GST_DEBUG_OBJECT (mux, "Requesting keyframe for immediate time signal"); - request_keyframe (mux, GST_CLOCK_TIME_NONE); } break; } @@ -1586,13 +1735,47 @@ handle_scte35_section (GstBaseTsMux * mux, GstMpegtsSection * section) return; } - GST_OBJECT_LOCK (mux); - GST_DEBUG_OBJECT (mux, "Storing SCTE section"); - if (mux->pending_scte35_section) - gst_mpegts_section_unref (mux->pending_scte35_section); - mux->pending_scte35_section = - gst_mpegts_section_from_scte_sit (sit_copy, mux->scte35_pid); - GST_OBJECT_UNLOCK (mux); + if (!translate) { + g_assert (section->data); + /* Calculate the final adjustment, as a sum of: + * - The adjustment in the original packet + * - The offset introduced between the original local PTS + * and the GStreamer PTS output by tsdemux + * - Our own 1-hour offset + */ + pts_adjust = sit->pts_adjustment + mpeg_pts_offset + TS_MUX_CLOCK_BASE; + pts_adjust &= 0x1ffffffff; + section_data = g_memdup (section->data, section->section_length); + section_data[4] |= pts_adjust >> 32; + section_data[5] = pts_adjust >> 24; + section_data[6] = pts_adjust >> 16; + section_data[7] = pts_adjust >> 8; + section_data[8] = pts_adjust; + + /* Now rewrite our checksum */ + crc = section_data + section->section_length - 4; + GST_WRITE_UINT32_BE (crc, _calc_crc32 (section_data, crc - section_data)); + + GST_OBJECT_LOCK (mux); + GST_DEBUG_OBJECT (mux, "Storing SCTE section"); + if (mux->pending_scte35_section) + gst_mpegts_section_unref (mux->pending_scte35_section); + mux->pending_scte35_section = + gst_mpegts_section_new (mux->scte35_pid, section_data, + section->section_length); + GST_OBJECT_UNLOCK (mux); + + gst_mpegts_section_unref (section); + } else { + GST_OBJECT_LOCK (mux); + GST_DEBUG_OBJECT (mux, "Storing SCTE section"); + gst_mpegts_section_unref (section); + if (mux->pending_scte35_section) + gst_mpegts_section_unref (mux->pending_scte35_section); + mux->pending_scte35_section = + gst_mpegts_section_from_scte_sit (sit, mux->scte35_pid);; + GST_OBJECT_UNLOCK (mux); + } } static gboolean @@ -1607,7 +1790,7 @@ gst_base_ts_mux_send_event (GstElement * element, GstEvent * event) GST_DEBUG ("Received event with mpegts section"); if (section->section_type == GST_MPEGTS_SECTION_SCTE_SIT) { - handle_scte35_section (mux, section); + handle_scte35_section (mux, section, 0, NULL); } else { /* TODO: Check that the section type is supported */ tsmux_add_mpegts_si_section (mux->tsmux, section); @@ -1656,9 +1839,17 @@ gst_base_ts_mux_sink_event (GstAggregator * agg, GstAggregatorPad * agg_pad, gst_structure_get (s, "section", GST_TYPE_MPEGTS_SECTION, §ion, NULL); - if (section) { - handle_scte35_section (mux, section); + guint64 mpeg_pts_offset = 0; + GstStructure *rtime_map = NULL; + + gst_structure_get (s, "running-time-map", GST_TYPE_STRUCTURE, + &rtime_map, NULL); + gst_structure_get_uint64 (s, "mpeg-pts-offset", &mpeg_pts_offset); + + handle_scte35_section (mux, section, mpeg_pts_offset, rtime_map); + if (rtime_map) + gst_structure_free (rtime_map); mux->last_scte35_event_seqnum = gst_event_get_seqnum (event); } else { GST_WARNING_OBJECT (ts_pad,