gstrtpj2kpay.c 15.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 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
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
16
17
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
18
19
20
 */

/**
21
 * SECTION:element-rtpj2kpay
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
 *
 * Payload encode JPEG 2000 pictures into RTP packets according to RFC 5371.
 * For detailed information see: http://www.rfc-editor.org/rfc/rfc5371.txt
 *
 * 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 "config.h"
#endif

#include <string.h>
#include <gst/rtp/gstrtpbuffer.h>

#include "gstrtpj2kpay.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")
    );

static GstStaticPadTemplate gst_rtp_j2k_pay_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("application/x-rtp, "
        "  media = (string) \"video\", "
        "  payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
        "  clock-rate = (int) 90000, "
        "  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,
75
  J2K_MARKER_SOP = 0x91,
Wim Taymans's avatar
Wim Taymans committed
76
  J2K_MARKER_EPH = 0x92,
77
  J2K_MARKER_SOD = 0x93,
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
  J2K_MARKER_EOC = 0xD9
} RtpJ2KMarker;

enum
{
  PROP_0,
  PROP_LAST
};

typedef struct
{
  guint tp:2;
  guint MHF:2;
  guint mh_id:3;
  guint T:1;
  guint priority:8;
  guint tile:16;
  guint offset:24;
} RtpJ2KHeader;

98
#define HEADER_SIZE 8
Wim Taymans's avatar
Wim Taymans committed
99
100
101
102
103
104

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,
    GValue * value, GParamSpec * pspec);

Wim Taymans's avatar
Wim Taymans committed
105
static gboolean gst_rtp_j2k_pay_setcaps (GstRTPBasePayload * basepayload,
106
107
    GstCaps * caps);

Wim Taymans's avatar
Wim Taymans committed
108
static GstFlowReturn gst_rtp_j2k_pay_handle_buffer (GstRTPBasePayload * pad,
109
110
    GstBuffer * buffer);

111
#define gst_rtp_j2k_pay_parent_class parent_class
Wim Taymans's avatar
Wim Taymans committed
112
G_DEFINE_TYPE (GstRtpJ2KPay, gst_rtp_j2k_pay, GST_TYPE_RTP_BASE_PAYLOAD);
113
114
115
116

static void
gst_rtp_j2k_pay_class_init (GstRtpJ2KPayClass * klass)
{
Wim Taymans's avatar
Wim Taymans committed
117
  GObjectClass *gobject_class;
118
  GstElementClass *gstelement_class;
Wim Taymans's avatar
Wim Taymans committed
119
  GstRTPBasePayloadClass *gstrtpbasepayload_class;
120

Wim Taymans's avatar
Wim Taymans committed
121
  gobject_class = (GObjectClass *) klass;
122
  gstelement_class = (GstElementClass *) klass;
Wim Taymans's avatar
Wim Taymans committed
123
  gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
124

Wim Taymans's avatar
Wim Taymans committed
125
126
127
  gobject_class->set_property = gst_rtp_j2k_pay_set_property;
  gobject_class->get_property = gst_rtp_j2k_pay_get_property;

128
129
130
131
132
  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));

133
  gst_element_class_set_static_metadata (gstelement_class,
134
135
136
137
      "RTP JPEG 2000 payloader", "Codec/Payloader/Network/RTP",
      "Payload-encodes JPEG 2000 pictures into RTP packets (RFC 5371)",
      "Wim Taymans <wim.taymans@gmail.com>");

Wim Taymans's avatar
Wim Taymans committed
138
139
  gstrtpbasepayload_class->set_caps = gst_rtp_j2k_pay_setcaps;
  gstrtpbasepayload_class->handle_buffer = gst_rtp_j2k_pay_handle_buffer;
140
141
142
143
144
145

  GST_DEBUG_CATEGORY_INIT (rtpj2kpay_debug, "rtpj2kpay", 0,
      "JPEG 2000 RTP Payloader");
}

static void
146
gst_rtp_j2k_pay_init (GstRtpJ2KPay * pay)
147
148
149
150
{
}

static gboolean
Wim Taymans's avatar
Wim Taymans committed
151
gst_rtp_j2k_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
152
153
154
155
{
  GstStructure *caps_structure = gst_caps_get_structure (caps, 0);
  GstRtpJ2KPay *pay;
  gint width = 0, height = 0;
156
  gboolean res;
157
158
159
160
161
162
163
164
165
166
167

  pay = GST_RTP_J2K_PAY (basepayload);

  /* 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;
  }

Wim Taymans's avatar
Wim Taymans committed
168
  gst_rtp_base_payload_set_options (basepayload, "video", TRUE, "JPEG2000",
169
      90000);
Wim Taymans's avatar
Wim Taymans committed
170
  res = gst_rtp_base_payload_set_outcaps (basepayload, NULL);
171

172
  return res;
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
}


static guint
gst_rtp_j2k_pay_header_size (const guint8 * data, guint offset)
{
  return data[offset] << 8 | data[offset + 1];
}

static RtpJ2KMarker
gst_rtp_j2k_pay_scan_marker (const guint8 * data, guint size, guint * offset)
{
  while ((data[(*offset)++] != J2K_MARKER) && ((*offset) < size));

  if (G_UNLIKELY ((*offset) >= size)) {
    return J2K_MARKER_EOC;
  } else {
    guint8 marker = data[(*offset)++];
    return marker;
  }
}

195
196
197
198
199
200
201
202
203
typedef struct
{
  RtpJ2KHeader header;
  gboolean bitstream;
  guint n_tiles;
  guint next_sot;
  gboolean force_packet;
} RtpJ2KState;

204
205
static guint
find_pu_end (GstRtpJ2KPay * pay, const guint8 * data, guint size,
206
    guint offset, RtpJ2KState * state)
207
{
208
209
210
  gboolean cut_sop = FALSE;
  RtpJ2KMarker marker;

211
  /* parse the j2k header for 'start of codestream' */
212
  GST_LOG_OBJECT (pay, "checking from offset %u", offset);
213
  while (offset < size) {
214
215
216
217
218
219
220
221
222
223
224
    marker = gst_rtp_j2k_pay_scan_marker (data, size, &offset);

    if (state->bitstream) {
      /* parsing bitstream, only look for SOP */
      switch (marker) {
        case J2K_MARKER_SOP:
          GST_LOG_OBJECT (pay, "found SOP at %u", offset);
          if (cut_sop)
            return offset - 2;
          cut_sop = TRUE;
          break;
Wim Taymans's avatar
Wim Taymans committed
225
226
227
228
        case J2K_MARKER_EPH:
          /* just skip over EPH */
          GST_LOG_OBJECT (pay, "found EPH at %u", offset);
          break;
229
230
231
232
233
        default:
          if (offset >= state->next_sot) {
            GST_LOG_OBJECT (pay, "reached next SOT at %u", offset);
            state->bitstream = FALSE;
            state->force_packet = TRUE;
Wim Taymans's avatar
Wim Taymans committed
234
235
            if (marker == J2K_MARKER_EOC && state->next_sot + 2 <= size)
              /* include EOC but never go past the max size */
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
              return state->next_sot + 2;
            else
              return state->next_sot;
          }
          break;
      }
    } else {
      switch (marker) {
        case J2K_MARKER_SOC:
          GST_LOG_OBJECT (pay, "found SOC at %u", offset);
          state->header.MHF = 1;
          break;
        case J2K_MARKER_SOT:
        {
          guint len, Psot;

          GST_LOG_OBJECT (pay, "found SOT at %u", offset);
          /* we found SOT but also had a header first */
          if (state->header.MHF) {
            state->force_packet = TRUE;
            return offset - 2;
          }

          /* parse SOT but do some sanity checks first */
          len = gst_rtp_j2k_pay_header_size (data, offset);
          GST_LOG_OBJECT (pay, "SOT length %u", len);
          if (len < 8)
            return size;
          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++;

          /* get offset of next tile, if it's 0, it 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;
          else
            state->next_sot = offset - 2 + Psot;

          offset += len;
          GST_LOG_OBJECT (pay, "Isot %u, Psot %u, next %u", state->header.tile,
              Psot, state->next_sot);
          break;
        }
        case 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 */
          cut_sop = TRUE;
          /* force a new packet when we see SOP, this can be optional but the
           * spec recommends packing headers separately */
          state->force_packet = TRUE;
          break;
        case J2K_MARKER_EOC:
          GST_LOG_OBJECT (pay, "found EOC at %u", offset);
          return offset;
        default:
        {
          guint len = gst_rtp_j2k_pay_header_size (data, offset);
          GST_LOG_OBJECT (pay, "skip 0x%02x len %u", marker, len);
          offset += len;
          break;
        }
311
312
313
314
315
316
317
318
      }
    }
  }
  GST_DEBUG_OBJECT (pay, "reached end of data");
  return size;
}

static GstFlowReturn
Wim Taymans's avatar
Wim Taymans committed
319
gst_rtp_j2k_pay_handle_buffer (GstRTPBasePayload * basepayload,
320
321
322
323
324
    GstBuffer * buffer)
{
  GstRtpJ2KPay *pay;
  GstClockTime timestamp;
  GstFlowReturn ret = GST_FLOW_ERROR;
325
  RtpJ2KState state;
Wim Taymans's avatar
Wim Taymans committed
326
  GstBufferList *list = NULL;
Wim Taymans's avatar
Wim Taymans committed
327
  GstMapInfo map;
328
  guint mtu, max_size;
329
  guint offset;
330
  guint end, pos;
331
332

  pay = GST_RTP_J2K_PAY (basepayload);
Wim Taymans's avatar
Wim Taymans committed
333
  mtu = GST_RTP_BASE_PAYLOAD_MTU (pay);
334

Wim Taymans's avatar
Wim Taymans committed
335
  gst_buffer_map (buffer, &map, GST_MAP_READ);
336
  timestamp = GST_BUFFER_TIMESTAMP (buffer);
337
  offset = pos = end = 0;
338

339
  GST_LOG_OBJECT (pay,
Wim Taymans's avatar
Wim Taymans committed
340
341
      "got buffer size %" G_GSIZE_FORMAT ", timestamp %" GST_TIME_FORMAT,
      map.size, GST_TIME_ARGS (timestamp));
342
343

  /* do some header defaults first */
344
345
346
347
348
349
  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.priority = 255;  /* always 255 for now */
  state.header.tile = 0;        /* no tile number */
350
  state.header.offset = 0;      /* offset of 0 */
351
352
353
354
  state.bitstream = FALSE;
  state.n_tiles = 0;
  state.next_sot = 0;
  state.force_packet = FALSE;
355

356
357
358
  /* get max packet length */
  max_size = gst_rtp_buffer_calc_payload_len (mtu - HEADER_SIZE, 0, 0);

359
360
  list = gst_buffer_list_new_sized ((mtu / max_size) + 1);

361
362
  do {
    GstBuffer *outbuf;
Wim Taymans's avatar
Wim Taymans committed
363
    guint8 *header;
364
365
    guint payload_size;
    guint pu_size;
366
    GstRTPBuffer rtp = { NULL };
367
368
369
370
371
372
373
374
375
376
377
378
379
380

    /* try to pack as much as we can */
    do {
      /* see how much we have scanned already */
      pu_size = end - offset;
      GST_DEBUG_OBJECT (pay, "scanned pu size %u", pu_size);

      /* we need to make a new packet */
      if (state.force_packet) {
        GST_DEBUG_OBJECT (pay, "need to force a new packet");
        state.force_packet = FALSE;
        pos = end;
        break;
      }
381

382
383
384
385
386
387
388
389
390
      /* else see if we have enough */
      if (pu_size > max_size) {
        if (pos != offset)
          /* the packet became too large, use previous scanpos */
          pu_size = pos - offset;
        else
          /* the already scanned data was already too big, make sure we start
           * scanning from the last searched position */
          pos = end;
391

392
393
394
395
396
        GST_DEBUG_OBJECT (pay, "max size exceeded pu_size %u", pu_size);
        break;
      }

      pos = end;
397
398

      /* exit when finished */
Wim Taymans's avatar
Wim Taymans committed
399
      if (pos == map.size)
400
401
        break;

402
      /* scan next packetization unit and fill in the header */
Wim Taymans's avatar
Wim Taymans committed
403
      end = find_pu_end (pay, map.data, map.size, pos, &state);
404
    } while (TRUE);
405
406

    while (pu_size > 0) {
407
      guint packet_size, data_size;
Wim Taymans's avatar
Wim Taymans committed
408
      GstBuffer *paybuf;
409
410
411

      /* calculate the packet size */
      packet_size =
Wim Taymans's avatar
Wim Taymans committed
412
          gst_rtp_buffer_calc_packet_len (pu_size + HEADER_SIZE, 0, 0);
413

414
415
416
417
418
419
420
421
      if (packet_size > mtu) {
        GST_DEBUG_OBJECT (pay, "needed packet size %u clamped to MTU %u",
            packet_size, mtu);
        packet_size = mtu;
      } else {
        GST_DEBUG_OBJECT (pay, "needed packet size %u fits in MTU %u",
            packet_size, mtu);
      }
422

Wim Taymans's avatar
Wim Taymans committed
423
424
425
426
      /* 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;

Wim Taymans's avatar
Wim Taymans committed
427
428
429
      /* make buffer for header */
      outbuf = gst_rtp_buffer_new_allocate (HEADER_SIZE, 0, 0);

430
431
      GST_BUFFER_TIMESTAMP (outbuf) = timestamp;

432
433
      gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);

Wim Taymans's avatar
Wim Taymans committed
434
      /* get pointer to header */
435
      header = gst_rtp_buffer_get_payload (&rtp);
436

Wim Taymans's avatar
Wim Taymans committed
437
      pu_size -= data_size;
438
439
      if (pu_size == 0) {
        /* reached the end of a packetization unit */
440
        if (state.header.MHF) {
441
442
443
          /* we were doing a header, see if all fit in one packet or if
           * we had to fragment it */
          if (offset == 0)
444
            state.header.MHF = 3;
445
          else
446
            state.header.MHF = 2;
447
        }
Wim Taymans's avatar
Wim Taymans committed
448
        if (end >= map.size)
449
          gst_rtp_buffer_set_marker (&rtp, TRUE);
450
451
      }

452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
      /*
       * RtpJ2KHeader:
       * @tp: type (0 progressive, 1 odd field, 2 even field)
       * @MHF: Main Header Flag
       * @mh_id: Main Header Identification
       * @T: Tile field invalidation flag
       * @priority: priority
       * @tile number: the tile number of the payload
       * @reserved: set to 0
       * @fragment offset: the byte offset of the current payload
       *
       *  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                   |
       * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
       */
      header[0] = (state.header.tp << 6) | (state.header.MHF << 4) |
          (state.header.mh_id << 1) | state.header.T;
      header[1] = state.header.priority;
      header[2] = state.header.tile >> 8;
      header[3] = state.header.tile & 0xff;
      header[4] = 0;
      header[5] = state.header.offset >> 16;
      header[6] = (state.header.offset >> 8) & 0xff;
      header[7] = state.header.offset & 0xff;
Wim Taymans's avatar
Wim Taymans committed
480

Wim Taymans's avatar
Wim Taymans committed
481
      gst_rtp_buffer_unmap (&rtp);
Wim Taymans's avatar
Wim Taymans committed
482

Wim Taymans's avatar
Wim Taymans committed
483
484
485
      /* make subbuffer of j2k data */
      paybuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_MEMORY,
          offset, data_size);
486

Wim Taymans's avatar
Wim Taymans committed
487
      outbuf = gst_buffer_append (outbuf, paybuf);
Wim Taymans's avatar
Wim Taymans committed
488

Wim Taymans's avatar
Wim Taymans committed
489
      gst_buffer_list_add (list, outbuf);
490
491

      /* reset header for next round */
492
493
494
      state.header.MHF = 0;
      state.header.T = 1;
      state.header.tile = 0;
495

Wim Taymans's avatar
Wim Taymans committed
496
      offset += data_size;
497
    }
498
    offset = pos;
Wim Taymans's avatar
Wim Taymans committed
499
  } while (offset < map.size);
500
501
502

  gst_buffer_unref (buffer);

Wim Taymans's avatar
Wim Taymans committed
503
504
  /* push the whole buffer list at once */
  ret = gst_rtp_base_payload_push_list (basepayload, list);
Wim Taymans's avatar
Wim Taymans committed
505

506
507
508
  return ret;
}

Wim Taymans's avatar
Wim Taymans committed
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
static void
gst_rtp_j2k_pay_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_pay_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;
  }
}

531
532
533
gboolean
gst_rtp_j2k_pay_plugin_init (GstPlugin * plugin)
{
534
  return gst_element_register (plugin, "rtpj2kpay", GST_RANK_SECONDARY,
535
536
      GST_TYPE_RTP_J2K_PAY);
}