summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Schmidt <thaytan@noraisin.net>2011-03-26 15:58:21 +1100
committerJan Schmidt <thaytan@noraisin.net>2011-03-26 16:18:22 +1100
commit9a26173a57b05ecb497fe1fca0168b5cbd4c0167 (patch)
treeb374acc5c9ef1ee232f29837d659417becc7eba8
parent2721e943e2b40a00c1a8c37ce978995354c2c2e1 (diff)
Rewrite M2TS packet output
Make sure we only write the bottom 30 bits of the PCR to the m2ts header. Don't use floating point computation for it, and remove weird bit fiddling that messes up the PCR in a way I can't find any justification/documentation for. Don't accidentally lose PCR packets from the output. Fix the description for the m2ts-mode property so it's clear it's a flag, and which setting does what. Fixes: #611061 #644429 Partially fixes: #645006
-rw-r--r--gst/mpegtsmux/mpegtsmux.c299
-rw-r--r--gst/mpegtsmux/mpegtsmux.h12
2 files changed, 184 insertions, 127 deletions
diff --git a/gst/mpegtsmux/mpegtsmux.c b/gst/mpegtsmux/mpegtsmux.c
index 16b39eebd..80cfd4553 100644
--- a/gst/mpegtsmux/mpegtsmux.c
+++ b/gst/mpegtsmux/mpegtsmux.c
@@ -192,8 +192,8 @@ mpegtsmux_class_init (MpegTsMuxClass * klass)
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_M2TS_MODE,
g_param_spec_boolean ("m2ts-mode", "M2TS(192 bytes) Mode",
- "Defines what packet size to use, normal TS format ie .ts(188 bytes) "
- "or Blue-Ray disc ie .m2ts(192 bytes).", FALSE,
+ "Set to TRUE to output Blu-Ray disc format with 192 byte packets. "
+ "FALSE for standard TS format with 188 byte packets.", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PAT_INTERVAL,
@@ -810,142 +810,195 @@ mpegtsmux_release_pad (GstElement * element, GstPad * pad)
gst_element_remove_pad (element, pad);
}
+static void
+new_packet_common_init (MpegTsMux * mux, GstBuffer * buf, guint8 * data,
+ guint len)
+{
+ /* Packets should be at least 188 bytes, but check anyway */
+ g_return_if_fail (len >= 2);
+
+ if (!mux->streamheader_sent) {
+ guint pid = ((data[1] & 0x1f) << 8) | data[2];
+ /* if it's a PAT or a PMT */
+ if (pid == 0x00 || (pid >= TSMUX_START_PMT_PID && pid < TSMUX_START_ES_PID)) {
+ mux->streamheader =
+ g_list_append (mux->streamheader, gst_buffer_copy (buf));
+ } else if (mux->streamheader) {
+ mpegtsdemux_set_header_on_caps (mux);
+ mux->streamheader_sent = TRUE;
+ }
+ }
+
+ /* Set the caps on the buffer only after possibly setting the stream headers
+ * into the pad caps above */
+ gst_buffer_set_caps (buf, GST_PAD_CAPS (mux->srcpad));
+
+ if (mux->is_delta) {
+ GST_LOG_OBJECT (mux, "marking as delta unit");
+ GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
+ } else {
+ GST_DEBUG_OBJECT (mux, "marking as non-delta unit");
+ mux->is_delta = TRUE;
+ }
+}
+
static gboolean
-new_packet_cb (guint8 * data, guint len, void *user_data, gint64 new_pcr)
+new_packet_m2ts (MpegTsMux * mux, guint8 * data, guint len, gint64 new_pcr)
{
- /* Called when the TsMux has prepared a packet for output. Return FALSE
- * on error */
- MpegTsMux *mux = (MpegTsMux *) user_data;
GstBuffer *buf, *out_buf;
GstFlowReturn ret;
- gfloat current_ts;
- gint64 m2ts_pcr, pcr_bytes, chunk_bytes;
- gint64 ts_rate;
+ guint64 chunk_bytes;
- if (mux->m2ts_mode == TRUE) {
- /* Enters when the m2ts-mode is set true */
- buf = gst_buffer_new_and_alloc (M2TS_PACKET_LENGTH);
- if (mux->is_delta) {
- GST_LOG_OBJECT (mux, "marking as delta unit");
- GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
- } else {
- GST_DEBUG_OBJECT (mux, "marking as non-delta unit");
- mux->is_delta = TRUE;
- }
- if (G_UNLIKELY (buf == NULL)) {
- mux->last_flow_ret = GST_FLOW_ERROR;
- return FALSE;
- }
- gst_buffer_set_caps (buf, GST_PAD_CAPS (mux->srcpad));
-
- /* copies the ts data of 188 bytes to the m2ts buffer at an offset
- of 4 bytes of timestamp */
- memcpy (GST_BUFFER_DATA (buf) + 4, data, len);
-
- if (new_pcr >= 0) {
- /*when there is a pcr value in ts data */
- pcr_bytes = 0;
- if (mux->first_pcr) {
- /*Incase of first pcr */
- /*writing the 4 byte timestamp value */
- GST_WRITE_UINT32_BE (GST_BUFFER_DATA (buf), new_pcr);
-
- GST_LOG_OBJECT (mux, "Outputting a packet of length %d",
- M2TS_PACKET_LENGTH);
- ret = gst_pad_push (mux->srcpad, buf);
- if (G_UNLIKELY (ret != GST_FLOW_OK)) {
- mux->last_flow_ret = ret;
- return FALSE;
- }
- mux->first_pcr = FALSE;
- mux->previous_pcr = new_pcr;
- pcr_bytes = M2TS_PACKET_LENGTH;
- }
- chunk_bytes = gst_adapter_available (mux->adapter);
-
- if (G_UNLIKELY (chunk_bytes)) {
- /* calculate rate based on latest and previous pcr values */
- ts_rate = ((chunk_bytes * STANDARD_TIME_CLOCK) / (new_pcr -
- mux->previous_pcr));
- while (1) {
- /*loop till all the accumulated ts packets are transformed to
- m2ts packets and pushed */
- current_ts = ((gfloat) mux->previous_pcr / STANDARD_TIME_CLOCK) +
- ((gfloat) pcr_bytes / ts_rate);
- m2ts_pcr = (((gint64) (STANDARD_TIME_CLOCK * current_ts / 300) &
- TWO_POW_33_MINUS1) * 300) + ((gint64) (STANDARD_TIME_CLOCK *
- current_ts) % 300);
-
- out_buf = gst_adapter_take_buffer (mux->adapter, M2TS_PACKET_LENGTH);
- if (G_UNLIKELY (!out_buf))
- break;
- gst_buffer_set_caps (out_buf, GST_PAD_CAPS (mux->srcpad));
-
- /*writing the 4 byte timestamp value */
- GST_WRITE_UINT32_BE (GST_BUFFER_DATA (out_buf), m2ts_pcr);
-
- GST_LOG_OBJECT (mux, "Outputting a packet of length %d",
- M2TS_PACKET_LENGTH);
- ret = gst_pad_push (mux->srcpad, out_buf);
- if (G_UNLIKELY (ret != GST_FLOW_OK)) {
- mux->last_flow_ret = ret;
- return FALSE;
- }
- pcr_bytes += M2TS_PACKET_LENGTH;
- }
- mux->previous_pcr = m2ts_pcr;
- }
- } else {
- /* If theres no pcr in current ts packet then push the packet
- to an adapter, which is used to create m2ts packets */
- gst_adapter_push (mux->adapter, buf);
- }
- } else {
- /* In case of Normal TS packets */
- GST_LOG_OBJECT (mux, "Outputting a packet of length %d", len);
- buf = gst_buffer_new_and_alloc (len);
- if (G_UNLIKELY (buf == NULL)) {
- mux->last_flow_ret = GST_FLOW_ERROR;
- return FALSE;
+ GST_LOG_OBJECT (mux, "Have buffer with new_pcr=%" G_GINT64_FORMAT " size %d",
+ new_pcr, len);
+
+ buf = gst_buffer_new_and_alloc (M2TS_PACKET_LENGTH);
+ if (G_UNLIKELY (buf == NULL)) {
+ GST_ELEMENT_ERROR (mux, STREAM, MUX,
+ ("Failed allocating output buffer"), (NULL));
+ mux->last_flow_ret = GST_FLOW_ERROR;
+ return FALSE;
+ }
+
+ new_packet_common_init (mux, buf, data, len);
+
+ /* copies the TS data of 188 bytes to the m2ts buffer at an offset
+ of 4 bytes to leave space for writing the timestamp later */
+ memcpy (GST_BUFFER_DATA (buf) + 4, data, len);
+
+ if (new_pcr < 0) {
+ /* If theres no pcr in current ts packet then just add the packet
+ to the adapter for later output when we see a PCR */
+ GST_LOG_OBJECT (mux, "Accumulating non-PCR packet");
+ gst_adapter_push (mux->adapter, buf);
+ return TRUE;
+ }
+
+ chunk_bytes = gst_adapter_available (mux->adapter);
+
+ /* We have a new PCR, output anything in the adapter */
+ if (mux->first_pcr) {
+ /* We can't generate sensible timestamps for anything that might
+ * be in the adapter preceding the first PCR and will hit a divide
+ * by zero, so empty the adapter. This is probably a null op. */
+ gst_adapter_clear (mux->adapter);
+ /* Warn if we threw anything away */
+ if (chunk_bytes) {
+ GST_ELEMENT_WARNING (mux, STREAM, MUX,
+ ("Discarding %d bytes from stream preceding first PCR",
+ chunk_bytes / M2TS_PACKET_LENGTH * NORMAL_TS_PACKET_LENGTH),
+ (NULL));
+ chunk_bytes = 0;
}
- gst_buffer_set_caps (buf, GST_PAD_CAPS (mux->srcpad));
-
- memcpy (GST_BUFFER_DATA (buf), data, len);
- GST_BUFFER_TIMESTAMP (buf) = mux->last_ts;
-
- if (!mux->streamheader_sent) {
- guint pid = ((data[1] & 0x1f) << 8) | data[2];
- /* if it's a PAT or a PMT */
- if (pid == 0x00 ||
- (pid >= TSMUX_START_PMT_PID && pid < TSMUX_START_ES_PID)) {
- mux->streamheader =
- g_list_append (mux->streamheader, gst_buffer_copy (buf));
- } else if (mux->streamheader) {
- mpegtsdemux_set_header_on_caps (mux);
- mux->streamheader_sent = TRUE;
- /* don't unset the streamheaders by pushing old caps */
- gst_buffer_set_caps (buf, GST_PAD_CAPS (mux->srcpad));
+ mux->first_pcr = FALSE;
+ }
+
+ if (chunk_bytes) {
+ /* Start the PCR offset counting at 192 bytes: At the end of the packet
+ * that had the last PCR */
+ guint64 pcr_bytes = M2TS_PACKET_LENGTH, ts_rate;
+
+ /* Include the pending packet size to get the ts_rate right */
+ chunk_bytes += M2TS_PACKET_LENGTH;
+
+ /* calculate rate based on latest and previous pcr values */
+ ts_rate = gst_util_uint64_scale (chunk_bytes, CLOCK_FREQ_SCR,
+ (new_pcr - mux->previous_pcr));
+ GST_LOG_OBJECT (mux, "Processing pending packets with ts_rate %"
+ G_GUINT64_FORMAT, ts_rate);
+
+ while (1) {
+ guint64 cur_pcr;
+
+ /* Loop, pulling packets of the adapter, updating their 4 byte
+ * timestamp header and pushing */
+
+ /* The header is the bottom 30 bits of the PCR, apparently not
+ * encoded into base + ext as in the packets themselves, so
+ * we can just interpolate, mask and insert */
+ cur_pcr = (mux->previous_pcr +
+ gst_util_uint64_scale (pcr_bytes, CLOCK_FREQ_SCR, ts_rate));
+
+ out_buf = gst_adapter_take_buffer (mux->adapter, M2TS_PACKET_LENGTH);
+ if (G_UNLIKELY (!out_buf))
+ break;
+ gst_buffer_set_caps (out_buf, GST_PAD_CAPS (mux->srcpad));
+ GST_BUFFER_TIMESTAMP (out_buf) = MPEG_SYS_TIME_TO_GSTTIME (cur_pcr);
+
+ /* Write the 4 byte timestamp value, bottom 30 bits only = PCR */
+ GST_WRITE_UINT32_BE (GST_BUFFER_DATA (out_buf), cur_pcr & 0x3FFFFFFF);
+
+ GST_LOG_OBJECT (mux, "Outputting a packet of length %d PCR %"
+ G_GUINT64_FORMAT, M2TS_PACKET_LENGTH, cur_pcr);
+ ret = gst_pad_push (mux->srcpad, out_buf);
+ if (G_UNLIKELY (ret != GST_FLOW_OK)) {
+ mux->last_flow_ret = ret;
+ return FALSE;
}
+ pcr_bytes += M2TS_PACKET_LENGTH;
}
+ }
- if (mux->is_delta) {
- GST_LOG_OBJECT (mux, "marking as delta unit");
- GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
- } else {
- GST_DEBUG_OBJECT (mux, "marking as non-delta unit");
- mux->is_delta = TRUE;
- }
+ /* Finally, output the passed in packet */
+ /* Only write the bottom 30 bits of the PCR */
+ GST_WRITE_UINT32_BE (GST_BUFFER_DATA (buf), new_pcr & 0x3FFFFFFF);
+ GST_BUFFER_TIMESTAMP (buf) = MPEG_SYS_TIME_TO_GSTTIME (new_pcr);
- ret = gst_pad_push (mux->srcpad, buf);
- if (G_UNLIKELY (ret != GST_FLOW_OK)) {
- mux->last_flow_ret = ret;
- return FALSE;
- }
+ GST_LOG_OBJECT (mux, "Outputting a packet of length %d PCR %"
+ G_GUINT64_FORMAT, M2TS_PACKET_LENGTH, new_pcr);
+ ret = gst_pad_push (mux->srcpad, buf);
+ if (G_UNLIKELY (ret != GST_FLOW_OK)) {
+ mux->last_flow_ret = ret;
+ return FALSE;
}
+ mux->previous_pcr = new_pcr;
+
return TRUE;
}
+static gboolean
+new_packet_normal_ts (MpegTsMux * mux, guint8 * data, guint len, gint64 new_pcr)
+{
+ GstBuffer *buf;
+ GstFlowReturn ret;
+
+ /* Output a normal TS packet */
+ GST_LOG_OBJECT (mux, "Outputting a packet of length %d", len);
+ buf = gst_buffer_new_and_alloc (len);
+ if (G_UNLIKELY (buf == NULL)) {
+ mux->last_flow_ret = GST_FLOW_ERROR;
+ return FALSE;
+ }
+
+ new_packet_common_init (mux, buf, data, len);
+
+ memcpy (GST_BUFFER_DATA (buf), data, len);
+ GST_BUFFER_TIMESTAMP (buf) = mux->last_ts;
+
+ ret = gst_pad_push (mux->srcpad, buf);
+ if (G_UNLIKELY (ret != GST_FLOW_OK)) {
+ mux->last_flow_ret = ret;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+new_packet_cb (guint8 * data, guint len, void *user_data, gint64 new_pcr)
+{
+ /* Called when the TsMux has prepared a packet for output. Return FALSE
+ * on error */
+ MpegTsMux *mux = (MpegTsMux *) user_data;
+
+ if (mux->m2ts_mode == TRUE) {
+ return new_packet_m2ts (mux, data, len, new_pcr);
+ }
+
+ return new_packet_normal_ts (mux, data, len, new_pcr);
+}
+
static void
mpegtsdemux_set_header_on_caps (MpegTsMux * mux)
{
diff --git a/gst/mpegtsmux/mpegtsmux.h b/gst/mpegtsmux/mpegtsmux.h
index a94b99184..b45dd34aa 100644
--- a/gst/mpegtsmux/mpegtsmux.h
+++ b/gst/mpegtsmux/mpegtsmux.h
@@ -163,18 +163,22 @@ struct MpegTsPadData {
GType mpegtsmux_get_type (void);
#define CLOCK_BASE 9LL
-#define CLOCK_FREQ (CLOCK_BASE * 10000)
+#define CLOCK_FREQ (CLOCK_BASE * 10000) /* 90 kHz PTS clock */
+#define CLOCK_FREQ_SCR (CLOCK_FREQ * 300) /* 27 MHz SCR clock */
#define MPEGTIME_TO_GSTTIME(time) (gst_util_uint64_scale ((time), \
GST_MSECOND/10, CLOCK_BASE))
#define GSTTIME_TO_MPEGTIME(time) (gst_util_uint64_scale ((time), \
CLOCK_BASE, GST_MSECOND/10))
+/* 27 MHz SCR conversions: */
+#define MPEG_SYS_TIME_TO_GSTTIME(time) (gst_util_uint64_scale ((time), \
+ GST_USECOND, CLOCK_FREQ_SCR / 1000000))
+#define GSTTIME_TO_MPEG_SYS_TIME(time) (gst_util_uint64_scale ((time), \
+ CLOCK_FREQ_SCR / 1000000, GST_USECOND))
+
#define NORMAL_TS_PACKET_LENGTH 188
#define M2TS_PACKET_LENGTH 192
-#define STANDARD_TIME_CLOCK 27000000
-/*33 bits as 1 ie 0x1ffffffff*/
-#define TWO_POW_33_MINUS1 ((0xffffffff * 2) - 1)
#define MAX_PROG_NUMBER 32
#define DEFAULT_PROG_ID 0