gstrtpvorbispay.c 21.5 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
        /* 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")
    );

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
61
#define gst_rtp_vorbis_pay_parent_class parent_class
Wim Taymans's avatar
Wim Taymans committed
62
G_DEFINE_TYPE (GstRtpVorbisPay, gst_rtp_vorbis_pay, GST_TYPE_RTP_BASE_PAYLOAD);
Wim Taymans's avatar
Wim Taymans committed
63

Wim Taymans's avatar
Wim Taymans committed
64
static gboolean gst_rtp_vorbis_pay_setcaps (GstRTPBasePayload * basepayload,
Wim Taymans's avatar
Wim Taymans committed
65
    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
static GstFlowReturn gst_rtp_vorbis_pay_handle_buffer (GstRTPBasePayload * pad,
Wim Taymans's avatar
Wim Taymans committed
69
    GstBuffer * buffer);
Wim Taymans's avatar
Wim Taymans committed
70
static gboolean gst_rtp_vorbis_pay_handle_event (GstRTPBasePayload * payload,
71
    GstEvent * event);
Wim Taymans's avatar
Wim Taymans committed
72 73 74 75 76

static void
gst_rtp_vorbis_pay_class_init (GstRtpVorbisPayClass * klass)
{
  GstElementClass *gstelement_class;
Wim Taymans's avatar
Wim Taymans committed
77
  GstRTPBasePayloadClass *gstrtpbasepayload_class;
Wim Taymans's avatar
Wim Taymans committed
78 79

  gstelement_class = (GstElementClass *) klass;
Wim Taymans's avatar
Wim Taymans committed
80
  gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
Wim Taymans's avatar
Wim Taymans committed
81

Wim Taymans's avatar
Wim Taymans committed
82 83
  gstelement_class->change_state = gst_rtp_vorbis_pay_change_state;

Wim Taymans's avatar
Wim Taymans committed
84 85 86
  gstrtpbasepayload_class->set_caps = gst_rtp_vorbis_pay_setcaps;
  gstrtpbasepayload_class->handle_buffer = gst_rtp_vorbis_pay_handle_buffer;
  gstrtpbasepayload_class->handle_event = gst_rtp_vorbis_pay_handle_event;
Wim Taymans's avatar
Wim Taymans committed
87

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
88 89 90 91 92 93 94 95 96 97 98
  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&gst_rtp_vorbis_pay_src_template));
  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&gst_rtp_vorbis_pay_sink_template));

  gst_element_class_set_details_simple (gstelement_class,
      "RTP Vorbis depayloader",
      "Codec/Payloader/Network/RTP",
      "Payload-encode Vorbis audio into RTP packets (RFC 5215)",
      "Wim Taymans <wimi.taymans@gmail.com>");

Wim Taymans's avatar
Wim Taymans committed
99 100 101 102 103
  GST_DEBUG_CATEGORY_INIT (rtpvorbispay_debug, "rtpvorbispay", 0,
      "Vorbis RTP Payloader");
}

static void
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
104
gst_rtp_vorbis_pay_init (GstRtpVorbisPay * rtpvorbispay)
Wim Taymans's avatar
Wim Taymans committed
105
{
106
  /* needed because of GST_BOILERPLATE */
Wim Taymans's avatar
Wim Taymans committed
107 108
}

109 110 111 112 113 114 115 116
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
117 118 119 120 121 122 123
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;

124
  gst_rtp_vorbis_pay_clear_packet (rtpvorbispay);
Wim Taymans's avatar
Wim Taymans committed
125 126
}

Wim Taymans's avatar
Wim Taymans committed
127
static gboolean
Wim Taymans's avatar
Wim Taymans committed
128
gst_rtp_vorbis_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
Wim Taymans's avatar
Wim Taymans committed
129 130 131 132 133
{
  GstRtpVorbisPay *rtpvorbispay;

  rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload);

134 135
  rtpvorbispay->need_headers = TRUE;

Wim Taymans's avatar
Wim Taymans committed
136 137 138 139
  return TRUE;
}

static void
140
gst_rtp_vorbis_pay_reset_packet (GstRtpVorbisPay * rtpvorbispay, guint8 VDT)
Wim Taymans's avatar
Wim Taymans committed
141 142
{
  guint payload_len;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
143
  GstRTPBuffer rtp;
Wim Taymans's avatar
Wim Taymans committed
144

145
  GST_LOG_OBJECT (rtpvorbispay, "reset packet");
Wim Taymans's avatar
Wim Taymans committed
146 147

  rtpvorbispay->payload_pos = 4;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
148 149 150
  gst_rtp_buffer_map (rtpvorbispay->packet, GST_MAP_READ, &rtp);
  payload_len = gst_rtp_buffer_get_payload_len (&rtp);
  gst_rtp_buffer_unmap (&rtp);
Wim Taymans's avatar
Wim Taymans committed
151 152 153
  rtpvorbispay->payload_left = payload_len - 4;
  rtpvorbispay->payload_duration = 0;
  rtpvorbispay->payload_F = 0;
154
  rtpvorbispay->payload_VDT = VDT;
Wim Taymans's avatar
Wim Taymans committed
155 156 157
  rtpvorbispay->payload_pkts = 0;
}

158
static void
159 160
gst_rtp_vorbis_pay_init_packet (GstRtpVorbisPay * rtpvorbispay, guint8 VDT,
    GstClockTime timestamp)
161
{
162
  GST_LOG_OBJECT (rtpvorbispay, "starting new packet, VDT: %d", VDT);
163 164 165 166 167 168

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

  /* new packet allocate max packet size */
  rtpvorbispay->packet =
Wim Taymans's avatar
Wim Taymans committed
169
      gst_rtp_buffer_new_allocate_len (GST_RTP_BASE_PAYLOAD_MTU
170 171
      (rtpvorbispay), 0, 0);
  gst_rtp_vorbis_pay_reset_packet (rtpvorbispay, VDT);
172
  GST_BUFFER_TIMESTAMP (rtpvorbispay->packet) = timestamp;
173 174
}

Wim Taymans's avatar
Wim Taymans committed
175 176 177 178 179 180
static GstFlowReturn
gst_rtp_vorbis_pay_flush_packet (GstRtpVorbisPay * rtpvorbispay)
{
  GstFlowReturn ret;
  guint8 *payload;
  guint hlen;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
181
  GstRTPBuffer rtp;
Wim Taymans's avatar
Wim Taymans committed
182 183

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

187
  GST_LOG_OBJECT (rtpvorbispay, "flushing packet");
Wim Taymans's avatar
Wim Taymans committed
188

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
189 190
  gst_rtp_buffer_map (rtpvorbispay->packet, GST_MAP_WRITE, &rtp);

Wim Taymans's avatar
Wim Taymans committed
191
  /* fix header */
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
192
  payload = gst_rtp_buffer_get_payload (&rtp);
Wim Taymans's avatar
Wim Taymans committed
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
  /*
   *  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);

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
211 212
  gst_rtp_buffer_unmap (&rtp);

Wim Taymans's avatar
Wim Taymans committed
213 214
  /* shrink the buffer size to the last written byte */
  hlen = gst_rtp_buffer_calc_header_len (0);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
215
  gst_buffer_resize (rtpvorbispay->packet, 0, hlen + rtpvorbispay->payload_pos);
Wim Taymans's avatar
Wim Taymans committed
216

217 218
  GST_BUFFER_DURATION (rtpvorbispay->packet) = rtpvorbispay->payload_duration;

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

  return ret;
}

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

  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
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
262 263 264 265 266 267 268 269 270
   * |                   Ident                       | length       ..
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * ..              | n. of headers |    length1    |    length2   ..
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * ..              |             Identification Header            ..
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * .................................................................
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * ..              |         Comment Header                       ..
271
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
272
   * .................................................................
273
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
274
   * ..                        Comment Header                        |
275 276 277
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * |                          Setup Header                        ..
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
278 279
   * .................................................................
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
280 281 282 283
   * ..                         Setup Header                         |
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   */

284 285 286 287 288
  /* 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 */
289
  length = 0;
290
  n_headers = 0;
291
  ident = fnv1_hash_32_new ();
292 293
  for (walk = rtpvorbispay->headers; walk; walk = g_list_next (walk)) {
    GstBuffer *buf = GST_BUFFER_CAST (walk->data);
Wim Taymans's avatar
Wim Taymans committed
294
    guint bsize, osize;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
295
    guint8 *data;
296

Wim Taymans's avatar
Wim Taymans committed
297
    bsize = osize = gst_buffer_get_size (buf);
298 299 300 301 302 303 304 305 306 307 308 309
    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 */
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
310
    data = gst_buffer_map (buf, NULL, NULL, GST_MAP_READ);
Wim Taymans's avatar
Wim Taymans committed
311
    ident = fnv1_hash_32_update (ident, data, osize);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
312
    gst_buffer_unmap (buf, data, -1);
313 314
  }

315 316 317
  /* packet length is header size + packet length */
  configlen = size + length;
  config = data = g_malloc (configlen);
318

319 320 321 322 323 324
  /* number of packed headers, we only pack 1 header */
  data[0] = 0;
  data[1] = 0;
  data[2] = 0;
  data[3] = 1;

325 326 327
  ident = fnv1_hash_32_to_24 (ident);
  rtpvorbispay->payload_ident = ident;
  GST_DEBUG_OBJECT (rtpvorbispay, "ident 0x%08x", ident);
328 329

  /* take lower 3 bytes */
330 331 332
  data[4] = (ident >> 16) & 0xff;
  data[5] = (ident >> 8) & 0xff;
  data[6] = ident & 0xff;
333

334 335 336 337 338 339 340 341 342 343 344 345
  /* 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;
346
    guint flag;
347 348 349 350 351

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

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
352
    bsize = gst_buffer_get_size (buf);
353 354 355 356 357 358 359 360 361

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

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
362
    bsize = gst_buffer_get_size (buf);
363
    /* write the size backwards */
364
    flag = 0;
365 366
    while (size) {
      size--;
367
      data[size] = (bsize & 0x7f) | flag;
368
      bsize >>= 7;
369
      flag = 0x80;              /* Flag bit on all bytes of the length except the last */
370 371 372
    }
    data += temp;
  }
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);

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
378 379
    gst_buffer_extract (buf, 0, data, gst_buffer_get_size (buf));
    data += gst_buffer_get_size (buf);
380 381
  }

382
  /* serialize to base64 */
383
  configuration = g_base64_encode (config, configlen);
384
  g_free (config);
385 386 387

  /* configure payloader settings */
  cstr = g_strdup_printf ("%d", rtpvorbispay->channels);
Wim Taymans's avatar
Wim Taymans committed
388
  gst_rtp_base_payload_set_options (basepayload, "audio", TRUE, "VORBIS",
389
      rtpvorbispay->rate);
390
  res =
Wim Taymans's avatar
Wim Taymans committed
391
      gst_rtp_base_payload_set_outcaps (basepayload, "encoding-params",
392
      G_TYPE_STRING, cstr, "configuration", G_TYPE_STRING, configuration, NULL);
393 394 395
  g_free (cstr);
  g_free (configuration);

396
  return res;
397 398 399 400 401 402 403 404 405

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

406
static gboolean
Wim Taymans's avatar
Wim Taymans committed
407
gst_rtp_vorbis_pay_parse_id (GstRTPBasePayload * basepayload, guint8 * data,
408
    guint size)
Wim Taymans's avatar
Wim Taymans committed
409
{
410
  GstRtpVorbisPay *rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload);
411 412
  guint8 channels;
  gint32 rate, version;
Wim Taymans's avatar
Wim Taymans committed
413

414 415
  if (G_UNLIKELY (size < 16))
    goto too_short;
Wim Taymans's avatar
Wim Taymans committed
416

417 418 419
  if (G_UNLIKELY (memcmp (data, "\001vorbis", 7)))
    goto invalid_start;
  data += 7;
Wim Taymans's avatar
Wim Taymans committed
420

421 422 423
  if (G_UNLIKELY ((version = GST_READ_UINT32_LE (data)) != 0))
    goto invalid_version;
  data += 4;
Wim Taymans's avatar
Wim Taymans committed
424

425 426
  if (G_UNLIKELY ((channels = *data++) < 1))
    goto invalid_channels;
Wim Taymans's avatar
Wim Taymans committed
427

428 429
  if (G_UNLIKELY ((rate = GST_READ_UINT32_LE (data)) < 1))
    goto invalid_rate;
Wim Taymans's avatar
Wim Taymans committed
430

431 432 433
  /* all fine, store the values */
  rtpvorbispay->channels = channels;
  rtpvorbispay->rate = rate;
Wim Taymans's avatar
Wim Taymans committed
434

435
  return TRUE;
Wim Taymans's avatar
Wim Taymans committed
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
  /* 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
468 469 470 471
  }
}

static GstFlowReturn
Wim Taymans's avatar
Wim Taymans committed
472
gst_rtp_vorbis_pay_handle_buffer (GstRTPBasePayload * basepayload,
Wim Taymans's avatar
Wim Taymans committed
473 474 475 476
    GstBuffer * buffer)
{
  GstRtpVorbisPay *rtpvorbispay;
  GstFlowReturn ret;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
477 478
  guint newsize;
  gsize size;
479
  guint8 *data;
Wim Taymans's avatar
Wim Taymans committed
480
  guint packet_len;
481
  GstClockTime duration, newduration, timestamp;
Wim Taymans's avatar
Wim Taymans committed
482
  gboolean flush;
483 484 485 486
  guint8 VDT;
  guint plen;
  guint8 *ppos, *payload;
  gboolean fragmented;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
487
  GstRTPBuffer rtp;
Wim Taymans's avatar
Wim Taymans committed
488 489 490

  rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload);

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
491
  data = gst_buffer_map (buffer, &size, NULL, GST_MAP_READ);
Wim Taymans's avatar
Wim Taymans committed
492
  duration = GST_BUFFER_DURATION (buffer);
493
  timestamp = GST_BUFFER_TIMESTAMP (buffer);
Wim Taymans's avatar
Wim Taymans committed
494

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
495 496
  GST_LOG_OBJECT (rtpvorbispay, "size %" G_GSIZE_FORMAT
      ", duration %" GST_TIME_FORMAT, size, GST_TIME_ARGS (duration));
Wim Taymans's avatar
Wim Taymans committed
497

498 499 500 501 502 503 504 505 506 507 508
  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;
509
    } else if (data[0] == 3) {
510
      /* comment */
511
      VDT = 2;
512 513 514
    } else if (data[0] == 5) {
      /* setup */
      VDT = 1;
515
    } else
516 517 518 519
      goto unknown_header;
  } else
    /* data */
    VDT = 0;
Wim Taymans's avatar
Wim Taymans committed
520

521 522
  if (rtpvorbispay->need_headers) {
    /* we need to collect the headers and construct a config string from them */
523
    if (VDT != 0) {
524 525
      GST_DEBUG_OBJECT (rtpvorbispay, "collecting header");
      /* append header to the list of headers */
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
526
      gst_buffer_unmap (buffer, data, -1);
527 528 529 530 531 532 533 534 535 536
      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
537 538 539 540 541 542 543 544 545
  /* 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 */
Wim Taymans's avatar
Wim Taymans committed
546
  flush = gst_rtp_base_payload_is_filled (basepayload, packet_len, newduration);
Wim Taymans's avatar
Wim Taymans committed
547 548
  /* we can store up to 15 vorbis packets in one RTP packet. */
  flush |= (rtpvorbispay->payload_pkts == 15);
549 550 551
  /* flush if we have a new VDT */
  if (rtpvorbispay->packet)
    flush |= (rtpvorbispay->payload_VDT != VDT);
Wim Taymans's avatar
Wim Taymans committed
552
  if (flush)
553
    gst_rtp_vorbis_pay_flush_packet (rtpvorbispay);
Wim Taymans's avatar
Wim Taymans committed
554

555
  /* create new packet if we must */
556 557 558
  if (!rtpvorbispay->packet) {
    gst_rtp_vorbis_pay_init_packet (rtpvorbispay, VDT, timestamp);
  }
559

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
560 561
  gst_rtp_buffer_map (rtpvorbispay->packet, GST_MAP_WRITE, &rtp);
  payload = gst_rtp_buffer_get_payload (&rtp);
562 563 564 565 566 567 568 569 570 571
  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);

572
    GST_LOG_OBJECT (rtpvorbispay, "append %u bytes", plen);
573 574 575 576 577

    /* 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
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) {
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
601
      gst_rtp_buffer_unmap (&rtp);
602 603 604 605 606 607 608
      /* 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,
609
            rtpvorbispay->payload_VDT, timestamp);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
610 611
        gst_rtp_buffer_map (rtpvorbispay->packet, GST_MAP_WRITE, &rtp);
        payload = gst_rtp_buffer_get_payload (&rtp);
612 613 614 615 616 617 618 619 620 621
        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;
    }
  }
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
622 623 624 625 626

  if (rtp.buffer)
    gst_rtp_buffer_unmap (&rtp);

  gst_buffer_unmap (buffer, data, -1);
Wim Taymans's avatar
Wim Taymans committed
627 628
  gst_buffer_unref (buffer);

629
done:
Wim Taymans's avatar
Wim Taymans committed
630
  return ret;
631 632 633 634 635 636

  /* ERRORS */
wrong_size:
  {
    GST_ELEMENT_WARNING (rtpvorbispay, STREAM, DECODE,
        ("Invalid packet size (1 < %d <= 0xffff)", size), (NULL));
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
637
    gst_buffer_unmap (buffer, data, -1);
Wim Taymans's avatar
Wim Taymans committed
638
    gst_buffer_unref (buffer);
639 640 641 642
    return GST_FLOW_OK;
  }
parse_id_failed:
  {
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
643
    gst_buffer_unmap (buffer, data, -1);
Wim Taymans's avatar
Wim Taymans committed
644
    gst_buffer_unref (buffer);
645 646 647 648 649
    return GST_FLOW_ERROR;
  }
unknown_header:
  {
    GST_ELEMENT_WARNING (rtpvorbispay, STREAM, DECODE,
650
        (NULL), ("Ignoring unknown header received"));
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
651
    gst_buffer_unmap (buffer, data, -1);
Wim Taymans's avatar
Wim Taymans committed
652
    gst_buffer_unref (buffer);
653 654 655 656 657 658
    return GST_FLOW_OK;
  }
header_error:
  {
    GST_ELEMENT_WARNING (rtpvorbispay, STREAM, DECODE,
        (NULL), ("Error initializing header config"));
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
659
    gst_buffer_unmap (buffer, data, -1);
Wim Taymans's avatar
Wim Taymans committed
660
    gst_buffer_unref (buffer);
661 662
    return GST_FLOW_OK;
  }
Wim Taymans's avatar
Wim Taymans committed
663 664
}

665
static gboolean
Wim Taymans's avatar
Wim Taymans committed
666
gst_rtp_vorbis_pay_handle_event (GstRTPBasePayload * payload, GstEvent * event)
667
{
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
668
  GstRtpVorbisPay *rtpvorbispay = GST_RTP_VORBIS_PAY (payload);
669 670 671 672 673 674 675 676 677 678 679 680

  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
681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706
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
707 708 709 710
gboolean
gst_rtp_vorbis_pay_plugin_init (GstPlugin * plugin)
{
  return gst_element_register (plugin, "rtpvorbispay",
711
      GST_RANK_SECONDARY, GST_TYPE_RTP_VORBIS_PAY);
Wim Taymans's avatar
Wim Taymans committed
712
}