If enabled, the payloader won't transmit empty frames.
Can be tested using:
opusenc dtx=true bitrate-type=vbr ! rtpopuspay dtx=true
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/967>
"presence": "always"
}
},
- "properties": {},
+ "properties": {
+ "dtx": {
+ "blurb": "If enabled, the payloader will not transmit empty packets",
+ "conditionally-available": false,
+ "construct": false,
+ "construct-only": false,
+ "controllable": false,
+ "default": "false",
+ "mutable": "playing",
+ "readable": true,
+ "type": "gboolean",
+ "writable": true
+ }
+ },
"rank": "primary"
},
"rtppcmadepay": {
GST_DEBUG_CATEGORY_STATIC (rtpopuspay_debug);
#define GST_CAT_DEFAULT (rtpopuspay_debug)
+enum
+{
+ PROP_0,
+ PROP_DTX,
+};
+
+#define DEFAULT_DTX FALSE
static GstStaticPadTemplate gst_rtp_opus_pay_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (rtpopuspay, "rtpopuspay",
GST_RANK_PRIMARY, GST_TYPE_RTP_OPUS_PAY, rtp_element_init (plugin));
+#define GST_RTP_OPUS_PAY_CAST(obj) ((GstRtpOPUSPay *)(obj))
+
+static void
+gst_rtp_opus_pay_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ GstRtpOPUSPay *self = GST_RTP_OPUS_PAY (object);
+
+ switch (prop_id) {
+ case PROP_DTX:
+ self->dtx = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_rtp_opus_pay_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ GstRtpOPUSPay *self = GST_RTP_OPUS_PAY (object);
+
+ switch (prop_id) {
+ case PROP_DTX:
+ g_value_set_boolean (value, self->dtx);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
static void
gst_rtp_opus_pay_class_init (GstRtpOPUSPayClass * klass)
{
GstRTPBasePayloadClass *gstbasertppayload_class;
GstElementClass *element_class;
+ GObjectClass *gobject_class;
gstbasertppayload_class = (GstRTPBasePayloadClass *) klass;
element_class = GST_ELEMENT_CLASS (klass);
+ gobject_class = (GObjectClass *) klass;
gstbasertppayload_class->set_caps = gst_rtp_opus_pay_setcaps;
gstbasertppayload_class->get_caps = gst_rtp_opus_pay_getcaps;
gstbasertppayload_class->handle_buffer = gst_rtp_opus_pay_handle_buffer;
+ gobject_class->set_property = gst_rtp_opus_pay_set_property;
+ gobject_class->get_property = gst_rtp_opus_pay_get_property;
+
gst_element_class_add_static_pad_template (element_class,
&gst_rtp_opus_pay_src_template);
gst_element_class_add_static_pad_template (element_class,
&gst_rtp_opus_pay_sink_template);
+ /**
+ * GstRtpOPUSPay:dtx:
+ *
+ * If enabled, the payloader will not transmit empty packets.
+ *
+ * Since: 1.20
+ */
+ g_object_class_install_property (gobject_class, PROP_DTX,
+ g_param_spec_boolean ("dtx", "Discontinuous Transmission",
+ "If enabled, the payloader will not transmit empty packets",
+ DEFAULT_DTX,
+ G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING |
+ G_PARAM_STATIC_STRINGS));
+
gst_element_class_set_static_metadata (element_class,
"RTP Opus payloader",
"Codec/Payloader/Network/RTP",
static void
gst_rtp_opus_pay_init (GstRtpOPUSPay * rtpopuspay)
{
+ rtpopuspay->dtx = DEFAULT_DTX;
}
static gboolean
gst_rtp_opus_pay_handle_buffer (GstRTPBasePayload * basepayload,
GstBuffer * buffer)
{
+ GstRtpOPUSPay *self = GST_RTP_OPUS_PAY_CAST (basepayload);
GstBuffer *outbuf;
GstClockTime pts, dts, duration;
+ /* DTX packets are zero-length frames, with a 1 or 2-bytes header */
+ if (self->dtx && gst_buffer_get_size (buffer) <= 2) {
+ GST_LOG_OBJECT (self,
+ "discard empty buffer as DTX is enabled: %" GST_PTR_FORMAT, buffer);
+ gst_buffer_unref (buffer);
+ return GST_FLOW_OK;
+ }
+
pts = GST_BUFFER_PTS (buffer);
dts = GST_BUFFER_DTS (buffer);
duration = GST_BUFFER_DURATION (buffer);
struct _GstRtpOPUSPay
{
GstRTPBasePayload payload;
+
+ gboolean dtx;
};
struct _GstRtpOPUSPayClass
#include <gst/check/gstharness.h>
#include <gst/audio/audio.h>
#include <gst/base/base.h>
+#include <gst/rtp/gstrtpbuffer.h>
#include <stdlib.h>
#define RELEASE_ELEMENT(x) if(x) {gst_object_unref(x); x = NULL;}
GST_END_TEST;
+static guint16
+pull_rtp_buffer (GstHarness * h)
+{
+ gint16 seq;
+ GstBuffer *buf;
+ GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
+
+ buf = gst_harness_try_pull (h);
+ fail_unless (buf);
+
+ fail_unless (gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp));
+ seq = gst_rtp_buffer_get_seq (&rtp);
+ gst_rtp_buffer_unmap (&rtp);
+
+ gst_buffer_unref (buf);
+ return seq;
+}
+
+static void
+test_rtp_opus_dtx (gboolean dtx)
+{
+ GstHarness *h;
+ GstBuffer *buf;
+ /* generated with a muted mic using:
+ * gst-launch-1.0 pulsesrc ! opusenc dtx=true bitrate-type=vbr ! fakesink silent=false dump=true -v
+ */
+ static const guint8 opus_empty[] = { 0xf8 };
+ static const guint8 opus_frame[] = { 0xf8, 0xff, 0xfe };
+ guint16 seq, expected_seq;
+
+ h = gst_harness_new_parse ("rtpopuspay");
+ fail_unless (h);
+
+ gst_harness_set (h, "rtpopuspay", "dtx", dtx, NULL);
+
+ gst_harness_set_caps_str (h,
+ "audio/x-opus, rate=48000, channels=1, channel-mapping-family=0",
+ "application/x-rtp, media=audio, clock-rate=48000, encoding-name=OPUS, sprop-stereo=(string)0, encoding-params=(string)2, sprop-maxcapturerate=(string)48000, payload=96");
+
+ /* push first opus frame */
+ buf =
+ gst_buffer_new_wrapped (g_memdup (opus_frame, sizeof (opus_frame)),
+ sizeof (opus_frame));
+ fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+ seq = pull_rtp_buffer (h);
+ expected_seq = seq + 1;
+
+ /* push empty frame */
+ buf =
+ gst_buffer_new_wrapped (g_memdup (opus_empty, sizeof (opus_empty)),
+ sizeof (opus_empty));
+ fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+ if (dtx) {
+ /* buffer is not transmitted if dtx is enabled */
+ buf = gst_harness_try_pull (h);
+ fail_if (buf);
+ } else {
+ seq = pull_rtp_buffer (h);
+ fail_unless_equals_int (seq, expected_seq);
+ expected_seq++;
+ }
+
+ /* push second opus frame */
+ buf =
+ gst_buffer_new_wrapped (g_memdup (opus_frame, sizeof (opus_frame)),
+ sizeof (opus_frame));
+ fail_unless_equals_int (gst_harness_push (h, buf), GST_FLOW_OK);
+ seq = pull_rtp_buffer (h);
+ fail_unless_equals_int (seq, expected_seq);
+
+ gst_harness_teardown (h);
+}
+
+GST_START_TEST (rtp_opus_dtx_disabled)
+{
+ test_rtp_opus_dtx (FALSE);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (rtp_opus_dtx_enabled)
+{
+ test_rtp_opus_dtx (TRUE);
+}
+
+GST_END_TEST;
+
/*
* Creates the test suite.
*
tcase_add_test (tc_chain, rtp_g729);
tcase_add_test (tc_chain, rtp_gst_custom_event);
tcase_add_test (tc_chain, rtp_vorbis_renegotiate);
+ tcase_add_test (tc_chain, rtp_opus_dtx_disabled);
+ tcase_add_test (tc_chain, rtp_opus_dtx_enabled);
return s;
}