1 /* GStreamer
2 * Copyright (C) <2005> Wim Taymans <wim.taymans@gmail.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 /**
21 * SECTION:element-rtpamrpay
22 * @title: rtpamrpay
23 * @see_also: rtpamrdepay
24 *
25 * Payload AMR audio into RTP packets according to RFC 3267.
26 * For detailed information see: http://www.rfc-editor.org/rfc/rfc3267.txt
27 *
28 * ## Example pipeline
29 * |[
30 * gst-launch-1.0 -v audiotestsrc ! amrnbenc ! rtpamrpay ! udpsink
31 * ]| This example pipeline will encode and payload an AMR stream. Refer to
32 * the rtpamrdepay example to depayload and decode the RTP stream.
33 *
34 */
35
36 /* references:
37 *
38 * RFC 3267 - Real-Time Transport Protocol (RTP) Payload Format and File
39 * Storage Format for the Adaptive Multi-Rate (AMR) and Adaptive
40 * Multi-Rate Wideband (AMR-WB) Audio Codecs.
41 *
42 * ETSI TS 126 201 V6.0.0 (2004-12) - Digital cellular telecommunications system (Phase 2+);
43 * Universal Mobile Telecommunications System (UMTS);
44 * AMR speech codec, wideband;
45 * Frame structure
46 * (3GPP TS 26.201 version 6.0.0 Release 6)
47 */
48
49 #ifdef HAVE_CONFIG_H
50 # include "config.h"
51 #endif
52
53 #include <string.h>
54
55 #include <gst/rtp/gstrtpbuffer.h>
56 #include <gst/audio/audio.h>
57
58 #include "gstrtpelements.h"
59 #include "gstrtpamrpay.h"
60 #include "gstrtputils.h"
61
62 GST_DEBUG_CATEGORY_STATIC (rtpamrpay_debug);
63 #define GST_CAT_DEFAULT (rtpamrpay_debug)
64
65 static GstStaticPadTemplate gst_rtp_amr_pay_sink_template =
66 GST_STATIC_PAD_TEMPLATE ("sink",
67 GST_PAD_SINK,
68 GST_PAD_ALWAYS,
69 GST_STATIC_CAPS ("audio/AMR, channels=(int)1, rate=(int)8000; "
70 "audio/AMR-WB, channels=(int)1, rate=(int)16000")
71 );
72
73 static GstStaticPadTemplate gst_rtp_amr_pay_src_template =
74 GST_STATIC_PAD_TEMPLATE ("src",
75 GST_PAD_SRC,
76 GST_PAD_ALWAYS,
77 GST_STATIC_CAPS ("application/x-rtp, "
78 "media = (string) \"audio\", "
79 "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
80 "clock-rate = (int) 8000, "
81 "encoding-name = (string) \"AMR\", "
82 "encoding-params = (string) \"1\", "
83 "octet-align = (string) \"1\", "
84 "crc = (string) \"0\", "
85 "robust-sorting = (string) \"0\", "
86 "interleaving = (string) \"0\", "
87 "mode-set = (int) [ 0, 7 ], "
88 "mode-change-period = (int) [ 1, MAX ], "
89 "mode-change-neighbor = (string) { \"0\", \"1\" }, "
90 "maxptime = (int) [ 20, MAX ], " "ptime = (int) [ 20, MAX ];"
91 "application/x-rtp, "
92 "media = (string) \"audio\", "
93 "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
94 "clock-rate = (int) 16000, "
95 "encoding-name = (string) \"AMR-WB\", "
96 "encoding-params = (string) \"1\", "
97 "octet-align = (string) \"1\", "
98 "crc = (string) \"0\", "
99 "robust-sorting = (string) \"0\", "
100 "interleaving = (string) \"0\", "
101 "mode-set = (int) [ 0, 7 ], "
102 "mode-change-period = (int) [ 1, MAX ], "
103 "mode-change-neighbor = (string) { \"0\", \"1\" }, "
104 "maxptime = (int) [ 20, MAX ], " "ptime = (int) [ 20, MAX ]")
105 );
106
107 static gboolean gst_rtp_amr_pay_setcaps (GstRTPBasePayload * basepayload,
108 GstCaps * caps);
109 static GstFlowReturn gst_rtp_amr_pay_handle_buffer (GstRTPBasePayload * pad,
110 GstBuffer * buffer);
111
112 static GstStateChangeReturn
113 gst_rtp_amr_pay_change_state (GstElement * element, GstStateChange transition);
114
115 #define gst_rtp_amr_pay_parent_class parent_class
116 G_DEFINE_TYPE (GstRtpAMRPay, gst_rtp_amr_pay, GST_TYPE_RTP_BASE_PAYLOAD);
117 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (rtpamrpay, "rtpamrpay",
118 GST_RANK_SECONDARY, GST_TYPE_RTP_AMR_PAY, rtp_element_init (plugin));
119
120 static void
gst_rtp_amr_pay_class_init(GstRtpAMRPayClass * klass)121 gst_rtp_amr_pay_class_init (GstRtpAMRPayClass * klass)
122 {
123 GstElementClass *gstelement_class;
124 GstRTPBasePayloadClass *gstrtpbasepayload_class;
125
126 gstelement_class = (GstElementClass *) klass;
127 gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
128
129 gstelement_class->change_state = gst_rtp_amr_pay_change_state;
130
131 gst_element_class_add_static_pad_template (gstelement_class,
132 &gst_rtp_amr_pay_src_template);
133 gst_element_class_add_static_pad_template (gstelement_class,
134 &gst_rtp_amr_pay_sink_template);
135
136 gst_element_class_set_static_metadata (gstelement_class, "RTP AMR payloader",
137 "Codec/Payloader/Network/RTP",
138 "Payload-encode AMR or AMR-WB audio into RTP packets (RFC 3267)",
139 "Wim Taymans <wim.taymans@gmail.com>");
140
141 gstrtpbasepayload_class->set_caps = gst_rtp_amr_pay_setcaps;
142 gstrtpbasepayload_class->handle_buffer = gst_rtp_amr_pay_handle_buffer;
143
144 GST_DEBUG_CATEGORY_INIT (rtpamrpay_debug, "rtpamrpay", 0,
145 "AMR/AMR-WB RTP Payloader");
146 }
147
148 static void
gst_rtp_amr_pay_init(GstRtpAMRPay * rtpamrpay)149 gst_rtp_amr_pay_init (GstRtpAMRPay * rtpamrpay)
150 {
151 }
152
153 static void
gst_rtp_amr_pay_reset(GstRtpAMRPay * pay)154 gst_rtp_amr_pay_reset (GstRtpAMRPay * pay)
155 {
156 pay->next_rtp_time = 0;
157 pay->first_ts = GST_CLOCK_TIME_NONE;
158 pay->first_rtp_time = 0;
159 }
160
161 static gboolean
gst_rtp_amr_pay_setcaps(GstRTPBasePayload * basepayload,GstCaps * caps)162 gst_rtp_amr_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
163 {
164 GstRtpAMRPay *rtpamrpay;
165 gboolean res;
166 const GstStructure *s;
167 const gchar *str;
168
169 rtpamrpay = GST_RTP_AMR_PAY (basepayload);
170
171 /* figure out the mode Narrow or Wideband */
172 s = gst_caps_get_structure (caps, 0);
173 if ((str = gst_structure_get_name (s))) {
174 if (strcmp (str, "audio/AMR") == 0)
175 rtpamrpay->mode = GST_RTP_AMR_P_MODE_NB;
176 else if (strcmp (str, "audio/AMR-WB") == 0)
177 rtpamrpay->mode = GST_RTP_AMR_P_MODE_WB;
178 else
179 goto wrong_type;
180 } else
181 goto wrong_type;
182
183 if (rtpamrpay->mode == GST_RTP_AMR_P_MODE_NB)
184 gst_rtp_base_payload_set_options (basepayload, "audio", TRUE, "AMR", 8000);
185 else
186 gst_rtp_base_payload_set_options (basepayload, "audio", TRUE, "AMR-WB",
187 16000);
188
189 res = gst_rtp_base_payload_set_outcaps (basepayload,
190 "encoding-params", G_TYPE_STRING, "1", "octet-align", G_TYPE_STRING, "1",
191 /* don't set the defaults
192 *
193 * "crc", G_TYPE_STRING, "0",
194 * "robust-sorting", G_TYPE_STRING, "0",
195 * "interleaving", G_TYPE_STRING, "0",
196 */
197 NULL);
198
199 return res;
200
201 /* ERRORS */
202 wrong_type:
203 {
204 GST_ERROR_OBJECT (rtpamrpay, "unsupported media type '%s'",
205 GST_STR_NULL (str));
206 return FALSE;
207 }
208 }
209
210 static void
gst_rtp_amr_pay_recalc_rtp_time(GstRtpAMRPay * rtpamrpay,GstClockTime timestamp)211 gst_rtp_amr_pay_recalc_rtp_time (GstRtpAMRPay * rtpamrpay,
212 GstClockTime timestamp)
213 {
214 /* re-sync rtp time */
215 if (GST_CLOCK_TIME_IS_VALID (rtpamrpay->first_ts) &&
216 GST_CLOCK_TIME_IS_VALID (timestamp) && timestamp >= rtpamrpay->first_ts) {
217 GstClockTime diff;
218 guint32 rtpdiff;
219
220 /* interpolate to reproduce gap from start, rather than intermediate
221 * intervals to avoid roundup accumulation errors */
222 diff = timestamp - rtpamrpay->first_ts;
223 rtpdiff = ((diff / GST_MSECOND) * 8) <<
224 (rtpamrpay->mode == GST_RTP_AMR_P_MODE_WB);
225 rtpamrpay->next_rtp_time = rtpamrpay->first_rtp_time + rtpdiff;
226 GST_DEBUG_OBJECT (rtpamrpay,
227 "elapsed time %" GST_TIME_FORMAT ", rtp %" G_GUINT32_FORMAT ", "
228 "new offset %" G_GUINT32_FORMAT, GST_TIME_ARGS (diff), rtpdiff,
229 rtpamrpay->next_rtp_time);
230 }
231 }
232
233 /* -1 is invalid */
234 static const gint nb_frame_size[16] = {
235 12, 13, 15, 17, 19, 20, 26, 31,
236 5, -1, -1, -1, -1, -1, -1, 0
237 };
238
239 static const gint wb_frame_size[16] = {
240 17, 23, 32, 36, 40, 46, 50, 58,
241 60, 5, -1, -1, -1, -1, -1, 0
242 };
243
244 static GstFlowReturn
gst_rtp_amr_pay_handle_buffer(GstRTPBasePayload * basepayload,GstBuffer * buffer)245 gst_rtp_amr_pay_handle_buffer (GstRTPBasePayload * basepayload,
246 GstBuffer * buffer)
247 {
248 GstRtpAMRPay *rtpamrpay;
249 const gint *frame_size;
250 GstFlowReturn ret;
251 guint payload_len;
252 GstMapInfo map;
253 GstBuffer *outbuf;
254 guint8 *payload, *ptr, *payload_amr;
255 GstClockTime timestamp, duration;
256 guint packet_len, mtu;
257 gint i, num_packets, num_nonempty_packets;
258 gint amr_len;
259 gboolean sid = FALSE;
260 GstRTPBuffer rtp = { NULL };
261
262 rtpamrpay = GST_RTP_AMR_PAY (basepayload);
263 mtu = GST_RTP_BASE_PAYLOAD_MTU (rtpamrpay);
264
265 gst_buffer_map (buffer, &map, GST_MAP_READ);
266
267 timestamp = GST_BUFFER_PTS (buffer);
268 duration = GST_BUFFER_DURATION (buffer);
269
270 /* setup frame size pointer */
271 if (rtpamrpay->mode == GST_RTP_AMR_P_MODE_NB)
272 frame_size = nb_frame_size;
273 else
274 frame_size = wb_frame_size;
275
276 GST_DEBUG_OBJECT (basepayload, "got %" G_GSIZE_FORMAT " bytes", map.size);
277
278 /* FIXME, only
279 * octet aligned, no interleaving, single channel, no CRC,
280 * no robust-sorting. To fix this you need to implement the downstream
281 * negotiation function. */
282
283 /* first count number of packets and total amr frame size */
284 amr_len = num_packets = num_nonempty_packets = 0;
285 for (i = 0; i < map.size; i++) {
286 guint8 FT;
287 gint fr_size;
288
289 FT = (map.data[i] & 0x78) >> 3;
290
291 fr_size = frame_size[FT];
292 GST_DEBUG_OBJECT (basepayload, "frame type %d, frame size %d", FT, fr_size);
293 /* FIXME, we don't handle this yet.. */
294 if (fr_size <= 0)
295 goto wrong_size;
296
297 if (fr_size == 5)
298 sid = TRUE;
299
300 amr_len += fr_size;
301 num_nonempty_packets++;
302 num_packets++;
303 i += fr_size;
304 }
305 if (amr_len > map.size)
306 goto incomplete_frame;
307
308 /* we need one extra byte for the CMR, the ToC is in the input
309 * data */
310 payload_len = map.size + 1;
311
312 /* get packet len to check against MTU */
313 packet_len = gst_rtp_buffer_calc_packet_len (payload_len, 0, 0);
314 if (packet_len > mtu)
315 goto too_big;
316
317 /* now alloc output buffer */
318 outbuf =
319 gst_rtp_base_payload_allocate_output_buffer (basepayload, payload_len, 0,
320 0);
321
322 gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
323
324 /* copy timestamp */
325 GST_BUFFER_PTS (outbuf) = timestamp;
326
327 if (duration != GST_CLOCK_TIME_NONE)
328 GST_BUFFER_DURATION (outbuf) = duration;
329 else {
330 GST_BUFFER_DURATION (outbuf) = num_packets * 20 * GST_MSECOND;
331 }
332
333 if (GST_BUFFER_IS_DISCONT (buffer)) {
334 GST_DEBUG_OBJECT (basepayload, "discont, setting marker bit");
335 GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
336 gst_rtp_buffer_set_marker (&rtp, TRUE);
337 gst_rtp_amr_pay_recalc_rtp_time (rtpamrpay, timestamp);
338 }
339
340 if (G_UNLIKELY (sid)) {
341 gst_rtp_amr_pay_recalc_rtp_time (rtpamrpay, timestamp);
342 }
343
344 /* perfect rtptime */
345 if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (rtpamrpay->first_ts))) {
346 rtpamrpay->first_ts = timestamp;
347 rtpamrpay->first_rtp_time = rtpamrpay->next_rtp_time;
348 }
349 GST_BUFFER_OFFSET (outbuf) = rtpamrpay->next_rtp_time;
350 rtpamrpay->next_rtp_time +=
351 (num_packets * 160) << (rtpamrpay->mode == GST_RTP_AMR_P_MODE_WB);
352
353 /* get payload, this is now writable */
354 payload = gst_rtp_buffer_get_payload (&rtp);
355
356 /* 0 1 2 3 4 5 6 7
357 * +-+-+-+-+-+-+-+-+
358 * | CMR |R|R|R|R|
359 * +-+-+-+-+-+-+-+-+
360 */
361 payload[0] = 0xF0; /* CMR, no specific mode requested */
362
363 /* this is where we copy the AMR data, after num_packets FTs and the
364 * CMR. */
365 payload_amr = payload + num_packets + 1;
366
367 /* copy data in payload, first we copy all the FTs then all
368 * the AMR data. The last FT has to have the F flag cleared. */
369 ptr = map.data;
370 for (i = 1; i <= num_packets; i++) {
371 guint8 FT;
372 gint fr_size;
373
374 /* 0 1 2 3 4 5 6 7
375 * +-+-+-+-+-+-+-+-+
376 * |F| FT |Q|P|P| more FT...
377 * +-+-+-+-+-+-+-+-+
378 */
379 FT = (*ptr & 0x78) >> 3;
380
381 fr_size = frame_size[FT];
382
383 if (i == num_packets)
384 /* last packet, clear F flag */
385 payload[i] = *ptr & 0x7f;
386 else
387 /* set F flag */
388 payload[i] = *ptr | 0x80;
389
390 memcpy (payload_amr, &ptr[1], fr_size);
391
392 /* all sizes are > 0 since we checked for that above */
393 ptr += fr_size + 1;
394 payload_amr += fr_size;
395 }
396
397 gst_buffer_unmap (buffer, &map);
398 gst_rtp_buffer_unmap (&rtp);
399
400 gst_rtp_copy_audio_meta (rtpamrpay, outbuf, buffer);
401
402 gst_buffer_unref (buffer);
403
404 ret = gst_rtp_base_payload_push (basepayload, outbuf);
405
406 return ret;
407
408 /* ERRORS */
409 wrong_size:
410 {
411 GST_ELEMENT_ERROR (basepayload, STREAM, FORMAT,
412 (NULL), ("received AMR frame with size <= 0"));
413 gst_buffer_unmap (buffer, &map);
414 gst_buffer_unref (buffer);
415
416 return GST_FLOW_ERROR;
417 }
418 incomplete_frame:
419 {
420 GST_ELEMENT_ERROR (basepayload, STREAM, FORMAT,
421 (NULL), ("received incomplete AMR frames"));
422 gst_buffer_unmap (buffer, &map);
423 gst_buffer_unref (buffer);
424
425 return GST_FLOW_ERROR;
426 }
427 too_big:
428 {
429 GST_ELEMENT_ERROR (basepayload, STREAM, FORMAT,
430 (NULL), ("received too many AMR frames for MTU"));
431 gst_buffer_unmap (buffer, &map);
432 gst_buffer_unref (buffer);
433
434 return GST_FLOW_ERROR;
435 }
436 }
437
438 static GstStateChangeReturn
gst_rtp_amr_pay_change_state(GstElement * element,GstStateChange transition)439 gst_rtp_amr_pay_change_state (GstElement * element, GstStateChange transition)
440 {
441 GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
442
443 /* handle upwards state changes here */
444 switch (transition) {
445 default:
446 break;
447 }
448
449 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
450
451 /* handle downwards state changes */
452 switch (transition) {
453 case GST_STATE_CHANGE_PAUSED_TO_READY:
454 gst_rtp_amr_pay_reset (GST_RTP_AMR_PAY (element));
455 break;
456 default:
457 break;
458 }
459
460 return ret;
461 }
462