/* GStreamer * Copyright (C) <2005> Philippe Khalaf * Copyright (C) <2006> Wim Taymans * * 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. */ /** * SECTION:gstrtpbuffer * @title: GstRTPBuffer * @short_description: Helper methods for dealing with RTP buffers * @see_also: #GstRTPBasePayload, #GstRTPBaseDepayload, gstrtcpbuffer * * The GstRTPBuffer helper functions makes it easy to parse and create regular * #GstBuffer objects that contain RTP payloads. These buffers are typically of * 'application/x-rtp' #GstCaps. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstrtpbuffer.h" #include #include #define GST_RTP_HEADER_LEN 12 /* Note: we use bitfields here to make sure the compiler doesn't add padding * between fields on certain architectures; can't assume aligned access either */ typedef struct _GstRTPHeader { #if G_BYTE_ORDER == G_LITTLE_ENDIAN unsigned int csrc_count:4; /* CSRC count */ unsigned int extension:1; /* header extension flag */ unsigned int padding:1; /* padding flag */ unsigned int version:2; /* protocol version */ unsigned int payload_type:7; /* payload type */ unsigned int marker:1; /* marker bit */ #elif G_BYTE_ORDER == G_BIG_ENDIAN unsigned int version:2; /* protocol version */ unsigned int padding:1; /* padding flag */ unsigned int extension:1; /* header extension flag */ unsigned int csrc_count:4; /* CSRC count */ unsigned int marker:1; /* marker bit */ unsigned int payload_type:7; /* payload type */ #else #error "G_BYTE_ORDER should be big or little endian." #endif unsigned int seq:16; /* sequence number */ unsigned int timestamp:32; /* timestamp */ unsigned int ssrc:32; /* synchronization source */ guint8 csrclist[4]; /* optional CSRC list, 32 bits each */ } GstRTPHeader; #define GST_RTP_HEADER_VERSION(data) (((GstRTPHeader *)(data))->version) #define GST_RTP_HEADER_PADDING(data) (((GstRTPHeader *)(data))->padding) #define GST_RTP_HEADER_EXTENSION(data) (((GstRTPHeader *)(data))->extension) #define GST_RTP_HEADER_CSRC_COUNT(data) (((GstRTPHeader *)(data))->csrc_count) #define GST_RTP_HEADER_MARKER(data) (((GstRTPHeader *)(data))->marker) #define GST_RTP_HEADER_PAYLOAD_TYPE(data) (((GstRTPHeader *)(data))->payload_type) #define GST_RTP_HEADER_SEQ(data) (((GstRTPHeader *)(data))->seq) #define GST_RTP_HEADER_TIMESTAMP(data) (((GstRTPHeader *)(data))->timestamp) #define GST_RTP_HEADER_SSRC(data) (((GstRTPHeader *)(data))->ssrc) #define GST_RTP_HEADER_CSRC_LIST_OFFSET(data,i) \ data + G_STRUCT_OFFSET(GstRTPHeader, csrclist) + \ ((i) * sizeof(guint32)) #define GST_RTP_HEADER_CSRC_SIZE(data) (GST_RTP_HEADER_CSRC_COUNT(data) * sizeof (guint32)) /** * gst_rtp_buffer_allocate_data: * @buffer: a #GstBuffer * @payload_len: the length of the payload * @pad_len: the amount of padding * @csrc_count: the number of CSRC entries * * Allocate enough data in @buffer to hold an RTP packet with @csrc_count CSRCs, * a payload length of @payload_len and padding of @pad_len. * @buffer must be writable and all previous memory in @buffer will be freed. * If @pad_len is >0, the padding bit will be set. All other RTP header fields * will be set to 0/FALSE. */ void gst_rtp_buffer_allocate_data (GstBuffer * buffer, guint payload_len, guint8 pad_len, guint8 csrc_count) { GstMapInfo map; GstMemory *mem; gsize hlen; g_return_if_fail (csrc_count <= 15); g_return_if_fail (GST_IS_BUFFER (buffer)); g_return_if_fail (gst_buffer_is_writable (buffer)); gst_buffer_remove_all_memory (buffer); hlen = GST_RTP_HEADER_LEN + csrc_count * sizeof (guint32); mem = gst_allocator_alloc (NULL, hlen, NULL); gst_memory_map (mem, &map, GST_MAP_WRITE); /* fill in defaults */ GST_RTP_HEADER_VERSION (map.data) = GST_RTP_VERSION; if (pad_len) GST_RTP_HEADER_PADDING (map.data) = TRUE; else GST_RTP_HEADER_PADDING (map.data) = FALSE; GST_RTP_HEADER_EXTENSION (map.data) = FALSE; GST_RTP_HEADER_CSRC_COUNT (map.data) = csrc_count; memset (GST_RTP_HEADER_CSRC_LIST_OFFSET (map.data, 0), 0, csrc_count * sizeof (guint32)); GST_RTP_HEADER_MARKER (map.data) = FALSE; GST_RTP_HEADER_PAYLOAD_TYPE (map.data) = 0; GST_RTP_HEADER_SEQ (map.data) = 0; GST_RTP_HEADER_TIMESTAMP (map.data) = 0; GST_RTP_HEADER_SSRC (map.data) = 0; gst_memory_unmap (mem, &map); gst_buffer_append_memory (buffer, mem); if (payload_len) { mem = gst_allocator_alloc (NULL, payload_len, NULL); gst_buffer_append_memory (buffer, mem); } if (pad_len) { mem = gst_allocator_alloc (NULL, pad_len, NULL); gst_memory_map (mem, &map, GST_MAP_WRITE); map.data[pad_len - 1] = pad_len; gst_memory_unmap (mem, &map); gst_buffer_append_memory (buffer, mem); } } /** * gst_rtp_buffer_new_take_data: * @data: (array length=len) (transfer full) (element-type guint8): * data for the new buffer * @len: the length of data * * Create a new buffer and set the data and size of the buffer to @data and @len * respectively. @data will be freed when the buffer is unreffed, so this * function transfers ownership of @data to the new buffer. * * Returns: A newly allocated buffer with @data and of size @len. */ GstBuffer * gst_rtp_buffer_new_take_data (gpointer data, gsize len) { g_return_val_if_fail (data != NULL, NULL); g_return_val_if_fail (len > 0, NULL); return gst_buffer_new_wrapped (data, len); } /** * gst_rtp_buffer_new_copy_data: * @data: (array length=len) (element-type guint8): data for the new * buffer * @len: the length of data * * Create a new buffer and set the data to a copy of @len * bytes of @data and the size to @len. The data will be freed when the buffer * is freed. * * Returns: A newly allocated buffer with a copy of @data and of size @len. */ GstBuffer * gst_rtp_buffer_new_copy_data (gconstpointer data, gsize len) { return gst_rtp_buffer_new_take_data (g_memdup (data, len), len); } /** * gst_rtp_buffer_new_allocate: * @payload_len: the length of the payload * @pad_len: the amount of padding * @csrc_count: the number of CSRC entries * * Allocate a new #GstBuffer with enough data to hold an RTP packet with * @csrc_count CSRCs, a payload length of @payload_len and padding of @pad_len. * All other RTP header fields will be set to 0/FALSE. * * Returns: A newly allocated buffer that can hold an RTP packet with given * parameters. */ GstBuffer * gst_rtp_buffer_new_allocate (guint payload_len, guint8 pad_len, guint8 csrc_count) { GstBuffer *result; g_return_val_if_fail (csrc_count <= 15, NULL); result = gst_buffer_new (); gst_rtp_buffer_allocate_data (result, payload_len, pad_len, csrc_count); return result; } /** * gst_rtp_buffer_new_allocate_len: * @packet_len: the total length of the packet * @pad_len: the amount of padding * @csrc_count: the number of CSRC entries * * Create a new #GstBuffer that can hold an RTP packet that is exactly * @packet_len long. The length of the payload depends on @pad_len and * @csrc_count and can be calculated with gst_rtp_buffer_calc_payload_len(). * All RTP header fields will be set to 0/FALSE. * * Returns: A newly allocated buffer that can hold an RTP packet of @packet_len. */ GstBuffer * gst_rtp_buffer_new_allocate_len (guint packet_len, guint8 pad_len, guint8 csrc_count) { guint len; g_return_val_if_fail (csrc_count <= 15, NULL); len = gst_rtp_buffer_calc_payload_len (packet_len, pad_len, csrc_count); return gst_rtp_buffer_new_allocate (len, pad_len, csrc_count); } /** * gst_rtp_buffer_calc_header_len: * @csrc_count: the number of CSRC entries * * Calculate the header length of an RTP packet with @csrc_count CSRC entries. * An RTP packet can have at most 15 CSRC entries. * * Returns: The length of an RTP header with @csrc_count CSRC entries. */ guint gst_rtp_buffer_calc_header_len (guint8 csrc_count) { g_return_val_if_fail (csrc_count <= 15, 0); return GST_RTP_HEADER_LEN + (csrc_count * sizeof (guint32)); } /** * gst_rtp_buffer_calc_packet_len: * @payload_len: the length of the payload * @pad_len: the amount of padding * @csrc_count: the number of CSRC entries * * Calculate the total length of an RTP packet with a payload size of @payload_len, * a padding of @pad_len and a @csrc_count CSRC entries. * * Returns: The total length of an RTP header with given parameters. */ guint gst_rtp_buffer_calc_packet_len (guint payload_len, guint8 pad_len, guint8 csrc_count) { g_return_val_if_fail (csrc_count <= 15, 0); return payload_len + GST_RTP_HEADER_LEN + (csrc_count * sizeof (guint32)) + pad_len; } /** * gst_rtp_buffer_calc_payload_len: * @packet_len: the length of the total RTP packet * @pad_len: the amount of padding * @csrc_count: the number of CSRC entries * * Calculate the length of the payload of an RTP packet with size @packet_len, * a padding of @pad_len and a @csrc_count CSRC entries. * * Returns: The length of the payload of an RTP packet with given parameters. */ guint gst_rtp_buffer_calc_payload_len (guint packet_len, guint8 pad_len, guint8 csrc_count) { g_return_val_if_fail (csrc_count <= 15, 0); if (packet_len < GST_RTP_HEADER_LEN + (csrc_count * sizeof (guint32)) + pad_len) return 0; return packet_len - GST_RTP_HEADER_LEN - (csrc_count * sizeof (guint32)) - pad_len; } /** * gst_rtp_buffer_map: * @buffer: a #GstBuffer * @flags: #GstMapFlags * @rtp: (out): a #GstRTPBuffer * * Map the contents of @buffer into @rtp. * * Returns: %TRUE if @buffer could be mapped. */ gboolean gst_rtp_buffer_map (GstBuffer * buffer, GstMapFlags flags, GstRTPBuffer * rtp) { guint8 padding; guint8 csrc_count; guint header_len; guint8 version, pt; guint8 *data; guint size; gsize bufsize, skip; guint idx, length; guint n_mem; g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE); g_return_val_if_fail (rtp != NULL, FALSE); g_return_val_if_fail (rtp->buffer == NULL, FALSE); n_mem = gst_buffer_n_memory (buffer); if (n_mem < 1) goto no_memory; /* map first memory, this should be the header */ if (!gst_buffer_map_range (buffer, 0, 1, &rtp->map[0], flags)) goto map_failed; data = rtp->data[0] = rtp->map[0].data; size = rtp->map[0].size; /* the header must be completely in the first buffer */ header_len = GST_RTP_HEADER_LEN; if (G_UNLIKELY (size < header_len)) goto wrong_length; /* check version */ version = (data[0] & 0xc0); if (G_UNLIKELY (version != (GST_RTP_VERSION << 6))) goto wrong_version; /* check reserved PT and marker bit, this is to check for RTCP * packets. We do a relaxed check, you can still use 72-76 as long * as the marker bit is cleared. */ pt = data[1]; if (G_UNLIKELY (pt >= 200 && pt <= 204)) goto reserved_pt; /* calc header length with csrc */ csrc_count = (data[0] & 0x0f); header_len += csrc_count * sizeof (guint32); rtp->size[0] = header_len; bufsize = gst_buffer_get_size (buffer); /* calc extension length when present. */ if (data[0] & 0x10) { guint8 *extdata; guint16 extlen; /* find memory for the extension bits, we find the block for the first 4 * bytes, all other extension bytes should also be in this block */ if (!gst_buffer_find_memory (buffer, header_len, 4, &idx, &length, &skip)) goto wrong_length; if (!gst_buffer_map_range (buffer, idx, length, &rtp->map[1], flags)) goto map_failed; extdata = rtp->data[1] = rtp->map[1].data + skip; /* skip id */ extdata += 2; /* read length as the number of 32 bits words */ extlen = GST_READ_UINT16_BE (extdata); extlen *= sizeof (guint32); /* add id and length */ extlen += 4; /* all extension bytes must be in this block */ if (G_UNLIKELY (rtp->map[1].size < extlen)) goto wrong_length; rtp->size[1] = extlen; header_len += rtp->size[1]; } else { rtp->data[1] = NULL; rtp->size[1] = 0; } /* check for padding unless flags says to skip */ if ((data[0] & 0x20) != 0 && (flags & GST_RTP_BUFFER_MAP_FLAG_SKIP_PADDING) == 0) { /* find memory for the padding bits */ if (!gst_buffer_find_memory (buffer, bufsize - 1, 1, &idx, &length, &skip)) goto wrong_length; if (!gst_buffer_map_range (buffer, idx, length, &rtp->map[3], flags)) goto map_failed; padding = rtp->map[3].data[skip]; rtp->data[3] = rtp->map[3].data + skip + 1 - padding; rtp->size[3] = padding; if (skip + 1 < padding) goto wrong_length; } else { rtp->data[3] = NULL; rtp->size[3] = 0; padding = 0; } /* check if padding and header not bigger than packet length */ if (G_UNLIKELY (bufsize < padding + header_len)) goto wrong_padding; rtp->buffer = buffer; if (n_mem == 1) { /* we have mapped the buffer already, so might just as well fill in the * payload pointer and size and avoid another buffer map/unmap later */ rtp->data[2] = rtp->map[0].data + header_len; rtp->size[2] = bufsize - header_len - padding; } else { /* we have not yet mapped the payload */ rtp->data[2] = NULL; rtp->size[2] = 0; } /* rtp->state = 0; *//* unused */ return TRUE; /* ERRORS */ no_memory: { GST_ERROR ("buffer without memory"); return FALSE; } map_failed: { GST_ERROR ("failed to map memory"); return FALSE; } wrong_length: { GST_DEBUG ("length check failed"); goto dump_packet; } wrong_version: { GST_DEBUG ("version check failed (%d != %d)", version, GST_RTP_VERSION); goto dump_packet; } reserved_pt: { GST_DEBUG ("reserved PT %d found", pt); goto dump_packet; } wrong_padding: { GST_DEBUG ("padding check failed (%" G_GSIZE_FORMAT " - %d < %d)", bufsize, header_len, padding); goto dump_packet; } dump_packet: { gint i; GST_MEMDUMP ("buffer", data, size); for (i = 0; i < G_N_ELEMENTS (rtp->map); ++i) { if (rtp->map[i].memory != NULL) gst_buffer_unmap (buffer, &rtp->map[i]); } return FALSE; } } /** * gst_rtp_buffer_unmap: * @rtp: a #GstRTPBuffer * * Unmap @rtp previously mapped with gst_rtp_buffer_map(). */ void gst_rtp_buffer_unmap (GstRTPBuffer * rtp) { gint i; g_return_if_fail (rtp != NULL); g_return_if_fail (rtp->buffer != NULL); for (i = 0; i < 4; i++) { if (rtp->map[i].memory != NULL) { gst_buffer_unmap (rtp->buffer, &rtp->map[i]); rtp->map[i].memory = NULL; } rtp->data[i] = NULL; rtp->size[i] = 0; } rtp->buffer = NULL; } /** * gst_rtp_buffer_set_packet_len: * @rtp: the RTP packet * @len: the new packet length * * Set the total @rtp size to @len. The data in the buffer will be made * larger if needed. Any padding will be removed from the packet. */ void gst_rtp_buffer_set_packet_len (GstRTPBuffer * rtp, guint len) { guint8 *data; data = rtp->data[0]; /* FIXME */ if (rtp->map[0].maxsize <= len) { /* FIXME, realloc bigger space */ g_warning ("not implemented"); } gst_buffer_set_size (rtp->buffer, len); rtp->map[0].size = len; /* remove any padding */ GST_RTP_HEADER_PADDING (data) = FALSE; } /** * gst_rtp_buffer_get_packet_len: * @rtp: the RTP packet * * Return the total length of the packet in @buffer. * * Returns: The total length of the packet in @buffer. */ guint gst_rtp_buffer_get_packet_len (GstRTPBuffer * rtp) { return gst_buffer_get_size (rtp->buffer); } /** * gst_rtp_buffer_get_header_len: * @rtp: the RTP packet * * Return the total length of the header in @buffer. This include the length of * the fixed header, the CSRC list and the extension header. * * Returns: The total length of the header in @buffer. */ guint gst_rtp_buffer_get_header_len (GstRTPBuffer * rtp) { return rtp->size[0] + rtp->size[1]; } /** * gst_rtp_buffer_get_version: * @rtp: the RTP packet * * Get the version number of the RTP packet in @buffer. * * Returns: The version of @buffer. */ guint8 gst_rtp_buffer_get_version (GstRTPBuffer * rtp) { return GST_RTP_HEADER_VERSION (rtp->data[0]); } /** * gst_rtp_buffer_set_version: * @rtp: the RTP packet * @version: the new version * * Set the version of the RTP packet in @buffer to @version. */ void gst_rtp_buffer_set_version (GstRTPBuffer * rtp, guint8 version) { g_return_if_fail (version < 0x04); GST_RTP_HEADER_VERSION (rtp->data[0]) = version; } /** * gst_rtp_buffer_get_padding: * @rtp: the RTP packet * * Check if the padding bit is set on the RTP packet in @buffer. * * Returns: TRUE if @buffer has the padding bit set. */ gboolean gst_rtp_buffer_get_padding (GstRTPBuffer * rtp) { return GST_RTP_HEADER_PADDING (rtp->data[0]); } /** * gst_rtp_buffer_set_padding: * @rtp: the buffer * @padding: the new padding * * Set the padding bit on the RTP packet in @buffer to @padding. */ void gst_rtp_buffer_set_padding (GstRTPBuffer * rtp, gboolean padding) { GST_RTP_HEADER_PADDING (rtp->data[0]) = padding; } /** * gst_rtp_buffer_pad_to: * @rtp: the RTP packet * @len: the new amount of padding * * Set the amount of padding in the RTP packet in @buffer to * @len. If @len is 0, the padding is removed. * * NOTE: This function does not work correctly. */ void gst_rtp_buffer_pad_to (GstRTPBuffer * rtp, guint len) { guint8 *data; data = rtp->data[0]; if (len > 0) GST_RTP_HEADER_PADDING (data) = TRUE; else GST_RTP_HEADER_PADDING (data) = FALSE; /* FIXME, set the padding byte at the end of the payload data */ } /** * gst_rtp_buffer_get_extension: * @rtp: the RTP packet * * Check if the extension bit is set on the RTP packet in @buffer. * * Returns: TRUE if @buffer has the extension bit set. */ gboolean gst_rtp_buffer_get_extension (GstRTPBuffer * rtp) { return GST_RTP_HEADER_EXTENSION (rtp->data[0]); } /** * gst_rtp_buffer_set_extension: * @rtp: the RTP packet * @extension: the new extension * * Set the extension bit on the RTP packet in @buffer to @extension. */ void gst_rtp_buffer_set_extension (GstRTPBuffer * rtp, gboolean extension) { GST_RTP_HEADER_EXTENSION (rtp->data[0]) = extension; } /** * gst_rtp_buffer_get_extension_data: (skip) * @rtp: the RTP packet * @bits: (out): location for result bits * @data: (out) (array) (element-type guint8) (transfer none): location for data * @wordlen: (out): location for length of @data in 32 bits words * * Get the extension data. @bits will contain the extension 16 bits of custom * data. @data will point to the data in the extension and @wordlen will contain * the length of @data in 32 bits words. * * If @buffer did not contain an extension, this function will return %FALSE * with @bits, @data and @wordlen unchanged. * * Returns: TRUE if @buffer had the extension bit set. */ gboolean gst_rtp_buffer_get_extension_data (GstRTPBuffer * rtp, guint16 * bits, gpointer * data, guint * wordlen) { guint8 *pdata; /* move to the extension */ pdata = rtp->data[1]; if (!pdata) return FALSE; if (bits) *bits = GST_READ_UINT16_BE (pdata); if (wordlen) *wordlen = GST_READ_UINT16_BE (pdata + 2); pdata += 4; if (data) *data = (gpointer *) pdata; return TRUE; } /** * gst_rtp_buffer_get_extension_bytes: (rename-to gst_rtp_buffer_get_extension_data) * @rtp: the RTP packet * @bits: (out): location for header bits * * Similar to gst_rtp_buffer_get_extension_data, but more suitable for language * bindings usage. @bits will contain the extension 16 bits of custom data and * the extension data (not including the extension header) is placed in a new * #GBytes structure. * * If @rtp did not contain an extension, this function will return %NULL, with * @bits unchanged. If there is an extension header but no extension data then * an empty #GBytes will be returned. * * Returns: (transfer full): A new #GBytes if an extension header was present * and %NULL otherwise. * * Since: 1.2 */ GBytes * gst_rtp_buffer_get_extension_bytes (GstRTPBuffer * rtp, guint16 * bits) { gpointer buf_data = NULL; guint buf_len; g_return_val_if_fail (rtp != NULL, FALSE); if (!gst_rtp_buffer_get_extension_data (rtp, bits, &buf_data, &buf_len)) return NULL; if (buf_len == 0) { /* if no extension data is present return an empty GBytes */ buf_data = NULL; } /* multiply length with 4 to get length in bytes */ return g_bytes_new (buf_data, 4 * buf_len); } static gboolean gst_rtp_buffer_map_payload (GstRTPBuffer * rtp) { guint hlen, plen; guint idx, length; gsize skip; if (rtp->map[2].memory != NULL) return TRUE; hlen = gst_rtp_buffer_get_header_len (rtp); plen = gst_buffer_get_size (rtp->buffer) - hlen - rtp->size[3]; if (!gst_buffer_find_memory (rtp->buffer, hlen, plen, &idx, &length, &skip)) return FALSE; if (!gst_buffer_map_range (rtp->buffer, idx, length, &rtp->map[2], rtp->map[0].flags)) return FALSE; rtp->data[2] = rtp->map[2].data + skip; rtp->size[2] = plen; return TRUE; } /* ensure header, payload and padding are in separate buffers */ static void ensure_buffers (GstRTPBuffer * rtp) { guint i, pos; gboolean changed = FALSE; /* make sure payload is mapped */ gst_rtp_buffer_map_payload (rtp); for (i = 0, pos = 0; i < 4; i++) { if (rtp->size[i]) { gsize offset = (guint8 *) rtp->data[i] - rtp->map[i].data; if (offset != 0 || rtp->map[i].size != rtp->size[i]) { GstMemory *mem; /* make copy */ mem = gst_memory_copy (rtp->map[i].memory, offset, rtp->size[i]); /* insert new memory */ gst_buffer_insert_memory (rtp->buffer, pos, mem); changed = TRUE; } pos++; } } if (changed) { GstBuffer *buf = rtp->buffer; gst_rtp_buffer_unmap (rtp); gst_buffer_remove_memory_range (buf, pos, -1); gst_rtp_buffer_map (buf, GST_MAP_READWRITE, rtp); } } /** * gst_rtp_buffer_set_extension_data: * @rtp: the RTP packet * @bits: the bits specific for the extension * @length: the length that counts the number of 32-bit words in * the extension, excluding the extension header ( therefore zero is a valid length) * * Set the extension bit of the rtp buffer and fill in the @bits and @length of the * extension header. If the existing extension data is not large enough, it will * be made larger. * * Returns: True if done. */ gboolean gst_rtp_buffer_set_extension_data (GstRTPBuffer * rtp, guint16 bits, guint16 length) { guint32 min_size = 0; guint8 *data; GstMemory *mem = NULL; ensure_buffers (rtp); /* this is the size of the extension data we need */ min_size = 4 + length * sizeof (guint32); /* we should allocate and map the extension data */ if (rtp->data[1] == NULL || min_size > rtp->size[1]) { GstMapInfo map; /* we don't have (enough) extension data, make some */ mem = gst_allocator_alloc (NULL, min_size, NULL); if (rtp->data[1]) { /* copy old data */ gst_memory_map (mem, &map, GST_MAP_WRITE); memcpy (map.data, rtp->data[1], rtp->size[1]); gst_memory_unmap (mem, &map); /* unmap old */ gst_buffer_unmap (rtp->buffer, &rtp->map[1]); gst_buffer_replace_memory (rtp->buffer, 1, mem); } else { /* we didn't have extension data, add */ gst_buffer_insert_memory (rtp->buffer, 1, mem); } /* map new */ gst_memory_map (mem, &rtp->map[1], GST_MAP_READWRITE); gst_memory_ref (mem); rtp->data[1] = rtp->map[1].data; rtp->size[1] = rtp->map[1].size; } /* now we can set the extension bit */ data = rtp->data[0]; GST_RTP_HEADER_EXTENSION (data) = TRUE; data = rtp->data[1]; GST_WRITE_UINT16_BE (data, bits); GST_WRITE_UINT16_BE (data + 2, length); return TRUE; } /** * gst_rtp_buffer_get_ssrc: * @rtp: the RTP packet * * Get the SSRC of the RTP packet in @buffer. * * Returns: the SSRC of @buffer in host order. */ guint32 gst_rtp_buffer_get_ssrc (GstRTPBuffer * rtp) { return g_ntohl (GST_RTP_HEADER_SSRC (rtp->data[0])); } /** * gst_rtp_buffer_set_ssrc: * @rtp: the RTP packet * @ssrc: the new SSRC * * Set the SSRC on the RTP packet in @buffer to @ssrc. */ void gst_rtp_buffer_set_ssrc (GstRTPBuffer * rtp, guint32 ssrc) { GST_RTP_HEADER_SSRC (rtp->data[0]) = g_htonl (ssrc); } /** * gst_rtp_buffer_get_csrc_count: * @rtp: the RTP packet * * Get the CSRC count of the RTP packet in @buffer. * * Returns: the CSRC count of @buffer. */ guint8 gst_rtp_buffer_get_csrc_count (GstRTPBuffer * rtp) { return GST_RTP_HEADER_CSRC_COUNT (rtp->data[0]); } /** * gst_rtp_buffer_get_csrc: * @rtp: the RTP packet * @idx: the index of the CSRC to get * * Get the CSRC at index @idx in @buffer. * * Returns: the CSRC at index @idx in host order. */ guint32 gst_rtp_buffer_get_csrc (GstRTPBuffer * rtp, guint8 idx) { guint8 *data; data = rtp->data[0]; g_return_val_if_fail (idx < GST_RTP_HEADER_CSRC_COUNT (data), 0); return GST_READ_UINT32_BE (GST_RTP_HEADER_CSRC_LIST_OFFSET (data, idx)); } /** * gst_rtp_buffer_set_csrc: * @rtp: the RTP packet * @idx: the CSRC index to set * @csrc: the CSRC in host order to set at @idx * * Modify the CSRC at index @idx in @buffer to @csrc. */ void gst_rtp_buffer_set_csrc (GstRTPBuffer * rtp, guint8 idx, guint32 csrc) { guint8 *data; data = rtp->data[0]; g_return_if_fail (idx < GST_RTP_HEADER_CSRC_COUNT (data)); GST_WRITE_UINT32_BE (GST_RTP_HEADER_CSRC_LIST_OFFSET (data, idx), csrc); } /** * gst_rtp_buffer_get_marker: * @rtp: the RTP packet * * Check if the marker bit is set on the RTP packet in @buffer. * * Returns: TRUE if @buffer has the marker bit set. */ gboolean gst_rtp_buffer_get_marker (GstRTPBuffer * rtp) { return GST_RTP_HEADER_MARKER (rtp->data[0]); } /** * gst_rtp_buffer_set_marker: * @rtp: the RTP packet * @marker: the new marker * * Set the marker bit on the RTP packet in @buffer to @marker. */ void gst_rtp_buffer_set_marker (GstRTPBuffer * rtp, gboolean marker) { GST_RTP_HEADER_MARKER (rtp->data[0]) = marker; } /** * gst_rtp_buffer_get_payload_type: * @rtp: the RTP packet * * Get the payload type of the RTP packet in @buffer. * * Returns: The payload type. */ guint8 gst_rtp_buffer_get_payload_type (GstRTPBuffer * rtp) { return GST_RTP_HEADER_PAYLOAD_TYPE (rtp->data[0]); } /** * gst_rtp_buffer_set_payload_type: * @rtp: the RTP packet * @payload_type: the new type * * Set the payload type of the RTP packet in @buffer to @payload_type. */ void gst_rtp_buffer_set_payload_type (GstRTPBuffer * rtp, guint8 payload_type) { g_return_if_fail (payload_type < 0x80); GST_RTP_HEADER_PAYLOAD_TYPE (rtp->data[0]) = payload_type; } /** * gst_rtp_buffer_get_seq: * @rtp: the RTP packet * * Get the sequence number of the RTP packet in @buffer. * * Returns: The sequence number in host order. */ guint16 gst_rtp_buffer_get_seq (GstRTPBuffer * rtp) { return g_ntohs (GST_RTP_HEADER_SEQ (rtp->data[0])); } /** * gst_rtp_buffer_set_seq: * @rtp: the RTP packet * @seq: the new sequence number * * Set the sequence number of the RTP packet in @buffer to @seq. */ void gst_rtp_buffer_set_seq (GstRTPBuffer * rtp, guint16 seq) { GST_RTP_HEADER_SEQ (rtp->data[0]) = g_htons (seq); } /** * gst_rtp_buffer_get_timestamp: * @rtp: the RTP packet * * Get the timestamp of the RTP packet in @buffer. * * Returns: The timestamp in host order. */ guint32 gst_rtp_buffer_get_timestamp (GstRTPBuffer * rtp) { return g_ntohl (GST_RTP_HEADER_TIMESTAMP (rtp->data[0])); } /** * gst_rtp_buffer_set_timestamp: * @rtp: the RTP packet * @timestamp: the new timestamp * * Set the timestamp of the RTP packet in @buffer to @timestamp. */ void gst_rtp_buffer_set_timestamp (GstRTPBuffer * rtp, guint32 timestamp) { GST_RTP_HEADER_TIMESTAMP (rtp->data[0]) = g_htonl (timestamp); } /** * gst_rtp_buffer_get_payload_subbuffer: * @rtp: the RTP packet * @offset: the offset in the payload * @len: the length in the payload * * Create a subbuffer of the payload of the RTP packet in @buffer. @offset bytes * are skipped in the payload and the subbuffer will be of size @len. * If @len is -1 the total payload starting from @offset is subbuffered. * * Returns: A new buffer with the specified data of the payload. */ GstBuffer * gst_rtp_buffer_get_payload_subbuffer (GstRTPBuffer * rtp, guint offset, guint len) { guint poffset, plen; plen = gst_rtp_buffer_get_payload_len (rtp); /* we can't go past the length */ if (G_UNLIKELY (offset > plen)) goto wrong_offset; /* apply offset */ poffset = gst_rtp_buffer_get_header_len (rtp) + offset; plen -= offset; /* see if we need to shrink the buffer based on @len */ if (len != -1 && len < plen) plen = len; return gst_buffer_copy_region (rtp->buffer, GST_BUFFER_COPY_ALL, poffset, plen); /* ERRORS */ wrong_offset: { g_warning ("offset=%u should be less than plen=%u", offset, plen); return NULL; } } /** * gst_rtp_buffer_get_payload_buffer: * @rtp: the RTP packet * * Create a buffer of the payload of the RTP packet in @buffer. This function * will internally create a subbuffer of @buffer so that a memcpy can be * avoided. * * Returns: A new buffer with the data of the payload. */ GstBuffer * gst_rtp_buffer_get_payload_buffer (GstRTPBuffer * rtp) { return gst_rtp_buffer_get_payload_subbuffer (rtp, 0, -1); } /** * gst_rtp_buffer_get_payload_len: * @rtp: the RTP packet * * Get the length of the payload of the RTP packet in @buffer. * * Returns: The length of the payload in @buffer. */ guint gst_rtp_buffer_get_payload_len (GstRTPBuffer * rtp) { return gst_buffer_get_size (rtp->buffer) - gst_rtp_buffer_get_header_len (rtp) - rtp->size[3]; } /** * gst_rtp_buffer_get_payload: (skip) * @rtp: the RTP packet * * Get a pointer to the payload data in @buffer. This pointer is valid as long * as a reference to @buffer is held. * * Returns: (array) (element-type guint8) (transfer none): A pointer * to the payload data in @buffer. */ gpointer gst_rtp_buffer_get_payload (GstRTPBuffer * rtp) { if (rtp->data[2]) return rtp->data[2]; if (!gst_rtp_buffer_map_payload (rtp)) return NULL; return rtp->data[2]; } /** * gst_rtp_buffer_get_payload_bytes: (rename-to gst_rtp_buffer_get_payload) * @rtp: the RTP packet * * Similar to gst_rtp_buffer_get_payload, but more suitable for language * bindings usage. The return value is a pointer to a #GBytes structure * containing the payload data in @rtp. * * Returns: (transfer full): A new #GBytes containing the payload data in @rtp. * * Since: 1.2 */ GBytes * gst_rtp_buffer_get_payload_bytes (GstRTPBuffer * rtp) { gpointer data; g_return_val_if_fail (rtp != NULL, NULL); data = gst_rtp_buffer_get_payload (rtp); if (data == NULL) return NULL; return g_bytes_new (data, gst_rtp_buffer_get_payload_len (rtp)); } /** * gst_rtp_buffer_default_clock_rate: * @payload_type: the static payload type * * Get the default clock-rate for the static payload type @payload_type. * * Returns: the default clock rate or -1 if the payload type is not static or * the clock-rate is undefined. */ guint32 gst_rtp_buffer_default_clock_rate (guint8 payload_type) { const GstRTPPayloadInfo *info; guint32 res; info = gst_rtp_payload_info_for_pt (payload_type); if (!info) return -1; res = info->clock_rate; /* 0 means unknown so we have to return -1 from this function */ if (res == 0) res = -1; return res; } /** * gst_rtp_buffer_compare_seqnum: * @seqnum1: a sequence number * @seqnum2: a sequence number * * Compare two sequence numbers, taking care of wraparounds. This function * returns the difference between @seqnum1 and @seqnum2. * * Returns: a negative value if @seqnum1 is bigger than @seqnum2, 0 if they * are equal or a positive value if @seqnum1 is smaller than @segnum2. */ gint gst_rtp_buffer_compare_seqnum (guint16 seqnum1, guint16 seqnum2) { /* See http://en.wikipedia.org/wiki/Serial_number_arithmetic * for an explanation why this does the right thing even for * wraparounds, under the assumption that the difference is * never bigger than 2**15 sequence numbers */ return (gint16) (seqnum2 - seqnum1); } /** * gst_rtp_buffer_ext_timestamp: * @exttimestamp: (inout): a previous extended timestamp * @timestamp: a new timestamp * * Update the @exttimestamp field with the extended timestamp of @timestamp * For the first call of the method, @exttimestamp should point to a location * with a value of -1. * * This function is able to handle both forward and backward timestamps taking * into account: * - timestamp wraparound making sure that the returned value is properly increased. * - timestamp unwraparound making sure that the returned value is properly decreased. * * Returns: The extended timestamp of @timestamp or 0 if the result can't go anywhere backwards. */ guint64 gst_rtp_buffer_ext_timestamp (guint64 * exttimestamp, guint32 timestamp) { guint64 result, ext; g_return_val_if_fail (exttimestamp != NULL, -1); ext = *exttimestamp; if (ext == -1) { result = timestamp; } else { /* pick wraparound counter from previous timestamp and add to new timestamp */ result = timestamp + (ext & ~(G_GUINT64_CONSTANT (0xffffffff))); /* check for timestamp wraparound */ if (result < ext) { guint64 diff = ext - result; if (diff > G_MAXINT32) { /* timestamp went backwards more than allowed, we wrap around and get * updated extended timestamp. */ result += (G_GUINT64_CONSTANT (1) << 32); } } else { guint64 diff = result - ext; if (diff > G_MAXINT32) { if (result < (G_GUINT64_CONSTANT (1) << 32)) { GST_WARNING ("Cannot unwrap, any wrapping took place yet. Returning 0 without updating extended timestamp."); return 0; } else { /* timestamp went forwards more than allowed, we unwrap around and get * updated extended timestamp. */ result -= (G_GUINT64_CONSTANT (1) << 32); /* We don't want the extended timestamp storage to go back, ever */ return result; } } } } *exttimestamp = result; return result; } /** * gst_rtp_buffer_get_extension_onebyte_header: * @rtp: the RTP packet * @id: The ID of the header extension to be read (between 1 and 14). * @nth: Read the nth extension packet with the requested ID * @data: (out) (array length=size) (element-type guint8) (transfer none): * location for data * @size: (out): the size of the data in bytes * * Parses RFC 5285 style header extensions with a one byte header. It will * return the nth extension with the requested id. * * Returns: TRUE if @buffer had the requested header extension */ gboolean gst_rtp_buffer_get_extension_onebyte_header (GstRTPBuffer * rtp, guint8 id, guint nth, gpointer * data, guint * size) { guint16 bits; guint8 *pdata; guint wordlen; gulong offset = 0; guint count = 0; g_return_val_if_fail (id > 0 && id < 15, FALSE); if (!gst_rtp_buffer_get_extension_data (rtp, &bits, (gpointer) & pdata, &wordlen)) return FALSE; if (bits != 0xBEDE) return FALSE; for (;;) { guint8 read_id, read_len; if (offset + 1 >= wordlen * 4) break; read_id = GST_READ_UINT8 (pdata + offset) >> 4; read_len = (GST_READ_UINT8 (pdata + offset) & 0x0F) + 1; offset += 1; /* ID 0 means its padding, skip */ if (read_id == 0) continue; /* ID 15 is special and means we should stop parsing */ if (read_id == 15) break; /* Ignore extension headers where the size does not fit */ if (offset + read_len > wordlen * 4) break; /* If we have the right one */ if (id == read_id) { if (nth == count) { if (data) *data = pdata + offset; if (size) *size = read_len; return TRUE; } count++; } offset += read_len; if (offset >= wordlen * 4) break; } return FALSE; } /** * gst_rtp_buffer_get_extension_twobytes_header: * @rtp: the RTP packet * @appbits: (out): Application specific bits * @id: The ID of the header extension to be read (between 1 and 14). * @nth: Read the nth extension packet with the requested ID * @data: (out) (array length=size) (element-type guint8) (transfer none): * location for data * @size: (out): the size of the data in bytes * * Parses RFC 5285 style header extensions with a two bytes header. It will * return the nth extension with the requested id. * * Returns: TRUE if @buffer had the requested header extension */ gboolean gst_rtp_buffer_get_extension_twobytes_header (GstRTPBuffer * rtp, guint8 * appbits, guint8 id, guint nth, gpointer * data, guint * size) { guint16 bits; guint8 *pdata = NULL; guint wordlen; guint bytelen; gulong offset = 0; guint count = 0; if (!gst_rtp_buffer_get_extension_data (rtp, &bits, (gpointer *) & pdata, &wordlen)) return FALSE; if (bits >> 4 != 0x100) return FALSE; bytelen = wordlen * 4; for (;;) { guint8 read_id, read_len; if (offset + 2 >= bytelen) break; read_id = GST_READ_UINT8 (pdata + offset); offset += 1; if (read_id == 0) continue; read_len = GST_READ_UINT8 (pdata + offset); offset += 1; /* Ignore extension headers where the size does not fit */ if (offset + read_len > bytelen) break; /* If we have the right one, return it */ if (id == read_id) { if (nth == count) { if (data) *data = pdata + offset; if (size) *size = read_len; if (appbits) *appbits = bits; return TRUE; } count++; } offset += read_len; } return FALSE; } static guint get_onebyte_header_end_offset (guint8 * pdata, guint wordlen) { guint offset = 0; guint bytelen = wordlen * 4; guint paddingcount = 0; while (offset + 1 < bytelen) { guint8 read_id, read_len; read_id = GST_READ_UINT8 (pdata + offset) >> 4; read_len = (GST_READ_UINT8 (pdata + offset) & 0x0F) + 1; offset += 1; /* ID 0 means its padding, skip */ if (read_id == 0) { paddingcount++; continue; } paddingcount = 0; /* ID 15 is special and means we should stop parsing */ /* It also means we can't add an extra packet */ if (read_id == 15) return 0; /* Ignore extension headers where the size does not fit */ if (offset + read_len > bytelen) return 0; offset += read_len; } return offset - paddingcount; } /** * gst_rtp_buffer_add_extension_onebyte_header: * @rtp: the RTP packet * @id: The ID of the header extension (between 1 and 14). * @data: (array length=size) (element-type guint8): location for data * @size: the size of the data in bytes * * Adds a RFC 5285 header extension with a one byte header to the end of the * RTP header. If there is already a RFC 5285 header extension with a one byte * header, the new extension will be appended. * It will not work if there is already a header extension that does not follow * the mecanism described in RFC 5285 or if there is a header extension with * a two bytes header as described in RFC 5285. In that case, use * gst_rtp_buffer_add_extension_twobytes_header() * * Returns: %TRUE if header extension could be added */ gboolean gst_rtp_buffer_add_extension_onebyte_header (GstRTPBuffer * rtp, guint8 id, gconstpointer data, guint size) { guint16 bits; guint8 *pdata = 0; guint wordlen; gboolean has_bit; guint extlen, offset = 0; g_return_val_if_fail (id > 0 && id < 15, FALSE); g_return_val_if_fail (size >= 1 && size <= 16, FALSE); g_return_val_if_fail (gst_buffer_is_writable (rtp->buffer), FALSE); has_bit = gst_rtp_buffer_get_extension_data (rtp, &bits, (gpointer) & pdata, &wordlen); if (has_bit) { if (bits != 0xBEDE) return FALSE; offset = get_onebyte_header_end_offset (pdata, wordlen); if (offset == 0) return FALSE; } /* the required size of the new extension data */ extlen = offset + size + 1; /* calculate amount of words */ wordlen = extlen / 4 + ((extlen % 4) ? 1 : 0); gst_rtp_buffer_set_extension_data (rtp, 0xBEDE, wordlen); gst_rtp_buffer_get_extension_data (rtp, &bits, (gpointer) & pdata, &wordlen); pdata += offset; pdata[0] = (id << 4) | (0x0F & (size - 1)); memcpy (pdata + 1, data, size); if (extlen % 4) memset (pdata + 1 + size, 0, 4 - (extlen % 4)); return TRUE; } static guint get_twobytes_header_end_offset (const guint8 * pdata, guint wordlen) { guint offset = 0; guint bytelen = wordlen * 4; guint paddingcount = 0; while (offset + 2 < bytelen) { guint8 read_id, read_len; read_id = GST_READ_UINT8 (pdata + offset); offset += 1; /* ID 0 means its padding, skip */ if (read_id == 0) { paddingcount++; continue; } paddingcount = 0; read_len = GST_READ_UINT8 (pdata + offset); offset += 1; /* Ignore extension headers where the size does not fit */ if (offset + read_len > bytelen) return 0; offset += read_len; } return offset - paddingcount; } /** * gst_rtp_buffer_add_extension_twobytes_header: * @rtp: the RTP packet * @appbits: Application specific bits * @id: The ID of the header extension * @data: (array length=size) (element-type guint8): location for data * @size: the size of the data in bytes * * Adds a RFC 5285 header extension with a two bytes header to the end of the * RTP header. If there is already a RFC 5285 header extension with a two bytes * header, the new extension will be appended. * It will not work if there is already a header extension that does not follow * the mecanism described in RFC 5285 or if there is a header extension with * a one byte header as described in RFC 5285. In that case, use * gst_rtp_buffer_add_extension_onebyte_header() * * Returns: %TRUE if header extension could be added */ gboolean gst_rtp_buffer_add_extension_twobytes_header (GstRTPBuffer * rtp, guint8 appbits, guint8 id, gconstpointer data, guint size) { guint16 bits; guint8 *pdata = 0; guint wordlen; gboolean has_bit; gulong offset = 0; guint extlen; g_return_val_if_fail ((appbits & 0xF0) == 0, FALSE); g_return_val_if_fail (size < 256, FALSE); g_return_val_if_fail (gst_buffer_is_writable (rtp->buffer), FALSE); has_bit = gst_rtp_buffer_get_extension_data (rtp, &bits, (gpointer) & pdata, &wordlen); if (has_bit) { if (bits != ((0x100 << 4) | (appbits & 0x0f))) return FALSE; offset = get_twobytes_header_end_offset (pdata, wordlen); if (offset == 0) return FALSE; } /* the required size of the new extension data */ extlen = offset + size + 2; /* calculate amount of words */ wordlen = extlen / 4 + ((extlen % 4) ? 1 : 0); gst_rtp_buffer_set_extension_data (rtp, (0x100 << 4) | (appbits & 0x0F), wordlen); gst_rtp_buffer_get_extension_data (rtp, &bits, (gpointer) & pdata, &wordlen); pdata += offset; pdata[0] = id; pdata[1] = size; memcpy (pdata + 2, data, size); if (extlen % 4) memset (pdata + 2 + size, 0, 4 - (extlen % 4)); return TRUE; }