gstreamer/gst/rtp/gstrtpj2kdepay.c
Tim-Philipp Müller 4a28e649c3 rtp: cache meta tag quarks and add more utility functions for metas
Every g_quark_from_static_string() is a hash table lookup serialised
on the global quark lock in GLib. Let's just look up the two quarks
we need once and cache them locally for future use. While we're at it,
add new utility functions for the two most commonly used tags
(audio + video). Make first argument a gpointer so we don't have to
cast and make the code ugly. These are used for logging purposes
only anyway.
2017-05-24 13:32:10 +01:00

667 lines
19 KiB
C

/* GStreamer
* Copyright (C) <2009> Wim Taymans <wim.taymans@gmail.com>
*
* 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., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:element-rtpj2kdepay
*
* Depayload an RTP-payloaded JPEG 2000 image into RTP packets according to RFC 5371
* and RFC 5372.
* For detailed information see: https://datatracker.ietf.org/doc/rfc5371/
* and https://datatracker.ietf.org/doc/rfc5372/
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <gst/rtp/gstrtpbuffer.h>
#include <gst/video/video.h>
#include <string.h>
#include "gstrtpj2kcommon.h"
#include "gstrtpj2kdepay.h"
#include "gstrtputils.h"
GST_DEBUG_CATEGORY_STATIC (rtpj2kdepay_debug);
#define GST_CAT_DEFAULT (rtpj2kdepay_debug)
static GstStaticPadTemplate gst_rtp_j2k_depay_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("image/x-jpc, "
"colorspace = (string) { sRGB, sYUV, GRAY }")
);
static GstStaticPadTemplate gst_rtp_j2k_depay_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("application/x-rtp, "
"media = (string) \"video\", " "clock-rate = (int) 90000, "
GST_RTP_J2K_SAMPLING_LIST ","
"encoding-name = (string) \"JPEG2000\";"
"application/x-rtp, "
"media = (string) \"video\", " "clock-rate = (int) 90000, "
"colorspace = (string) { sRGB, sYUV, GRAY }, "
"encoding-name = (string) \"JPEG2000\";")
);
enum
{
PROP_0,
PROP_LAST
};
#define gst_rtp_j2k_depay_parent_class parent_class
G_DEFINE_TYPE (GstRtpJ2KDepay, gst_rtp_j2k_depay, GST_TYPE_RTP_BASE_DEPAYLOAD);
static void gst_rtp_j2k_depay_finalize (GObject * object);
static void gst_rtp_j2k_depay_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_rtp_j2k_depay_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static GstStateChangeReturn
gst_rtp_j2k_depay_change_state (GstElement * element,
GstStateChange transition);
static gboolean gst_rtp_j2k_depay_setcaps (GstRTPBaseDepayload * depayload,
GstCaps * caps);
static GstBuffer *gst_rtp_j2k_depay_process (GstRTPBaseDepayload * depayload,
GstRTPBuffer * rtp);
static void
gst_rtp_j2k_depay_class_init (GstRtpJ2KDepayClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
GstRTPBaseDepayloadClass *gstrtpbasedepayload_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gstrtpbasedepayload_class = (GstRTPBaseDepayloadClass *) klass;
gobject_class->finalize = gst_rtp_j2k_depay_finalize;
gobject_class->set_property = gst_rtp_j2k_depay_set_property;
gobject_class->get_property = gst_rtp_j2k_depay_get_property;
gst_element_class_add_static_pad_template (gstelement_class,
&gst_rtp_j2k_depay_src_template);
gst_element_class_add_static_pad_template (gstelement_class,
&gst_rtp_j2k_depay_sink_template);
gst_element_class_set_static_metadata (gstelement_class,
"RTP JPEG 2000 depayloader", "Codec/Depayloader/Network/RTP",
"Extracts JPEG 2000 video from RTP packets (RFC 5371)",
"Wim Taymans <wim.taymans@gmail.com>");
gstelement_class->change_state = gst_rtp_j2k_depay_change_state;
gstrtpbasedepayload_class->set_caps = gst_rtp_j2k_depay_setcaps;
gstrtpbasedepayload_class->process_rtp_packet = gst_rtp_j2k_depay_process;
GST_DEBUG_CATEGORY_INIT (rtpj2kdepay_debug, "rtpj2kdepay", 0,
"J2K Video RTP Depayloader");
}
static void
gst_rtp_j2k_depay_init (GstRtpJ2KDepay * rtpj2kdepay)
{
rtpj2kdepay->pu_adapter = gst_adapter_new ();
rtpj2kdepay->t_adapter = gst_adapter_new ();
rtpj2kdepay->f_adapter = gst_adapter_new ();
}
static void
store_mheader (GstRtpJ2KDepay * rtpj2kdepay, guint idx, GstBuffer * buf)
{
GstBuffer *old;
GST_DEBUG_OBJECT (rtpj2kdepay, "storing main header %p at index %u", buf,
idx);
if ((old = rtpj2kdepay->MH[idx]))
gst_buffer_unref (old);
rtpj2kdepay->MH[idx] = buf;
}
static void
clear_mheaders (GstRtpJ2KDepay * rtpj2kdepay)
{
guint i;
for (i = 0; i < 8; i++)
store_mheader (rtpj2kdepay, i, NULL);
}
static void
gst_rtp_j2k_depay_reset (GstRtpJ2KDepay * rtpj2kdepay)
{
clear_mheaders (rtpj2kdepay);
gst_adapter_clear (rtpj2kdepay->pu_adapter);
gst_adapter_clear (rtpj2kdepay->t_adapter);
gst_adapter_clear (rtpj2kdepay->f_adapter);
rtpj2kdepay->next_frag = 0;
}
static void
gst_rtp_j2k_depay_finalize (GObject * object)
{
GstRtpJ2KDepay *rtpj2kdepay;
rtpj2kdepay = GST_RTP_J2K_DEPAY (object);
clear_mheaders (rtpj2kdepay);
g_object_unref (rtpj2kdepay->pu_adapter);
g_object_unref (rtpj2kdepay->t_adapter);
g_object_unref (rtpj2kdepay->f_adapter);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
gst_rtp_j2k_depay_setcaps (GstRTPBaseDepayload * depayload, GstCaps * caps)
{
GstStructure *structure = NULL;
gint clock_rate;
GstCaps *outcaps = NULL;
gboolean res = FALSE;
const gchar *colorspace = NULL;
const gchar *sampling = NULL;
structure = gst_caps_get_structure (caps, 0);
if (!gst_structure_get_int (structure, "clock-rate", &clock_rate))
clock_rate = 90000;
depayload->clock_rate = clock_rate;
sampling = gst_structure_get_string (structure, "sampling");
if (sampling) {
if (!strcmp (sampling, GST_RTP_J2K_RGB) ||
!strcmp (sampling, GST_RTP_J2K_RGBA) ||
!strcmp (sampling, GST_RTP_J2K_BGR) ||
!strcmp (sampling, GST_RTP_J2K_BGRA))
colorspace = "sRGB";
else if (!strcmp (sampling, GST_RTP_J2K_GRAYSCALE))
colorspace = "GRAY";
else
colorspace = "sYUV";
} else {
GST_ELEMENT_WARNING (depayload, STREAM, DEMUX, NULL,
("Non-compliant stream: sampling field missing. Frames my appear incorrect"));
colorspace = gst_structure_get_string (structure, "colorspace");
if (!strcmp (colorspace, "GRAY")) {
sampling = GST_RTP_J2K_GRAYSCALE;
}
}
outcaps = gst_caps_new_simple ("image/x-jpc",
"framerate", GST_TYPE_FRACTION, 0, 1,
"fields", G_TYPE_INT, 1, "colorspace", G_TYPE_STRING, colorspace, NULL);
if (sampling)
gst_caps_set_simple (outcaps, "sampling", G_TYPE_STRING, sampling, NULL);
res = gst_pad_set_caps (depayload->srcpad, outcaps);
gst_caps_unref (outcaps);
return res;
}
static void
gst_rtp_j2k_depay_clear_pu (GstRtpJ2KDepay * rtpj2kdepay)
{
gst_adapter_clear (rtpj2kdepay->pu_adapter);
rtpj2kdepay->have_sync = FALSE;
}
static GstFlowReturn
gst_rtp_j2k_depay_flush_pu (GstRTPBaseDepayload * depayload)
{
GstRtpJ2KDepay *rtpj2kdepay;
GstBuffer *mheader;
guint avail, MHF, mh_id;
rtpj2kdepay = GST_RTP_J2K_DEPAY (depayload);
/* take all available buffers */
avail = gst_adapter_available (rtpj2kdepay->pu_adapter);
if (avail == 0)
goto done;
MHF = rtpj2kdepay->pu_MHF;
mh_id = rtpj2kdepay->last_mh_id;
GST_DEBUG_OBJECT (rtpj2kdepay, "flushing PU of size %u", avail);
if (MHF == 0) {
GList *packets, *walk;
packets = gst_adapter_take_list (rtpj2kdepay->pu_adapter, avail);
/* append packets */
for (walk = packets; walk; walk = g_list_next (walk)) {
GstBuffer *buf = GST_BUFFER_CAST (walk->data);
GST_DEBUG_OBJECT (rtpj2kdepay,
"append pu packet of size %" G_GSIZE_FORMAT,
gst_buffer_get_size (buf));
gst_adapter_push (rtpj2kdepay->t_adapter, buf);
}
g_list_free (packets);
} else {
/* we have a header */
GST_DEBUG_OBJECT (rtpj2kdepay, "keeping header %u", mh_id);
/* we managed to see the start and end of the header, take all from
* adapter and keep in header */
mheader = gst_adapter_take_buffer (rtpj2kdepay->pu_adapter, avail);
store_mheader (rtpj2kdepay, mh_id, mheader);
}
done:
rtpj2kdepay->have_sync = FALSE;
return GST_FLOW_OK;
}
static GstFlowReturn
gst_rtp_j2k_depay_flush_tile (GstRTPBaseDepayload * depayload)
{
GstRtpJ2KDepay *rtpj2kdepay;
guint avail, mh_id;
GList *packets, *walk;
guint8 end[2];
GstFlowReturn ret = GST_FLOW_OK;
GstMapInfo map;
GstBuffer *buf;
rtpj2kdepay = GST_RTP_J2K_DEPAY (depayload);
/* flush pending PU */
gst_rtp_j2k_depay_flush_pu (depayload);
/* take all available buffers */
avail = gst_adapter_available (rtpj2kdepay->t_adapter);
if (avail == 0)
goto done;
mh_id = rtpj2kdepay->last_mh_id;
GST_DEBUG_OBJECT (rtpj2kdepay, "flushing tile of size %u", avail);
if (gst_adapter_available (rtpj2kdepay->f_adapter) == 0) {
GstBuffer *mheader;
/* we need a header now */
if ((mheader = rtpj2kdepay->MH[mh_id]) == NULL)
goto waiting_header;
/* push header in the adapter */
GST_DEBUG_OBJECT (rtpj2kdepay, "pushing header %u", mh_id);
gst_adapter_push (rtpj2kdepay->f_adapter, gst_buffer_ref (mheader));
}
/* check for last bytes */
gst_adapter_copy (rtpj2kdepay->t_adapter, end, avail - 2, 2);
/* now append the tile packets to the frame */
packets = gst_adapter_take_list (rtpj2kdepay->t_adapter, avail);
for (walk = packets; walk; walk = g_list_next (walk)) {
buf = GST_BUFFER_CAST (walk->data);
if (walk == packets) {
/* first buffer should contain the SOT */
gst_buffer_map (buf, &map, GST_MAP_READ);
if (map.size < 12)
goto invalid_tile;
if (map.data[0] == GST_J2K_MARKER && map.data[1] == GST_J2K_MARKER_SOT) {
guint Psot, nPsot;
if (end[0] == GST_J2K_MARKER && end[1] == GST_J2K_MARKER_EOC)
nPsot = avail - 2;
else
nPsot = avail;
Psot = GST_READ_UINT32_BE (&map.data[6]);
if (Psot != nPsot && Psot != 0) {
/* Psot must match the size of the tile */
GST_DEBUG_OBJECT (rtpj2kdepay, "set Psot from %u to %u", Psot, nPsot);
gst_buffer_unmap (buf, &map);
buf = gst_buffer_make_writable (buf);
gst_buffer_map (buf, &map, GST_MAP_WRITE);
GST_WRITE_UINT32_BE (&map.data[6], nPsot);
}
}
gst_buffer_unmap (buf, &map);
}
GST_DEBUG_OBJECT (rtpj2kdepay, "append pu packet of size %" G_GSIZE_FORMAT,
gst_buffer_get_size (buf));
gst_adapter_push (rtpj2kdepay->f_adapter, buf);
}
g_list_free (packets);
done:
rtpj2kdepay->last_tile = -1;
return ret;
/* errors */
waiting_header:
{
GST_DEBUG_OBJECT (rtpj2kdepay, "waiting for header %u", mh_id);
gst_adapter_clear (rtpj2kdepay->t_adapter);
rtpj2kdepay->last_tile = -1;
return ret;
}
invalid_tile:
{
GST_ELEMENT_WARNING (rtpj2kdepay, STREAM, DECODE, ("Invalid tile"), (NULL));
gst_buffer_unmap (buf, &map);
gst_adapter_clear (rtpj2kdepay->t_adapter);
rtpj2kdepay->last_tile = -1;
return ret;
}
}
static GstFlowReturn
gst_rtp_j2k_depay_flush_frame (GstRTPBaseDepayload * depayload)
{
GstRtpJ2KDepay *rtpj2kdepay;
guint8 end[2];
guint avail;
GstFlowReturn ret = GST_FLOW_OK;
rtpj2kdepay = GST_RTP_J2K_DEPAY (depayload);
/* flush pending tile */
gst_rtp_j2k_depay_flush_tile (depayload);
/* last buffer take all data out of the adapter */
avail = gst_adapter_available (rtpj2kdepay->f_adapter);
if (avail == 0)
goto done;
if (avail > 2) {
GstBuffer *outbuf;
/* take the last bytes of the JPEG 2000 data to see if there is an EOC
* marker */
gst_adapter_copy (rtpj2kdepay->f_adapter, end, avail - 2, 2);
if (end[0] != GST_J2K_MARKER && end[1] != GST_J2K_MARKER_EOC) {
end[0] = GST_J2K_MARKER;
end[1] = GST_J2K_MARKER_EOC;
GST_DEBUG_OBJECT (rtpj2kdepay, "no EOC marker, adding one");
/* no EOI marker, add one */
outbuf = gst_buffer_new_and_alloc (2);
gst_buffer_fill (outbuf, 0, end, 2);
gst_adapter_push (rtpj2kdepay->f_adapter, outbuf);
avail += 2;
}
GST_DEBUG_OBJECT (rtpj2kdepay, "pushing buffer of %u bytes", avail);
outbuf = gst_adapter_take_buffer (rtpj2kdepay->f_adapter, avail);
gst_rtp_drop_non_video_meta (depayload, outbuf);
ret = gst_rtp_base_depayload_push (depayload, outbuf);
} else {
GST_WARNING_OBJECT (rtpj2kdepay, "empty packet");
gst_adapter_clear (rtpj2kdepay->f_adapter);
}
/* we accept any mh_id now */
rtpj2kdepay->last_mh_id = -1;
/* reset state */
rtpj2kdepay->next_frag = 0;
rtpj2kdepay->have_sync = FALSE;
done:
/* we can't keep headers with mh_id of 0 */
store_mheader (rtpj2kdepay, 0, NULL);
return ret;
}
static GstBuffer *
gst_rtp_j2k_depay_process (GstRTPBaseDepayload * depayload, GstRTPBuffer * rtp)
{
GstRtpJ2KDepay *rtpj2kdepay;
guint8 *payload;
guint MHF, mh_id, frag_offset, tile, payload_len, j2klen;
gint gap;
guint32 rtptime;
rtpj2kdepay = GST_RTP_J2K_DEPAY (depayload);
payload = gst_rtp_buffer_get_payload (rtp);
payload_len = gst_rtp_buffer_get_payload_len (rtp);
/* we need at least a header */
if (payload_len < GST_RTP_J2K_HEADER_SIZE)
goto empty_packet;
rtptime = gst_rtp_buffer_get_timestamp (rtp);
/* new timestamp marks new frame */
if (rtpj2kdepay->last_rtptime != rtptime) {
rtpj2kdepay->last_rtptime = rtptime;
/* flush pending frame */
gst_rtp_j2k_depay_flush_frame (depayload);
}
/*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |tp |MHF|mh_id|T| priority | tile number |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |reserved | fragment offset |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
MHF = (payload[0] & 0x30) >> 4;
mh_id = (payload[0] & 0xe) >> 1;
if (rtpj2kdepay->last_mh_id == -1)
rtpj2kdepay->last_mh_id = mh_id;
else if (rtpj2kdepay->last_mh_id != mh_id)
goto wrong_mh_id;
tile = (payload[2] << 8) | payload[3];
frag_offset = (payload[5] << 16) | (payload[6] << 8) | payload[7];
j2klen = payload_len - GST_RTP_J2K_HEADER_SIZE;
GST_DEBUG_OBJECT (rtpj2kdepay, "MHF %u, tile %u, frag %u, expected %u", MHF,
tile, frag_offset, rtpj2kdepay->next_frag);
/* calculate the gap between expected frag */
gap = frag_offset - rtpj2kdepay->next_frag;
/* calculate next frag */
rtpj2kdepay->next_frag = frag_offset + j2klen;
if (gap != 0) {
GST_DEBUG_OBJECT (rtpj2kdepay, "discont of %d, clear PU", gap);
/* discont, clear pu adapter and resync */
gst_rtp_j2k_depay_clear_pu (rtpj2kdepay);
}
/* check for sync code */
if (j2klen > 2 && payload[GST_RTP_J2K_HEADER_SIZE] == GST_J2K_MARKER) {
guint marker = payload[GST_RTP_J2K_HEADER_SIZE + 1];
/* packets must start with SOC, SOT or SOP */
switch (marker) {
case GST_J2K_MARKER_SOC:
GST_DEBUG_OBJECT (rtpj2kdepay, "found SOC packet");
/* flush the previous frame, should have happened when the timestamp
* changed above. */
gst_rtp_j2k_depay_flush_frame (depayload);
rtpj2kdepay->have_sync = TRUE;
break;
case GST_J2K_MARKER_SOT:
/* flush the previous tile */
gst_rtp_j2k_depay_flush_tile (depayload);
GST_DEBUG_OBJECT (rtpj2kdepay, "found SOT packet");
rtpj2kdepay->have_sync = TRUE;
/* we sync on the tile now */
rtpj2kdepay->last_tile = tile;
break;
case GST_J2K_MARKER_SOP:
GST_DEBUG_OBJECT (rtpj2kdepay, "found SOP packet");
/* flush the previous PU */
gst_rtp_j2k_depay_flush_pu (depayload);
if (rtpj2kdepay->last_tile != tile) {
/* wrong tile, we lose sync and we need a new SOT or SOC to regain
* sync. First flush out the previous tile if we have one. */
if (rtpj2kdepay->last_tile != -1)
gst_rtp_j2k_depay_flush_tile (depayload);
/* now we have no more valid tile and no sync */
rtpj2kdepay->last_tile = -1;
rtpj2kdepay->have_sync = FALSE;
} else {
rtpj2kdepay->have_sync = TRUE;
}
break;
default:
GST_DEBUG_OBJECT (rtpj2kdepay, "no sync packet 0x%02d", marker);
break;
}
}
if (rtpj2kdepay->have_sync) {
GstBuffer *pu_frag;
if (gst_adapter_available (rtpj2kdepay->pu_adapter) == 0) {
/* first part of pu, record state */
GST_DEBUG_OBJECT (rtpj2kdepay, "first PU");
rtpj2kdepay->pu_MHF = MHF;
}
/* and push in pu adapter */
GST_DEBUG_OBJECT (rtpj2kdepay, "push pu of size %u in adapter", j2klen);
pu_frag = gst_rtp_buffer_get_payload_subbuffer (rtp, 8, -1);
gst_adapter_push (rtpj2kdepay->pu_adapter, pu_frag);
if (MHF & 2) {
/* last part of main header received, we can flush it */
GST_DEBUG_OBJECT (rtpj2kdepay, "header end, flush pu");
gst_rtp_j2k_depay_flush_pu (depayload);
}
} else {
GST_DEBUG_OBJECT (rtpj2kdepay, "discard packet, no sync");
}
/* marker bit finishes the frame */
if (gst_rtp_buffer_get_marker (rtp)) {
GST_DEBUG_OBJECT (rtpj2kdepay, "marker set, last buffer");
/* then flush frame */
gst_rtp_j2k_depay_flush_frame (depayload);
}
return NULL;
/* ERRORS */
empty_packet:
{
GST_ELEMENT_WARNING (rtpj2kdepay, STREAM, DECODE,
("Empty Payload."), (NULL));
return NULL;
}
wrong_mh_id:
{
GST_ELEMENT_WARNING (rtpj2kdepay, STREAM, DECODE,
("Invalid mh_id %u, expected %u", mh_id, rtpj2kdepay->last_mh_id),
(NULL));
gst_rtp_j2k_depay_clear_pu (rtpj2kdepay);
return NULL;
}
}
static void
gst_rtp_j2k_depay_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
switch (prop_id) {
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_rtp_j2k_depay_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
switch (prop_id) {
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GstStateChangeReturn
gst_rtp_j2k_depay_change_state (GstElement * element, GstStateChange transition)
{
GstRtpJ2KDepay *rtpj2kdepay;
GstStateChangeReturn ret;
rtpj2kdepay = GST_RTP_J2K_DEPAY (element);
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
gst_rtp_j2k_depay_reset (rtpj2kdepay);
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
gst_rtp_j2k_depay_reset (rtpj2kdepay);
break;
case GST_STATE_CHANGE_READY_TO_NULL:
break;
default:
break;
}
return ret;
}
gboolean
gst_rtp_j2k_depay_plugin_init (GstPlugin * plugin)
{
return gst_element_register (plugin, "rtpj2kdepay",
GST_RANK_SECONDARY, GST_TYPE_RTP_J2K_DEPAY);
}