gstrtpvorbispay.c 29.2 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
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
16 17
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
Wim Taymans's avatar
Wim Taymans committed
18 19 20 21 22 23 24 25 26
 */

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

#include <string.h>

#include <gst/rtp/gstrtpbuffer.h>
27
#include <gst/audio/audio.h>
Wim Taymans's avatar
Wim Taymans committed
28

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

GST_DEBUG_CATEGORY_STATIC (rtpvorbispay_debug);
#define GST_CAT_DEFAULT (rtpvorbispay_debug)

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

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\", "
46
        "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
47
        "clock-rate = (int) [1, MAX ], " "encoding-name = (string) \"VORBIS\""
Wim Taymans's avatar
Wim Taymans committed
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
        /* 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")
    );

63 64 65 66 67 68 69 70
#define DEFAULT_CONFIG_INTERVAL 0

enum
{
  PROP_0,
  PROP_CONFIG_INTERVAL
};

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
71
#define gst_rtp_vorbis_pay_parent_class parent_class
Wim Taymans's avatar
Wim Taymans committed
72
G_DEFINE_TYPE (GstRtpVorbisPay, gst_rtp_vorbis_pay, GST_TYPE_RTP_BASE_PAYLOAD);
Wim Taymans's avatar
Wim Taymans committed
73

Wim Taymans's avatar
Wim Taymans committed
74
static gboolean gst_rtp_vorbis_pay_setcaps (GstRTPBasePayload * basepayload,
Wim Taymans's avatar
Wim Taymans committed
75
    GstCaps * caps);
Wim Taymans's avatar
Wim Taymans committed
76 77
static GstStateChangeReturn gst_rtp_vorbis_pay_change_state (GstElement *
    element, GstStateChange transition);
Wim Taymans's avatar
Wim Taymans committed
78
static GstFlowReturn gst_rtp_vorbis_pay_handle_buffer (GstRTPBasePayload * pad,
Wim Taymans's avatar
Wim Taymans committed
79
    GstBuffer * buffer);
Wim Taymans's avatar
Wim Taymans committed
80
static gboolean gst_rtp_vorbis_pay_sink_event (GstRTPBasePayload * payload,
81
    GstEvent * event);
Wim Taymans's avatar
Wim Taymans committed
82

83 84 85 86 87
static gboolean gst_rtp_vorbis_pay_parse_id (GstRTPBasePayload * basepayload,
    guint8 * data, guint size);
static gboolean gst_rtp_vorbis_pay_finish_headers (GstRTPBasePayload *
    basepayload);

88 89 90 91 92
static void gst_rtp_vorbis_pay_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_rtp_vorbis_pay_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);

Wim Taymans's avatar
Wim Taymans committed
93 94 95
static void
gst_rtp_vorbis_pay_class_init (GstRtpVorbisPayClass * klass)
{
96
  GObjectClass *gobject_class;
Wim Taymans's avatar
Wim Taymans committed
97
  GstElementClass *gstelement_class;
Wim Taymans's avatar
Wim Taymans committed
98
  GstRTPBasePayloadClass *gstrtpbasepayload_class;
Wim Taymans's avatar
Wim Taymans committed
99

100
  gobject_class = (GObjectClass *) klass;
Wim Taymans's avatar
Wim Taymans committed
101
  gstelement_class = (GstElementClass *) klass;
Wim Taymans's avatar
Wim Taymans committed
102
  gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
Wim Taymans's avatar
Wim Taymans committed
103

Wim Taymans's avatar
Wim Taymans committed
104 105
  gstelement_class->change_state = gst_rtp_vorbis_pay_change_state;

Wim Taymans's avatar
Wim Taymans committed
106 107
  gstrtpbasepayload_class->set_caps = gst_rtp_vorbis_pay_setcaps;
  gstrtpbasepayload_class->handle_buffer = gst_rtp_vorbis_pay_handle_buffer;
Wim Taymans's avatar
Wim Taymans committed
108
  gstrtpbasepayload_class->sink_event = gst_rtp_vorbis_pay_sink_event;
Wim Taymans's avatar
Wim Taymans committed
109

110 111 112
  gobject_class->set_property = gst_rtp_vorbis_pay_set_property;
  gobject_class->get_property = gst_rtp_vorbis_pay_get_property;

113 114 115 116
  gst_element_class_add_static_pad_template (gstelement_class,
      &gst_rtp_vorbis_pay_src_template);
  gst_element_class_add_static_pad_template (gstelement_class,
      &gst_rtp_vorbis_pay_sink_template);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
117

118
  gst_element_class_set_static_metadata (gstelement_class,
119
      "RTP Vorbis payloader",
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
120 121
      "Codec/Payloader/Network/RTP",
      "Payload-encode Vorbis audio into RTP packets (RFC 5215)",
122
      "Wim Taymans <wim.taymans@gmail.com>");
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
123

Wim Taymans's avatar
Wim Taymans committed
124 125
  GST_DEBUG_CATEGORY_INIT (rtpvorbispay_debug, "rtpvorbispay", 0,
      "Vorbis RTP Payloader");
126 127 128 129 130 131 132 133

  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_CONFIG_INTERVAL,
      g_param_spec_uint ("config-interval", "Config Send Interval",
          "Send Config Insertion Interval in seconds (configuration headers "
          "will be multiplexed in the data stream when detected.) (0 = disabled)",
          0, 3600, DEFAULT_CONFIG_INTERVAL,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
      );
Wim Taymans's avatar
Wim Taymans committed
134 135 136
}

static void
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
137
gst_rtp_vorbis_pay_init (GstRtpVorbisPay * rtpvorbispay)
Wim Taymans's avatar
Wim Taymans committed
138
{
139
  rtpvorbispay->last_config = GST_CLOCK_TIME_NONE;
Wim Taymans's avatar
Wim Taymans committed
140 141
}

142 143 144 145 146 147
static void
gst_rtp_vorbis_pay_clear_packet (GstRtpVorbisPay * rtpvorbispay)
{
  if (rtpvorbispay->packet)
    gst_buffer_unref (rtpvorbispay->packet);
  rtpvorbispay->packet = NULL;
148 149
  g_list_free_full (rtpvorbispay->packet_buffers,
      (GDestroyNotify) gst_buffer_unref);
150
  rtpvorbispay->packet_buffers = NULL;
151 152
}

Wim Taymans's avatar
Wim Taymans committed
153 154 155
static void
gst_rtp_vorbis_pay_cleanup (GstRtpVorbisPay * rtpvorbispay)
{
156
  gst_rtp_vorbis_pay_clear_packet (rtpvorbispay);
157 158
  g_list_free_full (rtpvorbispay->headers, (GDestroyNotify) gst_buffer_unref);
  rtpvorbispay->headers = NULL;
159
  g_free (rtpvorbispay->config_data);
160 161
  rtpvorbispay->config_data = NULL;
  rtpvorbispay->last_config = GST_CLOCK_TIME_NONE;
Wim Taymans's avatar
Wim Taymans committed
162 163
}

Wim Taymans's avatar
Wim Taymans committed
164
static gboolean
Wim Taymans's avatar
Wim Taymans committed
165
gst_rtp_vorbis_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
Wim Taymans's avatar
Wim Taymans committed
166 167
{
  GstRtpVorbisPay *rtpvorbispay;
168 169 170 171 172
  GstStructure *s;
  const GValue *array;
  gint asize, i;
  GstBuffer *buf;
  GstMapInfo map;
Wim Taymans's avatar
Wim Taymans committed
173 174 175

  rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload);

176 177
  s = gst_caps_get_structure (caps, 0);

178 179
  rtpvorbispay->need_headers = TRUE;

180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
  if ((array = gst_structure_get_value (s, "streamheader")) == NULL)
    goto done;

  if (G_VALUE_TYPE (array) != GST_TYPE_ARRAY)
    goto done;

  if ((asize = gst_value_array_get_size (array)) < 3)
    goto done;

  for (i = 0; i < asize; i++) {
    const GValue *value;

    value = gst_value_array_get_value (array, i);
    if ((buf = gst_value_get_buffer (value)) == NULL)
      goto null_buffer;

    gst_buffer_map (buf, &map, GST_MAP_READ);
197 198 199
    if (map.size < 1)
      goto invalid_streamheader;

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
    /* no data packets allowed */
    if ((map.data[0] & 1) == 0)
      goto invalid_streamheader;

    /* we need packets with id 1, 3, 5 */
    if (map.data[0] != (i * 2) + 1)
      goto invalid_streamheader;

    if (i == 0) {
      /* identification, we need to parse this in order to get the clock rate. */
      if (G_UNLIKELY (!gst_rtp_vorbis_pay_parse_id (basepayload, map.data,
                  map.size)))
        goto parse_id_failed;
    }
    GST_DEBUG_OBJECT (rtpvorbispay, "collecting header %d", i);
    rtpvorbispay->headers =
        g_list_append (rtpvorbispay->headers, gst_buffer_ref (buf));
    gst_buffer_unmap (buf, &map);
  }
  if (!gst_rtp_vorbis_pay_finish_headers (basepayload))
    goto finish_failed;

done:
Wim Taymans's avatar
Wim Taymans committed
223
  return TRUE;
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247

  /* ERRORS */
null_buffer:
  {
    GST_WARNING_OBJECT (rtpvorbispay, "streamheader with null buffer received");
    return FALSE;
  }
invalid_streamheader:
  {
    GST_WARNING_OBJECT (rtpvorbispay, "unable to parse initial header");
    gst_buffer_unmap (buf, &map);
    return FALSE;
  }
parse_id_failed:
  {
    GST_WARNING_OBJECT (rtpvorbispay, "unable to parse initial header");
    gst_buffer_unmap (buf, &map);
    return FALSE;
  }
finish_failed:
  {
    GST_WARNING_OBJECT (rtpvorbispay, "unable to finish headers");
    return FALSE;
  }
Wim Taymans's avatar
Wim Taymans committed
248 249 250
}

static void
251
gst_rtp_vorbis_pay_reset_packet (GstRtpVorbisPay * rtpvorbispay, guint8 VDT)
Wim Taymans's avatar
Wim Taymans committed
252 253
{
  guint payload_len;
254
  GstRTPBuffer rtp = { NULL };
Wim Taymans's avatar
Wim Taymans committed
255

256
  GST_LOG_OBJECT (rtpvorbispay, "reset packet");
Wim Taymans's avatar
Wim Taymans committed
257 258

  rtpvorbispay->payload_pos = 4;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
259 260 261
  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
262 263 264
  rtpvorbispay->payload_left = payload_len - 4;
  rtpvorbispay->payload_duration = 0;
  rtpvorbispay->payload_F = 0;
265
  rtpvorbispay->payload_VDT = VDT;
Wim Taymans's avatar
Wim Taymans committed
266 267 268
  rtpvorbispay->payload_pkts = 0;
}

269
static void
270 271
gst_rtp_vorbis_pay_init_packet (GstRtpVorbisPay * rtpvorbispay, guint8 VDT,
    GstClockTime timestamp)
272
{
273
  GST_LOG_OBJECT (rtpvorbispay, "starting new packet, VDT: %d", VDT);
274

275
  gst_rtp_vorbis_pay_clear_packet (rtpvorbispay);
276 277 278

  /* new packet allocate max packet size */
  rtpvorbispay->packet =
Wim Taymans's avatar
Wim Taymans committed
279
      gst_rtp_buffer_new_allocate_len (GST_RTP_BASE_PAYLOAD_MTU
280 281
      (rtpvorbispay), 0, 0);
  gst_rtp_vorbis_pay_reset_packet (rtpvorbispay, VDT);
282

283
  GST_BUFFER_PTS (rtpvorbispay->packet) = timestamp;
284 285
}

Wim Taymans's avatar
Wim Taymans committed
286 287 288 289 290 291
static GstFlowReturn
gst_rtp_vorbis_pay_flush_packet (GstRtpVorbisPay * rtpvorbispay)
{
  GstFlowReturn ret;
  guint8 *payload;
  guint hlen;
292
  GstRTPBuffer rtp = { NULL };
293
  GList *l;
Wim Taymans's avatar
Wim Taymans committed
294 295

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

299
  GST_LOG_OBJECT (rtpvorbispay, "flushing packet");
Wim Taymans's avatar
Wim Taymans committed
300

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

Wim Taymans's avatar
Wim Taymans committed
303
  /* fix header */
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
304
  payload = gst_rtp_buffer_get_payload (&rtp);
Wim Taymans's avatar
Wim Taymans committed
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
  /*
   *  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
323 324
  gst_rtp_buffer_unmap (&rtp);

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

329 330
  GST_BUFFER_DURATION (rtpvorbispay->packet) = rtpvorbispay->payload_duration;

331 332 333 334 335 336 337 338 339
  for (l = g_list_last (rtpvorbispay->packet_buffers); l; l = l->prev) {
    GstBuffer *buf = GST_BUFFER_CAST (l->data);
    gst_rtp_copy_meta (GST_ELEMENT_CAST (rtpvorbispay), rtpvorbispay->packet,
        buf, g_quark_from_static_string (GST_META_TAG_AUDIO_STR));
    gst_buffer_unref (buf);
  }
  g_list_free (rtpvorbispay->packet_buffers);
  rtpvorbispay->packet_buffers = NULL;

Wim Taymans's avatar
Wim Taymans committed
340 341
  /* push, this gives away our ref to the packet, so clear it. */
  ret =
Wim Taymans's avatar
Wim Taymans committed
342
      gst_rtp_base_payload_push (GST_RTP_BASE_PAYLOAD (rtpvorbispay),
Wim Taymans's avatar
Wim Taymans committed
343 344 345 346 347 348
      rtpvorbispay->packet);
  rtpvorbispay->packet = NULL;

  return ret;
}

349
static gboolean
Wim Taymans's avatar
Wim Taymans committed
350
gst_rtp_vorbis_pay_finish_headers (GstRTPBasePayload * basepayload)
351 352 353
{
  GstRtpVorbisPay *rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload);
  GList *walk;
354
  guint length, size, n_headers, configlen, extralen;
355
  gchar *cstr, *configuration;
356
  guint8 *data, *config;
357
  guint32 ident;
358
  gboolean res;
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382

  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
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
383 384 385 386 387 388 389 390 391
   * |                   Ident                       | length       ..
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * ..              | n. of headers |    length1    |    length2   ..
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * ..              |             Identification Header            ..
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * .................................................................
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * ..              |         Comment Header                       ..
392
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
393
   * .................................................................
394
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
395
   * ..                        Comment Header                        |
396 397 398
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * |                          Setup Header                        ..
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
399 400
   * .................................................................
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
401 402 403 404
   * ..                         Setup Header                         |
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   */

405 406 407 408 409
  /* 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 */
410
  length = 0;
411
  n_headers = 0;
412
  ident = fnv1_hash_32_new ();
413
  extralen = 1;
414 415
  for (walk = rtpvorbispay->headers; walk; walk = g_list_next (walk)) {
    GstBuffer *buf = GST_BUFFER_CAST (walk->data);
Wim Taymans's avatar
Wim Taymans committed
416 417
    GstMapInfo map;
    guint bsize;
418

Wim Taymans's avatar
Wim Taymans committed
419
    bsize = gst_buffer_get_size (buf);
420 421 422 423 424 425 426 427
    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++;
428
        extralen++;
429 430 431 432
        bsize >>= 7;
      } while (bsize);
    }
    /* update hash */
Wim Taymans's avatar
Wim Taymans committed
433 434 435
    gst_buffer_map (buf, &map, GST_MAP_READ);
    ident = fnv1_hash_32_update (ident, map.data, map.size);
    gst_buffer_unmap (buf, &map);
436 437
  }

438 439 440
  /* packet length is header size + packet length */
  configlen = size + length;
  config = data = g_malloc (configlen);
441

442 443 444 445 446 447
  /* number of packed headers, we only pack 1 header */
  data[0] = 0;
  data[1] = 0;
  data[2] = 0;
  data[3] = 1;

448 449 450
  ident = fnv1_hash_32_to_24 (ident);
  rtpvorbispay->payload_ident = ident;
  GST_DEBUG_OBJECT (rtpvorbispay, "ident 0x%08x", ident);
451 452

  /* take lower 3 bytes */
453 454 455
  data[4] = (ident >> 16) & 0xff;
  data[5] = (ident >> 8) & 0xff;
  data[6] = ident & 0xff;
456

457 458 459 460 461 462 463 464 465 466 467 468
  /* 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;
469
    guint flag;
470 471 472 473 474

    /* 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
475
    bsize = gst_buffer_get_size (buf);
476 477 478 479 480 481 482 483 484

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

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
485
    bsize = gst_buffer_get_size (buf);
486
    /* write the size backwards */
487
    flag = 0;
488 489
    while (size) {
      size--;
490
      data[size] = (bsize & 0x7f) | flag;
491
      bsize >>= 7;
492
      flag = 0x80;              /* Flag bit on all bytes of the length except the last */
493 494 495
    }
    data += temp;
  }
496 497 498 499 500

  /* 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
501 502
    gst_buffer_extract (buf, 0, data, gst_buffer_get_size (buf));
    data += gst_buffer_get_size (buf);
503
  }
504
  rtpvorbispay->need_headers = FALSE;
505

506
  /* serialize to base64 */
507
  configuration = g_base64_encode (config, configlen);
508 509

  /* store for later re-sending */
510
  g_free (rtpvorbispay->config_data);
511 512 513 514 515 516
  rtpvorbispay->config_size = configlen - 4 - 3 - 2;
  rtpvorbispay->config_data = g_malloc (rtpvorbispay->config_size);
  rtpvorbispay->config_extra_len = extralen;
  memcpy (rtpvorbispay->config_data, config + 4 + 3 + 2,
      rtpvorbispay->config_size);

517
  g_free (config);
518 519 520

  /* configure payloader settings */
  cstr = g_strdup_printf ("%d", rtpvorbispay->channels);
Wim Taymans's avatar
Wim Taymans committed
521
  gst_rtp_base_payload_set_options (basepayload, "audio", TRUE, "VORBIS",
522
      rtpvorbispay->rate);
523
  res =
Wim Taymans's avatar
Wim Taymans committed
524
      gst_rtp_base_payload_set_outcaps (basepayload, "encoding-params",
525
      G_TYPE_STRING, cstr, "configuration", G_TYPE_STRING, configuration, NULL);
526 527 528
  g_free (cstr);
  g_free (configuration);

529
  return res;
530 531 532 533 534 535 536 537 538

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

539
static gboolean
Wim Taymans's avatar
Wim Taymans committed
540
gst_rtp_vorbis_pay_parse_id (GstRTPBasePayload * basepayload, guint8 * data,
541
    guint size)
Wim Taymans's avatar
Wim Taymans committed
542
{
543
  GstRtpVorbisPay *rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload);
544 545
  guint8 channels;
  gint32 rate, version;
Wim Taymans's avatar
Wim Taymans committed
546

547 548
  if (G_UNLIKELY (size < 16))
    goto too_short;
Wim Taymans's avatar
Wim Taymans committed
549

550 551 552
  if (G_UNLIKELY (memcmp (data, "\001vorbis", 7)))
    goto invalid_start;
  data += 7;
Wim Taymans's avatar
Wim Taymans committed
553

554 555 556
  if (G_UNLIKELY ((version = GST_READ_UINT32_LE (data)) != 0))
    goto invalid_version;
  data += 4;
Wim Taymans's avatar
Wim Taymans committed
557

558 559
  if (G_UNLIKELY ((channels = *data++) < 1))
    goto invalid_channels;
Wim Taymans's avatar
Wim Taymans committed
560

561 562
  if (G_UNLIKELY ((rate = GST_READ_UINT32_LE (data)) < 1))
    goto invalid_rate;
Wim Taymans's avatar
Wim Taymans committed
563

564 565 566
  /* all fine, store the values */
  rtpvorbispay->channels = channels;
  rtpvorbispay->rate = rate;
Wim Taymans's avatar
Wim Taymans committed
567

568
  return TRUE;
Wim Taymans's avatar
Wim Taymans committed
569

570 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
  /* 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
601 602 603 604
  }
}

static GstFlowReturn
605
gst_rtp_vorbis_pay_payload_buffer (GstRtpVorbisPay * rtpvorbispay, guint8 VDT,
606 607
    GstBuffer * buffer, guint8 * data, guint size, GstClockTime timestamp,
    GstClockTime duration, guint not_in_length)
Wim Taymans's avatar
Wim Taymans committed
608
{
609
  GstFlowReturn ret = GST_FLOW_OK;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
610
  guint newsize;
Wim Taymans's avatar
Wim Taymans committed
611
  guint packet_len;
612
  GstClockTime newduration;
Wim Taymans's avatar
Wim Taymans committed
613
  gboolean flush;
614 615 616
  guint plen;
  guint8 *ppos, *payload;
  gboolean fragmented;
617
  GstRTPBuffer rtp = { NULL };
Wim Taymans's avatar
Wim Taymans committed
618 619 620 621 622 623 624 625 626 627

  /* 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 */
628 629
  flush = gst_rtp_base_payload_is_filled (GST_RTP_BASE_PAYLOAD (rtpvorbispay),
      packet_len, newduration);
Wim Taymans's avatar
Wim Taymans committed
630 631
  /* we can store up to 15 vorbis packets in one RTP packet. */
  flush |= (rtpvorbispay->payload_pkts == 15);
632 633 634
  /* flush if we have a new VDT */
  if (rtpvorbispay->packet)
    flush |= (rtpvorbispay->payload_VDT != VDT);
Wim Taymans's avatar
Wim Taymans committed
635
  if (flush)
636
    ret = gst_rtp_vorbis_pay_flush_packet (rtpvorbispay);
Wim Taymans's avatar
Wim Taymans committed
637

638 639 640
  if (ret != GST_FLOW_OK)
    goto done;

641
  /* create new packet if we must */
642 643 644
  if (!rtpvorbispay->packet) {
    gst_rtp_vorbis_pay_init_packet (rtpvorbispay, VDT, timestamp);
  }
645

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
646 647
  gst_rtp_buffer_map (rtpvorbispay->packet, GST_MAP_WRITE, &rtp);
  payload = gst_rtp_buffer_get_payload (&rtp);
648 649 650 651 652
  ppos = payload + rtpvorbispay->payload_pos;
  fragmented = FALSE;

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

656
    GST_LOG_OBJECT (rtpvorbispay, "append %u bytes", plen);
657 658

    /* data is copied in the payload with a 2 byte length header */
659 660 661 662 663
    ppos[0] = ((plen - not_in_length) >> 8) & 0xff;
    ppos[1] = ((plen - not_in_length) & 0xff);
    if (plen)
      memcpy (&ppos[2], data, plen);

664 665 666 667 668 669 670 671 672 673 674 675
    if (buffer) {
      if (!rtpvorbispay->packet_buffers
          || rtpvorbispay->packet_buffers->data != (gpointer) buffer)
        rtpvorbispay->packet_buffers =
            g_list_prepend (rtpvorbispay->packet_buffers,
            gst_buffer_ref (buffer));
    } else {
      GList *l;

      for (l = rtpvorbispay->headers; l; l = l->next)
        rtpvorbispay->packet_buffers =
            g_list_prepend (rtpvorbispay->packet_buffers,
676
            gst_buffer_ref (l->data));
677 678
    }

679 680 681
    /* only first (only) configuration cuts length field */
    /* NOTE: spec (if any) is not clear on this ... */
    not_in_length = 0;
Wim Taymans's avatar
Wim Taymans committed
682

683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704
    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
705
      gst_rtp_buffer_unmap (&rtp);
706 707 708 709 710 711 712
      /* 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,
713
            rtpvorbispay->payload_VDT, timestamp);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
714 715
        gst_rtp_buffer_map (rtpvorbispay->packet, GST_MAP_WRITE, &rtp);
        payload = gst_rtp_buffer_get_payload (&rtp);
716 717 718 719 720 721 722 723 724
        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;
    }
725
  } while (size && ret == GST_FLOW_OK);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
726 727 728 729

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

730 731
done:

732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
  return ret;
}

static GstFlowReturn
gst_rtp_vorbis_pay_handle_buffer (GstRTPBasePayload * basepayload,
    GstBuffer * buffer)
{
  GstRtpVorbisPay *rtpvorbispay;
  GstFlowReturn ret;
  GstMapInfo map;
  gsize size;
  guint8 *data;
  GstClockTime duration, timestamp;
  guint8 VDT;

  rtpvorbispay = GST_RTP_VORBIS_PAY (basepayload);

  gst_buffer_map (buffer, &map, GST_MAP_READ);
  data = map.data;
  size = map.size;
  duration = GST_BUFFER_DURATION (buffer);
753
  timestamp = GST_BUFFER_PTS (buffer);
754 755 756 757

  GST_LOG_OBJECT (rtpvorbispay, "size %" G_GSIZE_FORMAT
      ", duration %" GST_TIME_FORMAT, size, GST_TIME_ARGS (duration));

758
  if (G_UNLIKELY (size < 1))
759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782
    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;
    } else if (data[0] == 3) {
      /* comment */
      VDT = 2;
    } else if (data[0] == 5) {
      /* setup */
      VDT = 1;
    } else
      goto unknown_header;
  } else
    /* data */
    VDT = 0;

  /* we need to collect the headers and construct a config string from them */
  if (VDT != 0) {
783
    if (!rtpvorbispay->need_headers && VDT == 1) {
784 785
      GST_INFO_OBJECT (rtpvorbispay, "getting new headers, replace existing");
      g_list_free_full (rtpvorbispay->headers,
786
          (GDestroyNotify) gst_buffer_unref);
787 788 789
      rtpvorbispay->headers = NULL;
      rtpvorbispay->need_headers = TRUE;
    }
790 791 792 793 794 795
    GST_DEBUG_OBJECT (rtpvorbispay, "collecting header");
    /* append header to the list of headers */
    gst_buffer_unmap (buffer, &map);
    rtpvorbispay->headers = g_list_append (rtpvorbispay->headers, buffer);
    ret = GST_FLOW_OK;
    goto done;
796 797 798
  } else if (rtpvorbispay->headers && rtpvorbispay->need_headers) {
    if (!gst_rtp_vorbis_pay_finish_headers (basepayload))
      goto header_error;
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837
  }

  /* there is a config request, see if we need to insert it */
  if (rtpvorbispay->config_interval > 0 && rtpvorbispay->config_data) {
    gboolean send_config = FALSE;

    if (rtpvorbispay->last_config != -1) {
      guint64 diff;

      GST_LOG_OBJECT (rtpvorbispay,
          "now %" GST_TIME_FORMAT ", last config %" GST_TIME_FORMAT,
          GST_TIME_ARGS (timestamp), GST_TIME_ARGS (rtpvorbispay->last_config));

      /* calculate diff between last config in milliseconds */
      if (timestamp > rtpvorbispay->last_config) {
        diff = timestamp - rtpvorbispay->last_config;
      } else {
        diff = 0;
      }

      GST_DEBUG_OBJECT (rtpvorbispay,
          "interval since last config %" GST_TIME_FORMAT, GST_TIME_ARGS (diff));

      /* bigger than interval, queue config */
      /* FIXME should convert timestamps to running time */
      if (GST_TIME_AS_SECONDS (diff) >= rtpvorbispay->config_interval) {
        GST_DEBUG_OBJECT (rtpvorbispay, "time to send config");
        send_config = TRUE;
      }
    } else {
      /* no known previous config time, send now */
      GST_DEBUG_OBJECT (rtpvorbispay, "no previous config time, send now");
      send_config = TRUE;
    }

    if (send_config) {
      /* we need to send config now first */
      /* different TDT type forces flush */
      gst_rtp_vorbis_pay_payload_buffer (rtpvorbispay, 1,
838
          NULL, rtpvorbispay->config_data, rtpvorbispay->config_size,
839 840 841 842 843 844 845 846
          timestamp, GST_CLOCK_TIME_NONE, rtpvorbispay->config_extra_len);

      if (timestamp != -1) {
        rtpvorbispay->last_config = timestamp;
      }
    }
  }

847 848
  ret =
      gst_rtp_vorbis_pay_payload_buffer (rtpvorbispay, VDT, buffer, data, size,
849 850
      timestamp, duration, 0);

Wim Taymans's avatar
Wim Taymans committed
851
  gst_buffer_unmap (buffer, &map);
Wim Taymans's avatar
Wim Taymans committed
852 853
  gst_buffer_unref (buffer);

854
done:
Wim Taymans's avatar
Wim Taymans committed
855
  return ret;
856 857 858 859 860

  /* ERRORS */
wrong_size:
  {
    GST_ELEMENT_WARNING (rtpvorbispay, STREAM, DECODE,
861
        ("Invalid packet size (1 < %" G_GSIZE_FORMAT ")", size), (NULL));
Wim Taymans's avatar
Wim Taymans committed
862
    gst_buffer_unmap (buffer, &map);
Wim Taymans's avatar
Wim Taymans committed
863
    gst_buffer_unref (buffer);
864 865 866 867
    return GST_FLOW_OK;
  }
parse_id_failed:
  {
Wim Taymans's avatar
Wim Taymans committed
868
    gst_buffer_unmap (buffer, &map);
Wim Taymans's avatar
Wim Taymans committed
869
    gst_buffer_unref (buffer);
870 871 872 873 874
    return GST_FLOW_ERROR;
  }
unknown_header:
  {
    GST_ELEMENT_WARNING (rtpvorbispay, STREAM, DECODE,
875
        (NULL), ("Ignoring unknown header received"));
Wim Taymans's avatar
Wim Taymans committed
876
    gst_buffer_unmap (buffer, &map);
Wim Taymans's avatar
Wim Taymans committed
877
    gst_buffer_unref (buffer);
878 879 880 881 882 883
    return GST_FLOW_OK;
  }
header_error:
  {
    GST_ELEMENT_WARNING (rtpvorbispay, STREAM, DECODE,
        (NULL), ("Error initializing header config"));
Wim Taymans's avatar
Wim Taymans committed
884
    gst_buffer_unmap (buffer, &map);
Wim Taymans's avatar
Wim Taymans committed
885
    gst_buffer_unref (buffer);
886 887
    return GST_FLOW_OK;
  }
Wim Taymans's avatar
Wim Taymans committed
888 889
}

890
static gboolean
Wim Taymans's avatar
Wim Taymans committed
891
gst_rtp_vorbis_pay_sink_event (GstRTPBasePayload * payload, GstEvent * event)
892
{
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
893
  GstRtpVorbisPay *rtpvorbispay = GST_RTP_VORBIS_PAY (payload);
894 895 896 897 898 899 900 901 902

  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 */
Wim Taymans's avatar
Wim Taymans committed
903
  return GST_RTP_BASE_PAYLOAD_CLASS (parent_class)->sink_event (payload, event);
904 905
}

Wim Taymans's avatar
Wim Taymans committed
906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931
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;
}

932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965
static void
gst_rtp_vorbis_pay_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstRtpVorbisPay *rtpvorbispay;

  rtpvorbispay = GST_RTP_VORBIS_PAY (object);

  switch (prop_id) {
    case PROP_CONFIG_INTERVAL:
      rtpvorbispay->config_interval = g_value_get_uint (value);
      break;
    default:
      break;
  }
}

static void
gst_rtp_vorbis_pay_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstRtpVorbisPay *rtpvorbispay;

  rtpvorbispay = GST_RTP_VORBIS_PAY (object);

  switch (prop_id) {
    case PROP_CONFIG_INTERVAL:
      g_value_set_uint (value, rtpvorbispay->config_interval);
      break;
    default:
      break;
  }
}

Wim Taymans's avatar
Wim Taymans committed
966 967 968 969
gboolean
gst_rtp_vorbis_pay_plugin_init (GstPlugin * plugin)
{
  return gst_element_register (plugin, "rtpvorbispay",
970
      GST_RANK_SECONDARY, GST_TYPE_RTP_VORBIS_PAY);
Wim Taymans's avatar
Wim Taymans committed
971
}