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
This commit is contained in:
Jan Schmidt 2011-03-26 15:58:21 +11:00
parent 2721e943e2
commit 9a26173a57
2 changed files with 187 additions and 130 deletions

View File

@ -192,8 +192,8 @@ mpegtsmux_class_init (MpegTsMuxClass * klass)
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_M2TS_MODE, g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_M2TS_MODE,
g_param_spec_boolean ("m2ts-mode", "M2TS(192 bytes) Mode", g_param_spec_boolean ("m2ts-mode", "M2TS(192 bytes) Mode",
"Defines what packet size to use, normal TS format ie .ts(188 bytes) " "Set to TRUE to output Blu-Ray disc format with 192 byte packets. "
"or Blue-Ray disc ie .m2ts(192 bytes).", FALSE, "FALSE for standard TS format with 188 byte packets.", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PAT_INTERVAL, g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PAT_INTERVAL,
@ -810,21 +810,29 @@ mpegtsmux_release_pad (GstElement * element, GstPad * pad)
gst_element_remove_pad (element, pad); gst_element_remove_pad (element, pad);
} }
static gboolean static void
new_packet_cb (guint8 * data, guint len, void *user_data, gint64 new_pcr) new_packet_common_init (MpegTsMux * mux, GstBuffer * buf, guint8 * data,
guint len)
{ {
/* Called when the TsMux has prepared a packet for output. Return FALSE /* Packets should be at least 188 bytes, but check anyway */
* on error */ g_return_if_fail (len >= 2);
MpegTsMux *mux = (MpegTsMux *) user_data;
GstBuffer *buf, *out_buf; if (!mux->streamheader_sent) {
GstFlowReturn ret; guint pid = ((data[1] & 0x1f) << 8) | data[2];
gfloat current_ts; /* if it's a PAT or a PMT */
gint64 m2ts_pcr, pcr_bytes, chunk_bytes; if (pid == 0x00 || (pid >= TSMUX_START_PMT_PID && pid < TSMUX_START_ES_PID)) {
gint64 ts_rate; 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->m2ts_mode == TRUE) {
/* Enters when the m2ts-mode is set true */
buf = gst_buffer_new_and_alloc (M2TS_PACKET_LENGTH);
if (mux->is_delta) { if (mux->is_delta) {
GST_LOG_OBJECT (mux, "marking as delta unit"); GST_LOG_OBJECT (mux, "marking as delta unit");
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT);
@ -832,60 +840,96 @@ new_packet_cb (guint8 * data, guint len, void *user_data, gint64 new_pcr)
GST_DEBUG_OBJECT (mux, "marking as non-delta unit"); GST_DEBUG_OBJECT (mux, "marking as non-delta unit");
mux->is_delta = TRUE; mux->is_delta = TRUE;
} }
}
static gboolean
new_packet_m2ts (MpegTsMux * mux, guint8 * data, guint len, gint64 new_pcr)
{
GstBuffer *buf, *out_buf;
GstFlowReturn ret;
guint64 chunk_bytes;
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)) { if (G_UNLIKELY (buf == NULL)) {
GST_ELEMENT_ERROR (mux, STREAM, MUX,
("Failed allocating output buffer"), (NULL));
mux->last_flow_ret = GST_FLOW_ERROR; mux->last_flow_ret = GST_FLOW_ERROR;
return FALSE; 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 new_packet_common_init (mux, buf, data, len);
of 4 bytes of timestamp */
/* 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); memcpy (GST_BUFFER_DATA (buf) + 4, data, len);
if (new_pcr >= 0) { if (new_pcr < 0) {
/*when there is a pcr value in ts data */ /* If theres no pcr in current ts packet then just add the packet
pcr_bytes = 0; to the adapter for later output when we see a PCR */
if (mux->first_pcr) { GST_LOG_OBJECT (mux, "Accumulating non-PCR packet");
/*Incase of first pcr */ gst_adapter_push (mux->adapter, buf);
/*writing the 4 byte timestamp value */ return TRUE;
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); chunk_bytes = gst_adapter_available (mux->adapter);
if (G_UNLIKELY (chunk_bytes)) { /* 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;
}
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 */ /* calculate rate based on latest and previous pcr values */
ts_rate = ((chunk_bytes * STANDARD_TIME_CLOCK) / (new_pcr - ts_rate = gst_util_uint64_scale (chunk_bytes, CLOCK_FREQ_SCR,
mux->previous_pcr)); (new_pcr - mux->previous_pcr));
GST_LOG_OBJECT (mux, "Processing pending packets with ts_rate %"
G_GUINT64_FORMAT, ts_rate);
while (1) { while (1) {
/*loop till all the accumulated ts packets are transformed to guint64 cur_pcr;
m2ts packets and pushed */
current_ts = ((gfloat) mux->previous_pcr / STANDARD_TIME_CLOCK) + /* Loop, pulling packets of the adapter, updating their 4 byte
((gfloat) pcr_bytes / ts_rate); * timestamp header and pushing */
m2ts_pcr = (((gint64) (STANDARD_TIME_CLOCK * current_ts / 300) &
TWO_POW_33_MINUS1) * 300) + ((gint64) (STANDARD_TIME_CLOCK * /* The header is the bottom 30 bits of the PCR, apparently not
current_ts) % 300); * 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); out_buf = gst_adapter_take_buffer (mux->adapter, M2TS_PACKET_LENGTH);
if (G_UNLIKELY (!out_buf)) if (G_UNLIKELY (!out_buf))
break; break;
gst_buffer_set_caps (out_buf, GST_PAD_CAPS (mux->srcpad)); gst_buffer_set_caps (out_buf, GST_PAD_CAPS (mux->srcpad));
GST_BUFFER_TIMESTAMP (out_buf) = MPEG_SYS_TIME_TO_GSTTIME (cur_pcr);
/*writing the 4 byte timestamp value */ /* Write the 4 byte timestamp value, bottom 30 bits only = PCR */
GST_WRITE_UINT32_BE (GST_BUFFER_DATA (out_buf), m2ts_pcr); GST_WRITE_UINT32_BE (GST_BUFFER_DATA (out_buf), cur_pcr & 0x3FFFFFFF);
GST_LOG_OBJECT (mux, "Outputting a packet of length %d", GST_LOG_OBJECT (mux, "Outputting a packet of length %d PCR %"
M2TS_PACKET_LENGTH); G_GUINT64_FORMAT, M2TS_PACKET_LENGTH, cur_pcr);
ret = gst_pad_push (mux->srcpad, out_buf); ret = gst_pad_push (mux->srcpad, out_buf);
if (G_UNLIKELY (ret != GST_FLOW_OK)) { if (G_UNLIKELY (ret != GST_FLOW_OK)) {
mux->last_flow_ret = ret; mux->last_flow_ret = ret;
@ -893,59 +937,68 @@ new_packet_cb (guint8 * data, guint len, void *user_data, gint64 new_pcr)
} }
pcr_bytes += M2TS_PACKET_LENGTH; pcr_bytes += M2TS_PACKET_LENGTH;
} }
mux->previous_pcr = m2ts_pcr;
} }
} else {
/* If theres no pcr in current ts packet then push the packet /* Finally, output the passed in packet */
to an adapter, which is used to create m2ts packets */ /* Only write the bottom 30 bits of the PCR */
gst_adapter_push (mux->adapter, buf); GST_WRITE_UINT32_BE (GST_BUFFER_DATA (buf), new_pcr & 0x3FFFFFFF);
GST_BUFFER_TIMESTAMP (buf) = MPEG_SYS_TIME_TO_GSTTIME (new_pcr);
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;
} }
} else {
/* In case of Normal TS packets */ 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); GST_LOG_OBJECT (mux, "Outputting a packet of length %d", len);
buf = gst_buffer_new_and_alloc (len); buf = gst_buffer_new_and_alloc (len);
if (G_UNLIKELY (buf == NULL)) { if (G_UNLIKELY (buf == NULL)) {
mux->last_flow_ret = GST_FLOW_ERROR; mux->last_flow_ret = GST_FLOW_ERROR;
return FALSE; return FALSE;
} }
gst_buffer_set_caps (buf, GST_PAD_CAPS (mux->srcpad));
new_packet_common_init (mux, buf, data, len);
memcpy (GST_BUFFER_DATA (buf), data, len); memcpy (GST_BUFFER_DATA (buf), data, len);
GST_BUFFER_TIMESTAMP (buf) = mux->last_ts; 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));
}
}
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;
}
ret = gst_pad_push (mux->srcpad, buf); ret = gst_pad_push (mux->srcpad, buf);
if (G_UNLIKELY (ret != GST_FLOW_OK)) { if (G_UNLIKELY (ret != GST_FLOW_OK)) {
mux->last_flow_ret = ret; mux->last_flow_ret = ret;
return FALSE; return FALSE;
} }
}
return TRUE; 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 static void
mpegtsdemux_set_header_on_caps (MpegTsMux * mux) mpegtsdemux_set_header_on_caps (MpegTsMux * mux)
{ {

View File

@ -163,18 +163,22 @@ struct MpegTsPadData {
GType mpegtsmux_get_type (void); GType mpegtsmux_get_type (void);
#define CLOCK_BASE 9LL #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), \ #define MPEGTIME_TO_GSTTIME(time) (gst_util_uint64_scale ((time), \
GST_MSECOND/10, CLOCK_BASE)) GST_MSECOND/10, CLOCK_BASE))
#define GSTTIME_TO_MPEGTIME(time) (gst_util_uint64_scale ((time), \ #define GSTTIME_TO_MPEGTIME(time) (gst_util_uint64_scale ((time), \
CLOCK_BASE, GST_MSECOND/10)) 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 NORMAL_TS_PACKET_LENGTH 188
#define M2TS_PACKET_LENGTH 192 #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 MAX_PROG_NUMBER 32
#define DEFAULT_PROG_ID 0 #define DEFAULT_PROG_ID 0