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
#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <gst/rtp/gstrtpbuffer.h>
47
#include <gst/audio/audio.h>
48

49
#include <stdlib.h>
50
#include <string.h>
51
#include "gstrtpamrdepay.h"
52
#include "gstrtputils.h"
53

54 55 56
GST_DEBUG_CATEGORY_STATIC (rtpamrdepay_debug);
#define GST_CAT_DEFAULT (rtpamrdepay_debug)

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

enum
{
66
  PROP_0
67 68
};

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

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

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

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

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

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

143 144 145 146
  gst_element_class_add_static_pad_template (gstelement_class,
      &gst_rtp_amr_depay_src_template);
  gst_element_class_add_static_pad_template (gstelement_class,
      &gst_rtp_amr_depay_sink_template);
147

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

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

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

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

Wim Taymans's avatar
Wim Taymans committed
165
  depayload = GST_RTP_BASE_DEPAYLOAD (rtpamrdepay);
166

Wim Taymans's avatar
Wim Taymans committed
167
  gst_pad_use_fixed_caps (GST_RTP_BASE_DEPAYLOAD_SRCPAD (depayload));
168 169
}

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

181
  rtpamrdepay = GST_RTP_AMR_DEPAY (depayload);
182 183 184

  structure = gst_caps_get_structure (caps, 0);

185 186 187 188
  /* 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;
189
      need_clock_rate = 8000;
190 191 192
      type = "audio/AMR";
    } else if (strcmp (str, "AMR-WB") == 0) {
      rtpamrdepay->mode = GST_RTP_AMR_DP_MODE_WB;
193
      need_clock_rate = 16000;
194 195 196 197 198 199
      type = "audio/AMR-WB";
    } else
      goto invalid_mode;
  } else
    goto invalid_mode;

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

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

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

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

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

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

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

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

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

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

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

264
  return res;
265 266 267 268 269 270 271

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

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

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

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

294
  rtpamrdepay = GST_RTP_AMR_DEPAY (depayload);
295

296 297 298 299 300 301
  /* 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
302
  /* when we get here, 1 channel, 8000/16000 Hz, octet aligned, no CRC,
303
   * no robust sorting, no interleaving data is to be depayloaded */
304
  {
305 306 307 308
    guint8 *payload, *p, *dp;
    gint i, num_packets, num_nonempty_packets;
    gint amr_len;
    gint ILL, ILP;
309

310
    payload_len = gst_rtp_buffer_get_payload_len (rtp);
311 312

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

316
    payload = gst_rtp_buffer_get_payload (rtp);
317

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

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

332 333
    GST_DEBUG_OBJECT (rtpamrdepay, "payload len %d", payload_len);

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

      payload_len -= 1;
      payload += 1;

341 342
      if (ILP > ILL)
        goto wrong_interleaving;
343 344
    }

Wim Taymans's avatar
Wim Taymans committed
345 346
    /*
     *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
     * +-+-+-+-+-+-+-+-+..
     * |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];
364
      GST_DEBUG_OBJECT (rtpamrdepay, "frame size %d", fr_size);
365 366
      if (fr_size == -1)
        goto wrong_framesize;
367 368 369 370 371 372 373 374 375 376 377

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

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

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

388 389
    outbuf = gst_buffer_new_and_alloc (payload_len);

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

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

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

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

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

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

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

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

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

430
    gst_rtp_copy_audio_meta (rtpamrdepay, outbuf, rtp->buffer);
431
  }
432

433
  return outbuf;
434

435
  /* ERRORS */
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
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;
  }
466 467
bad_packet:
  {
468
    /* no fatal error */
469
    return NULL;
470 471 472 473
  }
}

gboolean
474
gst_rtp_amr_depay_plugin_init (GstPlugin * plugin)
475
{
476
  return gst_element_register (plugin, "rtpamrdepay",
477
      GST_RANK_SECONDARY, GST_TYPE_RTP_AMR_DEPAY);
478
}