gstrtpmp4gpay.c 16.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
/* GStreamer
 * Copyright (C) <2006> Wim Taymans <wim@fluendo.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
12
13
14
15
16
17
 * 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.
18
19
20
21
22
23
24
25
26
27
28
29
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <string.h>

#include <gst/rtp/gstrtpbuffer.h>

#include "gstrtpmp4gpay.h"

30
GST_DEBUG_CATEGORY_STATIC (rtpmp4gpay_debug);
31
32
33
#define GST_CAT_DEFAULT (rtpmp4gpay_debug)

/* elementfactory information */
34
static const GstElementDetails gst_rtp_mp4gpay_details =
35
GST_ELEMENT_DETAILS ("RTP packet payloader",
Wim Taymans's avatar
Wim Taymans committed
36
37
38
    "Codec/Payloader/Network",
    "Payload MPEG4 elementary streams as RTP packets (RFC 3640)",
    "Wim Taymans <wim@fluendo.com>");
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

static GstStaticPadTemplate gst_rtp_mp4g_pay_sink_template =
    GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/mpeg,"
        "mpegversion=(int) 4,"
        "systemstream=(boolean)false;" "audio/mpeg," "mpegversion=(int) 4")
    );

static GstStaticPadTemplate gst_rtp_mp4g_pay_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("application/x-rtp, "
54
        "media = (string) { \"video\", \"audio\", \"application\" }, "
55
        "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
56
        "clock-rate = (int) [1, MAX ], "
57
        "encoding-name = (string) \"MPEG4-GENERIC\", "
58
        /* required string params */
59
60
        "streamtype = (string) { \"4\", \"5\" }, "      /* 4 = video, 5 = audio */
        /* "profile-level-id = (string) [1,MAX], " */
61
        /* "config = (string) [1,MAX]" */
62
        "mode = (string) { \"generic\", \"CELP-cbr\", \"CELP-vbr\", \"AAC-lbr\", \"AAC-hbr\" } "
63
        /* Optional general parameters */
64
65
66
67
68
        /* "objecttype = (string) [1,MAX], " */
        /* "constantsize = (string) [1,MAX], " *//* constant size of each AU */
        /* "constantduration = (string) [1,MAX], " *//* constant duration of each AU */
        /* "maxdisplacement = (string) [1,MAX], " */
        /* "de-interleavebuffersize = (string) [1,MAX], " */
69
        /* Optional configuration parameters */
70
71
72
73
74
75
76
77
        /* "sizelength = (string) [1, 16], " *//* max 16 bits, should be enough... */
        /* "indexlength = (string) [1, 8], " */
        /* "indexdeltalength = (string) [1, 8], " */
        /* "ctsdeltalength = (string) [1, 64], " */
        /* "dtsdeltalength = (string) [1, 64], " */
        /* "randomaccessindication = (string) {0, 1}, " */
        /* "streamstateindication = (string) [0, 64], " */
        /* "auxiliarydatasizelength = (string) [0, 64]" */ )
78
79
80
81
82
83
84
85
86
87
    );


static void gst_rtp_mp4g_pay_class_init (GstRtpMP4GPayClass * klass);
static void gst_rtp_mp4g_pay_base_init (GstRtpMP4GPayClass * klass);
static void gst_rtp_mp4g_pay_init (GstRtpMP4GPay * rtpmp4gpay);
static void gst_rtp_mp4g_pay_finalize (GObject * object);

static gboolean gst_rtp_mp4g_pay_setcaps (GstBaseRTPPayload * payload,
    GstCaps * caps);
Wim Taymans's avatar
Wim Taymans committed
88
89
static GstStateChangeReturn gst_rtp_mp4g_pay_change_state (GstElement * element,
    GstStateChange transition);
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
static GstFlowReturn gst_rtp_mp4g_pay_handle_buffer (GstBaseRTPPayload *
    payload, GstBuffer * buffer);

static GstBaseRTPPayloadClass *parent_class = NULL;

static GType
gst_rtp_mp4g_pay_get_type (void)
{
  static GType rtpmp4gpay_type = 0;

  if (!rtpmp4gpay_type) {
    static const GTypeInfo rtpmp4gpay_info = {
      sizeof (GstRtpMP4GPayClass),
      (GBaseInitFunc) gst_rtp_mp4g_pay_base_init,
      NULL,
      (GClassInitFunc) gst_rtp_mp4g_pay_class_init,
      NULL,
      NULL,
      sizeof (GstRtpMP4GPay),
      0,
      (GInstanceInitFunc) gst_rtp_mp4g_pay_init,
    };

    rtpmp4gpay_type =
        g_type_register_static (GST_TYPE_BASE_RTP_PAYLOAD, "GstRtpMP4GPay",
        &rtpmp4gpay_info, 0);
  }
  return rtpmp4gpay_type;
}

static void
gst_rtp_mp4g_pay_base_init (GstRtpMP4GPayClass * klass)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);

  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&gst_rtp_mp4g_pay_src_template));
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&gst_rtp_mp4g_pay_sink_template));

  gst_element_class_set_details (element_class, &gst_rtp_mp4gpay_details);
}

static void
gst_rtp_mp4g_pay_class_init (GstRtpMP4GPayClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;
  GstBaseRTPPayloadClass *gstbasertppayload_class;

  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;
  gstbasertppayload_class = (GstBaseRTPPayloadClass *) klass;

144
  parent_class = g_type_class_peek_parent (klass);
145
146
147

  gobject_class->finalize = gst_rtp_mp4g_pay_finalize;

Wim Taymans's avatar
Wim Taymans committed
148
149
  gstelement_class->change_state = gst_rtp_mp4g_pay_change_state;

150
151
152
153
154
155
156
157
158
159
160
161
  gstbasertppayload_class->set_caps = gst_rtp_mp4g_pay_setcaps;
  gstbasertppayload_class->handle_buffer = gst_rtp_mp4g_pay_handle_buffer;

  GST_DEBUG_CATEGORY_INIT (rtpmp4gpay_debug, "rtpmp4gpay", 0,
      "MP4-generic RTP Payloader");
}

static void
gst_rtp_mp4g_pay_init (GstRtpMP4GPay * rtpmp4gpay)
{
  rtpmp4gpay->adapter = gst_adapter_new ();
  rtpmp4gpay->rate = 90000;
162
  rtpmp4gpay->profile = g_strdup ("1");
163
  rtpmp4gpay->mode = "";
164
165
166
167
168
169
170
171
172
173
174
}

static void
gst_rtp_mp4g_pay_finalize (GObject * object)
{
  GstRtpMP4GPay *rtpmp4gpay;

  rtpmp4gpay = GST_RTP_MP4G_PAY (object);

  g_object_unref (rtpmp4gpay->adapter);
  rtpmp4gpay->adapter = NULL;
175
176
  g_free (rtpmp4gpay->params);
  rtpmp4gpay->params = NULL;
177

Wim Taymans's avatar
Wim Taymans committed
178
179
180
181
  if (rtpmp4gpay->config)
    gst_buffer_unref (rtpmp4gpay->config);
  rtpmp4gpay->config = NULL;

182
183
184
  g_free (rtpmp4gpay->profile);
  rtpmp4gpay->profile = NULL;

185
186
187
  G_OBJECT_CLASS (parent_class)->finalize (object);
}

188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
static unsigned sampling_table[16] = {
  96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
  16000, 12000, 11025, 8000, 7350, 0, 0, 0
};

static gboolean
gst_rtp_mp4g_pay_parse_audio_config (GstRtpMP4GPay * rtpmp4gpay,
    GstBuffer * buffer)
{
  guint8 *data;
  guint size;
  guint8 objectType;
  guint8 samplingIdx;
  guint8 channelCfg;

  data = GST_BUFFER_DATA (buffer);
  size = GST_BUFFER_SIZE (buffer);

  if (size < 2)
    goto too_short;

209
  /* any object type is fine, we need to copy it to the profile-level-id field. */
210
  objectType = (data[0] & 0xf8) >> 3;
211
212
  if (objectType == 0)
    goto invalid_object;
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235

  samplingIdx = ((data[0] & 0x07) << 1) | ((data[1] & 0x80) >> 7);
  /* only fixed values for now */
  if (samplingIdx > 12 && samplingIdx != 15)
    goto wrong_freq;

  channelCfg = ((data[1] & 0x78) >> 3);
  if (channelCfg > 7)
    goto wrong_channels;

  /* rtp rate depends on sampling rate of the audio */
  if (samplingIdx == 15) {
    if (size < 5)
      goto too_short;

    /* index of 15 means we get the rate in the next 24 bits */
    rtpmp4gpay->rate = ((data[1] & 0x7f) << 17) |
        ((data[2]) << 9) | ((data[3]) << 1) | ((data[4] & 0x80) >> 7);
  } else {
    /* else use the rate from the table */
    rtpmp4gpay->rate = sampling_table[samplingIdx];
  }
  /* extra rtp params contain the number of channels */
236
237
  g_free (rtpmp4gpay->params);
  rtpmp4gpay->params = g_strdup_printf ("%d", channelCfg);
238
  /* audio stream type */
239
  rtpmp4gpay->streamtype = "5";
240
  /* mode only high bitrate for now */
241
  rtpmp4gpay->mode = "AAC-hbr";
242
  /* profile */
243
  g_free (rtpmp4gpay->profile);
244
  rtpmp4gpay->profile = g_strdup_printf ("%d", objectType);
245
246
247
248
249
250
251
252
253
254
255

  GST_DEBUG_OBJECT (rtpmp4gpay,
      "objectType: %d, samplingIdx: %d (%d), channelCfg: %d", objectType,
      samplingIdx, rtpmp4gpay->rate, channelCfg);

  return TRUE;

  /* ERROR */
too_short:
  {
    GST_ELEMENT_ERROR (rtpmp4gpay, STREAM, FORMAT,
256
        (NULL), ("config string too short, expected 2 bytes, got %d", size));
257
258
    return FALSE;
  }
259
invalid_object:
260
  {
261
262
    GST_ELEMENT_ERROR (rtpmp4gpay, STREAM, FORMAT,
        (NULL), ("invalid object type 0"));
263
264
265
266
267
268
269
270
271
272
273
    return FALSE;
  }
wrong_freq:
  {
    GST_ELEMENT_ERROR (rtpmp4gpay, STREAM, NOT_IMPLEMENTED,
        (NULL), ("unsupported frequency index %d", samplingIdx));
    return FALSE;
  }
wrong_channels:
  {
    GST_ELEMENT_ERROR (rtpmp4gpay, STREAM, NOT_IMPLEMENTED,
274
        (NULL), ("unsupported number of channels %d, must < 8", channelCfg));
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
    return FALSE;
  }
}

#define VOS_STARTCODE                   0x000001B0

static gboolean
gst_rtp_mp4g_pay_parse_video_config (GstRtpMP4GPay * rtpmp4gpay,
    GstBuffer * buffer)
{
  guint8 *data;
  guint size;
  guint32 code;

  data = GST_BUFFER_DATA (buffer);
  size = GST_BUFFER_SIZE (buffer);

  if (size < 5)
    goto too_short;

  code = GST_READ_UINT32_BE (data);
296
297

  g_free (rtpmp4gpay->profile);
298
299
  if (code == VOS_STARTCODE) {
    /* get profile */
300
    rtpmp4gpay->profile = g_strdup_printf ("%d", (gint) data[4]);
301
302
  } else {
    GST_ELEMENT_WARNING (rtpmp4gpay, STREAM, FORMAT,
303
304
        (NULL), ("profile not found in config string, assuming \'1\'"));
    rtpmp4gpay->profile = g_strdup ("1");
305
306
307
308
309
  }

  /* fixed rate */
  rtpmp4gpay->rate = 90000;
  /* video stream type */
310
  rtpmp4gpay->streamtype = "4";
311
  /* no params for video */
312
  rtpmp4gpay->params = NULL;
313
314
315
  /* mode */
  rtpmp4gpay->mode = "generic";

316
  GST_LOG_OBJECT (rtpmp4gpay, "profile %s", rtpmp4gpay->profile);
317
318
319
320
321
322
323
324
325
326
327
328

  return TRUE;

  /* ERROR */
too_short:
  {
    GST_ELEMENT_ERROR (rtpmp4gpay, STREAM, FORMAT,
        (NULL), ("config string too short"));
    return FALSE;
  }
}

329
330
331
static void
gst_rtp_mp4g_pay_new_caps (GstRtpMP4GPay * rtpmp4gpay)
{
332
  gchar *config;
333
334
  GValue v = { 0 };

335
336
337
338
339
340
341
342
#define MP4GCAPS						\
  "streamtype", G_TYPE_STRING, rtpmp4gpay->streamtype, 		\
  "profile-level-id", G_TYPE_STRING, rtpmp4gpay->profile,	\
  "mode", G_TYPE_STRING, rtpmp4gpay->mode,			\
  "config", G_TYPE_STRING, config,				\
  "sizelength", G_TYPE_STRING, "13",				\
  "indexlength", G_TYPE_STRING, "3",				\
  "indexdeltalength", G_TYPE_STRING, "3",			\
343
344
  NULL

345
346
347
348
  g_value_init (&v, GST_TYPE_BUFFER);
  gst_value_set_buffer (&v, rtpmp4gpay->config);
  config = gst_value_serialize (&v);

349
350
351
  /* hmm, silly */
  if (rtpmp4gpay->params) {
    gst_basertppayload_set_outcaps (GST_BASE_RTP_PAYLOAD (rtpmp4gpay),
352
        "encoding-params", G_TYPE_STRING, rtpmp4gpay->params, MP4GCAPS);
353
354
355
356
  } else {
    gst_basertppayload_set_outcaps (GST_BASE_RTP_PAYLOAD (rtpmp4gpay),
        MP4GCAPS);
  }
357
358
359

  g_value_unset (&v);
  g_free (config);
360
361

#undef MP4GCAPS
362
363
364
365
366
367
}

static gboolean
gst_rtp_mp4g_pay_setcaps (GstBaseRTPPayload * payload, GstCaps * caps)
{
  GstRtpMP4GPay *rtpmp4gpay;
368
  GstStructure *structure;
369
  const GValue *codec_data;
370
  gchar *media_type = NULL;
371
372
373

  rtpmp4gpay = GST_RTP_MP4G_PAY (payload);

374
375
  structure = gst_caps_get_structure (caps, 0);

376
377
378
379
  codec_data = gst_structure_get_value (structure, "codec_data");
  if (codec_data) {
    GST_LOG_OBJECT (rtpmp4gpay, "got codec_data");
    if (G_VALUE_TYPE (codec_data) == GST_TYPE_BUFFER) {
380
381
      GstBuffer *buffer;
      const gchar *name;
382
      gboolean res;
383

384
385
      buffer = gst_value_get_buffer (codec_data);
      GST_LOG_OBJECT (rtpmp4gpay, "configuring codec_data");
386
387
388
389
390
391

      name = gst_structure_get_name (structure);

      /* parse buffer */
      if (!strcmp (name, "audio/mpeg")) {
        res = gst_rtp_mp4g_pay_parse_audio_config (rtpmp4gpay, buffer);
392
        media_type = "audio";
393
394
      } else if (!strcmp (name, "video/mpeg")) {
        res = gst_rtp_mp4g_pay_parse_video_config (rtpmp4gpay, buffer);
395
        media_type = "video";
396
397
398
399
400
401
402
403
404
405
406
407
408
      } else {
        res = FALSE;
      }
      if (!res)
        goto config_failed;

      /* now we can configure the buffer */
      if (rtpmp4gpay->config)
        gst_buffer_unref (rtpmp4gpay->config);

      rtpmp4gpay->config = gst_buffer_copy (buffer);
    }
  }
409
410
  if (media_type == NULL)
    goto config_failed;
411

412
  gst_basertppayload_set_options (payload, media_type, TRUE, "MPEG4-GENERIC",
413
414
      rtpmp4gpay->rate);

415
416
  gst_rtp_mp4g_pay_new_caps (rtpmp4gpay);

417
  return TRUE;
418
419
420
421
422
423
424

  /* ERRORS */
config_failed:
  {
    GST_DEBUG_OBJECT (rtpmp4gpay, "failed to parse config");
    return FALSE;
  }
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
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
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
}

static GstFlowReturn
gst_rtp_mp4g_pay_flush (GstRtpMP4GPay * rtpmp4gpay)
{
  guint avail, total;
  GstBuffer *outbuf;
  GstFlowReturn ret;
  gboolean fragmented;
  guint mtu;

  fragmented = FALSE;

  /* the data available in the adapter is either smaller
   * than the MTU or bigger. In the case it is smaller, the complete
   * adapter contents can be put in one packet. In the case the
   * adapter has more than one MTU, we need to fragment the MPEG data
   * over multiple packets. */
  total = avail = gst_adapter_available (rtpmp4gpay->adapter);

  ret = GST_FLOW_OK;
  mtu = GST_BASE_RTP_PAYLOAD_MTU (rtpmp4gpay);

  while (avail > 0) {
    guint towrite;
    guint8 *payload;
    guint payload_len;
    guint packet_len;

    /* this will be the total lenght of the packet */
    packet_len = gst_rtp_buffer_calc_packet_len (avail, 0, 0);

    /* fill one MTU or all available bytes, we need 4 spare bytes for
     * the AU header. */
    towrite = MIN (packet_len, mtu - 4);

    /* this is the payload length */
    payload_len = gst_rtp_buffer_calc_payload_len (towrite, 0, 0);

    GST_DEBUG_OBJECT (rtpmp4gpay,
        "avail %d, towrite %d, packet_len %d, payload_len %d", avail, towrite,
        packet_len, payload_len);

    /* create buffer to hold the payload, also make room for the 4 header bytes. */
    outbuf = gst_rtp_buffer_new_allocate (payload_len + 4, 0, 0);

    /* copy payload */
    payload = gst_rtp_buffer_get_payload (outbuf);

    /* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- .. -+-+-+-+-+-+-+-+-+-+
     * |AU-headers-length|AU-header|AU-header|      |AU-header|padding|
     * |                 |   (1)   |   (2)   |      |   (n)   | bits  |
     * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- .. -+-+-+-+-+-+-+-+-+-+
     */
    /* AU-headers-length, we only have 1 AU-header */
    payload[0] = 0x00;
    payload[1] = 0x10;          /* we use 16 bits for the header */

    /* +---------------------------------------+
     * |     AU-size                           |
     * +---------------------------------------+
     * |     AU-Index / AU-Index-delta         |
     * +---------------------------------------+
     * |     CTS-flag                          |
     * +---------------------------------------+
     * |     CTS-delta                         |
     * +---------------------------------------+
     * |     DTS-flag                          |
     * +---------------------------------------+
     * |     DTS-delta                         |
     * +---------------------------------------+
     * |     RAP-flag                          |
     * +---------------------------------------+
     * |     Stream-state                      |
     * +---------------------------------------+
     */
    /* The AU-header, no CTS, DTS, RAP, Stream-state 
     *
     * AU-size is always the total size of the AU, not the fragmented size 
     */
    payload[2] = (total & 0x1fe0) >> 5;
    payload[3] = (total & 0x1f) << 3;   /* we use 13 bits for the size, 3 bits index */

508
509
    /* copy stuff from adapter to payload */
    gst_adapter_copy (rtpmp4gpay->adapter, &payload[4], 0, payload_len);
510
511
    gst_adapter_flush (rtpmp4gpay->adapter, payload_len);

512
    /* marker only if the packet is complete */
513
    gst_rtp_buffer_set_marker (outbuf, avail <= payload_len);
514

Wim Taymans's avatar
Wim Taymans committed
515
516
    GST_BUFFER_TIMESTAMP (outbuf) = rtpmp4gpay->first_timestamp;
    GST_BUFFER_DURATION (outbuf) = rtpmp4gpay->first_duration;
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539

    ret = gst_basertppayload_push (GST_BASE_RTP_PAYLOAD (rtpmp4gpay), outbuf);

    avail -= payload_len;
    fragmented = TRUE;
  }

  return ret;
}

/* we expect buffers as exactly one complete AU
 */
static GstFlowReturn
gst_rtp_mp4g_pay_handle_buffer (GstBaseRTPPayload * basepayload,
    GstBuffer * buffer)
{
  GstRtpMP4GPay *rtpmp4gpay;
  GstFlowReturn ret;

  ret = GST_FLOW_OK;

  rtpmp4gpay = GST_RTP_MP4G_PAY (basepayload);

Wim Taymans's avatar
Wim Taymans committed
540
541
  rtpmp4gpay->first_timestamp = GST_BUFFER_TIMESTAMP (buffer);
  rtpmp4gpay->first_duration = GST_BUFFER_DURATION (buffer);
542
543
544
545
546
547
548
549

  /* we always encode and flush a full AU */
  gst_adapter_push (rtpmp4gpay->adapter, buffer);
  ret = gst_rtp_mp4g_pay_flush (rtpmp4gpay);

  return ret;
}

Wim Taymans's avatar
Wim Taymans committed
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
static GstStateChangeReturn
gst_rtp_mp4g_pay_change_state (GstElement * element, GstStateChange transition)
{
  GstRtpMP4GPay *rtpmp4gpay;
  GstStateChangeReturn ret;

  rtpmp4gpay = GST_RTP_MP4G_PAY (element);

  switch (transition) {
    default:
      break;
  }

  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);

  switch (transition) {
    default:
      break;
  }
  return ret;
}


573
574
575
576
577
578
gboolean
gst_rtp_mp4g_pay_plugin_init (GstPlugin * plugin)
{
  return gst_element_register (plugin, "rtpmp4gpay",
      GST_RANK_NONE, GST_TYPE_RTP_MP4G_PAY);
}