| /* GStreamer |
| * Copyright (C) <2008> Wim Taymans <wim.taymans@gmail.com> |
| * |
| * 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., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #include <string.h> |
| |
| #include <gst/rtp/gstrtpbuffer.h> |
| #include <gst/audio/audio.h> |
| |
| #include "gstrtpmp4apay.h" |
| #include "gstrtputils.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (rtpmp4apay_debug); |
| #define GST_CAT_DEFAULT (rtpmp4apay_debug) |
| |
| /* FIXME: add framed=(boolean)true once our encoders have this field set |
| * on their output caps */ |
| static GstStaticPadTemplate gst_rtp_mp4a_pay_sink_template = |
| GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("audio/mpeg, mpegversion=(int)4, " |
| "stream-format=(string)raw") |
| ); |
| |
| static GstStaticPadTemplate gst_rtp_mp4a_pay_src_template = |
| GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("application/x-rtp, " |
| "media = (string) \"audio\", " |
| "payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", " |
| "clock-rate = (int) [1, MAX ], " |
| "encoding-name = (string) \"MP4A-LATM\"" |
| /* All optional parameters |
| * |
| * "cpresent = (string) \"0\"" |
| * "config=" |
| */ |
| ) |
| ); |
| |
| static void gst_rtp_mp4a_pay_finalize (GObject * object); |
| |
| static gboolean gst_rtp_mp4a_pay_setcaps (GstRTPBasePayload * payload, |
| GstCaps * caps); |
| static GstFlowReturn gst_rtp_mp4a_pay_handle_buffer (GstRTPBasePayload * |
| payload, GstBuffer * buffer); |
| |
| #define gst_rtp_mp4a_pay_parent_class parent_class |
| G_DEFINE_TYPE (GstRtpMP4APay, gst_rtp_mp4a_pay, GST_TYPE_RTP_BASE_PAYLOAD) |
| |
| static void gst_rtp_mp4a_pay_class_init (GstRtpMP4APayClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstElementClass *gstelement_class; |
| GstRTPBasePayloadClass *gstrtpbasepayload_class; |
| |
| gobject_class = (GObjectClass *) klass; |
| gstelement_class = (GstElementClass *) klass; |
| gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass; |
| |
| gobject_class->finalize = gst_rtp_mp4a_pay_finalize; |
| |
| gstrtpbasepayload_class->set_caps = gst_rtp_mp4a_pay_setcaps; |
| gstrtpbasepayload_class->handle_buffer = gst_rtp_mp4a_pay_handle_buffer; |
| |
| gst_element_class_add_pad_template (gstelement_class, |
| gst_static_pad_template_get (&gst_rtp_mp4a_pay_src_template)); |
| gst_element_class_add_pad_template (gstelement_class, |
| gst_static_pad_template_get (&gst_rtp_mp4a_pay_sink_template)); |
| |
| gst_element_class_set_static_metadata (gstelement_class, |
| "RTP MPEG4 audio payloader", "Codec/Payloader/Network/RTP", |
| "Payload MPEG4 audio as RTP packets (RFC 3016)", |
| "Wim Taymans <wim.taymans@gmail.com>"); |
| |
| GST_DEBUG_CATEGORY_INIT (rtpmp4apay_debug, "rtpmp4apay", 0, |
| "MP4A-LATM RTP Payloader"); |
| } |
| |
| static void |
| gst_rtp_mp4a_pay_init (GstRtpMP4APay * rtpmp4apay) |
| { |
| rtpmp4apay->rate = 90000; |
| rtpmp4apay->profile = g_strdup ("1"); |
| } |
| |
| static void |
| gst_rtp_mp4a_pay_finalize (GObject * object) |
| { |
| GstRtpMP4APay *rtpmp4apay; |
| |
| rtpmp4apay = GST_RTP_MP4A_PAY (object); |
| |
| g_free (rtpmp4apay->params); |
| rtpmp4apay->params = NULL; |
| |
| if (rtpmp4apay->config) |
| gst_buffer_unref (rtpmp4apay->config); |
| rtpmp4apay->config = NULL; |
| |
| g_free (rtpmp4apay->profile); |
| rtpmp4apay->profile = NULL; |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static const unsigned int sampling_table[16] = { |
| 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, |
| 16000, 12000, 11025, 8000, 7350, 0, 0, 0 |
| }; |
| |
| static gboolean |
| gst_rtp_mp4a_pay_parse_audio_config (GstRtpMP4APay * rtpmp4apay, |
| GstBuffer * buffer) |
| { |
| GstMapInfo map; |
| guint8 *data; |
| gsize size; |
| guint8 objectType; |
| guint8 samplingIdx; |
| guint8 channelCfg; |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| data = map.data; |
| size = map.size; |
| |
| if (size < 2) |
| goto too_short; |
| |
| /* any object type is fine, we need to copy it to the profile-level-id field. */ |
| objectType = (data[0] & 0xf8) >> 3; |
| if (objectType == 0) |
| goto invalid_object; |
| |
| samplingIdx = ((data[0] & 0x07) << 1) | ((data[1] & 0x80) >> 7); |
| /* only fixed values for now */ |
| if (samplingIdx > 12 && samplingIdx != 15) |
| goto wrong_freq; |
| |
| channelCfg = ((data[1] & 0x78) >> 3); |
| if (channelCfg > 7) |
| goto wrong_channels; |
| |
| /* rtp rate depends on sampling rate of the audio */ |
| if (samplingIdx == 15) { |
| if (size < 5) |
| goto too_short; |
| |
| /* index of 15 means we get the rate in the next 24 bits */ |
| rtpmp4apay->rate = ((data[1] & 0x7f) << 17) | |
| ((data[2]) << 9) | ((data[3]) << 1) | ((data[4] & 0x80) >> 7); |
| } else { |
| /* else use the rate from the table */ |
| rtpmp4apay->rate = sampling_table[samplingIdx]; |
| } |
| /* extra rtp params contain the number of channels */ |
| g_free (rtpmp4apay->params); |
| rtpmp4apay->params = g_strdup_printf ("%d", channelCfg); |
| /* audio stream type */ |
| rtpmp4apay->streamtype = "5"; |
| /* profile */ |
| g_free (rtpmp4apay->profile); |
| rtpmp4apay->profile = g_strdup_printf ("%d", objectType); |
| |
| GST_DEBUG_OBJECT (rtpmp4apay, |
| "objectType: %d, samplingIdx: %d (%d), channelCfg: %d", objectType, |
| samplingIdx, rtpmp4apay->rate, channelCfg); |
| |
| gst_buffer_unmap (buffer, &map); |
| |
| return TRUE; |
| |
| /* ERROR */ |
| too_short: |
| { |
| GST_ELEMENT_ERROR (rtpmp4apay, STREAM, FORMAT, |
| (NULL), |
| ("config string too short, expected 2 bytes, got %" G_GSIZE_FORMAT, |
| size)); |
| gst_buffer_unmap (buffer, &map); |
| return FALSE; |
| } |
| invalid_object: |
| { |
| GST_ELEMENT_ERROR (rtpmp4apay, STREAM, FORMAT, |
| (NULL), ("invalid object type 0")); |
| gst_buffer_unmap (buffer, &map); |
| return FALSE; |
| } |
| wrong_freq: |
| { |
| GST_ELEMENT_ERROR (rtpmp4apay, STREAM, NOT_IMPLEMENTED, |
| (NULL), ("unsupported frequency index %d", samplingIdx)); |
| gst_buffer_unmap (buffer, &map); |
| return FALSE; |
| } |
| wrong_channels: |
| { |
| GST_ELEMENT_ERROR (rtpmp4apay, STREAM, NOT_IMPLEMENTED, |
| (NULL), ("unsupported number of channels %d, must < 8", channelCfg)); |
| gst_buffer_unmap (buffer, &map); |
| return FALSE; |
| } |
| } |
| |
| static gboolean |
| gst_rtp_mp4a_pay_new_caps (GstRtpMP4APay * rtpmp4apay) |
| { |
| gchar *config; |
| GValue v = { 0 }; |
| gboolean res; |
| |
| g_value_init (&v, GST_TYPE_BUFFER); |
| gst_value_set_buffer (&v, rtpmp4apay->config); |
| config = gst_value_serialize (&v); |
| |
| res = gst_rtp_base_payload_set_outcaps (GST_RTP_BASE_PAYLOAD (rtpmp4apay), |
| "cpresent", G_TYPE_STRING, "0", "config", G_TYPE_STRING, config, NULL); |
| |
| g_value_unset (&v); |
| g_free (config); |
| |
| return res; |
| } |
| |
| static gboolean |
| gst_rtp_mp4a_pay_setcaps (GstRTPBasePayload * payload, GstCaps * caps) |
| { |
| GstRtpMP4APay *rtpmp4apay; |
| GstStructure *structure; |
| const GValue *codec_data; |
| gboolean res, framed = TRUE; |
| const gchar *stream_format; |
| |
| rtpmp4apay = GST_RTP_MP4A_PAY (payload); |
| |
| structure = gst_caps_get_structure (caps, 0); |
| |
| /* this is already handled by the template caps, but it is better |
| * to leave here to have meaningful warning messages when linking |
| * fails */ |
| stream_format = gst_structure_get_string (structure, "stream-format"); |
| if (stream_format) { |
| if (strcmp (stream_format, "raw") != 0) { |
| GST_WARNING_OBJECT (rtpmp4apay, "AAC's stream-format must be 'raw', " |
| "%s is not supported", stream_format); |
| return FALSE; |
| } |
| } else { |
| GST_WARNING_OBJECT (rtpmp4apay, "AAC's stream-format not specified, " |
| "assuming 'raw'"); |
| } |
| |
| codec_data = gst_structure_get_value (structure, "codec_data"); |
| if (codec_data) { |
| GST_LOG_OBJECT (rtpmp4apay, "got codec_data"); |
| if (G_VALUE_TYPE (codec_data) == GST_TYPE_BUFFER) { |
| GstBuffer *buffer, *cbuffer; |
| GstMapInfo map; |
| GstMapInfo cmap; |
| guint i; |
| |
| buffer = gst_value_get_buffer (codec_data); |
| GST_LOG_OBJECT (rtpmp4apay, "configuring codec_data"); |
| |
| /* parse buffer */ |
| res = gst_rtp_mp4a_pay_parse_audio_config (rtpmp4apay, buffer); |
| |
| if (!res) |
| goto config_failed; |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| |
| /* make the StreamMuxConfig, we need 15 bits for the header */ |
| cbuffer = gst_buffer_new_and_alloc (map.size + 2); |
| gst_buffer_map (cbuffer, &cmap, GST_MAP_WRITE); |
| |
| memset (cmap.data, 0, map.size + 2); |
| |
| /* Create StreamMuxConfig according to ISO/IEC 14496-3: |
| * |
| * audioMuxVersion == 0 (1 bit) |
| * allStreamsSameTimeFraming == 1 (1 bit) |
| * numSubFrames == numSubFrames (6 bits) |
| * numProgram == 0 (4 bits) |
| * numLayer == 0 (3 bits) |
| */ |
| cmap.data[0] = 0x40; |
| cmap.data[1] = 0x00; |
| |
| /* append the config bits, shifting them 1 bit left */ |
| for (i = 0; i < map.size; i++) { |
| cmap.data[i + 1] |= ((map.data[i] & 0x80) >> 7); |
| cmap.data[i + 2] |= ((map.data[i] & 0x7f) << 1); |
| } |
| |
| gst_buffer_unmap (cbuffer, &cmap); |
| gst_buffer_unmap (buffer, &map); |
| |
| /* now we can configure the buffer */ |
| if (rtpmp4apay->config) |
| gst_buffer_unref (rtpmp4apay->config); |
| rtpmp4apay->config = cbuffer; |
| } |
| } |
| |
| if (gst_structure_get_boolean (structure, "framed", &framed) && !framed) { |
| GST_WARNING_OBJECT (payload, "Need framed AAC data as input!"); |
| } |
| |
| gst_rtp_base_payload_set_options (payload, "audio", TRUE, "MP4A-LATM", |
| rtpmp4apay->rate); |
| |
| res = gst_rtp_mp4a_pay_new_caps (rtpmp4apay); |
| |
| return res; |
| |
| /* ERRORS */ |
| config_failed: |
| { |
| GST_DEBUG_OBJECT (rtpmp4apay, "failed to parse config"); |
| return FALSE; |
| } |
| } |
| |
| #define RTP_HEADER_LEN 12 |
| |
| /* we expect buffers as exactly one complete AU |
| */ |
| static GstFlowReturn |
| gst_rtp_mp4a_pay_handle_buffer (GstRTPBasePayload * basepayload, |
| GstBuffer * buffer) |
| { |
| GstRtpMP4APay *rtpmp4apay; |
| GstFlowReturn ret; |
| GstBufferList *list; |
| guint mtu; |
| guint offset; |
| gsize size; |
| gboolean fragmented; |
| GstClockTime timestamp; |
| |
| ret = GST_FLOW_OK; |
| |
| rtpmp4apay = GST_RTP_MP4A_PAY (basepayload); |
| |
| offset = 0; |
| size = gst_buffer_get_size (buffer); |
| |
| timestamp = GST_BUFFER_PTS (buffer); |
| |
| fragmented = FALSE; |
| mtu = GST_RTP_BASE_PAYLOAD_MTU (rtpmp4apay); |
| |
| list = gst_buffer_list_new_sized (size / (mtu - RTP_HEADER_LEN) + 1); |
| |
| while (size > 0) { |
| guint towrite; |
| GstBuffer *outbuf; |
| guint payload_len; |
| guint packet_len; |
| guint header_len; |
| GstBuffer *paybuf; |
| GstRTPBuffer rtp = { NULL }; |
| |
| header_len = 0; |
| if (!fragmented) { |
| guint count; |
| /* first packet calculate space for the packet including the header */ |
| count = size; |
| while (count >= 0xff) { |
| header_len++; |
| count -= 0xff; |
| } |
| header_len++; |
| } |
| |
| packet_len = gst_rtp_buffer_calc_packet_len (header_len + size, 0, 0); |
| towrite = MIN (packet_len, mtu); |
| payload_len = gst_rtp_buffer_calc_payload_len (towrite, 0, 0); |
| payload_len -= header_len; |
| |
| GST_DEBUG_OBJECT (rtpmp4apay, |
| "avail %" G_GSIZE_FORMAT |
| ", header_len %d, packet_len %d, payload_len %d", size, header_len, |
| packet_len, payload_len); |
| |
| /* create buffer to hold the payload. */ |
| outbuf = gst_rtp_buffer_new_allocate (header_len, 0, 0); |
| |
| /* copy payload */ |
| gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp); |
| |
| if (!fragmented) { |
| guint8 *payload = gst_rtp_buffer_get_payload (&rtp); |
| guint count; |
| |
| /* first packet write the header */ |
| count = size; |
| while (count >= 0xff) { |
| *payload++ = 0xff; |
| count -= 0xff; |
| } |
| *payload++ = count; |
| } |
| |
| /* marker only if the packet is complete */ |
| gst_rtp_buffer_set_marker (&rtp, size == payload_len); |
| |
| gst_rtp_buffer_unmap (&rtp); |
| |
| /* create a new buf to hold the payload */ |
| paybuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL, |
| offset, payload_len); |
| |
| /* join memory parts */ |
| gst_rtp_copy_meta (GST_ELEMENT_CAST (rtpmp4apay), outbuf, paybuf, |
| g_quark_from_static_string (GST_META_TAG_AUDIO_STR)); |
| outbuf = gst_buffer_append (outbuf, paybuf); |
| gst_buffer_list_add (list, outbuf); |
| offset += payload_len; |
| size -= payload_len; |
| |
| /* copy incomming timestamp (if any) to outgoing buffers */ |
| GST_BUFFER_PTS (outbuf) = timestamp; |
| |
| fragmented = TRUE; |
| } |
| |
| ret = |
| gst_rtp_base_payload_push_list (GST_RTP_BASE_PAYLOAD (rtpmp4apay), list); |
| |
| gst_buffer_unref (buffer); |
| |
| return ret; |
| } |
| |
| gboolean |
| gst_rtp_mp4a_pay_plugin_init (GstPlugin * plugin) |
| { |
| return gst_element_register (plugin, "rtpmp4apay", |
| GST_RANK_SECONDARY, GST_TYPE_RTP_MP4A_PAY); |
| } |