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:
parent
2721e943e2
commit
9a26173a57
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user