mpegtsmux: Expose bitrate property

This allows outputting a Transport Stream with a constant bitrate,
by inserting null packets.
This commit is contained in:
Mathieu Duponchelle 2019-04-22 22:11:29 +02:00 committed by Mathieu Duponchelle
parent 4d53a7ac09
commit 07235bbf46
7 changed files with 106 additions and 3 deletions

View file

@ -115,7 +115,8 @@ enum
PROP_PAT_INTERVAL, PROP_PAT_INTERVAL,
PROP_PMT_INTERVAL, PROP_PMT_INTERVAL,
PROP_ALIGNMENT, PROP_ALIGNMENT,
PROP_SI_INTERVAL PROP_SI_INTERVAL,
PROP_BITRATE,
}; };
#define MPEGTSMUX_DEFAULT_ALIGNMENT -1 #define MPEGTSMUX_DEFAULT_ALIGNMENT -1
@ -305,6 +306,13 @@ mpegtsmux_class_init (MpegTsMuxClass * klass)
"Set the interval (in ticks of the 90kHz clock) for writing out the Service" "Set the interval (in ticks of the 90kHz clock) for writing out the Service"
"Information tables", 1, G_MAXUINT, TSMUX_DEFAULT_SI_INTERVAL, "Information tables", 1, G_MAXUINT, TSMUX_DEFAULT_SI_INTERVAL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BITRATE,
g_param_spec_uint64 ("bitrate", "Bitrate (in bits per second)",
"Set the target bitrate, will insert null packets as padding "
" to achieve multiplex-wide constant bitrate",
0, G_MAXUINT64, TSMUX_DEFAULT_BITRATE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
} }
static void static void
@ -338,6 +346,7 @@ mpegtsmux_init (MpegTsMux * mux)
mux->si_interval = TSMUX_DEFAULT_SI_INTERVAL; mux->si_interval = TSMUX_DEFAULT_SI_INTERVAL;
mux->prog_map = NULL; mux->prog_map = NULL;
mux->alignment = MPEGTSMUX_DEFAULT_ALIGNMENT; mux->alignment = MPEGTSMUX_DEFAULT_ALIGNMENT;
mux->bitrate = TSMUX_DEFAULT_BITRATE;
/* initial state */ /* initial state */
mpegtsmux_reset (mux, TRUE); mpegtsmux_reset (mux, TRUE);
@ -511,6 +520,11 @@ gst_mpegtsmux_set_property (GObject * object, guint prop_id,
mux->si_interval = g_value_get_uint (value); mux->si_interval = g_value_get_uint (value);
tsmux_set_si_interval (mux->tsmux, mux->si_interval); tsmux_set_si_interval (mux->tsmux, mux->si_interval);
break; break;
case PROP_BITRATE:
mux->bitrate = g_value_get_uint64 (value);
if (mux->tsmux)
tsmux_set_bitrate (mux->tsmux, mux->bitrate);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -542,6 +556,9 @@ gst_mpegtsmux_get_property (GObject * object, guint prop_id,
case PROP_SI_INTERVAL: case PROP_SI_INTERVAL:
g_value_set_uint (value, mux->si_interval); g_value_set_uint (value, mux->si_interval);
break; break;
case PROP_BITRATE:
g_value_set_uint64 (value, mux->bitrate);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;

View file

@ -139,6 +139,7 @@ struct MpegTsMux {
guint pmt_interval; guint pmt_interval;
gint alignment; gint alignment;
guint si_interval; guint si_interval;
guint64 bitrate;
/* state */ /* state */
gboolean first; gboolean first;

View file

@ -634,6 +634,8 @@ tsmux_packet_out (TsMux * mux, GstBuffer * buf, gint64 pcr)
return TRUE; return TRUE;
} }
mux->n_bytes += gst_buffer_get_size (buf);
return mux->write_func (buf, mux->write_func_data, pcr); return mux->write_func (buf, mux->write_func_data, pcr);
} }
@ -1019,6 +1021,67 @@ tsmux_write_si (TsMux * mux)
} }
static void
tsmux_write_null_ts_header (guint8 * buf)
{
*buf++ = TSMUX_SYNC_BYTE;
*buf++ = 0x1f;
*buf++ = 0xff;
*buf++ = 0x10;
}
static gboolean
pad_stream (TsMux * mux, TsMuxStream * stream, gint64 cur_ts)
{
guint64 bitrate;
GstBuffer *buf = NULL;
GstMapInfo map;
gboolean ret = TRUE;
do {
if (GST_CLOCK_STIME_IS_VALID (cur_ts)) {
GstClockTimeDiff diff;
if (!GST_CLOCK_STIME_IS_VALID (stream->first_ts))
stream->first_ts = cur_ts;
diff = GST_CLOCK_DIFF (stream->first_ts, cur_ts);
if (diff) {
bitrate =
gst_util_uint64_scale (mux->n_bytes * 8, TSMUX_CLOCK_FREQ, diff);
GST_LOG ("Transport stream bitrate: %" G_GUINT64_FORMAT, bitrate);
if (bitrate < mux->bitrate) {
GST_LOG_OBJECT (mux, "Padding transport stream");
if (!tsmux_get_buffer (mux, &buf)) {
ret = FALSE;
goto done;
}
gst_buffer_map (buf, &map, GST_MAP_READ);
tsmux_write_null_ts_header (map.data);
gst_buffer_unmap (buf, &map);
if (!(ret = tsmux_packet_out (mux, buf, -1)))
goto done;
}
} else {
break;
}
} else {
break;
}
} while (bitrate < mux->bitrate);
done:
return ret;
}
/** /**
* tsmux_write_stream_packet: * tsmux_write_stream_packet:
* @mux: a #TsMux * @mux: a #TsMux
@ -1052,6 +1115,11 @@ tsmux_write_stream_packet (TsMux * mux, TsMuxStream * stream)
else else
cur_ts = tsmux_stream_get_pts (stream); cur_ts = tsmux_stream_get_pts (stream);
if (mux->bitrate) {
if (!pad_stream (mux, stream, cur_ts))
goto fail;
}
cur_pcr = 0; cur_pcr = 0;
if (cur_ts != G_MININT64) { if (cur_ts != G_MININT64) {
TS_DEBUG ("TS for PCR stream is %" G_GINT64_FORMAT, cur_ts); TS_DEBUG ("TS for PCR stream is %" G_GINT64_FORMAT, cur_ts);
@ -1165,9 +1233,10 @@ tsmux_write_stream_packet (TsMux * mux, TsMuxStream * stream)
/* ERRORS */ /* ERRORS */
fail: fail:
{ {
if (buf) {
gst_buffer_unmap (buf, &map); gst_buffer_unmap (buf, &map);
if (buf)
gst_buffer_unref (buf); gst_buffer_unref (buf);
}
return FALSE; return FALSE;
} }
} }
@ -1312,3 +1381,9 @@ tsmux_write_pmt (TsMux * mux, TsMuxProgram * program)
return tsmux_section_write_packet (GINT_TO_POINTER (GST_MPEGTS_SECTION_PMT), return tsmux_section_write_packet (GINT_TO_POINTER (GST_MPEGTS_SECTION_PMT),
&program->pmt, mux); &program->pmt, mux);
} }
void
tsmux_set_bitrate (TsMux * mux, guint64 bitrate)
{
mux->bitrate = bitrate;
}

View file

@ -178,6 +178,9 @@ struct TsMux {
/* scratch space for writing ES_info descriptors */ /* scratch space for writing ES_info descriptors */
guint8 es_info_buf[TSMUX_MAX_ES_INFO_LENGTH]; guint8 es_info_buf[TSMUX_MAX_ES_INFO_LENGTH];
guint64 bitrate;
guint64 n_bytes;
}; };
/* create/free new muxer session */ /* create/free new muxer session */
@ -191,6 +194,7 @@ void tsmux_set_pat_interval (TsMux *mux, guint interval);
guint tsmux_get_pat_interval (TsMux *mux); guint tsmux_get_pat_interval (TsMux *mux);
void tsmux_resend_pat (TsMux *mux); void tsmux_resend_pat (TsMux *mux);
guint16 tsmux_get_new_pid (TsMux *mux); guint16 tsmux_get_new_pid (TsMux *mux);
void tsmux_set_bitrate (TsMux *mux, guint64 bitrate);
/* pid/program management */ /* pid/program management */
TsMuxProgram * tsmux_program_new (TsMux *mux, gint prog_id); TsMuxProgram * tsmux_program_new (TsMux *mux, gint prog_id);

View file

@ -121,6 +121,8 @@ G_BEGIN_DECLS
#define TSMUX_DEFAULT_PMT_INTERVAL (TSMUX_CLOCK_FREQ / 10) #define TSMUX_DEFAULT_PMT_INTERVAL (TSMUX_CLOCK_FREQ / 10)
/* SI interval (1/10th sec) */ /* SI interval (1/10th sec) */
#define TSMUX_DEFAULT_SI_INTERVAL (TSMUX_CLOCK_FREQ / 10) #define TSMUX_DEFAULT_SI_INTERVAL (TSMUX_CLOCK_FREQ / 10)
/* Bitrate (bits per second) */
#define TSMUX_DEFAULT_BITRATE 0
typedef struct TsMuxPacketInfo TsMuxPacketInfo; typedef struct TsMuxPacketInfo TsMuxPacketInfo;
typedef struct TsMuxProgram TsMuxProgram; typedef struct TsMuxProgram TsMuxProgram;

View file

@ -224,6 +224,8 @@ tsmux_stream_new (guint16 pid, TsMuxStreamType stream_type)
break; break;
} }
stream->first_ts = GST_CLOCK_STIME_NONE;
stream->last_pts = GST_CLOCK_STIME_NONE; stream->last_pts = GST_CLOCK_STIME_NONE;
stream->last_dts = GST_CLOCK_STIME_NONE; stream->last_dts = GST_CLOCK_STIME_NONE;

View file

@ -197,6 +197,8 @@ struct TsMuxStream {
gint64 last_dts; gint64 last_dts;
gint64 last_pts; gint64 last_pts;
gint64 first_ts;
/* count of programs using this as PCR */ /* count of programs using this as PCR */
gint pcr_ref; gint pcr_ref;
/* Next time PCR should be written */ /* Next time PCR should be written */