gstrtpmp4vpay.c 13.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/* GStreamer
 * Copyright (C) <2005> 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
 * Library General Public License for more 
 */

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

#include <string.h>

#include <gst/rtp/gstrtpbuffer.h>

23
#include "gstrtpmp4vpay.h"
24

25 26 27
GST_DEBUG_CATEGORY (rtpmp4vpay_debug);
#define GST_CAT_DEFAULT (rtpmp4vpay_debug)

28
/* elementfactory information */
29
static GstElementDetails gst_rtp_mp4vpay_details = {
30
  "RTP packet parser",
31 32
  "Codec/Payloader/Network",
  "Payode MPEG4 video as RTP packets (RFC 3016)",
33 34 35
  "Wim Taymans <wim@fluendo.com>"
};

36
static GstStaticPadTemplate gst_rtp_mp4v_pay_sink_template =
37 38 39 40 41 42 43
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("video/mpeg,"
        "mpegversion=(int) 4," "systemstream=(boolean)false")
    );

44
static GstStaticPadTemplate gst_rtp_mp4v_pay_src_template =
45 46 47
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
48 49
    GST_STATIC_CAPS ("application/x-rtp, "
        "media = (string) \"video\", "
50
        "payload = (int) [ 96, 127 ], "
51 52
        "clock-rate = (int) [1, MAX ], " "encoding-name = (string) \"MP4V-ES\""
        /* two string params
53
         *
54 55
         "profile-level-id = (string) [1,MAX]"
         "config = (string) [1,MAX]"
56
         */
57 58 59
    )
    );

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
60
#define DEFAULT_SEND_CONFIG     FALSE
Wim Taymans's avatar
Wim Taymans committed
61 62 63 64 65 66 67

enum
{
  ARG_0,
  ARG_SEND_CONFIG
};

68

69 70 71 72
static void gst_rtp_mp4v_pay_class_init (GstRtpMP4VPayClass * klass);
static void gst_rtp_mp4v_pay_base_init (GstRtpMP4VPayClass * klass);
static void gst_rtp_mp4v_pay_init (GstRtpMP4VPay * rtpmp4vpay);
static void gst_rtp_mp4v_pay_finalize (GObject * object);
73

74
static void gst_rtp_mp4v_pay_set_property (GObject * object, guint prop_id,
Wim Taymans's avatar
Wim Taymans committed
75
    const GValue * value, GParamSpec * pspec);
76
static void gst_rtp_mp4v_pay_get_property (GObject * object, guint prop_id,
Wim Taymans's avatar
Wim Taymans committed
77 78
    GValue * value, GParamSpec * pspec);

79
static gboolean gst_rtp_mp4v_pay_setcaps (GstBaseRTPPayload * payload,
80
    GstCaps * caps);
81 82
static GstFlowReturn gst_rtp_mp4v_pay_handle_buffer (GstBaseRTPPayload *
    payload, GstBuffer * buffer);
83

84
static GstBaseRTPPayloadClass *parent_class = NULL;
85 86

static GType
87
gst_rtp_mp4v_pay_get_type (void)
88
{
89
  static GType rtpmp4vpay_type = 0;
90

91 92 93 94
  if (!rtpmp4vpay_type) {
    static const GTypeInfo rtpmp4vpay_info = {
      sizeof (GstRtpMP4VPayClass),
      (GBaseInitFunc) gst_rtp_mp4v_pay_base_init,
95
      NULL,
96
      (GClassInitFunc) gst_rtp_mp4v_pay_class_init,
97 98
      NULL,
      NULL,
99
      sizeof (GstRtpMP4VPay),
100
      0,
101
      (GInstanceInitFunc) gst_rtp_mp4v_pay_init,
102 103
    };

104 105 106
    rtpmp4vpay_type =
        g_type_register_static (GST_TYPE_BASE_RTP_PAYLOAD, "GstRtpMP4VPay",
        &rtpmp4vpay_info, 0);
107
  }
108
  return rtpmp4vpay_type;
109 110 111
}

static void
112
gst_rtp_mp4v_pay_base_init (GstRtpMP4VPayClass * klass)
113 114 115 116
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);

  gst_element_class_add_pad_template (element_class,
117
      gst_static_pad_template_get (&gst_rtp_mp4v_pay_src_template));
118
  gst_element_class_add_pad_template (element_class,
119
      gst_static_pad_template_get (&gst_rtp_mp4v_pay_sink_template));
120

121
  gst_element_class_set_details (element_class, &gst_rtp_mp4vpay_details);
122 123 124
}

static void
125
gst_rtp_mp4v_pay_class_init (GstRtpMP4VPayClass * klass)
126 127 128
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;
129
  GstBaseRTPPayloadClass *gstbasertppayload_class;
130 131 132

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

135 136
  parent_class = g_type_class_ref (GST_TYPE_BASE_RTP_PAYLOAD);

137 138
  gobject_class->set_property = gst_rtp_mp4v_pay_set_property;
  gobject_class->get_property = gst_rtp_mp4v_pay_get_property;
Wim Taymans's avatar
Wim Taymans committed
139 140 141 142 143 144

  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SEND_CONFIG,
      g_param_spec_boolean ("send-config", "Send Config",
          "Send the config parameters in RTP packets as well",
          DEFAULT_SEND_CONFIG, G_PARAM_READWRITE));

145
  gobject_class->finalize = gst_rtp_mp4v_pay_finalize;
146

147 148
  gstbasertppayload_class->set_caps = gst_rtp_mp4v_pay_setcaps;
  gstbasertppayload_class->handle_buffer = gst_rtp_mp4v_pay_handle_buffer;
149 150 151 152

  GST_DEBUG_CATEGORY_INIT (rtpmp4vpay_debug, "rtpmp4vpay", 0,
      "MP4 video RTP Payloader");

153 154 155
}

static void
156
gst_rtp_mp4v_pay_init (GstRtpMP4VPay * rtpmp4vpay)
157
{
158 159 160 161
  rtpmp4vpay->adapter = gst_adapter_new ();
  rtpmp4vpay->rate = 90000;
  rtpmp4vpay->profile = 1;
  rtpmp4vpay->send_config = DEFAULT_SEND_CONFIG;
162 163
}

164
static void
165
gst_rtp_mp4v_pay_finalize (GObject * object)
166
{
167
  GstRtpMP4VPay *rtpmp4vpay;
168

169
  rtpmp4vpay = GST_RTP_MP4V_PAY (object);
170

171 172
  g_object_unref (rtpmp4vpay->adapter);
  rtpmp4vpay->adapter = NULL;
173

174 175 176 177
  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
178
gst_rtp_mp4v_pay_new_caps (GstRtpMP4VPay * rtpmp4vpay)
179
{
180 181 182
  gchar *profile, *config;
  GValue v = { 0 };

183
  profile = g_strdup_printf ("%d", rtpmp4vpay->profile);
184
  g_value_init (&v, GST_TYPE_BUFFER);
185
  gst_value_set_buffer (&v, rtpmp4vpay->config);
186 187
  config = gst_value_serialize (&v);

188
  gst_basertppayload_set_outcaps (GST_BASE_RTP_PAYLOAD (rtpmp4vpay),
189 190 191 192 193 194 195
      "profile-level-id", G_TYPE_STRING, profile,
      "config", G_TYPE_STRING, config, NULL);

  g_value_unset (&v);

  g_free (profile);
  g_free (config);
196 197 198
}

static gboolean
199
gst_rtp_mp4v_pay_setcaps (GstBaseRTPPayload * payload, GstCaps * caps)
200
{
201
  GstRtpMP4VPay *rtpmp4vpay;
202 203
  GstStructure *structure;
  const GValue *codec_info;
204

205
  rtpmp4vpay = GST_RTP_MP4V_PAY (payload);
206

207 208 209
  gst_basertppayload_set_options (payload, "video", TRUE, "MP4V-ES",
      rtpmp4vpay->rate);

210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
  structure = gst_caps_get_structure (caps, 0);
  codec_info = gst_structure_get_value (structure, "codec_info");
  if (codec_info) {
    GST_LOG_OBJECT (rtpmp4vpay, "got codec_info");
    if (G_VALUE_TYPE (codec_info) == GST_TYPE_BUFFER) {
      GstBuffer *buffer;
      guint8 *data;
      guint size;

      buffer = gst_value_get_buffer (codec_info);

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

      if (size < 5)
        goto done;

      rtpmp4vpay->profile = data[4];
      GST_LOG_OBJECT (rtpmp4vpay, "configuring codec_info, profile %d",
          data[4]);

      if (rtpmp4vpay->config)
        gst_buffer_unref (rtpmp4vpay->config);
      rtpmp4vpay->config = gst_buffer_copy (buffer);
      gst_rtp_mp4v_pay_new_caps (rtpmp4vpay);
    }
  }

done:
239 240 241 242
  return TRUE;
}

static GstFlowReturn
243
gst_rtp_mp4v_pay_flush (GstRtpMP4VPay * rtpmp4vpay)
244 245 246 247 248 249 250 251 252 253
{
  guint avail;
  GstBuffer *outbuf;
  GstFlowReturn ret;

  /* 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 split the MP4V data
   * over multiple packets. */
254
  avail = gst_adapter_available (rtpmp4vpay->adapter);
255 256 257 258 259 260 261 262 263 264 265

  ret = GST_FLOW_OK;

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

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

    /* fill one MTU or all available bytes */
269
    towrite = MIN (packet_len, GST_BASE_RTP_PAYLOAD_MTU (rtpmp4vpay));
270 271

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

    /* create buffer to hold the payload */
275
    outbuf = gst_rtp_buffer_new_allocate (payload_len, 0, 0);
276

277
    /* copy payload */
278 279
    payload = gst_rtp_buffer_get_payload (outbuf);
    data = (guint8 *) gst_adapter_peek (rtpmp4vpay->adapter, payload_len);
280
    memcpy (payload, data, payload_len);
281

282
    gst_adapter_flush (rtpmp4vpay->adapter, payload_len);
283 284 285

    avail -= payload_len;

286
    gst_rtp_buffer_set_marker (outbuf, avail == 0);
287

288
    GST_BUFFER_TIMESTAMP (outbuf) = rtpmp4vpay->first_ts;
289

290
    ret = gst_basertppayload_push (GST_BASE_RTP_PAYLOAD (rtpmp4vpay), outbuf);
291 292 293 294 295
  }

  return ret;
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
296 297 298 299 300 301
#define VOS_STARTCODE                   0x000001B0
#define VOS_ENDCODE                     0x000001B1
#define USER_DATA_STARTCODE             0x000001B2
#define GOP_STARTCODE                   0x000001B3
#define VISUAL_OBJECT_STARTCODE         0x000001B5
#define VOP_STARTCODE                   0x000001B6
302 303

static gboolean
304
gst_rtp_mp4v_pay_depay_data (GstRtpMP4VPay * enc, guint8 * data, guint size,
Wim Taymans's avatar
Wim Taymans committed
305
    gint * strip)
306 307 308 309
{
  guint32 code;
  gboolean result;

Wim Taymans's avatar
Wim Taymans committed
310 311
  *strip = 0;

312 313 314 315
  if (size < 5)
    return FALSE;

  code = GST_READ_UINT32_BE (data);
316
  GST_DEBUG_OBJECT (enc, "start code 0x%08x", code);
317 318 319 320 321 322 323 324 325 326 327 328

  switch (code) {
    case VOS_STARTCODE:
    {
      gint i;
      guint8 profile;
      gboolean newprofile = FALSE;
      gboolean equal;

      /* profile_and_level_indication */
      profile = data[4];

329 330
      GST_DEBUG_OBJECT (enc, "VOS profile 0x%08x", profile);

331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
      if (profile != enc->profile) {
        newprofile = TRUE;
        enc->profile = profile;
      }

      /* up to the next GOP_STARTCODE or VOP_STARTCODE is
       * the config information */
      code = 0xffffffff;
      for (i = 5; i < size - 4; i++) {
        code = (code << 8) | data[i];
        if (code == GOP_STARTCODE || code == VOP_STARTCODE)
          break;
      }
      i -= 3;
      /* see if config changed */
      equal = FALSE;
      if (enc->config) {
        if (GST_BUFFER_SIZE (enc->config) == i) {
          equal = memcmp (GST_BUFFER_DATA (enc->config), data, i) == 0;
        }
      }
      /* if config string changed or new profile, make new caps */
      if (!equal || newprofile) {
        if (enc->config)
          gst_buffer_unref (enc->config);
        enc->config = gst_buffer_new_and_alloc (i);
        memcpy (GST_BUFFER_DATA (enc->config), data, i);
358
        gst_rtp_mp4v_pay_new_caps (enc);
359
      }
Wim Taymans's avatar
Wim Taymans committed
360 361
      *strip = i;
      /* we need to flush out the current packet. */
362 363 364 365
      result = TRUE;
      break;
    }
    case VOP_STARTCODE:
366
      GST_DEBUG_OBJECT (enc, "VOP");
Wim Taymans's avatar
Wim Taymans committed
367
      /* VOP startcode, we don't have to flush the packet */
368 369 370
      result = FALSE;
      break;
    default:
371
      GST_DEBUG_OBJECT (enc, "other startcode");
Wim Taymans's avatar
Wim Taymans committed
372
      /* all other startcodes need a flush */
373 374 375 376 377 378
      result = TRUE;
      break;
  }
  return result;
}

379 380 381
/* we expect buffers starting on startcodes. 
 */
static GstFlowReturn
382
gst_rtp_mp4v_pay_handle_buffer (GstBaseRTPPayload * basepayload,
383
    GstBuffer * buffer)
384
{
385
  GstRtpMP4VPay *rtpmp4vpay;
386 387 388
  GstFlowReturn ret;
  guint size, avail;
  guint packet_len;
389 390
  guint8 *data;
  gboolean flush;
Wim Taymans's avatar
Wim Taymans committed
391
  gint strip;
392
  GstClockTime duration;
Wim Taymans's avatar
Wim Taymans committed
393 394

  ret = GST_FLOW_OK;
395

396
  rtpmp4vpay = GST_RTP_MP4V_PAY (basepayload);
397 398

  size = GST_BUFFER_SIZE (buffer);
399
  data = GST_BUFFER_DATA (buffer);
400
  duration = GST_BUFFER_DURATION (buffer);
401
  avail = gst_adapter_available (rtpmp4vpay->adapter);
402

Wim Taymans's avatar
Wim Taymans committed
403 404
  /* empty buffer, take timestamp */
  if (avail == 0) {
405 406
    rtpmp4vpay->first_ts = GST_BUFFER_TIMESTAMP (buffer);
    rtpmp4vpay->duration = 0;
Wim Taymans's avatar
Wim Taymans committed
407 408
  }

409
  /* depay incomming data and see if we need to start a new RTP
410
   * packet */
411
  flush = gst_rtp_mp4v_pay_depay_data (rtpmp4vpay, data, size, &strip);
Wim Taymans's avatar
Wim Taymans committed
412 413
  if (strip) {
    /* strip off config if requested */
414
    if (!rtpmp4vpay->send_config) {
Wim Taymans's avatar
Wim Taymans committed
415 416 417 418
      GstBuffer *subbuf;

      /* strip off header */
      subbuf = gst_buffer_create_sub (buffer, strip, size - strip);
419
      GST_BUFFER_TIMESTAMP (subbuf) = GST_BUFFER_TIMESTAMP (buffer);
Wim Taymans's avatar
Wim Taymans committed
420 421 422 423 424 425 426
      gst_buffer_unref (buffer);
      buffer = subbuf;

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

Wim Taymans's avatar
Wim Taymans committed
428 429
  /* if we need to flush, do so now */
  if (flush) {
430 431 432
    ret = gst_rtp_mp4v_pay_flush (rtpmp4vpay);
    rtpmp4vpay->first_ts = GST_BUFFER_TIMESTAMP (buffer);
    rtpmp4vpay->duration = 0;
433
    avail = 0;
434 435
  }

Wim Taymans's avatar
Wim Taymans committed
436
  /* get packet length of data and see if we exceeded MTU. */
437
  packet_len = gst_rtp_buffer_calc_packet_len (avail + size, 0, 0);
438

439
  if (gst_basertppayload_is_filled (basepayload,
440 441 442 443
          packet_len, rtpmp4vpay->duration + duration)) {
    ret = gst_rtp_mp4v_pay_flush (rtpmp4vpay);
    rtpmp4vpay->first_ts = GST_BUFFER_TIMESTAMP (buffer);
    rtpmp4vpay->duration = 0;
444 445
  }

446
  /* push new data */
447 448
  gst_adapter_push (rtpmp4vpay->adapter, buffer);
  rtpmp4vpay->duration += duration;
449

450 451 452
  return ret;
}

Wim Taymans's avatar
Wim Taymans committed
453
static void
454
gst_rtp_mp4v_pay_set_property (GObject * object, guint prop_id,
Wim Taymans's avatar
Wim Taymans committed
455 456
    const GValue * value, GParamSpec * pspec)
{
457
  GstRtpMP4VPay *rtpmp4vpay;
Wim Taymans's avatar
Wim Taymans committed
458

459
  rtpmp4vpay = GST_RTP_MP4V_PAY (object);
Wim Taymans's avatar
Wim Taymans committed
460 461 462

  switch (prop_id) {
    case ARG_SEND_CONFIG:
463
      rtpmp4vpay->send_config = g_value_get_boolean (value);
Wim Taymans's avatar
Wim Taymans committed
464 465 466 467 468 469 470
      break;
    default:
      break;
  }
}

static void
471
gst_rtp_mp4v_pay_get_property (GObject * object, guint prop_id,
Wim Taymans's avatar
Wim Taymans committed
472 473
    GValue * value, GParamSpec * pspec)
{
474
  GstRtpMP4VPay *rtpmp4vpay;
Wim Taymans's avatar
Wim Taymans committed
475

476
  rtpmp4vpay = GST_RTP_MP4V_PAY (object);
Wim Taymans's avatar
Wim Taymans committed
477 478 479

  switch (prop_id) {
    case ARG_SEND_CONFIG:
480
      g_value_set_boolean (value, rtpmp4vpay->send_config);
Wim Taymans's avatar
Wim Taymans committed
481 482 483 484 485 486
      break;
    default:
      break;
  }
}

487
gboolean
488
gst_rtp_mp4v_pay_plugin_init (GstPlugin * plugin)
489
{
490 491
  return gst_element_register (plugin, "rtpmp4vpay",
      GST_RANK_NONE, GST_TYPE_RTP_MP4V_PAY);
492
}