mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-25 01:30:38 +00:00
2b75eb85c4
It may be possible that only one of the two sink pads is linked in that case, the events need to be created from the other pad.
1116 lines
32 KiB
C
1116 lines
32 KiB
C
/*
|
|
* GStreamer - GStreamer SRTP decoder
|
|
*
|
|
* Copyright 2009-2011 Collabora Ltd.
|
|
* @author: Gabriel Millaire <gabriel.millaire@collabora.co.uk>
|
|
* @author: Olivier Crete <olivier.crete@collabora.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* Alternatively, the contents of this file may be used under the
|
|
* GNU Lesser General Public License Version 2.1 (the "LGPL"), in
|
|
* which case the following provisions apply instead of the ones
|
|
* mentioned above:
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-srtpdec
|
|
* @see_also: srtpenc
|
|
*
|
|
* gstrtpdec acts as a decoder that removes security from SRTP and SRTCP
|
|
* packets (encryption and authentication) and out RTP and RTCP. It
|
|
* receives packet of type 'application/x-srtp' or 'application/x-srtcp'
|
|
* on its sink pad, and outs packets of type 'application/x-rtp' or
|
|
* 'application/x-rtcp' on its sink pad.
|
|
*
|
|
* For each packet received, it checks if the internal SSRC is in the list
|
|
* of streams already in use. If this is not the case, it sends a signal to
|
|
* the user to get the needed parameters to create a new stream : master
|
|
* key, encryption and authentication mecanisms for both RTP and RTCP. If
|
|
* the user can't provide those parameters, the buffer is dropped and a
|
|
* warning is emitted.
|
|
*
|
|
* This element uses libsrtp library. The encryption and authentication
|
|
* mecanisms available are :
|
|
*
|
|
* Encryption
|
|
* - AES_128_ICM (default, maximum security)
|
|
* - STRONGHOLD_CIPHER (same as AES_128_ICM)
|
|
* - NULL
|
|
*
|
|
* Authentication
|
|
* - HMAC_SHA1 (default, maximum protection)
|
|
* - STRONGHOLD_AUTH (same as HMAC_SHA1)
|
|
* - NULL
|
|
*
|
|
* Note that for SRTP protection, authentication is mandatory (non-null)
|
|
* if encryption is used (non-null).
|
|
*
|
|
* Each packet received is first analysed (checked for valid SSRC) then
|
|
* its buffer is unprotected with libsrtp, then pushed on the source pad.
|
|
* If protection failed or the stream could not be created, the buffer
|
|
* is dropped and a warning is emitted.
|
|
*
|
|
* When the maximum usage of the master key is reached, a soft-limit
|
|
* signal is sent to the user, and new parameters (master key) are needed
|
|
* in return. If the hard limit is reached, a flag is set and every
|
|
* subsequent packet is dropped, until a new key is set and the stream
|
|
* has been updated.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example pipelines</title>
|
|
* |[
|
|
* gst-launch-1.0 udpsrc port=5004 caps='application/x-srtp, payload=(int)8, ssrc=(uint)1356955624, srtp-key=(buffer)012345678901234567890123456789012345678901234567890123456789, srtp-cipher=(string)aes-128-icm, srtp-auth=(string)hmac-sha1-80, srtcp-cipher=(string)aes-128-icm, srtcp-auth=(string)hmac-sha1-80' ! srtpdec ! rtppcmadepay ! alawdec ! pulsesink
|
|
* ]| Receive PCMA SRTP packets through UDP using caps to specify
|
|
* master key and protection.
|
|
* |[
|
|
* gst-launch-1.0 audiotestsrc ! alawenc ! rtppcmapay ! 'application/x-rtp, payload=(int)8, ssrc=(uint)1356955624' ! srtpenc key="012345678901234567890123456789012345678901234567890123456789" ! udpsink port=5004
|
|
* ]| Send PCMA SRTP packets through UDP, nothing how the SSRC is forced so
|
|
* that the receiver will recognize it.
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <gst/gst.h>
|
|
#include <gst/rtp/gstrtpbuffer.h>
|
|
#include <string.h>
|
|
|
|
#include "gstsrtp.h"
|
|
#include "gstsrtp-enumtypes.h"
|
|
|
|
#include "gstsrtpdec.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_srtp_dec_debug);
|
|
#define GST_CAT_DEFAULT gst_srtp_dec_debug
|
|
|
|
/* Filter signals and args */
|
|
enum
|
|
{
|
|
SIGNAL_REQUEST_KEY = 1,
|
|
SIGNAL_CLEAR_KEYS,
|
|
SIGNAL_SOFT_LIMIT,
|
|
SIGNAL_HARD_LIMIT,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0
|
|
};
|
|
|
|
/* the capabilities of the inputs and outputs.
|
|
*
|
|
* describe the real formats here.
|
|
*/
|
|
static GstStaticPadTemplate rtp_sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("rtp_sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("application/x-srtp")
|
|
);
|
|
|
|
static GstStaticPadTemplate rtp_src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("rtp_src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("application/x-rtp")
|
|
);
|
|
|
|
static GstStaticPadTemplate rtcp_sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("rtcp_sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("application/x-srtcp")
|
|
);
|
|
|
|
static GstStaticPadTemplate rtcp_src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("rtcp_src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("application/x-rtcp")
|
|
);
|
|
|
|
static guint gst_srtp_dec_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
G_DEFINE_TYPE (GstSrtpDec, gst_srtp_dec, GST_TYPE_ELEMENT);
|
|
|
|
static void gst_srtp_dec_clear_streams (GstSrtpDec * filter);
|
|
|
|
static gboolean gst_srtp_dec_sink_event_rtp (GstPad * pad, GstObject * parent,
|
|
GstEvent * event);
|
|
static gboolean gst_srtp_dec_sink_event_rtcp (GstPad * pad, GstObject * parent,
|
|
GstEvent * event);
|
|
|
|
static gboolean gst_srtp_dec_sink_query_rtp (GstPad * pad, GstObject * parent,
|
|
GstQuery * query);
|
|
static gboolean gst_srtp_dec_sink_query_rtcp (GstPad * pad,
|
|
GstObject * parent, GstQuery * query);
|
|
|
|
|
|
static GstIterator *gst_srtp_dec_iterate_internal_links_rtp (GstPad * pad,
|
|
GstObject * parent);
|
|
static GstIterator *gst_srtp_dec_iterate_internal_links_rtcp (GstPad * pad,
|
|
GstObject * parent);
|
|
|
|
static GstFlowReturn gst_srtp_dec_chain_rtp (GstPad * pad,
|
|
GstObject * parent, GstBuffer * buf);
|
|
static GstFlowReturn gst_srtp_dec_chain_rtcp (GstPad * pad,
|
|
GstObject * parent, GstBuffer * buf);
|
|
|
|
static GstStateChangeReturn gst_srtp_dec_change_state (GstElement * element,
|
|
GstStateChange transition);
|
|
|
|
static GstSrtpDecSsrcStream *request_key_with_signal (GstSrtpDec * filter,
|
|
guint32 ssrc, gint signal);
|
|
|
|
struct _GstSrtpDecSsrcStream
|
|
{
|
|
guint32 ssrc;
|
|
|
|
GstCaps *caps;
|
|
GstBuffer *key;
|
|
GstSrtpCipherType rtp_cipher;
|
|
GstSrtpAuthType rtp_auth;
|
|
GstSrtpCipherType rtcp_cipher;
|
|
GstSrtpAuthType rtcp_auth;
|
|
};
|
|
|
|
#define STREAM_HAS_CRYPTO(stream) \
|
|
(stream->rtp_cipher != GST_SRTP_CIPHER_NULL || \
|
|
stream->rtcp_cipher != GST_SRTP_CIPHER_NULL || \
|
|
stream->rtp_auth != GST_SRTP_AUTH_NULL || \
|
|
stream->rtcp_auth != GST_SRTP_AUTH_NULL)
|
|
|
|
/* initialize the srtpdec's class */
|
|
static void
|
|
gst_srtp_dec_class_init (GstSrtpDecClass * klass)
|
|
{
|
|
GstElementClass *gstelement_class;
|
|
|
|
gstelement_class = (GstElementClass *) klass;
|
|
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&rtp_src_template));
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&rtp_sink_template));
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&rtcp_src_template));
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&rtcp_sink_template));
|
|
|
|
gst_element_class_set_static_metadata (gstelement_class, "SRTP decoder",
|
|
"Filter/Network/SRTP",
|
|
"A SRTP and SRTCP decoder",
|
|
"Gabriel Millaire <millaire.gabriel@collabora.com>");
|
|
|
|
gstelement_class->change_state =
|
|
GST_DEBUG_FUNCPTR (gst_srtp_dec_change_state);
|
|
klass->clear_streams = GST_DEBUG_FUNCPTR (gst_srtp_dec_clear_streams);
|
|
|
|
/**
|
|
* GstSrtpDec::request-key:
|
|
* @gstsrtpdec: the element on which the signal is emitted
|
|
* @ssrc: The unique SSRC of the stream
|
|
*
|
|
* Signal emited to get the parameters relevant to stream
|
|
* with @ssrc. User should provide the key and the RTP and
|
|
* RTCP encryption ciphers and authentication, and return
|
|
* them wrapped in a GstCaps.
|
|
*/
|
|
gst_srtp_dec_signals[SIGNAL_REQUEST_KEY] =
|
|
g_signal_new ("request-key", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT);
|
|
|
|
/**
|
|
* GstSrtpDec::clear-keys:
|
|
* @gstsrtpdec: the element on which the signal is emitted
|
|
*
|
|
* Clear the internal list of streams
|
|
*/
|
|
gst_srtp_dec_signals[SIGNAL_CLEAR_KEYS] =
|
|
g_signal_new ("clear-keys", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
G_STRUCT_OFFSET (GstSrtpDecClass, clear_streams), NULL, NULL, NULL,
|
|
G_TYPE_NONE, 0, G_TYPE_NONE);
|
|
|
|
/**
|
|
* GstSrtpDec::soft-limit:
|
|
* @gstsrtpdec: the element on which the signal is emitted
|
|
* @ssrc: The unique SSRC of the stream
|
|
*
|
|
* Signal emited when the stream with @ssrc has reached the
|
|
* soft limit of utilisation of it's master encryption key.
|
|
* User should provide a new key and new RTP and RTCP encryption
|
|
* ciphers and authentication, and return them wrapped in a
|
|
* GstCaps.
|
|
*/
|
|
gst_srtp_dec_signals[SIGNAL_SOFT_LIMIT] =
|
|
g_signal_new ("soft-limit", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT);
|
|
|
|
/**
|
|
* GstSrtpDec::hard-limit:
|
|
* @gstsrtpdec: the element on which the signal is emitted
|
|
* @ssrc: The unique SSRC of the stream
|
|
*
|
|
* Signal emited when the stream with @ssrc has reached the
|
|
* hard limit of utilisation of it's master encryption key.
|
|
* User should provide a new key and new RTP and RTCP encryption
|
|
* ciphers and authentication, and return them wrapped in a
|
|
* GstCaps. If user could not provide those parameters or signal
|
|
* is not answered, the buffers of this stream will be dropped.
|
|
*/
|
|
gst_srtp_dec_signals[SIGNAL_HARD_LIMIT] =
|
|
g_signal_new ("hard-limit", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, GST_TYPE_CAPS, 1, G_TYPE_UINT);
|
|
}
|
|
|
|
/* initialize the new element
|
|
* instantiate pads and add them to element
|
|
* set pad calback functions
|
|
* initialize instance structure
|
|
*/
|
|
static void
|
|
gst_srtp_dec_init (GstSrtpDec * filter)
|
|
{
|
|
filter->rtp_sinkpad =
|
|
gst_pad_new_from_static_template (&rtp_sink_template, "rtp_sink");
|
|
gst_pad_set_event_function (filter->rtp_sinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_srtp_dec_sink_event_rtp));
|
|
gst_pad_set_query_function (filter->rtp_sinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_srtp_dec_sink_query_rtp));
|
|
gst_pad_set_iterate_internal_links_function (filter->rtp_sinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_srtp_dec_iterate_internal_links_rtp));
|
|
gst_pad_set_chain_function (filter->rtp_sinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_srtp_dec_chain_rtp));
|
|
|
|
filter->rtp_srcpad =
|
|
gst_pad_new_from_static_template (&rtp_src_template, "rtp_src");
|
|
gst_pad_set_iterate_internal_links_function (filter->rtp_srcpad,
|
|
GST_DEBUG_FUNCPTR (gst_srtp_dec_iterate_internal_links_rtp));
|
|
|
|
gst_pad_set_element_private (filter->rtp_sinkpad, filter->rtp_srcpad);
|
|
gst_pad_set_element_private (filter->rtp_srcpad, filter->rtp_sinkpad);
|
|
|
|
gst_element_add_pad (GST_ELEMENT (filter), filter->rtp_sinkpad);
|
|
gst_element_add_pad (GST_ELEMENT (filter), filter->rtp_srcpad);
|
|
|
|
|
|
filter->rtcp_sinkpad =
|
|
gst_pad_new_from_static_template (&rtcp_sink_template, "rtcp_sink");
|
|
gst_pad_set_event_function (filter->rtcp_sinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_srtp_dec_sink_event_rtcp));
|
|
gst_pad_set_query_function (filter->rtcp_sinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_srtp_dec_sink_query_rtcp));
|
|
gst_pad_set_iterate_internal_links_function (filter->rtcp_sinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_srtp_dec_iterate_internal_links_rtcp));
|
|
gst_pad_set_chain_function (filter->rtcp_sinkpad,
|
|
GST_DEBUG_FUNCPTR (gst_srtp_dec_chain_rtcp));
|
|
|
|
filter->rtcp_srcpad =
|
|
gst_pad_new_from_static_template (&rtcp_src_template, "rtcp_src");
|
|
gst_pad_set_iterate_internal_links_function (filter->rtcp_srcpad,
|
|
GST_DEBUG_FUNCPTR (gst_srtp_dec_iterate_internal_links_rtcp));
|
|
|
|
gst_pad_set_element_private (filter->rtcp_sinkpad, filter->rtcp_srcpad);
|
|
gst_pad_set_element_private (filter->rtcp_srcpad, filter->rtcp_sinkpad);
|
|
|
|
gst_element_add_pad (GST_ELEMENT (filter), filter->rtcp_sinkpad);
|
|
gst_element_add_pad (GST_ELEMENT (filter), filter->rtcp_srcpad);
|
|
|
|
filter->first_session = TRUE;
|
|
}
|
|
|
|
static void
|
|
remove_stream_by_ssrc (GstSrtpDec * filter, guint32 ssrc)
|
|
{
|
|
GstSrtpDecSsrcStream *stream = NULL;
|
|
|
|
stream = g_hash_table_lookup (filter->streams, GUINT_TO_POINTER (ssrc));
|
|
|
|
if (stream) {
|
|
srtp_remove_stream (filter->session, ssrc);
|
|
g_hash_table_remove (filter->streams, GUINT_TO_POINTER (ssrc));
|
|
}
|
|
}
|
|
|
|
static GstSrtpDecSsrcStream *
|
|
find_stream_by_ssrc (GstSrtpDec * filter, guint32 ssrc)
|
|
{
|
|
return g_hash_table_lookup (filter->streams, GUINT_TO_POINTER (ssrc));
|
|
}
|
|
|
|
|
|
/* get info from buffer caps
|
|
*/
|
|
static GstSrtpDecSsrcStream *
|
|
get_stream_from_caps (GstSrtpDec * filter, GstCaps * caps, guint32 ssrc)
|
|
{
|
|
GstSrtpDecSsrcStream *stream;
|
|
GstStructure *s;
|
|
GstBuffer *buf;
|
|
const gchar *rtp_cipher, *rtp_auth, *rtcp_cipher, *rtcp_auth;
|
|
|
|
/* Create new stream structure and set default values */
|
|
stream = g_slice_new0 (GstSrtpDecSsrcStream);
|
|
stream->ssrc = ssrc;
|
|
stream->key = NULL;
|
|
|
|
/* Get info from caps */
|
|
s = gst_caps_get_structure (caps, 0);
|
|
if (!s)
|
|
goto error;
|
|
|
|
rtp_cipher = gst_structure_get_string (s, "srtp-cipher");
|
|
rtp_auth = gst_structure_get_string (s, "srtp-auth");
|
|
rtcp_cipher = gst_structure_get_string (s, "srtcp-cipher");
|
|
rtcp_auth = gst_structure_get_string (s, "srtcp-auth");
|
|
if (!rtp_cipher || !rtp_auth || !rtcp_cipher || !rtcp_auth)
|
|
goto error;
|
|
|
|
|
|
stream->rtp_cipher = enum_value_from_nick (GST_TYPE_SRTP_CIPHER_TYPE,
|
|
rtp_cipher);
|
|
stream->rtp_auth = enum_value_from_nick (GST_TYPE_SRTP_AUTH_TYPE, rtp_auth);
|
|
stream->rtcp_cipher = enum_value_from_nick (GST_TYPE_SRTP_CIPHER_TYPE,
|
|
rtcp_cipher);
|
|
stream->rtcp_auth = enum_value_from_nick (GST_TYPE_SRTP_AUTH_TYPE, rtcp_auth);
|
|
|
|
if (stream->rtp_cipher == -1 || stream->rtp_auth == -1 ||
|
|
stream->rtcp_cipher == -1 || stream->rtcp_auth == -1) {
|
|
GST_WARNING_OBJECT (filter, "Invalid caps for stream,"
|
|
" unknown cipher or auth type");
|
|
goto error;
|
|
}
|
|
|
|
if (stream->rtcp_cipher != NULL_CIPHER && stream->rtcp_auth == NULL_AUTH) {
|
|
GST_WARNING_OBJECT (filter,
|
|
"Cannot have SRTP NULL authentication with a not-NULL encryption"
|
|
" cipher.");
|
|
goto error;
|
|
}
|
|
|
|
if (gst_structure_get (s, "srtp-key", GST_TYPE_BUFFER, &buf, NULL) || !buf) {
|
|
GST_DEBUG ("Got key [%p]", buf);
|
|
stream->key = buf;
|
|
} else if (STREAM_HAS_CRYPTO (stream)) {
|
|
goto error;
|
|
}
|
|
|
|
return stream;
|
|
|
|
error:
|
|
g_slice_free (GstSrtpDecSsrcStream, stream);
|
|
return NULL;
|
|
}
|
|
|
|
/* Get SRTP params by signal
|
|
*/
|
|
static GstCaps *
|
|
signal_get_srtp_params (GstSrtpDec * filter, guint32 ssrc, gint signal)
|
|
{
|
|
GstCaps *caps = NULL;
|
|
|
|
g_signal_emit (filter, gst_srtp_dec_signals[signal], 0, ssrc, &caps);
|
|
|
|
if (caps != NULL)
|
|
GST_DEBUG_OBJECT (filter, "Caps received");
|
|
|
|
return caps;
|
|
}
|
|
|
|
/* Create a stream in the session
|
|
*/
|
|
static err_status_t
|
|
init_session_stream (GstSrtpDec * filter, guint32 ssrc,
|
|
GstSrtpDecSsrcStream * stream)
|
|
{
|
|
err_status_t ret;
|
|
srtp_policy_t policy;
|
|
GstMapInfo map;
|
|
guchar tmp[1];
|
|
|
|
memset (&policy, 0, sizeof (srtp_policy_t));
|
|
|
|
if (!stream)
|
|
return err_status_bad_param;
|
|
|
|
GST_INFO_OBJECT (filter, "Setting RTP policy...");
|
|
set_crypto_policy_cipher_auth (stream->rtp_cipher, stream->rtp_auth,
|
|
&policy.rtp);
|
|
GST_INFO_OBJECT (filter, "Setting RTCP policy...");
|
|
set_crypto_policy_cipher_auth (stream->rtcp_cipher, stream->rtcp_auth,
|
|
&policy.rtcp);
|
|
|
|
if (stream->key) {
|
|
gst_buffer_map (stream->key, &map, GST_MAP_READ);
|
|
policy.key = (guchar *) map.data;
|
|
} else {
|
|
policy.key = tmp;
|
|
}
|
|
|
|
policy.ssrc.value = ssrc;
|
|
policy.ssrc.type = ssrc_specific;
|
|
policy.next = NULL;
|
|
|
|
/* If it is the first stream, create the session
|
|
* If not, add the stream policy to the session
|
|
*/
|
|
if (filter->first_session)
|
|
ret = srtp_create (&filter->session, &policy);
|
|
else
|
|
ret = srtp_add_stream (filter->session, &policy);
|
|
|
|
if (stream->key)
|
|
gst_buffer_unmap (stream->key, &map);
|
|
|
|
if (ret == err_status_ok) {
|
|
filter->first_session = FALSE;
|
|
g_hash_table_insert (filter->streams, GUINT_TO_POINTER (stream->ssrc),
|
|
stream);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Return a stream structure for a given buffer
|
|
*/
|
|
static GstSrtpDecSsrcStream *
|
|
validate_buffer (GstSrtpDec * filter, GstBuffer * buf, guint32 * ssrc,
|
|
gboolean * is_rtcp)
|
|
{
|
|
GstSrtpDecSsrcStream *stream = NULL;
|
|
|
|
if (!(*is_rtcp)) {
|
|
GstRTPBuffer rtpbuf = GST_RTP_BUFFER_INIT;
|
|
|
|
if (gst_rtp_buffer_map (buf, GST_MAP_READ, &rtpbuf)) {
|
|
if (gst_rtp_buffer_get_payload_type (&rtpbuf) < 64
|
|
|| gst_rtp_buffer_get_payload_type (&rtpbuf) > 80) {
|
|
*ssrc = gst_rtp_buffer_get_ssrc (&rtpbuf);
|
|
|
|
gst_rtp_buffer_unmap (&rtpbuf);
|
|
goto have_ssrc;
|
|
}
|
|
gst_rtp_buffer_unmap (&rtpbuf);
|
|
}
|
|
}
|
|
|
|
if (rtcp_buffer_get_ssrc (buf, ssrc)) {
|
|
*is_rtcp = TRUE;
|
|
} else {
|
|
GST_WARNING_OBJECT (filter, "No SSRC found in buffer");
|
|
return NULL;
|
|
}
|
|
|
|
have_ssrc:
|
|
|
|
stream = find_stream_by_ssrc (filter, *ssrc);
|
|
|
|
if (stream)
|
|
return stream;
|
|
|
|
return request_key_with_signal (filter, *ssrc, SIGNAL_REQUEST_KEY);
|
|
}
|
|
|
|
/* Create new stream from params in caps
|
|
*/
|
|
static GstSrtpDecSsrcStream *
|
|
update_session_stream_from_caps (GstSrtpDec * filter, guint32 ssrc,
|
|
GstCaps * caps)
|
|
{
|
|
GstSrtpDecSsrcStream *stream = NULL;
|
|
err_status_t err;
|
|
|
|
g_return_val_if_fail (GST_IS_SRTP_DEC (filter), NULL);
|
|
g_return_val_if_fail (GST_IS_CAPS (caps), NULL);
|
|
|
|
/* Remove existing stream, if any */
|
|
remove_stream_by_ssrc (filter, ssrc);
|
|
stream = get_stream_from_caps (filter, caps, ssrc);
|
|
|
|
if (stream) {
|
|
/* Create new session stream */
|
|
err = init_session_stream (filter, ssrc, stream);
|
|
|
|
if (err != err_status_ok) {
|
|
if (stream->key)
|
|
gst_buffer_unref (stream->key);
|
|
g_slice_free (GstSrtpDecSsrcStream, stream);
|
|
stream = NULL;
|
|
}
|
|
}
|
|
|
|
return stream;
|
|
}
|
|
|
|
static void
|
|
clear_stream (GstSrtpDecSsrcStream * stream)
|
|
{
|
|
if (stream->key)
|
|
gst_buffer_unref (stream->key);
|
|
g_slice_free (GstSrtpDecSsrcStream, stream);
|
|
}
|
|
|
|
static gboolean
|
|
remove_yes (gpointer key, gpointer value, gpointer user_data)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
/* Clear the policy list
|
|
*/
|
|
static void
|
|
gst_srtp_dec_clear_streams (GstSrtpDec * filter)
|
|
{
|
|
guint nb = 0;
|
|
|
|
GST_OBJECT_LOCK (filter);
|
|
|
|
if (!filter->first_session)
|
|
srtp_dealloc (filter->session);
|
|
|
|
if (filter->streams)
|
|
nb = g_hash_table_foreach_remove (filter->streams, remove_yes, NULL);
|
|
|
|
filter->first_session = TRUE;
|
|
|
|
GST_OBJECT_UNLOCK (filter);
|
|
|
|
GST_DEBUG_OBJECT (filter, "Cleared %d streams", nb);
|
|
}
|
|
|
|
/* Send a signal
|
|
*/
|
|
static GstSrtpDecSsrcStream *
|
|
request_key_with_signal (GstSrtpDec * filter, guint32 ssrc, gint signal)
|
|
{
|
|
GstCaps *caps;
|
|
GstSrtpDecSsrcStream *stream = NULL;
|
|
|
|
caps = signal_get_srtp_params (filter, ssrc, signal);
|
|
|
|
if (caps) {
|
|
stream = update_session_stream_from_caps (filter, ssrc, caps);
|
|
if (stream)
|
|
GST_DEBUG_OBJECT (filter, "New stream set with SSRC %d", ssrc);
|
|
else
|
|
GST_WARNING_OBJECT (filter, "Could not set stream with SSRC %d", ssrc);
|
|
gst_caps_unref (caps);
|
|
}
|
|
|
|
return stream;
|
|
}
|
|
|
|
static gboolean
|
|
gst_srtp_dec_sink_setcaps (GstPad * pad, GstObject * parent,
|
|
GstCaps * caps, gboolean is_rtcp)
|
|
{
|
|
GstSrtpDec *filter = GST_SRTP_DEC (parent);
|
|
GstPad *otherpad;
|
|
GstStructure *ps;
|
|
gboolean ret = FALSE;
|
|
|
|
g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE);
|
|
|
|
ps = gst_caps_get_structure (caps, 0);
|
|
|
|
if (gst_structure_has_field_typed (ps, "ssrc", G_TYPE_UINT) &&
|
|
gst_structure_has_field_typed (ps, "srtp-cipher", G_TYPE_STRING) &&
|
|
gst_structure_has_field_typed (ps, "srtp-auth", G_TYPE_STRING) &&
|
|
gst_structure_has_field_typed (ps, "srtcp-cipher", G_TYPE_STRING) &&
|
|
gst_structure_has_field_typed (ps, "srtcp-auth", G_TYPE_STRING)) {
|
|
guint ssrc;
|
|
|
|
gst_structure_get_uint (ps, "ssrc", &ssrc);
|
|
|
|
if (!update_session_stream_from_caps (filter, ssrc, caps)) {
|
|
GST_WARNING_OBJECT (pad, "Could not create session from pad caps: %"
|
|
GST_PTR_FORMAT, caps);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
caps = gst_caps_copy (caps);
|
|
ps = gst_caps_get_structure (caps, 0);
|
|
gst_structure_remove_fields (ps, "srtp-key", "srtp-cipher", "srtp-auth",
|
|
"srtcp-cipher", "srtcp-auth", NULL);
|
|
|
|
if (is_rtcp)
|
|
gst_structure_set_name (ps, "application/x-rtcp");
|
|
else
|
|
gst_structure_set_name (ps, "application/x-rtp");
|
|
|
|
otherpad = gst_pad_get_element_private (pad);
|
|
|
|
ret = gst_pad_set_caps (otherpad, caps);
|
|
|
|
gst_caps_unref (caps);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_srtp_dec_sink_event_rtp (GstPad * pad, GstObject * parent, GstEvent * event)
|
|
{
|
|
GstCaps *caps;
|
|
GstSrtpDec *filter = GST_SRTP_DEC (parent);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_CAPS:
|
|
gst_event_parse_caps (event, &caps);
|
|
return gst_srtp_dec_sink_setcaps (pad, parent, caps, FALSE);
|
|
case GST_EVENT_SEGMENT:
|
|
filter->rtp_has_segment = TRUE;
|
|
break;
|
|
case GST_EVENT_FLUSH_STOP:
|
|
filter->rtp_has_segment = FALSE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return gst_pad_event_default (pad, parent, event);
|
|
}
|
|
|
|
static gboolean
|
|
gst_srtp_dec_sink_event_rtcp (GstPad * pad, GstObject * parent,
|
|
GstEvent * event)
|
|
{
|
|
GstCaps *caps;
|
|
GstSrtpDec *filter = GST_SRTP_DEC (parent);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_CAPS:
|
|
gst_event_parse_caps (event, &caps);
|
|
return gst_srtp_dec_sink_setcaps (pad, parent, caps, TRUE);
|
|
case GST_EVENT_SEGMENT:
|
|
filter->rtcp_has_segment = TRUE;
|
|
break;
|
|
case GST_EVENT_FLUSH_STOP:
|
|
filter->rtcp_has_segment = FALSE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return gst_pad_event_default (pad, parent, event);
|
|
}
|
|
|
|
static gboolean
|
|
gst_srtp_dec_sink_query (GstPad * pad, GstObject * parent, GstQuery * query,
|
|
gboolean is_rtcp)
|
|
{
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_CAPS:
|
|
{
|
|
GstCaps *filter = NULL;
|
|
GstCaps *other_filter = NULL;
|
|
GstCaps *template_caps;
|
|
GstPad *otherpad;
|
|
GstCaps *other_caps;
|
|
GstCaps *ret;
|
|
int i;
|
|
|
|
gst_query_parse_caps (query, &filter);
|
|
|
|
otherpad = (GstPad *) gst_pad_get_element_private (pad);
|
|
|
|
if (filter) {
|
|
other_filter = gst_caps_copy (filter);
|
|
|
|
for (i = 0; i < gst_caps_get_size (other_filter); i++) {
|
|
GstStructure *ps = gst_caps_get_structure (other_filter, i);
|
|
if (is_rtcp)
|
|
gst_structure_set_name (ps, "application/x-rtcp");
|
|
else
|
|
gst_structure_set_name (ps, "application/x-rtp");
|
|
gst_structure_remove_fields (ps, "srtp-key", "srtp-cipher",
|
|
"srtp-auth", "srtcp-cipher", "srtcp-auth", NULL);
|
|
}
|
|
}
|
|
|
|
|
|
other_caps = gst_pad_peer_query_caps (otherpad, other_filter);
|
|
if (other_filter)
|
|
gst_caps_unref (other_filter);
|
|
if (!other_caps) {
|
|
goto return_template;
|
|
}
|
|
|
|
template_caps = gst_pad_get_pad_template_caps (otherpad);
|
|
ret = gst_caps_intersect_full (other_caps, template_caps,
|
|
GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref (other_caps);
|
|
gst_caps_unref (template_caps);
|
|
|
|
ret = gst_caps_make_writable (ret);
|
|
|
|
for (i = 0; i < gst_caps_get_size (ret); i++) {
|
|
GstStructure *ps = gst_caps_get_structure (ret, i);
|
|
if (is_rtcp)
|
|
gst_structure_set_name (ps, "application/x-srtcp");
|
|
else
|
|
gst_structure_set_name (ps, "application/x-srtp");
|
|
}
|
|
|
|
if (filter) {
|
|
GstCaps *tmp;
|
|
|
|
tmp = gst_caps_intersect (ret, filter);
|
|
gst_caps_unref (ret);
|
|
ret = tmp;
|
|
}
|
|
|
|
gst_query_set_caps_result (query, ret);
|
|
return TRUE;
|
|
|
|
return_template:
|
|
|
|
ret = gst_pad_get_pad_template_caps (pad);
|
|
gst_query_set_caps_result (query, ret);
|
|
gst_caps_unref (ret);
|
|
return TRUE;
|
|
}
|
|
default:
|
|
return gst_pad_query_default (pad, parent, query);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_srtp_dec_sink_query_rtp (GstPad * pad, GstObject * parent, GstQuery * query)
|
|
{
|
|
return gst_srtp_dec_sink_query (pad, parent, query, FALSE);
|
|
}
|
|
|
|
static gboolean
|
|
gst_srtp_dec_sink_query_rtcp (GstPad * pad, GstObject * parent,
|
|
GstQuery * query)
|
|
{
|
|
return gst_srtp_dec_sink_query (pad, parent, query, TRUE);
|
|
}
|
|
|
|
static GstIterator *
|
|
gst_srtp_dec_iterate_internal_links (GstPad * pad, GstObject * parent,
|
|
gboolean is_rtcp)
|
|
{
|
|
GstSrtpDec *filter = GST_SRTP_DEC (parent);
|
|
GstPad *otherpad = NULL;
|
|
GstIterator *it = NULL;
|
|
|
|
otherpad = (GstPad *) gst_pad_get_element_private (pad);
|
|
|
|
if (otherpad) {
|
|
GValue val = { 0 };
|
|
|
|
g_value_init (&val, GST_TYPE_PAD);
|
|
g_value_set_object (&val, otherpad);
|
|
it = gst_iterator_new_single (GST_TYPE_PAD, &val);
|
|
g_value_unset (&val);
|
|
} else {
|
|
GST_ELEMENT_ERROR (GST_ELEMENT_CAST (filter), CORE, PAD, (NULL),
|
|
("Unable to get linked pad"));
|
|
}
|
|
|
|
return it;
|
|
}
|
|
|
|
static GstIterator *
|
|
gst_srtp_dec_iterate_internal_links_rtp (GstPad * pad, GstObject * parent)
|
|
{
|
|
return gst_srtp_dec_iterate_internal_links (pad, parent, FALSE);
|
|
}
|
|
|
|
static GstIterator *
|
|
gst_srtp_dec_iterate_internal_links_rtcp (GstPad * pad, GstObject * parent)
|
|
{
|
|
return gst_srtp_dec_iterate_internal_links (pad, parent, TRUE);
|
|
}
|
|
|
|
static void
|
|
gst_srtp_dec_push_early_events (GstSrtpDec * filter, GstPad * pad,
|
|
GstPad * otherpad, gboolean is_rtcp)
|
|
{
|
|
GstEvent *otherev, *ev;
|
|
|
|
ev = gst_pad_get_sticky_event (pad, GST_EVENT_STREAM_START, 0);
|
|
if (ev) {
|
|
gst_event_unref (ev);
|
|
} else {
|
|
gchar *new_stream_id;
|
|
|
|
otherev = gst_pad_get_sticky_event (otherpad, GST_EVENT_STREAM_START, 0);
|
|
|
|
if (otherev) {
|
|
const gchar *other_stream_id;
|
|
|
|
gst_event_parse_stream_start (otherev, &other_stream_id);
|
|
|
|
new_stream_id = g_strdup_printf ("%s/%s", other_stream_id,
|
|
is_rtcp ? "rtcp" : "rtp");
|
|
gst_event_unref (otherev);
|
|
} else {
|
|
new_stream_id = gst_pad_create_stream_id (pad, GST_ELEMENT (filter),
|
|
is_rtcp ? "rtcp" : "rtp");
|
|
}
|
|
|
|
ev = gst_event_new_stream_start (new_stream_id);
|
|
g_free (new_stream_id);
|
|
|
|
gst_pad_push_event (pad, ev);
|
|
}
|
|
|
|
ev = gst_pad_get_sticky_event (pad, GST_EVENT_CAPS, 0);
|
|
if (ev) {
|
|
gst_event_unref (ev);
|
|
} else {
|
|
GstCaps *caps;
|
|
|
|
if (is_rtcp)
|
|
caps = gst_caps_new_empty_simple ("application/x-rtcp");
|
|
else
|
|
caps = gst_caps_new_empty_simple ("application/x-rtp");
|
|
|
|
gst_pad_set_caps (pad, caps);
|
|
gst_caps_unref (caps);
|
|
}
|
|
|
|
ev = gst_pad_get_sticky_event (pad, GST_EVENT_SEGMENT, 0);
|
|
if (ev) {
|
|
gst_event_unref (ev);
|
|
} else {
|
|
ev = gst_pad_get_sticky_event (otherpad, GST_EVENT_SEGMENT, 0);
|
|
|
|
if (ev)
|
|
gst_pad_push_event (pad, ev);
|
|
}
|
|
|
|
if (is_rtcp)
|
|
filter->rtcp_has_segment = TRUE;
|
|
else
|
|
filter->rtp_has_segment = TRUE;
|
|
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_srtp_dec_chain (GstPad * pad, GstObject * parent, GstBuffer * buf,
|
|
gboolean is_rtcp)
|
|
{
|
|
GstSrtpDec *filter = GST_SRTP_DEC (parent);
|
|
GstPad *otherpad;
|
|
err_status_t err = err_status_ok;
|
|
GstSrtpDecSsrcStream *stream = NULL;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
gint size;
|
|
guint32 ssrc = 0;
|
|
GstMapInfo map;
|
|
|
|
GST_OBJECT_LOCK (filter);
|
|
|
|
/* Check if this stream exists, if not create a new stream */
|
|
|
|
if (!(stream = validate_buffer (filter, buf, &ssrc, &is_rtcp))) {
|
|
GST_OBJECT_UNLOCK (filter);
|
|
GST_WARNING_OBJECT (filter, "Invalid buffer, dropping");
|
|
goto drop_buffer;
|
|
}
|
|
|
|
if (!STREAM_HAS_CRYPTO (stream)) {
|
|
GST_OBJECT_UNLOCK (filter);
|
|
goto push_out;
|
|
}
|
|
|
|
GST_LOG_OBJECT (pad, "Received %s buffer of size %" G_GSIZE_FORMAT
|
|
" with SSRC = %u", is_rtcp ? "RTCP" : "RTP", gst_buffer_get_size (buf),
|
|
ssrc);
|
|
|
|
/* Change buffer to remove protection */
|
|
buf = gst_buffer_make_writable (buf);
|
|
|
|
unprotect:
|
|
|
|
gst_buffer_map (buf, &map, GST_MAP_READWRITE);
|
|
size = map.size;
|
|
|
|
gst_srtp_init_event_reporter ();
|
|
|
|
if (is_rtcp)
|
|
err = srtp_unprotect_rtcp (filter->session, map.data, &size);
|
|
else
|
|
err = srtp_unprotect (filter->session, map.data, &size);
|
|
|
|
gst_buffer_unmap (buf, &map);
|
|
|
|
GST_OBJECT_UNLOCK (filter);
|
|
|
|
if (err != err_status_ok) {
|
|
GST_WARNING_OBJECT (pad,
|
|
"Unable to unprotect buffer (unprotect failed code %d)", err);
|
|
|
|
/* Signal user depending on type of error */
|
|
switch (err) {
|
|
case err_status_key_expired:
|
|
GST_OBJECT_LOCK (filter);
|
|
|
|
/* Update stream */
|
|
if ((stream = find_stream_by_ssrc (filter, ssrc))) {
|
|
GST_OBJECT_UNLOCK (filter);
|
|
if (request_key_with_signal (filter, ssrc, SIGNAL_HARD_LIMIT)) {
|
|
GST_OBJECT_LOCK (filter);
|
|
goto unprotect;
|
|
} else {
|
|
GST_WARNING_OBJECT (filter, "Hard limit reached, no new key, "
|
|
"dropping");
|
|
}
|
|
} else {
|
|
GST_WARNING_OBJECT (filter, "Could not find matching stream, "
|
|
"dropping");
|
|
}
|
|
break;
|
|
case err_status_auth_fail:
|
|
GST_WARNING_OBJECT (filter, "Error authentication packet, dropping");
|
|
break;
|
|
case err_status_cipher_fail:
|
|
GST_WARNING_OBJECT (filter, "Error while decrypting packet, dropping");
|
|
break;
|
|
default:
|
|
GST_WARNING_OBJECT (filter, "Other error, dropping");
|
|
break;
|
|
}
|
|
|
|
goto drop_buffer;
|
|
}
|
|
|
|
gst_buffer_set_size (buf, size);
|
|
|
|
/* If all is well, we may have reached soft limit */
|
|
if (gst_srtp_get_soft_limit_reached ())
|
|
request_key_with_signal (filter, ssrc, SIGNAL_SOFT_LIMIT);
|
|
|
|
push_out:
|
|
/* Push buffer to source pad */
|
|
if (is_rtcp) {
|
|
otherpad = filter->rtcp_srcpad;
|
|
if (!filter->rtcp_has_segment)
|
|
gst_srtp_dec_push_early_events (filter, filter->rtcp_srcpad,
|
|
filter->rtp_srcpad, TRUE);
|
|
} else {
|
|
otherpad = filter->rtp_srcpad;
|
|
if (!filter->rtp_has_segment)
|
|
gst_srtp_dec_push_early_events (filter, filter->rtp_srcpad,
|
|
filter->rtcp_srcpad, FALSE);
|
|
}
|
|
ret = gst_pad_push (otherpad, buf);
|
|
|
|
return ret;
|
|
|
|
drop_buffer:
|
|
/* Drop buffer, except if gst_pad_push returned OK or an error */
|
|
|
|
gst_buffer_unref (buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_srtp_dec_chain_rtp (GstPad * pad, GstObject * parent, GstBuffer * buf)
|
|
{
|
|
return gst_srtp_dec_chain (pad, parent, buf, FALSE);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_srtp_dec_chain_rtcp (GstPad * pad, GstObject * parent, GstBuffer * buf)
|
|
{
|
|
return gst_srtp_dec_chain (pad, parent, buf, TRUE);
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_srtp_dec_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstStateChangeReturn res;
|
|
GstSrtpDec *filter;
|
|
|
|
filter = GST_SRTP_DEC (element);
|
|
GST_OBJECT_LOCK (filter);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
filter->streams = g_hash_table_new_full (g_direct_hash, g_direct_equal,
|
|
NULL, (GDestroyNotify) clear_stream);
|
|
filter->rtp_has_segment = FALSE;
|
|
filter->rtcp_has_segment = FALSE;
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
GST_OBJECT_UNLOCK (filter);
|
|
|
|
res = GST_ELEMENT_CLASS (gst_srtp_dec_parent_class)->change_state (element,
|
|
transition);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
|
break;
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
gst_srtp_dec_clear_streams (filter);
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
g_hash_table_unref (filter->streams);
|
|
filter->streams = NULL;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
/* entry point to initialize the plug-in
|
|
* initialize the plug-in itself
|
|
* register the element factories and other features
|
|
*/
|
|
gboolean
|
|
gst_srtp_dec_plugin_init (GstPlugin * srtpdec)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (gst_srtp_dec_debug, "srtpdec", 0, "SRTP dec");
|
|
|
|
return gst_element_register (srtpdec, "srtpdec", GST_RANK_NONE,
|
|
GST_TYPE_SRTP_DEC);
|
|
}
|