1 From: Andreas Frisch <fraxinas@opendreambox.org>
2 Date: Sat, 17 Sep 2011 23:29:21 +0200
3 Subject: [PATCH] [mpegtsmux] introduce an alignment property which specifies
4 the amount of packets, that buffers must contain before
5 they are pushed. On EOS, dummy packets are generated and
6 used for padding until the final buffer is also aligned.
7 With a value of 32 given, this allows creating blu-ray
10 From: Andreas Frisch <fraxinas@opendreambox.org>
11 Date: Mon, 28 Mar 2011 10:31:23 +0200
12 Subject: [PATCH] [mpegtsmux] add indexing capabilities to generate a SPN/PTS map on the fly in m2ts-mode
14 diff -u a/gst/mpegtsmux/mpegtsmux.c b/gst/mpegtsmux/mpegtsmux.c
15 --- a/gst/mpegtsmux/mpegtsmux.c 2011-04-21 15:17:35.000000000 +0200
16 +++ b/gst/mpegtsmux/mpegtsmux.c 2011-09-17 23:39:08.671044710 +0200
26 static GstStaticPadTemplate mpegtsmux_sink_factory =
28 static GstStateChangeReturn mpegtsmux_change_state (GstElement * element,
29 GstStateChange transition);
30 static void mpegtsdemux_set_header_on_caps (MpegTsMux * mux);
31 +static void mpegtsmux_set_index (GstElement * element, GstIndex * index);
32 +static GstIndex *mpegtsmux_get_index (GstElement * element);
34 +static GstFormat pts_format;
35 +static GstFormat spn_format;
37 GST_BOILERPLATE (MpegTsMux, mpegtsmux, GstElement, GST_TYPE_ELEMENT);
40 "MPEG Transport Stream Muxer", "Codec/Muxer",
41 "Multiplexes media streams into an MPEG Transport Stream",
42 "Fluendo <contact@fluendo.com>");
45 + gst_format_register ("PTS", "MPEG System Presentation Time Stamp");
46 + spn_format = gst_format_register ("SPN", "Source Packet Number");
51 gstelement_class->release_pad = mpegtsmux_release_pad;
52 gstelement_class->change_state = mpegtsmux_change_state;
54 + gstelement_class->set_index = GST_DEBUG_FUNCPTR (mpegtsmux_set_index);
55 + gstelement_class->get_index = GST_DEBUG_FUNCPTR (mpegtsmux_get_index);
57 g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PROG_MAP,
58 g_param_spec_boxed ("prog-map", "Program map",
59 "A GstStructure specifies the mapping from elementary streams to programs",
61 "Set the interval (in ticks of the 90kHz clock) for writing out the PMT table",
62 1, G_MAXUINT, TSMUX_DEFAULT_PMT_INTERVAL,
63 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
65 + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_ALIGNMENT,
66 + g_param_spec_uint ("alignment", "packet alignment",
67 + "Queue this amount of ts/m2ts packets before pushing buffer. On EOS, pad with dummy packets until aligned. Default: 32 for m2ts streams, else disabled.",
68 + 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
75 mux->streamheader = NULL;
76 mux->streamheader_sent = FALSE;
78 + mux->last_key_ts = 0;
79 + mux->last_key_spn = 0;
82 + mux->element_index = NULL;
83 + mux->element_index_writer_id = -1;
85 + mux->arbitrary_align = FALSE;
86 + mux->alignment_adapter = gst_adapter_new ();
87 + mux->packets_per_buffer = 0;
92 g_list_free (mux->streamheader);
93 mux->streamheader = NULL;
95 + if (mux->alignment_adapter) {
96 + gst_adapter_clear (mux->alignment_adapter);
97 + g_object_unref (mux->alignment_adapter);
98 + mux->alignment_adapter = NULL;
101 + if (mux->element_index)
102 + gst_object_unref (mux->element_index);
104 GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
107 @@ -322,12 +361,27 @@
108 walk = g_slist_next (walk);
111 + case ARG_ALIGNMENT:
112 + mux->packets_per_buffer = g_value_get_uint (value);
113 + mux->arbitrary_align = TRUE;
116 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
122 +get_packets_per_buffer (MpegTsMux * mux)
124 + if (mux->arbitrary_align == TRUE) {
125 + return mux->packets_per_buffer;
126 + } else if (mux->m2ts_mode) {
127 + return BDMV_PACKETS_PER_BUFFER;
129 + return DEFAULT_PACKETS_PER_BUFFER;
133 gst_mpegtsmux_get_property (GObject * object, guint prop_id,
134 GValue * value, GParamSpec * pspec)
136 case ARG_PMT_INTERVAL:
137 g_value_set_uint (value, mux->pmt_interval);
139 + case ARG_ALIGNMENT:
140 + g_value_set_uint (value, get_packets_per_buffer (mux));
143 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
149 +mpegtsmux_set_index (GstElement * element, GstIndex * index)
151 + MpegTsMux *mux = GST_MPEG_TSMUX (element);
153 + GST_OBJECT_LOCK (mux);
154 + if (mux->element_index)
155 + gst_object_unref (mux->element_index);
156 + mux->element_index = index ? gst_object_ref (index) : NULL;
157 + GST_OBJECT_UNLOCK (mux);
158 + GST_DEBUG_OBJECT (mux, "Set index %" GST_PTR_FORMAT, mux->element_index);
159 + gst_index_add_format (index, mux->element_index_writer_id, pts_format);
160 + gst_index_add_format (index, mux->element_index_writer_id, spn_format);
164 +mpegtsmux_get_index (GstElement * element)
166 + GstIndex *result = NULL;
167 + MpegTsMux *mux = GST_MPEG_TSMUX (element);
169 + GST_OBJECT_LOCK (mux);
170 + if (mux->element_index)
171 + result = gst_object_ref (mux->element_index);
172 + GST_OBJECT_UNLOCK (mux);
174 + GST_DEBUG_OBJECT (mux, "Returning index %" GST_PTR_FORMAT, result);
180 release_buffer_cb (guint8 * data, void *user_data)
182 GstBuffer *buf = (GstBuffer *) user_data;
187 + if (mux->element_index) {
188 + gboolean parsed = FALSE;
189 + if (ts_data->stream->is_video_stream) {
190 + if (gst_structure_get_boolean (s, "parsed", &parsed) && parsed) {
191 + if (mux->element_index_writer_id == -1) {
192 + gst_index_get_writer_id (mux->element_index, GST_OBJECT (mux),
193 + &mux->element_index_writer_id);
194 + GST_INFO_OBJECT (mux,
195 + "created GstIndex writer_id = %d for PID 0x%04x",
196 + mux->element_index_writer_id, ts_data->pid);
199 + GST_WARNING_OBJECT (pad,
200 + "Indexing capability for PID=0x%04x disabled - parsed input stream is required!",
206 gst_caps_unref (caps);
208 @@ -642,12 +748,113 @@
212 - gst_buffer_unref (gst_collect_pads_pop (mux->collect, c_best));
214 + if ((buffer = gst_collect_pads_pop (mux->collect, c_best)))
215 + gst_buffer_unref (buffer);
221 +static GstFlowReturn
222 +aligned_push (MpegTsMux * mux, GstBuffer * buf)
224 + guint accu_bytes, packet_length;
225 + GstBuffer *out_buf;
227 + if (get_packets_per_buffer (mux) == 0) {
228 + return gst_pad_push (mux->srcpad, buf);
231 + packet_length = mux->m2ts_mode ? M2TS_PACKET_LENGTH : NORMAL_TS_PACKET_LENGTH;
232 + gst_adapter_push (mux->alignment_adapter, buf);
234 + accu_bytes = gst_adapter_available (mux->alignment_adapter);
235 + GST_DEBUG_OBJECT (mux,
236 + "Accumulating packet in alignment adapter, accu_bytes=%i", accu_bytes);
238 + if (accu_bytes == get_packets_per_buffer (mux) * packet_length) {
239 + out_buf = gst_adapter_take_buffer (mux->alignment_adapter, accu_bytes);
240 + gst_buffer_set_caps (out_buf, GST_PAD_CAPS (mux->srcpad));
241 + gst_adapter_clear (mux->alignment_adapter);
242 + GST_DEBUG_OBJECT (mux,
243 + "Accumulated desired amount of packets in alignment unit, handing off %i bytes",
245 + return gst_pad_push (mux->srcpad, out_buf);
246 + } else if (accu_bytes > get_packets_per_buffer (mux) * packet_length) {
247 + GST_WARNING_OBJECT (mux, "Packet alignment error!");
248 + gst_adapter_clear (mux->alignment_adapter);
249 + return GST_FLOW_CUSTOM_ERROR;
252 + return GST_FLOW_OK;
256 +mpegtsmux_eos_align (MpegTsMux * mux)
258 + guint accu_bytes, packet_length, packets_needed, dummy_packet_count;
259 + guint32 m2ts_header;
260 + guint continuity_counter;
261 + unsigned char header[4];
265 + accu_bytes = gst_adapter_available (mux->alignment_adapter);
266 + packet_length = mux->m2ts_mode ? M2TS_PACKET_LENGTH : NORMAL_TS_PACKET_LENGTH;
267 + packets_needed = get_packets_per_buffer (mux) - accu_bytes / packet_length;
269 + if (get_packets_per_buffer (mux) == 0 || accu_bytes == 0) {
273 + GST_DEBUG_OBJECT (mux,
274 + "received EOS - %i bytes accumulated in alignment adapter -> %i dummy packets needed for padding!\n",
275 + accu_bytes, packets_needed);
277 + if (mux->m2ts_mode) {
278 + gst_adapter_copy (mux->alignment_adapter, header,
279 + accu_bytes - packet_length, 4);
280 + m2ts_header = GST_READ_UINT32_BE (header);
281 + gst_adapter_copy (mux->alignment_adapter, header,
282 + accu_bytes - packet_length + 7, 1);
284 + gst_adapter_copy (mux->alignment_adapter, header,
285 + accu_bytes - packet_length + 3, 1);
288 + continuity_counter = header[0] & 0xF;
290 + for (dummy_packet_count = 0; dummy_packet_count < packets_needed;
291 + dummy_packet_count++) {
292 + buf = gst_buffer_new_and_alloc (packet_length);
293 + if (mux->m2ts_mode) {
294 + // monotonically increase m2ts_header
296 + GST_WRITE_UINT32_BE (GST_BUFFER_DATA (buf), m2ts_header);
297 + p = GST_BUFFER_DATA (buf) + 4;
299 + p = GST_BUFFER_DATA (buf);
301 + GST_WRITE_UINT8 (p++, TSMUX_SYNC_BYTE);
303 + GST_WRITE_UINT16_BE (p, 0x1FFF);
305 + // adaptation field exists | no payload exists | continuity counter
306 + GST_WRITE_UINT8 (p++, 0x20 + ((++continuity_counter) & 0xF));
307 + // adaptation field length | flags
308 + GST_WRITE_UINT16_BE (p, 0xB700);
310 + // adaptation field
311 + memset (p, 0xFF, 0xB6);
313 + aligned_push (mux, buf);
314 + GST_LOG_OBJECT (mux,
315 + "generated dummy packet %i with m2ts_header=0x%x, contiuity=0x%02x\n",
316 + dummy_packet_count, m2ts_header, continuity_counter);
320 #define COLLECT_DATA_PAD(collect_data) (((GstCollectData *)(collect_data))->pad)
323 @@ -679,20 +886,19 @@
325 GST_ELEMENT_ERROR (mux, STREAM, MUX,
326 ("Stream on pad %" GST_PTR_FORMAT
327 - " is not associated with any program", best), (NULL));
328 + " is not associated with any program", COLLECT_DATA_PAD (best)),
330 return GST_FLOW_ERROR;
333 if (G_UNLIKELY (prog->pcr_stream == NULL)) {
335 - /* Take the first data stream for the PCR */
336 - GST_DEBUG_OBJECT (COLLECT_DATA_PAD (best),
337 - "Use stream (pid=%d) from pad as PCR for program (prog_id = %d)",
338 - MPEG_TS_PAD_DATA (best)->pid, MPEG_TS_PAD_DATA (best)->prog_id);
339 + /* Take the first data stream for the PCR */
340 + GST_DEBUG_OBJECT (COLLECT_DATA_PAD (best),
341 + "Use stream (pid=%d) from pad as PCR for program (prog_id = %d)",
342 + MPEG_TS_PAD_DATA (best)->pid, MPEG_TS_PAD_DATA (best)->prog_id);
344 - /* Set the chosen PCR stream */
345 - tsmux_program_set_pcr_stream (prog, best->stream);
347 + /* Set the chosen PCR stream */
348 + tsmux_program_set_pcr_stream (prog, best->stream);
351 g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
353 G_GINT64_FORMAT, GST_TIME_ARGS (best->cur_ts), pts);
356 + if (G_UNLIKELY (!delta))
357 + mux->last_key_ts = best->cur_ts;
359 tsmux_stream_add_data (best->stream, GST_BUFFER_DATA (buf),
360 GST_BUFFER_SIZE (buf), buf, pts, -1, !delta);
361 best->queued_buf = NULL;
364 /* FIXME: Drain all remaining streams */
366 + mpegtsmux_eos_align (mux);
367 gst_pad_push_event (mux->srcpad, gst_event_new_eos ());
372 GST_DEBUG_OBJECT (mux, "marking as non-delta unit");
373 mux->is_delta = TRUE;
374 + mux->last_key_spn = mux->spn_count;
380 new_packet_common_init (mux, buf, data, len);
384 /* copies the TS data of 188 bytes to the m2ts buffer at an offset
385 of 4 bytes to leave space for writing the timestamp later */
386 memcpy (GST_BUFFER_DATA (buf) + 4, data, len);
387 @@ -925,12 +1138,22 @@
388 gst_buffer_set_caps (out_buf, GST_PAD_CAPS (mux->srcpad));
389 GST_BUFFER_TIMESTAMP (out_buf) = MPEG_SYS_TIME_TO_GSTTIME (cur_pcr);
391 + if (mux->element_index) {
392 + if (!GST_BUFFER_FLAG_IS_SET (out_buf, GST_BUFFER_FLAG_DELTA_UNIT)) {
393 + gst_index_add_association (mux->element_index,
394 + mux->element_index_writer_id,
395 + GST_ASSOCIATION_FLAG_KEY_UNIT, spn_format,
396 + mux->last_key_spn, pts_format,
397 + GSTTIME_TO_MPEGTIME (mux->last_key_ts), NULL);
401 /* Write the 4 byte timestamp value, bottom 30 bits only = PCR */
402 GST_WRITE_UINT32_BE (GST_BUFFER_DATA (out_buf), cur_pcr & 0x3FFFFFFF);
404 GST_LOG_OBJECT (mux, "Outputting a packet of length %d PCR %"
405 G_GUINT64_FORMAT, M2TS_PACKET_LENGTH, cur_pcr);
406 - ret = gst_pad_push (mux->srcpad, out_buf);
407 + ret = aligned_push (mux, out_buf);
408 if (G_UNLIKELY (ret != GST_FLOW_OK)) {
409 mux->last_flow_ret = ret;
413 GST_LOG_OBJECT (mux, "Outputting a packet of length %d PCR %"
414 G_GUINT64_FORMAT, M2TS_PACKET_LENGTH, new_pcr);
415 - ret = gst_pad_push (mux->srcpad, buf);
416 + ret = aligned_push (mux, buf);
417 if (G_UNLIKELY (ret != GST_FLOW_OK)) {
418 mux->last_flow_ret = ret;
421 memcpy (GST_BUFFER_DATA (buf), data, len);
422 GST_BUFFER_TIMESTAMP (buf) = mux->last_ts;
424 - ret = gst_pad_push (mux->srcpad, buf);
425 + ret = aligned_push (mux, buf);
426 if (G_UNLIKELY (ret != GST_FLOW_OK)) {
427 mux->last_flow_ret = ret;
429 @@ -1074,6 +1297,8 @@
430 case GST_STATE_CHANGE_READY_TO_NULL:
432 gst_adapter_clear (mux->adapter);
433 + if (mux->alignment_adapter)
434 + gst_adapter_clear (mux->alignment_adapter);
438 diff -u gst-original/mpegtsmux/mpegtsmux.h b/gst/mpegtsmux/mpegtsmux.h
439 --- gst-original/mpegtsmux/mpegtsmux.h 2011-04-08 01:35:37.000000000 +0200
440 +++ b/gst/mpegtsmux/mpegtsmux.h 2011-09-17 23:39:06.427894850 +0200
444 gboolean streamheader_sent;
447 + guint64 last_key_ts;
448 + guint32 last_key_spn;
449 + GstIndex *element_index;
450 + gint element_index_writer_id;
452 + gboolean arbitrary_align;
453 + guint packets_per_buffer;
454 + GstAdapter *alignment_adapter;
457 struct MpegTsMuxClass {
459 #define MAX_PROG_NUMBER 32
460 #define DEFAULT_PROG_ID 0
462 +#define DEFAULT_PACKETS_PER_BUFFER 0
463 +#define BDMV_PACKETS_PER_BUFFER 32