gstrtpvorbispay.c 20.8 KB
Newer Older
Wim Taymans's avatar
Wim Taymans committed
1
/* GStreamer
2
 * Copyright (C) <2006> Wim Taymans <wim.taymans@gmail.com>
Wim Taymans's avatar
Wim Taymans committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 *
 * 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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

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

#include <string.h>

#include <gst/rtp/gstrtpbuffer.h>

28
#include "fnv1hash.h"
Wim Taymans's avatar
Wim Taymans committed
29 30 31 32 33 34
#include "gstrtpvorbispay.h"

GST_DEBUG_CATEGORY_STATIC (rtpvorbispay_debug);
#define GST_CAT_DEFAULT (rtpvorbispay_debug)

/* references:
35
 * http://www.rfc-editor.org/rfc/rfc5215.txt
Wim Taymans's avatar
Wim Taymans committed
36 37 38 39 40 41 42 43
 */

static GstStaticPadTemplate gst_rtp_vorbis_pay_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("application/x-rtp, "
        "media = (string) \"audio\", "
44
        "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
45
        "clock-rate = (int) [1, MAX ], " "encoding-name = (string) \"VORBIS\""
Wim Taymans's avatar
Wim Taymans committed
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
        /* All required parameters
         *
         * "encoding-params = (string) <num channels>"
         * "configuration = (string) ANY"
         */
    )
    );

static GstStaticPadTemplate gst_rtp_vorbis_pay_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-vorbis")
    );

GST_BOILERPLATE (GstRtpVorbisPay, gst_rtp_vorbis_pay, GstBaseRTPPayload,
    GST_TYPE_BASE_RTP_PAYLOAD);

static gboolean gst_rtp_vorbis_pay_setcaps (GstBaseRTPPayload * basepayload,
    GstCaps * caps);
Wim Taymans's avatar
Wim Taymans committed
66 67
static GstStateChangeReturn gst_rtp_vorbis_pay_change_state (GstElement *
    element, GstStateChange transition);
Wim Taymans's avatar
Wim Taymans committed
68 69
static GstFlowReturn gst_rtp_vorbis_pay_handle_buffer (GstBaseRTPPayload * pad,
    GstBuffer * buffer);
70 71
static gboolean gst_rtp_vorbis_pay_handle_event (GstPad * pad,
    GstEvent * event);
Wim Taymans's avatar
Wim Taymans committed
72 73 74 75 76 77 78 79 80 81 82

static void
gst_rtp_vorbis_pay_base_init (gpointer klass)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);

  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&gst_rtp_vorbis_pay_src_template));
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&gst_rtp_vorbis_pay_sink_template));

83
  gst_element_class_set_details_simple (element_class, "RTP Vorbis depayloader",
Wim Taymans's avatar
Wim Taymans committed
84
      "Codec/Payloader/Network/RTP",
85 86
      "Payload-encode Vorbis audio into RTP packets (RFC 5215)",
      "Wim Taymans <wimi.taymans@gmail.com>");
Wim Taymans's avatar
Wim Taymans committed
87 88 89 90 91 92 93 94 95 96 97
}

static void
gst_rtp_vorbis_pay_class_init (GstRtpVorbisPayClass * klass)
{
  GstElementClass *gstelement_class;
  GstBaseRTPPayloadClass *gstbasertppayload_class;

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

Wim Taymans's avatar
Wim Taymans committed
98 99
  gstelement_class->change_state = gst_rtp_vorbis_pay_change_state;

Wim Taymans's avatar
Wim Taymans committed
100 101
  gstbasertppayload_class->set_caps = gst_rtp_vorbis_pay_setcaps;
  gstbasertppayload_class->handle_buffer = gst_rtp_vorbis_pay_handle_buffer;
102
  gstbasertppayload_class->handle_event = gst_rtp_vorbis_pay_handle_event;
Wim Taymans's avatar
Wim Taymans committed
103 104 105 106 107 108 109 110 111

  GST_DEBUG_CATEGORY_INIT (rtpvorbispay_debug, "rtpvorbispay", 0,
      "Vorbis RTP Payloader");
}

static void
gst_rtp_vorbis_pay_init (GstRtpVorbisPay * rtpvorbispay,
    GstRtpVorbisPayClass * klass)
{
112
  /* needed because of GST_BOILERPLATE */
Wim Taymans's avatar
Wim Taymans committed
113 114
}

115 116 117 118 119 120 121 122
static void
gst_rtp_vorbis_pay_clear_packet (GstRtpVorbisPay * rtpvorbispay)
{
  if (rtpvorbispay->packet)
    gst_buffer_unref (rtpvorbispay->packet);
  rtpvorbispay->packet = NULL;
}

Wim Taymans's avatar
Wim Taymans committed
123 124 125 126 127 128 129
static void
gst_rtp_vorbis_pay_cleanup (GstRtpVorbisPay * rtpvorbispay)
{
  g_list_foreach (rtpvorbispay->headers, (GFunc) gst_mini_object_unref, NULL);
  g_list_free (rtpvorbispay->headers);
  rtpvorbispay->headers = NULL;

130
  gst_rtp_vorbis_pay_clear_packet (rtpvorbispay);
Wim Taymans's avatar
Wim Taymans committed
131 132
}

Wim Taymans's avatar
Wim Taymans committed
133 134 135 136 137 138 139
static gboolean
gst_rtp_vorbis_pay_setcaps (GstBaseRTPPayload * basepayload, GstCaps * caps)
{
  GstRtpVorbisPay *rtpvorbispay;

  rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload);

140 141
  rtpvorbispay->need_headers = TRUE;

Wim Taymans's avatar
Wim Taymans committed
142 143 144 145
  return TRUE;
}

static void
146
gst_rtp_vorbis_pay_reset_packet (GstRtpVorbisPay * rtpvorbispay, guint8 VDT)
Wim Taymans's avatar
Wim Taymans committed
147 148 149
{
  guint payload_len;

150
  GST_LOG_OBJECT (rtpvorbispay, "reset packet");
Wim Taymans's avatar
Wim Taymans committed
151 152 153 154 155 156

  rtpvorbispay->payload_pos = 4;
  payload_len = gst_rtp_buffer_get_payload_len (rtpvorbispay->packet);
  rtpvorbispay->payload_left = payload_len - 4;
  rtpvorbispay->payload_duration = 0;
  rtpvorbispay->payload_F = 0;
157
  rtpvorbispay->payload_VDT = VDT;
Wim Taymans's avatar
Wim Taymans committed
158 159 160
  rtpvorbispay->payload_pkts = 0;
}

161
static void
162 163
gst_rtp_vorbis_pay_init_packet (GstRtpVorbisPay * rtpvorbispay, guint8 VDT,
    GstClockTime timestamp)
164
{
165
  GST_LOG_OBJECT (rtpvorbispay, "starting new packet, VDT: %d", VDT);
166 167 168 169 170 171 172 173 174

  if (rtpvorbispay->packet)
    gst_buffer_unref (rtpvorbispay->packet);

  /* new packet allocate max packet size */
  rtpvorbispay->packet =
      gst_rtp_buffer_new_allocate_len (GST_BASE_RTP_PAYLOAD_MTU
      (rtpvorbispay), 0, 0);
  gst_rtp_vorbis_pay_reset_packet (rtpvorbispay, VDT);
175
  GST_BUFFER_TIMESTAMP (rtpvorbispay->packet) = timestamp;
176 177
}

Wim Taymans's avatar
Wim Taymans committed
178 179 180 181 182 183 184 185
static GstFlowReturn
gst_rtp_vorbis_pay_flush_packet (GstRtpVorbisPay * rtpvorbispay)
{
  GstFlowReturn ret;
  guint8 *payload;
  guint hlen;

  /* check for empty packet */
186
  if (!rtpvorbispay->packet || rtpvorbispay->payload_pos <= 4)
Wim Taymans's avatar
Wim Taymans committed
187 188
    return GST_FLOW_OK;

189
  GST_LOG_OBJECT (rtpvorbispay, "flushing packet");
Wim Taymans's avatar
Wim Taymans committed
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214

  /* fix header */
  payload = gst_rtp_buffer_get_payload (rtpvorbispay->packet);
  /*
   *  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
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * |                     Ident                     | F |VDT|# pkts.|
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   *
   * F: Fragment type (0=none, 1=start, 2=cont, 3=end)
   * VDT: Vorbis data type (0=vorbis, 1=config, 2=comment, 3=reserved)
   * pkts: number of packets.
   */
  payload[0] = (rtpvorbispay->payload_ident >> 16) & 0xff;
  payload[1] = (rtpvorbispay->payload_ident >> 8) & 0xff;
  payload[2] = (rtpvorbispay->payload_ident) & 0xff;
  payload[3] = (rtpvorbispay->payload_F & 0x3) << 6 |
      (rtpvorbispay->payload_VDT & 0x3) << 4 |
      (rtpvorbispay->payload_pkts & 0xf);

  /* shrink the buffer size to the last written byte */
  hlen = gst_rtp_buffer_calc_header_len (0);
  GST_BUFFER_SIZE (rtpvorbispay->packet) = hlen + rtpvorbispay->payload_pos;

215 216
  GST_BUFFER_DURATION (rtpvorbispay->packet) = rtpvorbispay->payload_duration;

Wim Taymans's avatar
Wim Taymans committed
217 218 219 220 221 222 223 224 225
  /* push, this gives away our ref to the packet, so clear it. */
  ret =
      gst_basertppayload_push (GST_BASE_RTP_PAYLOAD (rtpvorbispay),
      rtpvorbispay->packet);
  rtpvorbispay->packet = NULL;

  return ret;
}

226 227 228 229 230
static gboolean
gst_rtp_vorbis_pay_finish_headers (GstBaseRTPPayload * basepayload)
{
  GstRtpVorbisPay *rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload);
  GList *walk;
231
  guint length, size, n_headers, configlen;
232
  gchar *cstr, *configuration;
233
  guint8 *data, *config;
234
  guint32 ident;
235
  gboolean res;
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259

  GST_DEBUG_OBJECT (rtpvorbispay, "finish headers");

  if (!rtpvorbispay->headers)
    goto no_headers;

  /* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * |                     Number of packed headers                  |
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * |                          Packed header                        |
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * |                          Packed header                        |
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * |                          ....                                 |
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   *
   * We only construct a config containing 1 packed header like this:
   *
   *  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
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
260 261 262 263 264 265 266 267 268
   * |                   Ident                       | length       ..
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * ..              | n. of headers |    length1    |    length2   ..
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * ..              |             Identification Header            ..
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * .................................................................
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * ..              |         Comment Header                       ..
269
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
270
   * .................................................................
271
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
272
   * ..                        Comment Header                        |
273 274 275
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * |                          Setup Header                        ..
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
276 277
   * .................................................................
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
278 279 280 281
   * ..                         Setup Header                         |
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   */

282 283 284 285 286
  /* we need 4 bytes for the number of headers (which is always 1), 3 bytes for
   * the ident, 2 bytes for length, 1 byte for n. of headers. */
  size = 4 + 3 + 2 + 1;

  /* count the size of the headers first and update the hash */
287
  length = 0;
288
  n_headers = 0;
289
  ident = fnv1_hash_32_new ();
290 291
  for (walk = rtpvorbispay->headers; walk; walk = g_list_next (walk)) {
    GstBuffer *buf = GST_BUFFER_CAST (walk->data);
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
    guint bsize;

    bsize = GST_BUFFER_SIZE (buf);
    length += bsize;
    n_headers++;

    /* count number of bytes needed for length fields, we don't need this for
     * the last header. */
    if (g_list_next (walk)) {
      do {
        size++;
        bsize >>= 7;
      } while (bsize);
    }
    /* update hash */
    ident = fnv1_hash_32_update (ident, GST_BUFFER_DATA (buf),
308
        GST_BUFFER_SIZE (buf));
309 310
  }

311 312 313
  /* packet length is header size + packet length */
  configlen = size + length;
  config = data = g_malloc (configlen);
314

315 316 317 318 319 320
  /* number of packed headers, we only pack 1 header */
  data[0] = 0;
  data[1] = 0;
  data[2] = 0;
  data[3] = 1;

321 322 323
  ident = fnv1_hash_32_to_24 (ident);
  rtpvorbispay->payload_ident = ident;
  GST_DEBUG_OBJECT (rtpvorbispay, "ident 0x%08x", ident);
324 325

  /* take lower 3 bytes */
326 327 328
  data[4] = (ident >> 16) & 0xff;
  data[5] = (ident >> 8) & 0xff;
  data[6] = ident & 0xff;
329

330 331 332 333 334 335 336 337 338 339 340 341
  /* store length of all vorbis headers */
  data[7] = ((length) >> 8) & 0xff;
  data[8] = (length) & 0xff;

  /* store number of headers minus one. */
  data[9] = n_headers - 1;
  data += 10;

  /* store length for each header */
  for (walk = rtpvorbispay->headers; walk; walk = g_list_next (walk)) {
    GstBuffer *buf = GST_BUFFER_CAST (walk->data);
    guint bsize, size, temp;
342
    guint flag;
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359

    /* only need to store the length when it's not the last header */
    if (!g_list_next (walk))
      break;

    bsize = GST_BUFFER_SIZE (buf);

    /* calc size */
    size = 0;
    do {
      size++;
      bsize >>= 7;
    } while (bsize);
    temp = size;

    bsize = GST_BUFFER_SIZE (buf);
    /* write the size backwards */
360
    flag = 0;
361 362
    while (size) {
      size--;
363
      data[size] = (bsize & 0x7f) | flag;
364
      bsize >>= 7;
365
      flag = 0x80;              /* Flag bit on all bytes of the length except the last */
366 367 368
    }
    data += temp;
  }
369 370 371 372 373 374 375 376 377

  /* copy header data */
  for (walk = rtpvorbispay->headers; walk; walk = g_list_next (walk)) {
    GstBuffer *buf = GST_BUFFER_CAST (walk->data);

    memcpy (data, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
    data += GST_BUFFER_SIZE (buf);
  }

378
  /* serialize to base64 */
379
  configuration = g_base64_encode (config, configlen);
380
  g_free (config);
381 382 383

  /* configure payloader settings */
  cstr = g_strdup_printf ("%d", rtpvorbispay->channels);
384
  gst_basertppayload_set_options (basepayload, "audio", TRUE, "VORBIS",
385
      rtpvorbispay->rate);
386 387 388
  res =
      gst_basertppayload_set_outcaps (basepayload, "encoding-params",
      G_TYPE_STRING, cstr, "configuration", G_TYPE_STRING, configuration, NULL);
389 390 391
  g_free (cstr);
  g_free (configuration);

392
  return res;
393 394 395 396 397 398 399 400 401

  /* ERRORS */
no_headers:
  {
    GST_DEBUG_OBJECT (rtpvorbispay, "finish headers");
    return FALSE;
  }
}

402 403 404
static gboolean
gst_rtp_vorbis_pay_parse_id (GstBaseRTPPayload * basepayload, guint8 * data,
    guint size)
Wim Taymans's avatar
Wim Taymans committed
405
{
406
  GstRtpVorbisPay *rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload);
407 408
  guint8 channels;
  gint32 rate, version;
Wim Taymans's avatar
Wim Taymans committed
409

410 411
  if (G_UNLIKELY (size < 16))
    goto too_short;
Wim Taymans's avatar
Wim Taymans committed
412

413 414 415
  if (G_UNLIKELY (memcmp (data, "\001vorbis", 7)))
    goto invalid_start;
  data += 7;
Wim Taymans's avatar
Wim Taymans committed
416

417 418 419
  if (G_UNLIKELY ((version = GST_READ_UINT32_LE (data)) != 0))
    goto invalid_version;
  data += 4;
Wim Taymans's avatar
Wim Taymans committed
420

421 422
  if (G_UNLIKELY ((channels = *data++) < 1))
    goto invalid_channels;
Wim Taymans's avatar
Wim Taymans committed
423

424 425
  if (G_UNLIKELY ((rate = GST_READ_UINT32_LE (data)) < 1))
    goto invalid_rate;
Wim Taymans's avatar
Wim Taymans committed
426

427 428 429
  /* all fine, store the values */
  rtpvorbispay->channels = channels;
  rtpvorbispay->rate = rate;
Wim Taymans's avatar
Wim Taymans committed
430

431
  return TRUE;
Wim Taymans's avatar
Wim Taymans committed
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
  /* ERRORS */
too_short:
  {
    GST_ELEMENT_ERROR (basepayload, STREAM, DECODE,
        ("Identification packet is too short, need at least 16, got %d", size),
        (NULL));
    return FALSE;
  }
invalid_start:
  {
    GST_ELEMENT_ERROR (basepayload, STREAM, DECODE,
        ("Invalid header start in identification packet"), (NULL));
    return FALSE;
  }
invalid_version:
  {
    GST_ELEMENT_ERROR (basepayload, STREAM, DECODE,
        ("Invalid version, expected 0, got %d", version), (NULL));
    return FALSE;
  }
invalid_rate:
  {
    GST_ELEMENT_ERROR (basepayload, STREAM, DECODE,
        ("Invalid rate %d", rate), (NULL));
    return FALSE;
  }
invalid_channels:
  {
    GST_ELEMENT_ERROR (basepayload, STREAM, DECODE,
        ("Invalid channels %d", channels), (NULL));
    return FALSE;
Wim Taymans's avatar
Wim Taymans committed
464 465 466 467 468 469 470 471 472 473
  }
}

static GstFlowReturn
gst_rtp_vorbis_pay_handle_buffer (GstBaseRTPPayload * basepayload,
    GstBuffer * buffer)
{
  GstRtpVorbisPay *rtpvorbispay;
  GstFlowReturn ret;
  guint size, newsize;
474
  guint8 *data;
Wim Taymans's avatar
Wim Taymans committed
475
  guint packet_len;
476
  GstClockTime duration, newduration, timestamp;
Wim Taymans's avatar
Wim Taymans committed
477
  gboolean flush;
478 479 480 481
  guint8 VDT;
  guint plen;
  guint8 *ppos, *payload;
  gboolean fragmented;
Wim Taymans's avatar
Wim Taymans committed
482 483 484 485

  rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload);

  size = GST_BUFFER_SIZE (buffer);
486
  data = GST_BUFFER_DATA (buffer);
Wim Taymans's avatar
Wim Taymans committed
487
  duration = GST_BUFFER_DURATION (buffer);
488
  timestamp = GST_BUFFER_TIMESTAMP (buffer);
Wim Taymans's avatar
Wim Taymans committed
489

490
  GST_LOG_OBJECT (rtpvorbispay, "size %u, duration %" GST_TIME_FORMAT,
Wim Taymans's avatar
Wim Taymans committed
491 492
      size, GST_TIME_ARGS (duration));

493 494 495 496 497 498 499 500 501 502 503
  if (G_UNLIKELY (size < 1 || size > 0xffff))
    goto wrong_size;

  /* find packet type */
  if (data[0] & 1) {
    /* header */
    if (data[0] == 1) {
      /* identification, we need to parse this in order to get the clock rate. */
      if (G_UNLIKELY (!gst_rtp_vorbis_pay_parse_id (basepayload, data, size)))
        goto parse_id_failed;
      VDT = 1;
504
    } else if (data[0] == 3) {
505
      /* comment */
506
      VDT = 2;
507 508 509
    } else if (data[0] == 5) {
      /* setup */
      VDT = 1;
510
    } else
511 512 513 514
      goto unknown_header;
  } else
    /* data */
    VDT = 0;
Wim Taymans's avatar
Wim Taymans committed
515

516 517
  if (rtpvorbispay->need_headers) {
    /* we need to collect the headers and construct a config string from them */
518
    if (VDT != 0) {
519 520 521 522 523 524 525 526 527 528 529 530
      GST_DEBUG_OBJECT (rtpvorbispay, "collecting header");
      /* append header to the list of headers */
      rtpvorbispay->headers = g_list_append (rtpvorbispay->headers, buffer);
      ret = GST_FLOW_OK;
      goto done;
    } else {
      if (!gst_rtp_vorbis_pay_finish_headers (basepayload))
        goto header_error;
      rtpvorbispay->need_headers = FALSE;
    }
  }

Wim Taymans's avatar
Wim Taymans committed
531 532 533 534 535 536 537 538 539 540 541 542
  /* size increases with packet length and 2 bytes size eader. */
  newduration = rtpvorbispay->payload_duration;
  if (duration != GST_CLOCK_TIME_NONE)
    newduration += duration;

  newsize = rtpvorbispay->payload_pos + 2 + size;
  packet_len = gst_rtp_buffer_calc_packet_len (newsize, 0, 0);

  /* check buffer filled against length and max latency */
  flush = gst_basertppayload_is_filled (basepayload, packet_len, newduration);
  /* we can store up to 15 vorbis packets in one RTP packet. */
  flush |= (rtpvorbispay->payload_pkts == 15);
543 544 545
  /* flush if we have a new VDT */
  if (rtpvorbispay->packet)
    flush |= (rtpvorbispay->payload_VDT != VDT);
Wim Taymans's avatar
Wim Taymans committed
546
  if (flush)
547
    gst_rtp_vorbis_pay_flush_packet (rtpvorbispay);
Wim Taymans's avatar
Wim Taymans committed
548

549
  /* create new packet if we must */
550 551 552
  if (!rtpvorbispay->packet) {
    gst_rtp_vorbis_pay_init_packet (rtpvorbispay, VDT, timestamp);
  }
553 554 555 556 557 558 559 560 561 562 563 564

  payload = gst_rtp_buffer_get_payload (rtpvorbispay->packet);
  ppos = payload + rtpvorbispay->payload_pos;
  fragmented = FALSE;

  ret = GST_FLOW_OK;

  /* put buffer in packet, it either fits completely or needs to be fragmented
   * over multiple RTP packets. */
  while (size) {
    plen = MIN (rtpvorbispay->payload_left - 2, size);

565
    GST_LOG_OBJECT (rtpvorbispay, "append %u bytes", plen);
566 567 568 569 570

    /* data is copied in the payload with a 2 byte length header */
    ppos[0] = (plen >> 8) & 0xff;
    ppos[1] = (plen & 0xff);
    memcpy (&ppos[2], data, plen);
Wim Taymans's avatar
Wim Taymans committed
571

572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
    size -= plen;
    data += plen;

    rtpvorbispay->payload_pos += plen + 2;
    rtpvorbispay->payload_left -= plen + 2;

    if (fragmented) {
      if (size == 0)
        /* last fragment, set F to 0x3. */
        rtpvorbispay->payload_F = 0x3;
      else
        /* fragment continues, set F to 0x2. */
        rtpvorbispay->payload_F = 0x2;
    } else {
      if (size > 0) {
        /* fragmented packet starts, set F to 0x1, mark ourselves as
         * fragmented. */
        rtpvorbispay->payload_F = 0x1;
        fragmented = TRUE;
      }
    }
    if (fragmented) {
      /* fragmented packets are always flushed and have ptks of 0 */
      rtpvorbispay->payload_pkts = 0;
      ret = gst_rtp_vorbis_pay_flush_packet (rtpvorbispay);

      if (size > 0) {
        /* start new packet and get pointers. VDT stays the same. */
        gst_rtp_vorbis_pay_init_packet (rtpvorbispay,
601
            rtpvorbispay->payload_VDT, timestamp);
602 603 604 605 606 607 608 609 610 611 612
        payload = gst_rtp_buffer_get_payload (rtpvorbispay->packet);
        ppos = payload + rtpvorbispay->payload_pos;
      }
    } else {
      /* unfragmented packet, update stats for next packet, size == 0 and we
       * exit the while loop */
      rtpvorbispay->payload_pkts++;
      if (duration != GST_CLOCK_TIME_NONE)
        rtpvorbispay->payload_duration += duration;
    }
  }
Wim Taymans's avatar
Wim Taymans committed
613 614
  gst_buffer_unref (buffer);

615
done:
Wim Taymans's avatar
Wim Taymans committed
616
  return ret;
617 618 619 620 621 622

  /* ERRORS */
wrong_size:
  {
    GST_ELEMENT_WARNING (rtpvorbispay, STREAM, DECODE,
        ("Invalid packet size (1 < %d <= 0xffff)", size), (NULL));
Wim Taymans's avatar
Wim Taymans committed
623
    gst_buffer_unref (buffer);
624 625 626 627
    return GST_FLOW_OK;
  }
parse_id_failed:
  {
Wim Taymans's avatar
Wim Taymans committed
628
    gst_buffer_unref (buffer);
629 630 631 632 633
    return GST_FLOW_ERROR;
  }
unknown_header:
  {
    GST_ELEMENT_WARNING (rtpvorbispay, STREAM, DECODE,
634
        (NULL), ("Ignoring unknown header received"));
Wim Taymans's avatar
Wim Taymans committed
635
    gst_buffer_unref (buffer);
636 637 638 639 640 641
    return GST_FLOW_OK;
  }
header_error:
  {
    GST_ELEMENT_WARNING (rtpvorbispay, STREAM, DECODE,
        (NULL), ("Error initializing header config"));
Wim Taymans's avatar
Wim Taymans committed
642
    gst_buffer_unref (buffer);
643 644
    return GST_FLOW_OK;
  }
Wim Taymans's avatar
Wim Taymans committed
645 646
}

647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
static gboolean
gst_rtp_vorbis_pay_handle_event (GstPad * pad, GstEvent * event)
{
  GstRtpVorbisPay *rtpvorbispay = GST_RTP_VORBIS_PAY (GST_PAD_PARENT (pad));

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_FLUSH_STOP:
      gst_rtp_vorbis_pay_clear_packet (rtpvorbispay);
      break;
    default:
      break;
  }
  /* false to let parent handle event as well */
  return FALSE;
}

Wim Taymans's avatar
Wim Taymans committed
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688
static GstStateChangeReturn
gst_rtp_vorbis_pay_change_state (GstElement * element,
    GstStateChange transition)
{
  GstRtpVorbisPay *rtpvorbispay;
  GstStateChangeReturn ret;

  rtpvorbispay = GST_RTP_VORBIS_PAY (element);

  switch (transition) {
    default:
      break;
  }

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

  switch (transition) {
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      gst_rtp_vorbis_pay_cleanup (rtpvorbispay);
      break;
    default:
      break;
  }
  return ret;
}

Wim Taymans's avatar
Wim Taymans committed
689 690 691 692
gboolean
gst_rtp_vorbis_pay_plugin_init (GstPlugin * plugin)
{
  return gst_element_register (plugin, "rtpvorbispay",
693
      GST_RANK_SECONDARY, GST_TYPE_RTP_VORBIS_PAY);
Wim Taymans's avatar
Wim Taymans committed
694
}