gstrtpamrdepay.c 13.7 KB
Newer Older
1
/* GStreamer
2
 * Copyright (C) <2005> Wim Taymans <wim.taymans@gmail.com>
3 4 5 6 7 8 9 10 11
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 13 14 15
 * 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.
18 19
 */

Wim Taymans's avatar
Wim Taymans committed
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
/**
 * SECTION:element-rtpamrdepay
 * @see_also: rtpamrpay
 *
 * Extract AMR audio from RTP packets according to RFC 3267.
 * For detailed information see: http://www.rfc-editor.org/rfc/rfc3267.txt
 *
 * <refsect2>
 * <title>Example pipeline</title>
 * |[
 * gst-launch-1.0 udpsrc caps='application/x-rtp, media=(string)audio, clock-rate=(int)8000, encoding-name=(string)AMR, encoding-params=(string)1, octet-align=(string)1, payload=(int)96' ! rtpamrdepay ! amrnbdec ! pulsesink
 * ]| This example pipeline will depayload and decode an RTP AMR stream. Refer to
 * the rtpamrpay example to create the RTP stream.
 * </refsect2>
 */

/*
 * RFC 3267 - Real-Time Transport Protocol (RTP) Payload Format and File
 * Storage Format for the Adaptive Multi-Rate (AMR) and Adaptive Multi-Rate
 * Wideband (AMR-WB) Audio Codecs.
 *
 */
42 43 44 45 46 47
#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <gst/rtp/gstrtpbuffer.h>

48
#include <stdlib.h>
49
#include <string.h>
50
#include "gstrtpamrdepay.h"
51

52 53 54
GST_DEBUG_CATEGORY_STATIC (rtpamrdepay_debug);
#define GST_CAT_DEFAULT (rtpamrdepay_debug)

55
/* RtpAMRDepay signals and args */
56 57 58 59 60 61 62 63
enum
{
  /* FILL ME */
  LAST_SIGNAL
};

enum
{
64
  PROP_0
65 66
};

67
/* input is an RTP packet
68 69 70
 *
 * params see RFC 3267, section 8.1
 */
71
static GstStaticPadTemplate gst_rtp_amr_depay_sink_template =
72
    GST_STATIC_PAD_TEMPLATE ("sink",
73 74
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
75
    GST_STATIC_CAPS ("application/x-rtp, "
76
        "media = (string) \"audio\", "
77
        "clock-rate = (int) 8000, " "encoding-name = (string) \"AMR\", "
78 79
        /* This is the default, so the peer doesn't have to specify it
         * "encoding-params = (string) \"1\", " */
80 81
        /* NOTE that all values must be strings in orde to be able to do SDP <->
         * GstCaps mapping. */
82
        "octet-align = (string) \"1\";"
83 84
        /* following options are not needed for a decoder
         *
85 86 87
         "crc = (string) { \"0\", \"1\" }, "
         "robust-sorting = (string) \"0\", "
         "interleaving = (string) \"0\";"
88 89 90 91 92 93 94 95
         "mode-set = (int) [ 0, 7 ], "
         "mode-change-period = (int) [ 1, MAX ], "
         "mode-change-neighbor = (boolean) { TRUE, FALSE }, "
         "maxptime = (int) [ 20, MAX ], "
         "ptime = (int) [ 20, MAX ]"
         */
        "application/x-rtp, "
        "media = (string) \"audio\", "
96
        "clock-rate = (int) 16000, " "encoding-name = (string) \"AMR-WB\", "
97 98
        /* This is the default, so the peer doesn't have to specify it
         * "encoding-params = (string) \"1\", " */
99 100
        /* NOTE that all values must be strings in orde to be able to do SDP <->
         * GstCaps mapping. */
101
        "octet-align = (string) \"1\";"
102
        /* following options are not needed for a decoder
103
         *
104 105 106
         "crc = (string) { \"0\", \"1\" }, "
         "robust-sorting = (string) \"0\", "
         "interleaving = (string) \"0\""
107 108 109 110 111 112 113
         "mode-set = (int) [ 0, 7 ], "
         "mode-change-period = (int) [ 1, MAX ], "
         "mode-change-neighbor = (boolean) { TRUE, FALSE }, "
         "maxptime = (int) [ 20, MAX ], "
         "ptime = (int) [ 20, MAX ]"
         */
    )
114 115
    );

116
static GstStaticPadTemplate gst_rtp_amr_depay_src_template =
117
    GST_STATIC_PAD_TEMPLATE ("src",
118 119
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
120 121
    GST_STATIC_CAPS ("audio/AMR, " "channels = (int) 1," "rate = (int) 8000;"
        "audio/AMR-WB, " "channels = (int) 1," "rate = (int) 16000")
122
    );
123

Wim Taymans's avatar
Wim Taymans committed
124
static gboolean gst_rtp_amr_depay_setcaps (GstRTPBaseDepayload * depayload,
125
    GstCaps * caps);
Wim Taymans's avatar
Wim Taymans committed
126
static GstBuffer *gst_rtp_amr_depay_process (GstRTPBaseDepayload * depayload,
127
    GstRTPBuffer * rtp);
128

Wim Taymans's avatar
Wim Taymans committed
129
#define gst_rtp_amr_depay_parent_class parent_class
Wim Taymans's avatar
Wim Taymans committed
130
G_DEFINE_TYPE (GstRtpAMRDepay, gst_rtp_amr_depay, GST_TYPE_RTP_BASE_DEPAYLOAD);
131 132

static void
Wim Taymans's avatar
Wim Taymans committed
133
gst_rtp_amr_depay_class_init (GstRtpAMRDepayClass * klass)
134
{
Wim Taymans's avatar
Wim Taymans committed
135
  GstElementClass *gstelement_class;
Wim Taymans's avatar
Wim Taymans committed
136
  GstRTPBaseDepayloadClass *gstrtpbasedepayload_class;
Wim Taymans's avatar
Wim Taymans committed
137 138

  gstelement_class = (GstElementClass *) klass;
Wim Taymans's avatar
Wim Taymans committed
139
  gstrtpbasedepayload_class = (GstRTPBaseDepayloadClass *) klass;
140

Wim Taymans's avatar
Wim Taymans committed
141
  gst_element_class_add_pad_template (gstelement_class,
142
      gst_static_pad_template_get (&gst_rtp_amr_depay_src_template));
Wim Taymans's avatar
Wim Taymans committed
143
  gst_element_class_add_pad_template (gstelement_class,
144
      gst_static_pad_template_get (&gst_rtp_amr_depay_sink_template));
145

146 147
  gst_element_class_set_static_metadata (gstelement_class,
      "RTP AMR depayloader", "Codec/Depayloader/Network/RTP",
148 149
      "Extracts AMR or AMR-WB audio from RTP packets (RFC 3267)",
      "Wim Taymans <wim.taymans@gmail.com>");
150

151
  gstrtpbasedepayload_class->process_rtp_packet = gst_rtp_amr_depay_process;
Wim Taymans's avatar
Wim Taymans committed
152
  gstrtpbasedepayload_class->set_caps = gst_rtp_amr_depay_setcaps;
153 154 155

  GST_DEBUG_CATEGORY_INIT (rtpamrdepay_debug, "rtpamrdepay", 0,
      "AMR/AMR-WB RTP Depayloader");
156 157 158
}

static void
Wim Taymans's avatar
Wim Taymans committed
159
gst_rtp_amr_depay_init (GstRtpAMRDepay * rtpamrdepay)
160
{
Wim Taymans's avatar
Wim Taymans committed
161
  GstRTPBaseDepayload *depayload;
162

Wim Taymans's avatar
Wim Taymans committed
163
  depayload = GST_RTP_BASE_DEPAYLOAD (rtpamrdepay);
164

Wim Taymans's avatar
Wim Taymans committed
165
  gst_pad_use_fixed_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (depayload));
166 167
}

168
static gboolean
Wim Taymans's avatar
Wim Taymans committed
169
gst_rtp_amr_depay_setcaps (GstRTPBaseDepayload * depayload, GstCaps * caps)
170 171 172
{
  GstStructure *structure;
  GstCaps *srccaps;
173
  GstRtpAMRDepay *rtpamrdepay;
174
  const gchar *params;
175 176
  const gchar *str, *type;
  gint clock_rate, need_clock_rate;
177
  gboolean res;
178

179
  rtpamrdepay = GST_RTP_AMR_DEPAY (depayload);
180 181 182

  structure = gst_caps_get_structure (caps, 0);

183 184 185 186
  /* figure out the mode first and set the clock rates */
  if ((str = gst_structure_get_string (structure, "encoding-name"))) {
    if (strcmp (str, "AMR") == 0) {
      rtpamrdepay->mode = GST_RTP_AMR_DP_MODE_NB;
187
      need_clock_rate = 8000;
188 189 190
      type = "audio/AMR";
    } else if (strcmp (str, "AMR-WB") == 0) {
      rtpamrdepay->mode = GST_RTP_AMR_DP_MODE_WB;
191
      need_clock_rate = 16000;
192 193 194 195 196 197
      type = "audio/AMR-WB";
    } else
      goto invalid_mode;
  } else
    goto invalid_mode;

Wim Taymans's avatar
Wim Taymans committed
198
  if (!(str = gst_structure_get_string (structure, "octet-align")))
199
    rtpamrdepay->octet_align = FALSE;
Wim Taymans's avatar
Wim Taymans committed
200
  else
201
    rtpamrdepay->octet_align = (atoi (str) == 1);
202

Wim Taymans's avatar
Wim Taymans committed
203
  if (!(str = gst_structure_get_string (structure, "crc")))
204
    rtpamrdepay->crc = FALSE;
Wim Taymans's avatar
Wim Taymans committed
205
  else
206
    rtpamrdepay->crc = (atoi (str) == 1);
207

208
  if (rtpamrdepay->crc) {
209
    /* crc mode implies octet aligned mode */
210
    rtpamrdepay->octet_align = TRUE;
211 212
  }

Wim Taymans's avatar
Wim Taymans committed
213
  if (!(str = gst_structure_get_string (structure, "robust-sorting")))
214
    rtpamrdepay->robust_sorting = FALSE;
Wim Taymans's avatar
Wim Taymans committed
215
  else
216
    rtpamrdepay->robust_sorting = (atoi (str) == 1);
217

218
  if (rtpamrdepay->robust_sorting) {
219
    /* robust_sorting mode implies octet aligned mode */
220
    rtpamrdepay->octet_align = TRUE;
221 222
  }

Wim Taymans's avatar
Wim Taymans committed
223
  if (!(str = gst_structure_get_string (structure, "interleaving")))
224
    rtpamrdepay->interleaving = FALSE;
Wim Taymans's avatar
Wim Taymans committed
225
  else
226
    rtpamrdepay->interleaving = (atoi (str) == 1);
227

228
  if (rtpamrdepay->interleaving) {
229
    /* interleaving mode implies octet aligned mode */
230
    rtpamrdepay->octet_align = TRUE;
231 232
  }

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
233
  if (!(params = gst_structure_get_string (structure, "encoding-params")))
234
    rtpamrdepay->channels = 1;
235
  else {
236
    rtpamrdepay->channels = atoi (params);
237 238
  }

239 240
  if (!gst_structure_get_int (structure, "clock-rate", &clock_rate))
    clock_rate = need_clock_rate;
241
  depayload->clock_rate = clock_rate;
242

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
243
  /* we require 1 channel, 8000 Hz, octet aligned, no CRC,
244
   * no robust sorting, no interleaving for now */
245
  if (rtpamrdepay->channels != 1)
246
    return FALSE;
247
  if (clock_rate != need_clock_rate)
248
    return FALSE;
249
  if (rtpamrdepay->octet_align != TRUE)
250
    return FALSE;
251
  if (rtpamrdepay->robust_sorting != FALSE)
252
    return FALSE;
253
  if (rtpamrdepay->interleaving != FALSE)
254 255
    return FALSE;

256
  srccaps = gst_caps_new_simple (type,
257
      "channels", G_TYPE_INT, rtpamrdepay->channels,
258
      "rate", G_TYPE_INT, clock_rate, NULL);
Wim Taymans's avatar
Wim Taymans committed
259
  res = gst_pad_set_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (depayload), srccaps);
260 261
  gst_caps_unref (srccaps);

262
  return res;
263 264 265 266 267 268 269

  /* ERRORS */
invalid_mode:
  {
    GST_ERROR_OBJECT (rtpamrdepay, "invalid encoding-name");
    return FALSE;
  }
270 271
}

272
/* -1 is invalid */
273
static const gint nb_frame_size[16] = {
274 275 276
  12, 13, 15, 17, 19, 20, 26, 31,
  5, -1, -1, -1, -1, -1, -1, 0
};
Wim Taymans's avatar
Wim Taymans committed
277

278
static const gint wb_frame_size[16] = {
279
  17, 23, 32, 36, 40, 46, 50, 58,
280
  60, 5, -1, -1, -1, -1, -1, 0
281
};
282

283
static GstBuffer *
284
gst_rtp_amr_depay_process (GstRTPBaseDepayload * depayload, GstRTPBuffer * rtp)
285
{
286
  GstRtpAMRDepay *rtpamrdepay;
287
  const gint *frame_size;
288
  GstBuffer *outbuf = NULL;
289
  gint payload_len;
Wim Taymans's avatar
Wim Taymans committed
290
  GstMapInfo map;
291

292
  rtpamrdepay = GST_RTP_AMR_DEPAY (depayload);
293

294 295 296 297 298 299
  /* setup frame size pointer */
  if (rtpamrdepay->mode == GST_RTP_AMR_DP_MODE_NB)
    frame_size = nb_frame_size;
  else
    frame_size = wb_frame_size;

Wim Taymans's avatar
Wim Taymans committed
300
  /* when we get here, 1 channel, 8000/16000 Hz, octet aligned, no CRC,
301
   * no robust sorting, no interleaving data is to be depayloaded */
302
  {
303 304 305 306
    guint8 *payload, *p, *dp;
    gint i, num_packets, num_nonempty_packets;
    gint amr_len;
    gint ILL, ILP;
307

308
    payload_len = gst_rtp_buffer_get_payload_len (rtp);
309 310

    /* need at least 2 bytes for the header */
311 312
    if (payload_len < 2)
      goto too_small;
313

314
    payload = gst_rtp_buffer_get_payload (rtp);
315

316
    /* depay CMR. The CMR is used by the sender to request
317 318
     * a new encoding mode.
     *
Wim Taymans's avatar
Wim Taymans committed
319
     *  0 1 2 3 4 5 6 7
320 321 322
     * +-+-+-+-+-+-+-+-+
     * | CMR   |R|R|R|R|
     * +-+-+-+-+-+-+-+-+
323
     */
324
    /* CMR = (payload[0] & 0xf0) >> 4; */
325

326
    /* strip CMR header now, pack FT and the data for the decoder */
327 328
    payload_len -= 1;
    payload += 1;
329

330 331
    GST_DEBUG_OBJECT (rtpamrdepay, "payload len %d", payload_len);

332
    if (rtpamrdepay->interleaving) {
333 334 335 336 337 338
      ILL = (payload[0] & 0xf0) >> 4;
      ILP = (payload[0] & 0x0f);

      payload_len -= 1;
      payload += 1;

339 340
      if (ILP > ILL)
        goto wrong_interleaving;
341 342
    }

Wim Taymans's avatar
Wim Taymans committed
343 344
    /*
     *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
     * +-+-+-+-+-+-+-+-+..
     * |F|  FT   |Q|P|P| more FT..
     * +-+-+-+-+-+-+-+-+..
     */
    /* count number of packets by counting the FTs. Also
     * count number of amr data bytes and number of non-empty
     * packets (this is also the number of CRCs if present). */
    amr_len = 0;
    num_nonempty_packets = 0;
    num_packets = 0;
    for (i = 0; i < payload_len; i++) {
      gint fr_size;
      guint8 FT;

      FT = (payload[i] & 0x78) >> 3;

      fr_size = frame_size[FT];
362
      GST_DEBUG_OBJECT (rtpamrdepay, "frame size %d", fr_size);
363 364
      if (fr_size == -1)
        goto wrong_framesize;
365 366 367 368 369 370 371 372 373 374 375

      if (fr_size > 0) {
        amr_len += fr_size;
        num_nonempty_packets++;
      }
      num_packets++;

      if ((payload[i] & 0x80) == 0)
        break;
    }

376
    if (rtpamrdepay->crc) {
377
      /* data len + CRC len + header bytes should be smaller than payload_len */
378 379
      if (num_packets + num_nonempty_packets + amr_len > payload_len)
        goto wrong_length_1;
380 381
    } else {
      /* data len + header bytes should be smaller than payload_len */
382 383
      if (num_packets + amr_len > payload_len)
        goto wrong_length_2;
384 385
    }

386 387
    outbuf = gst_buffer_new_and_alloc (payload_len);

388
    /* point to destination */
Wim Taymans's avatar
Wim Taymans committed
389
    gst_buffer_map (outbuf, &map, GST_MAP_WRITE);
Wim Taymans's avatar
Wim Taymans committed
390

391
    /* point to first data packet */
Wim Taymans's avatar
Wim Taymans committed
392
    p = map.data;
393
    dp = payload + num_packets;
394
    if (rtpamrdepay->crc) {
395 396 397 398 399 400 401
      /* skip CRC if present */
      dp += num_nonempty_packets;
    }

    for (i = 0; i < num_packets; i++) {
      gint fr_size;

402 403 404
      /* copy FT, clear F bit */
      *p++ = payload[i] & 0x7f;

405 406 407 408
      fr_size = frame_size[(payload[i] & 0x78) >> 3];
      if (fr_size > 0) {
        /* copy data packet, FIXME, calc CRC here. */
        memcpy (p, dp, fr_size);
409

410 411 412 413
        p += fr_size;
        dp += fr_size;
      }
    }
Wim Taymans's avatar
Wim Taymans committed
414
    gst_buffer_unmap (outbuf, &map);
Wim Taymans's avatar
Wim Taymans committed
415

416 417 418
    /* we can set the duration because each packet is 20 milliseconds */
    GST_BUFFER_DURATION (outbuf) = num_packets * 20 * GST_MSECOND;

419
    if (gst_rtp_buffer_get_marker (rtp)) {
420
      /* marker bit marks a buffer after a talkspurt. */
421
      GST_DEBUG_OBJECT (depayload, "marker bit was set");
422
      GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_RESYNC);
423 424
    }

425
    GST_DEBUG_OBJECT (depayload, "pushing buffer of size %" G_GSIZE_FORMAT,
Wim Taymans's avatar
Wim Taymans committed
426
        gst_buffer_get_size (outbuf));
427
  }
428

429
  return outbuf;
430

431
  /* ERRORS */
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
too_small:
  {
    GST_ELEMENT_WARNING (rtpamrdepay, STREAM, DECODE,
        (NULL), ("AMR RTP payload too small (%d)", payload_len));
    goto bad_packet;
  }
wrong_interleaving:
  {
    GST_ELEMENT_WARNING (rtpamrdepay, STREAM, DECODE,
        (NULL), ("AMR RTP wrong interleaving"));
    goto bad_packet;
  }
wrong_framesize:
  {
    GST_ELEMENT_WARNING (rtpamrdepay, STREAM, DECODE,
        (NULL), ("AMR RTP frame size == -1"));
    goto bad_packet;
  }
wrong_length_1:
  {
    GST_ELEMENT_WARNING (rtpamrdepay, STREAM, DECODE,
        (NULL), ("AMR RTP wrong length 1"));
    goto bad_packet;
  }
wrong_length_2:
  {
    GST_ELEMENT_WARNING (rtpamrdepay, STREAM, DECODE,
        (NULL), ("AMR RTP wrong length 2"));
    goto bad_packet;
  }
462 463
bad_packet:
  {
464
    /* no fatal error */
465
    return NULL;
466 467 468 469
  }
}

gboolean
470
gst_rtp_amr_depay_plugin_init (GstPlugin * plugin)
471
{
472
  return gst_element_register (plugin, "rtpamrdepay",
473
      GST_RANK_SECONDARY, GST_TYPE_RTP_AMR_DEPAY);
474
}