*
* 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.
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
*/
-/**
+ /**
* SECTION:element-rtpj2kpay
+ * @title: rtpj2kpay
*
- * Payload encode JPEG 2000 pictures into RTP packets according to RFC 5371.
- * For detailed information see: http://www.rfc-editor.org/rfc/rfc5371.txt
+ * Payload encode JPEG 2000 images 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/
+ *
+ * The payloader takes a JPEG 2000 image, scans it for "packetization
+ * units" and constructs the RTP packet header followed by the JPEG 2000
+ * codestream. A "packetization unit" is defined as either a JPEG 2000 main header,
+ * a JPEG 2000 tile-part header, or a JPEG 2000 packet.
*
- * The payloader takes a JPEG 2000 picture, scans the header for packetization
- * units and constructs the RTP packet header followed by the actual JPEG 2000
- * codestream.
*/
#ifdef HAVE_CONFIG_H
#include <string.h>
#include <gst/rtp/gstrtpbuffer.h>
-
+#include <gst/video/video.h>
+#include "gstrtpj2kcommon.h"
#include "gstrtpj2kpay.h"
+#include "gstrtputils.h"
static GstStaticPadTemplate gst_rtp_j2k_pay_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
- GST_STATIC_CAPS ("image/x-jpc")
+ GST_STATIC_CAPS ("image/x-jpc, " GST_RTP_J2K_SAMPLING_LIST)
);
+
static GstStaticPadTemplate gst_rtp_j2k_pay_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
" media = (string) \"video\", "
" payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
" clock-rate = (int) 90000, "
- " encoding-name = (string) \"JPEG2000\"")
+ GST_RTP_J2K_SAMPLING_LIST "," " encoding-name = (string) \"JPEG2000\"")
);
GST_DEBUG_CATEGORY_STATIC (rtpj2kpay_debug);
#define GST_CAT_DEFAULT (rtpj2kpay_debug)
-/*
- * RtpJ2KMarker:
- * @J2K_MARKER: Prefix for JPEG 2000 marker
- * @J2K_MARKER_SOC: Start of Codestream
- * @J2K_MARKER_SOT: Start of tile
- * @J2K_MARKER_EOC: End of Codestream
- *
- * Identifers for markers in JPEG 2000 codestreams
- */
-typedef enum
-{
- J2K_MARKER = 0xFF,
- J2K_MARKER_SOC = 0x4F,
- J2K_MARKER_SOT = 0x90,
- J2K_MARKER_SOP = 0x91,
- J2K_MARKER_EPH = 0x92,
- J2K_MARKER_SOD = 0x93,
- J2K_MARKER_EOC = 0xD9
-} RtpJ2KMarker;
-
-#define DEFAULT_BUFFER_LIST TRUE
enum
{
PROP_0,
- PROP_BUFFER_LIST,
PROP_LAST
};
guint offset:24;
} RtpJ2KHeader;
-#define HEADER_SIZE 8
-
static void gst_rtp_j2k_pay_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_rtp_j2k_pay_get_property (GObject * object, guint prop_id,
gobject_class->set_property = gst_rtp_j2k_pay_set_property;
gobject_class->get_property = gst_rtp_j2k_pay_get_property;
- g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BUFFER_LIST,
- g_param_spec_boolean ("buffer-list", "Buffer List",
- "Use Buffer Lists",
- DEFAULT_BUFFER_LIST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
- gst_element_class_add_pad_template (gstelement_class,
- gst_static_pad_template_get (&gst_rtp_j2k_pay_src_template));
- gst_element_class_add_pad_template (gstelement_class,
- gst_static_pad_template_get (&gst_rtp_j2k_pay_sink_template));
+ gst_element_class_add_static_pad_template (gstelement_class,
+ &gst_rtp_j2k_pay_src_template);
+ gst_element_class_add_static_pad_template (gstelement_class,
+ &gst_rtp_j2k_pay_sink_template);
- gst_element_class_set_details_simple (gstelement_class,
+ gst_element_class_set_static_metadata (gstelement_class,
"RTP JPEG 2000 payloader", "Codec/Payloader/Network/RTP",
"Payload-encodes JPEG 2000 pictures into RTP packets (RFC 5371)",
"Wim Taymans <wim.taymans@gmail.com>");
static void
gst_rtp_j2k_pay_init (GstRtpJ2KPay * pay)
{
- pay->buffer_list = DEFAULT_BUFFER_LIST;
}
static gboolean
gst_rtp_j2k_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
{
GstStructure *caps_structure = gst_caps_get_structure (caps, 0);
- GstRtpJ2KPay *pay;
- gint width = 0, height = 0;
gboolean res;
+ gint width = 0, height = 0;
+ const gchar *sampling = NULL;
- pay = GST_RTP_J2K_PAY (basepayload);
+ gboolean has_width = gst_structure_get_int (caps_structure, "width", &width);
+ gboolean has_height =
+ gst_structure_get_int (caps_structure, "height", &height);
- /* these properties are not mandatory, we can get them from the stream */
- if (gst_structure_get_int (caps_structure, "height", &height)) {
- pay->height = height;
- }
- if (gst_structure_get_int (caps_structure, "width", &width)) {
- pay->width = width;
- }
+
+ /* sampling is a required field */
+ sampling = gst_structure_get_string (caps_structure, "sampling");
gst_rtp_base_payload_set_options (basepayload, "video", TRUE, "JPEG2000",
90000);
- res = gst_rtp_base_payload_set_outcaps (basepayload, NULL);
+ if (has_width && has_height)
+ res = gst_rtp_base_payload_set_outcaps (basepayload,
+ "sampling", G_TYPE_STRING, sampling, "width", G_TYPE_INT, width,
+ "height", G_TYPE_INT, height, NULL);
+ else
+ res =
+ gst_rtp_base_payload_set_outcaps (basepayload, "sampling",
+ G_TYPE_STRING, sampling, NULL);
return res;
}
return data[offset] << 8 | data[offset + 1];
}
-static RtpJ2KMarker
+
+static GstRtpJ2KMarker
gst_rtp_j2k_pay_scan_marker (const guint8 * data, guint size, guint * offset)
{
- while ((data[(*offset)++] != J2K_MARKER) && ((*offset) < size));
+ while ((data[(*offset)++] != GST_J2K_MARKER) && ((*offset) < size));
if (G_UNLIKELY ((*offset) >= size)) {
- return J2K_MARKER_EOC;
+ return GST_J2K_MARKER_EOC;
} else {
guint8 marker = data[(*offset)++];
- return marker;
+ return (GstRtpJ2KMarker) marker;
}
}
typedef struct
{
RtpJ2KHeader header;
+ gboolean multi_tile;
gboolean bitstream;
- guint n_tiles;
guint next_sot;
gboolean force_packet;
} RtpJ2KState;
+
+/* Note: The standard recommends that headers be put in their own RTP packets, so we follow
+ * this recommendation in the code. Also, this method groups together all J2K packets
+ * for a tile part and treats this group as a packetization unit. According to the RFC,
+ * only an individual J2K packet is considered a packetization unit.
+ */
+
static guint
find_pu_end (GstRtpJ2KPay * pay, const guint8 * data, guint size,
guint offset, RtpJ2KState * state)
{
gboolean cut_sop = FALSE;
- RtpJ2KMarker marker;
+ GstRtpJ2KMarker marker;
/* parse the j2k header for 'start of codestream' */
GST_LOG_OBJECT (pay, "checking from offset %u", offset);
if (state->bitstream) {
/* parsing bitstream, only look for SOP */
switch (marker) {
- case J2K_MARKER_SOP:
+ case GST_J2K_MARKER_SOP:
GST_LOG_OBJECT (pay, "found SOP at %u", offset);
if (cut_sop)
return offset - 2;
cut_sop = TRUE;
break;
- case J2K_MARKER_EPH:
+ case GST_J2K_MARKER_EPH:
/* just skip over EPH */
GST_LOG_OBJECT (pay, "found EPH at %u", offset);
break;
GST_LOG_OBJECT (pay, "reached next SOT at %u", offset);
state->bitstream = FALSE;
state->force_packet = TRUE;
- if (marker == J2K_MARKER_EOC && state->next_sot + 2 <= size)
+ if (marker == GST_J2K_MARKER_EOC && state->next_sot + 2 <= size)
/* include EOC but never go past the max size */
return state->next_sot + 2;
else
}
} else {
switch (marker) {
- case J2K_MARKER_SOC:
+ case GST_J2K_MARKER_SOC:
GST_LOG_OBJECT (pay, "found SOC at %u", offset);
- state->header.MHF = 1;
+ /* start off by assuming that we will fit the entire header
+ into the RTP payload */
+ state->header.MHF = 3;
break;
- case J2K_MARKER_SOT:
+ case GST_J2K_MARKER_SOT:
{
- guint len, Psot;
+ guint len, Psot, tile;
GST_LOG_OBJECT (pay, "found SOT at %u", offset);
- /* we found SOT but also had a header first */
+ /* SOT for first tile part in code stream:
+ force close of current RTP packet, so that it
+ only contains main header */
if (state->header.MHF) {
state->force_packet = TRUE;
return offset - 2;
if (offset + len >= size)
return size;
- if (state->n_tiles == 0)
- /* first tile, T is valid */
- state->header.T = 0;
- else
- /* more tiles, T becomes invalid */
- state->header.T = 1;
- state->header.tile = GST_READ_UINT16_BE (&data[offset + 2]);
- state->n_tiles++;
+ /* Isot */
+ tile = GST_READ_UINT16_BE (&data[offset + 2]);
+
+ if (!state->multi_tile) {
+ /* we have detected multiple tiles in this rtp packet : tile bit is now invalid */
+ if (state->header.T == 0 && state->header.tile != tile) {
+ state->header.T = 1;
+ state->multi_tile = TRUE;
+ } else {
+ state->header.T = 0;
+ }
+ }
+ state->header.tile = tile;
+
+ /* Note: Tile parts from multiple tiles in single RTP packet
+ will make T invalid.
+ This cannot happen in our case since we always
+ send tile headers in their own RTP packets, so we cannot mix
+ tile parts in a single RTP packet */
- /* get offset of next tile, if it's 0, it goes all the way to the end of
- * the data */
+ /* Psot: offset of next tile. If it's 0, next tile goes all the way
+ to the end of the data */
Psot = GST_READ_UINT32_BE (&data[offset + 4]);
if (Psot == 0)
state->next_sot = size;
Psot, state->next_sot);
break;
}
- case J2K_MARKER_SOD:
+ case GST_J2K_MARKER_SOD:
GST_LOG_OBJECT (pay, "found SOD at %u", offset);
- /* can't have more tiles now */
- state->n_tiles = 0;
/* go to bitstream parsing */
state->bitstream = TRUE;
/* cut at the next SOP or else include all data */
* spec recommends packing headers separately */
state->force_packet = TRUE;
break;
- case J2K_MARKER_EOC:
+ case GST_J2K_MARKER_EOC:
GST_LOG_OBJECT (pay, "found EOC at %u", offset);
return offset;
default:
GstClockTime timestamp;
GstFlowReturn ret = GST_FLOW_ERROR;
RtpJ2KState state;
-#if 0
GstBufferList *list = NULL;
-#endif
GstMapInfo map;
guint mtu, max_size;
guint offset;
mtu = GST_RTP_BASE_PAYLOAD_MTU (pay);
gst_buffer_map (buffer, &map, GST_MAP_READ);
- timestamp = GST_BUFFER_TIMESTAMP (buffer);
+ timestamp = GST_BUFFER_PTS (buffer);
offset = pos = end = 0;
GST_LOG_OBJECT (pay,
state.header.tp = 0; /* only progressive scan */
state.header.MHF = 0; /* no header */
state.header.mh_id = 0; /* always 0 for now */
- state.header.T = 1; /* invalid tile */
+ state.header.T = 1; /* invalid tile, because we always begin with the main header */
state.header.priority = 255; /* always 255 for now */
- state.header.tile = 0; /* no tile number */
+ state.header.tile = 0xffff; /* no tile number */
state.header.offset = 0; /* offset of 0 */
+ state.multi_tile = FALSE;
state.bitstream = FALSE;
- state.n_tiles = 0;
state.next_sot = 0;
state.force_packet = FALSE;
-#if 0
- if (pay->buffer_list) {
- list = gst_buffer_list_new ();
- it = gst_buffer_list_iterate (list);
- }
-#endif
-
/* get max packet length */
- max_size = gst_rtp_buffer_calc_payload_len (mtu - HEADER_SIZE, 0, 0);
+ max_size =
+ gst_rtp_buffer_calc_payload_len (mtu - GST_RTP_J2K_HEADER_SIZE, 0, 0);
+
+ list = gst_buffer_list_new_sized ((mtu / max_size) + 1);
do {
GstBuffer *outbuf;
while (pu_size > 0) {
guint packet_size, data_size;
+ GstBuffer *paybuf;
/* calculate the packet size */
packet_size =
- gst_rtp_buffer_calc_packet_len (pu_size + HEADER_SIZE, 0, 0);
+ gst_rtp_buffer_calc_packet_len (pu_size + GST_RTP_J2K_HEADER_SIZE, 0,
+ 0);
if (packet_size > mtu) {
GST_DEBUG_OBJECT (pay, "needed packet size %u clamped to MTU %u",
/* get total payload size and data size */
payload_size = gst_rtp_buffer_calc_payload_len (packet_size, 0, 0);
- data_size = payload_size - HEADER_SIZE;
+ data_size = payload_size - GST_RTP_J2K_HEADER_SIZE;
-#if 0
- if (pay->buffer_list) {
- /* make buffer for header */
- outbuf = gst_rtp_buffer_new_allocate (HEADER_SIZE, 0, 0);
- } else
-#endif
- {
- /* make buffer for header and data */
- outbuf = gst_rtp_buffer_new_allocate (payload_size, 0, 0);
- }
- GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
+ /* make buffer for header */
+ outbuf = gst_rtp_buffer_new_allocate (GST_RTP_J2K_HEADER_SIZE, 0, 0);
+
+ GST_BUFFER_PTS (outbuf) = timestamp;
gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
header = gst_rtp_buffer_get_payload (&rtp);
pu_size -= data_size;
- if (pu_size == 0) {
- /* reached the end of a packetization unit */
- if (state.header.MHF) {
- /* we were doing a header, see if all fit in one packet or if
- * we had to fragment it */
- if (offset == 0)
- state.header.MHF = 3;
- else
- state.header.MHF = 2;
+
+ /* reached the end of a packetization unit */
+ if (pu_size == 0 && end >= map.size) {
+ gst_rtp_buffer_set_marker (&rtp, TRUE);
+ }
+ /* If we were processing a header, see if all fits in one RTP packet
+ or if we have to fragment it */
+ if (state.header.MHF) {
+ switch (state.header.MHF) {
+ case 3:
+ if (pu_size > 0)
+ state.header.MHF = 1;
+ break;
+ case 1:
+ if (pu_size == 0)
+ state.header.MHF = 2;
+ break;
+ default:
+ break;
}
- if (end >= map.size)
- gst_rtp_buffer_set_marker (&rtp, TRUE);
}
/*
header[6] = (state.header.offset >> 8) & 0xff;
header[7] = state.header.offset & 0xff;
-#if 0
- if (pay->buffer_list) {
- GstBuffer *paybuf;
+ gst_rtp_buffer_unmap (&rtp);
- /* make subbuffer of j2k data */
- paybuf = gst_buffer_create_sub (buffer, offset, data_size);
+ /* make subbuffer of j2k data */
+ paybuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL,
+ offset, data_size);
+ gst_rtp_copy_video_meta (basepayload, outbuf, paybuf);
+ outbuf = gst_buffer_append (outbuf, paybuf);
- /* create a new group to hold the header and the payload */
- gst_buffer_list_iterator_add_group (it);
+ gst_buffer_list_add (list, outbuf);
- /* add both buffers to the buffer list */
- gst_buffer_list_iterator_add (it, outbuf);
- gst_buffer_list_iterator_add (it, paybuf);
- } else
-#endif
- {
- /* copy payload */
- memcpy (header + HEADER_SIZE, &map.data[offset], data_size);
- gst_rtp_buffer_unmap (&rtp);
-
- ret = gst_rtp_base_payload_push (basepayload, outbuf);
- if (ret != GST_FLOW_OK)
- goto done;
- }
+ /* reset multi_tile */
+ state.multi_tile = FALSE;
+
+
+ /* set MHF to zero if there is no more main header to process */
+ if (state.header.MHF & 2)
+ state.header.MHF = 0;
+
+ /* tile is valid, if there is no more header to process */
+ if (!state.header.MHF)
+ state.header.T = 0;
- /* reset header for next round */
- state.header.MHF = 0;
- state.header.T = 1;
- state.header.tile = 0;
offset += data_size;
+ state.header.offset = offset;
}
offset = pos;
} while (offset < map.size);
-done:
+ gst_buffer_unmap (buffer, &map);
gst_buffer_unref (buffer);
-#if 0
- if (pay->buffer_list) {
- /* free iterator and push the whole buffer list at once */
- gst_buffer_list_iterator_free (it);
- ret = gst_rtp_base_payload_push_list (basepayload, list);
- }
-#endif
+ /* push the whole buffer list at once */
+ ret = gst_rtp_base_payload_push_list (basepayload, list);
return ret;
}
gst_rtp_j2k_pay_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
- GstRtpJ2KPay *rtpj2kpay;
-
- rtpj2kpay = GST_RTP_J2K_PAY (object);
-
switch (prop_id) {
- case PROP_BUFFER_LIST:
- rtpj2kpay->buffer_list = g_value_get_boolean (value);
- break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
gst_rtp_j2k_pay_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
- GstRtpJ2KPay *rtpj2kpay;
-
- rtpj2kpay = GST_RTP_J2K_PAY (object);
-
switch (prop_id) {
- case PROP_BUFFER_LIST:
- g_value_set_boolean (value, rtpj2kpay->buffer_list);
- break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;