/* GStreamer * Copyright (C) <2007> Wim Taymans * * gstrtcpbuffer.h: various helper functions to manipulate buffers * with RTCP payload. * * 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:gstrtcpbuffer * @title: GstRTCPBuffer * @short_description: Helper methods for dealing with RTCP buffers * @see_also: #GstRTPBasePayload, #GstRTPBaseDepayload, #gstrtpbuffer * * Note: The API in this module is not yet declared stable. * * The GstRTPCBuffer helper functions makes it easy to parse and create regular * #GstBuffer objects that contain compound RTCP packets. These buffers are typically * of 'application/x-rtcp' #GstCaps. * * An RTCP buffer consists of 1 or more #GstRTCPPacket structures that you can * retrieve with gst_rtcp_buffer_get_first_packet(). #GstRTCPPacket acts as a pointer * into the RTCP buffer; you can move to the next packet with * gst_rtcp_packet_move_to_next(). * */ #include #include "gstrtcpbuffer.h" /** * gst_rtcp_buffer_new_take_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 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_rtcp_buffer_new_take_data (gpointer data, guint len) { GstBuffer *result; g_return_val_if_fail (data != NULL, NULL); g_return_val_if_fail (len > 0, NULL); result = gst_buffer_new_wrapped (data, len); return result; } /** * gst_rtcp_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_rtcp_buffer_new_copy_data (gconstpointer data, guint len) { return gst_rtcp_buffer_new_take_data (g_memdup (data, len), len); } static gboolean gst_rtcp_buffer_validate_data_internal (guint8 * data, guint len, guint16 valid_mask) { guint16 header_mask; guint header_len; guint8 version; guint data_len; gboolean padding; guint8 pad_bytes; g_return_val_if_fail (data != NULL, FALSE); /* we need 4 bytes for the type and length */ if (G_UNLIKELY (len < 4)) goto wrong_length; /* first packet must be RR or SR and version must be 2 */ header_mask = ((data[0] << 8) | data[1]) & valid_mask; if (G_UNLIKELY (header_mask != GST_RTCP_VALID_VALUE)) goto wrong_mask; /* no padding when mask succeeds */ padding = FALSE; /* store len */ data_len = len; while (TRUE) { /* get packet length */ header_len = (((data[2] << 8) | data[3]) + 1) << 2; if (data_len < header_len) goto wrong_length; /* move to next compount packet */ data += header_len; data_len -= header_len; /* we are at the end now */ if (data_len < 4) break; /* padding only allowed on last packet */ if (padding) break; /* check version of new packet */ version = data[0] & 0xc0; if (version != (GST_RTCP_VERSION << 6)) goto wrong_version; /* check padding of new packet */ if (data[0] & 0x20) { padding = TRUE; /* last byte of padding contains the number of padded bytes including * itself. must be a multiple of 4, but cannot be 0. */ pad_bytes = data[data_len - 1]; if (pad_bytes == 0 || (pad_bytes & 0x3)) goto wrong_padding; } } if (data_len != 0) { /* some leftover bytes */ goto wrong_length; } return TRUE; /* ERRORS */ wrong_length: { GST_DEBUG ("len check failed"); return FALSE; } wrong_mask: { GST_DEBUG ("mask check failed (%04x != %04x)", header_mask, valid_mask); return FALSE; } wrong_version: { GST_DEBUG ("wrong version (%d < 2)", version >> 6); return FALSE; } wrong_padding: { GST_DEBUG ("padding check failed"); return FALSE; } } /** * gst_rtcp_buffer_validate_data_reduced: * @data: (array length=len): the data to validate * @len: the length of @data to validate * * Check if the @data and @size point to the data of a valid RTCP packet. * Use this function to validate a packet before using the other functions in * this module. * * This function is updated to support reduced size rtcp packets according to * RFC 5506 and will validate full compound RTCP packets as well as reduced * size RTCP packets. * * Returns: TRUE if the data points to a valid RTCP packet. * * Since: 1.6 */ gboolean gst_rtcp_buffer_validate_data_reduced (guint8 * data, guint len) { return gst_rtcp_buffer_validate_data_internal (data, len, GST_RTCP_REDUCED_SIZE_VALID_MASK); } /** * gst_rtcp_buffer_validate_data: * @data: (array length=len): the data to validate * @len: the length of @data to validate * * Check if the @data and @size point to the data of a valid compound, * non-reduced size RTCP packet. * Use this function to validate a packet before using the other functions in * this module. * * Returns: TRUE if the data points to a valid RTCP packet. */ gboolean gst_rtcp_buffer_validate_data (guint8 * data, guint len) { return gst_rtcp_buffer_validate_data_internal (data, len, GST_RTCP_VALID_MASK); } /** * gst_rtcp_buffer_validate_reduced: * @buffer: the buffer to validate * * Check if the data pointed to by @buffer is a valid RTCP packet using * gst_rtcp_buffer_validate_reduced(). * * Returns: TRUE if @buffer is a valid RTCP packet. * * Since: 1.6 */ gboolean gst_rtcp_buffer_validate_reduced (GstBuffer * buffer) { gboolean res; GstMapInfo map; g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE); gst_buffer_map (buffer, &map, GST_MAP_READ); res = gst_rtcp_buffer_validate_data_reduced (map.data, map.size); gst_buffer_unmap (buffer, &map); return res; } /** * gst_rtcp_buffer_validate: * @buffer: the buffer to validate * * Check if the data pointed to by @buffer is a valid RTCP packet using * gst_rtcp_buffer_validate_data(). * * Returns: TRUE if @buffer is a valid RTCP packet. */ gboolean gst_rtcp_buffer_validate (GstBuffer * buffer) { gboolean res; GstMapInfo map; g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE); gst_buffer_map (buffer, &map, GST_MAP_READ); res = gst_rtcp_buffer_validate_data (map.data, map.size); gst_buffer_unmap (buffer, &map); return res; } /** * gst_rtcp_buffer_new: * @mtu: the maximum mtu size. * * Create a new buffer for constructing RTCP packets. The packet will have a * maximum size of @mtu. * * Returns: A newly allocated buffer. */ GstBuffer * gst_rtcp_buffer_new (guint mtu) { GstBuffer *result; guint8 *data; g_return_val_if_fail (mtu > 0, NULL); data = g_malloc0 (mtu); result = gst_buffer_new_wrapped_full (0, data, mtu, 0, 0, data, g_free); return result; } /** * gst_rtcp_buffer_map: * @buffer: a buffer with an RTCP packet * @flags: flags for the mapping * @rtcp: resulting #GstRTCPBuffer * * Open @buffer for reading or writing, depending on @flags. The resulting RTCP * buffer state is stored in @rtcp. */ gboolean gst_rtcp_buffer_map (GstBuffer * buffer, GstMapFlags flags, GstRTCPBuffer * rtcp) { g_return_val_if_fail (rtcp != NULL, FALSE); g_return_val_if_fail (rtcp->buffer == NULL, FALSE); g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE); g_return_val_if_fail (flags & GST_MAP_READ, FALSE); rtcp->buffer = buffer; gst_buffer_map (buffer, &rtcp->map, flags); return TRUE; } /** * gst_rtcp_buffer_unmap: * @rtcp: a buffer with an RTCP packet * * Finish @rtcp after being constructed. This function is usually called * after gst_rtcp_buffer_map() and after adding the RTCP items to the new buffer. * * The function adjusts the size of @rtcp with the total length of all the * added packets. */ gboolean gst_rtcp_buffer_unmap (GstRTCPBuffer * rtcp) { g_return_val_if_fail (rtcp != NULL, FALSE); g_return_val_if_fail (GST_IS_BUFFER (rtcp->buffer), FALSE); if (rtcp->map.flags & GST_MAP_WRITE) { /* shrink size */ gst_buffer_resize (rtcp->buffer, 0, rtcp->map.size); } gst_buffer_unmap (rtcp->buffer, &rtcp->map); rtcp->buffer = NULL; return TRUE; } /** * gst_rtcp_buffer_get_packet_count: * @rtcp: a valid RTCP buffer * * Get the number of RTCP packets in @rtcp. * * Returns: the number of RTCP packets in @rtcp. */ guint gst_rtcp_buffer_get_packet_count (GstRTCPBuffer * rtcp) { GstRTCPPacket packet; guint count; g_return_val_if_fail (rtcp != NULL, 0); g_return_val_if_fail (GST_IS_BUFFER (rtcp->buffer), 0); g_return_val_if_fail (rtcp != NULL, 0); g_return_val_if_fail (rtcp->map.flags & GST_MAP_READ, 0); count = 0; if (gst_rtcp_buffer_get_first_packet (rtcp, &packet)) { do { count++; } while (gst_rtcp_packet_move_to_next (&packet)); } return count; } /** * read_packet_header: * @packet: a packet * * Read the packet headers for the packet pointed to by @packet. * * Returns: TRUE if @packet pointed to a valid header. */ static gboolean read_packet_header (GstRTCPPacket * packet) { guint8 *data; gsize maxsize; guint offset; g_return_val_if_fail (packet != NULL, FALSE); data = packet->rtcp->map.data; maxsize = packet->rtcp->map.size; offset = packet->offset; /* check if we are at the end of the buffer, we add 4 because we also want to * ensure we can read the header. */ if (offset + 4 > maxsize) return FALSE; if ((data[offset] & 0xc0) != (GST_RTCP_VERSION << 6)) return FALSE; /* read count, type and length */ packet->padding = (data[offset] & 0x20) == 0x20; packet->count = data[offset] & 0x1f; packet->type = data[offset + 1]; packet->length = (data[offset + 2] << 8) | data[offset + 3]; packet->item_offset = 4; packet->item_count = 0; packet->entry_offset = 4; /* Ensure no overread from the claimed data size. The packet length is expressed in multiple of 32 bits, to make things obvious. */ if (offset + 4 + packet->length * 4 > maxsize) return FALSE; return TRUE; } /** * gst_rtcp_buffer_get_first_packet: * @rtcp: a valid RTCP buffer * @packet: a #GstRTCPPacket * * Initialize a new #GstRTCPPacket pointer that points to the first packet in * @rtcp. * * Returns: TRUE if the packet existed in @rtcp. */ gboolean gst_rtcp_buffer_get_first_packet (GstRTCPBuffer * rtcp, GstRTCPPacket * packet) { g_return_val_if_fail (rtcp != NULL, FALSE); g_return_val_if_fail (GST_IS_BUFFER (rtcp->buffer), FALSE); g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (rtcp != NULL, 0); g_return_val_if_fail (rtcp->map.flags & GST_MAP_READ, 0); /* init to 0 */ packet->rtcp = rtcp; packet->offset = 0; packet->type = GST_RTCP_TYPE_INVALID; if (!read_packet_header (packet)) return FALSE; return TRUE; } /** * gst_rtcp_packet_move_to_next: * @packet: a #GstRTCPPacket * * Move the packet pointer @packet to the next packet in the payload. * Use gst_rtcp_buffer_get_first_packet() to initialize @packet. * * Returns: TRUE if @packet is pointing to a valid packet after calling this * function. */ gboolean gst_rtcp_packet_move_to_next (GstRTCPPacket * packet) { g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type != GST_RTCP_TYPE_INVALID, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, FALSE); /* if we have a padding or invalid packet, it must be the last, * return FALSE */ if (packet->type == GST_RTCP_TYPE_INVALID || packet->padding) goto end; /* move to next packet. Add 4 because the header is not included in length */ packet->offset += (packet->length << 2) + 4; /* try to read new header */ if (!read_packet_header (packet)) goto end; return TRUE; /* ERRORS */ end: { packet->type = GST_RTCP_TYPE_INVALID; return FALSE; } } /** * gst_rtcp_buffer_add_packet: * @rtcp: a valid RTCP buffer * @type: the #GstRTCPType of the new packet * @packet: pointer to new packet * * Add a new packet of @type to @rtcp. @packet will point to the newly created * packet. * * Returns: %TRUE if the packet could be created. This function returns %FALSE * if the max mtu is exceeded for the buffer. */ gboolean gst_rtcp_buffer_add_packet (GstRTCPBuffer * rtcp, GstRTCPType type, GstRTCPPacket * packet) { guint len; gsize maxsize; guint8 *data; gboolean result; g_return_val_if_fail (rtcp != NULL, FALSE); g_return_val_if_fail (GST_IS_BUFFER (rtcp->buffer), FALSE); g_return_val_if_fail (type != GST_RTCP_TYPE_INVALID, FALSE); g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (rtcp->map.flags & GST_MAP_WRITE, FALSE); /* find free space */ if (gst_rtcp_buffer_get_first_packet (rtcp, packet)) while (gst_rtcp_packet_move_to_next (packet)); maxsize = rtcp->map.maxsize; /* packet->offset is now pointing to the next free offset in the buffer to * start a compount packet. Next we figure out if we have enough free space in * the buffer to continue. */ switch (type) { case GST_RTCP_TYPE_SR: len = 28; break; case GST_RTCP_TYPE_RR: len = 8; break; case GST_RTCP_TYPE_SDES: len = 4; break; case GST_RTCP_TYPE_BYE: len = 4; break; case GST_RTCP_TYPE_APP: len = 12; break; case GST_RTCP_TYPE_RTPFB: len = 12; break; case GST_RTCP_TYPE_PSFB: len = 12; break; case GST_RTCP_TYPE_XR: len = 4; break; default: goto unknown_type; } if (packet->offset + len >= maxsize) goto no_space; rtcp->map.size += len; data = rtcp->map.data + packet->offset; data[0] = (GST_RTCP_VERSION << 6); data[1] = type; /* length is stored in multiples of 32 bit words minus the length of the * header */ len = (len - 4) >> 2; data[2] = len >> 8; data[3] = len & 0xff; /* now try to position to the packet */ result = read_packet_header (packet); return result; /* ERRORS */ unknown_type: { g_warning ("unknown type %d", type); return FALSE; } no_space: { return FALSE; } } /** * gst_rtcp_packet_remove: * @packet: a #GstRTCPPacket * * Removes the packet pointed to by @packet and moves pointer to the next one * * Returns: TRUE if @packet is pointing to a valid packet after calling this * function. */ gboolean gst_rtcp_packet_remove (GstRTCPPacket * packet) { gboolean ret = FALSE; guint offset = 0; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type != GST_RTCP_TYPE_INVALID, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE, FALSE); /* The next packet starts at offset + length + 4 (the header) */ offset = packet->offset + (packet->length << 2) + 4; /* Overwrite this packet with the rest of the data */ memmove (packet->rtcp->map.data + packet->offset, packet->rtcp->map.data + offset, packet->rtcp->map.size - offset); packet->rtcp->map.size -= offset - packet->offset; /* try to read next header */ ret = read_packet_header (packet); if (!ret) packet->type = GST_RTCP_TYPE_INVALID; return ret; } /** * gst_rtcp_packet_get_padding: * @packet: a valid #GstRTCPPacket * * Get the packet padding of the packet pointed to by @packet. * * Returns: If the packet has the padding bit set. */ gboolean gst_rtcp_packet_get_padding (GstRTCPPacket * packet) { g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type != GST_RTCP_TYPE_INVALID, FALSE); return packet->padding; } /** * gst_rtcp_packet_get_type: * @packet: a valid #GstRTCPPacket * * Get the packet type of the packet pointed to by @packet. * * Returns: The packet type or GST_RTCP_TYPE_INVALID when @packet is not * pointing to a valid packet. */ GstRTCPType gst_rtcp_packet_get_type (GstRTCPPacket * packet) { g_return_val_if_fail (packet != NULL, GST_RTCP_TYPE_INVALID); return packet->type; } /** * gst_rtcp_packet_get_count: * @packet: a valid #GstRTCPPacket * * Get the count field in @packet. * * Returns: The count field in @packet or -1 if @packet does not point to a * valid packet. */ guint8 gst_rtcp_packet_get_count (GstRTCPPacket * packet) { g_return_val_if_fail (packet != NULL, -1); g_return_val_if_fail (packet->type != GST_RTCP_TYPE_INVALID, -1); return packet->count; } /** * gst_rtcp_packet_get_length: * @packet: a valid #GstRTCPPacket * * Get the length field of @packet. This is the length of the packet in * 32-bit words minus one. * * Returns: The length field of @packet. */ guint16 gst_rtcp_packet_get_length (GstRTCPPacket * packet) { g_return_val_if_fail (packet != NULL, 0); g_return_val_if_fail (packet->type != GST_RTCP_TYPE_INVALID, 0); return packet->length; } /** * gst_rtcp_packet_sr_get_sender_info: * @packet: a valid SR #GstRTCPPacket * @ssrc: (out): result SSRC * @ntptime: (out): result NTP time * @rtptime: (out): result RTP time * @packet_count: (out): result packet count * @octet_count: (out): result octet count * * Parse the SR sender info and store the values. */ void gst_rtcp_packet_sr_get_sender_info (GstRTCPPacket * packet, guint32 * ssrc, guint64 * ntptime, guint32 * rtptime, guint32 * packet_count, guint32 * octet_count) { guint8 *data; g_return_if_fail (packet != NULL); g_return_if_fail (packet->type == GST_RTCP_TYPE_SR); g_return_if_fail (packet->rtcp != NULL); g_return_if_fail (packet->rtcp->map.flags & GST_MAP_READ); data = packet->rtcp->map.data; /* skip header */ data += packet->offset + 4; if (ssrc) *ssrc = GST_READ_UINT32_BE (data); data += 4; if (ntptime) *ntptime = GST_READ_UINT64_BE (data); data += 8; if (rtptime) *rtptime = GST_READ_UINT32_BE (data); data += 4; if (packet_count) *packet_count = GST_READ_UINT32_BE (data); data += 4; if (octet_count) *octet_count = GST_READ_UINT32_BE (data); } /** * gst_rtcp_packet_sr_set_sender_info: * @packet: a valid SR #GstRTCPPacket * @ssrc: the SSRC * @ntptime: the NTP time * @rtptime: the RTP time * @packet_count: the packet count * @octet_count: the octet count * * Set the given values in the SR packet @packet. */ void gst_rtcp_packet_sr_set_sender_info (GstRTCPPacket * packet, guint32 ssrc, guint64 ntptime, guint32 rtptime, guint32 packet_count, guint32 octet_count) { guint8 *data; g_return_if_fail (packet != NULL); g_return_if_fail (packet->type == GST_RTCP_TYPE_SR); g_return_if_fail (packet->rtcp != NULL); g_return_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE); data = packet->rtcp->map.data; /* skip header */ data += packet->offset + 4; GST_WRITE_UINT32_BE (data, ssrc); data += 4; GST_WRITE_UINT64_BE (data, ntptime); data += 8; GST_WRITE_UINT32_BE (data, rtptime); data += 4; GST_WRITE_UINT32_BE (data, packet_count); data += 4; GST_WRITE_UINT32_BE (data, octet_count); } /** * gst_rtcp_packet_rr_get_ssrc: * @packet: a valid RR #GstRTCPPacket * * Get the ssrc field of the RR @packet. * * Returns: the ssrc. */ guint32 gst_rtcp_packet_rr_get_ssrc (GstRTCPPacket * packet) { guint8 *data; guint32 ssrc; g_return_val_if_fail (packet != NULL, 0); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_RR, 0); g_return_val_if_fail (packet->rtcp != NULL, 0); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, 0); data = packet->rtcp->map.data; /* skip header */ data += packet->offset + 4; ssrc = GST_READ_UINT32_BE (data); return ssrc; } /** * gst_rtcp_packet_rr_set_ssrc: * @packet: a valid RR #GstRTCPPacket * @ssrc: the SSRC to set * * Set the ssrc field of the RR @packet. */ void gst_rtcp_packet_rr_set_ssrc (GstRTCPPacket * packet, guint32 ssrc) { guint8 *data; g_return_if_fail (packet != NULL); g_return_if_fail (packet->type == GST_RTCP_TYPE_RR); g_return_if_fail (packet->rtcp != NULL); g_return_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE); data = packet->rtcp->map.data; /* skip header */ data += packet->offset + 4; GST_WRITE_UINT32_BE (data, ssrc); } /** * gst_rtcp_packet_get_rb_count: * @packet: a valid SR or RR #GstRTCPPacket * * Get the number of report blocks in @packet. * * Returns: The number of report blocks in @packet. */ guint gst_rtcp_packet_get_rb_count (GstRTCPPacket * packet) { g_return_val_if_fail (packet != NULL, 0); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_RR || packet->type == GST_RTCP_TYPE_SR, 0); g_return_val_if_fail (packet->rtcp != NULL, 0); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, 0); return packet->count; } /** * gst_rtcp_packet_get_rb: * @packet: a valid SR or RR #GstRTCPPacket * @nth: the nth report block in @packet * @ssrc: (out): result for data source being reported * @fractionlost: (out): result for fraction lost since last SR/RR * @packetslost: (out): result for the cumululative number of packets lost * @exthighestseq: (out): result for the extended last sequence number received * @jitter: (out): result for the interarrival jitter * @lsr: (out): result for the last SR packet from this source * @dlsr: (out): result for the delay since last SR packet * * Parse the values of the @nth report block in @packet and store the result in * the values. */ void gst_rtcp_packet_get_rb (GstRTCPPacket * packet, guint nth, guint32 * ssrc, guint8 * fractionlost, gint32 * packetslost, guint32 * exthighestseq, guint32 * jitter, guint32 * lsr, guint32 * dlsr) { guint offset; guint8 *data; guint32 tmp; g_return_if_fail (packet != NULL); g_return_if_fail (packet->type == GST_RTCP_TYPE_RR || packet->type == GST_RTCP_TYPE_SR); g_return_if_fail (packet->rtcp != NULL); g_return_if_fail (packet->rtcp->map.flags & GST_MAP_READ); g_return_if_fail (nth < packet->count); /* get offset in 32-bits words into packet, skip the header */ if (packet->type == GST_RTCP_TYPE_RR) offset = 2; else offset = 7; /* move to requested index */ offset += (nth * 6); /* check that we don't go past the packet length */ if (offset > packet->length) return; /* scale to bytes */ offset <<= 2; offset += packet->offset; /* check if the packet is valid */ if (offset + 24 > packet->rtcp->map.size) return; data = packet->rtcp->map.data; data += offset; if (ssrc) *ssrc = GST_READ_UINT32_BE (data); data += 4; tmp = GST_READ_UINT32_BE (data); if (fractionlost) *fractionlost = (tmp >> 24); if (packetslost) { /* sign extend */ if (tmp & 0x00800000) tmp |= 0xff000000; else tmp &= 0x00ffffff; *packetslost = (gint32) tmp; } data += 4; if (exthighestseq) *exthighestseq = GST_READ_UINT32_BE (data); data += 4; if (jitter) *jitter = GST_READ_UINT32_BE (data); data += 4; if (lsr) *lsr = GST_READ_UINT32_BE (data); data += 4; if (dlsr) *dlsr = GST_READ_UINT32_BE (data); } /** * gst_rtcp_packet_add_rb: * @packet: a valid SR or RR #GstRTCPPacket * @ssrc: data source being reported * @fractionlost: fraction lost since last SR/RR * @packetslost: the cumululative number of packets lost * @exthighestseq: the extended last sequence number received * @jitter: the interarrival jitter * @lsr: the last SR packet from this source * @dlsr: the delay since last SR packet * * Add a new report block to @packet with the given values. * * Returns: %TRUE if the packet was created. This function can return %FALSE if * the max MTU is exceeded or the number of report blocks is greater than * #GST_RTCP_MAX_RB_COUNT. */ gboolean gst_rtcp_packet_add_rb (GstRTCPPacket * packet, guint32 ssrc, guint8 fractionlost, gint32 packetslost, guint32 exthighestseq, guint32 jitter, guint32 lsr, guint32 dlsr) { guint8 *data; guint maxsize, offset; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_RR || packet->type == GST_RTCP_TYPE_SR, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE, FALSE); /* if profile-specific extension is added, fail for now!? */ g_return_val_if_fail (gst_rtcp_packet_get_profile_specific_ext_length (packet) == 0, FALSE); if (packet->count >= GST_RTCP_MAX_RB_COUNT) goto no_space; data = packet->rtcp->map.data; maxsize = packet->rtcp->map.maxsize; /* skip header */ offset = packet->offset + 4; if (packet->type == GST_RTCP_TYPE_RR) offset += 4; else offset += 24; /* move to current index */ offset += (packet->count * 24); /* we need 24 free bytes now */ if (offset + 24 >= maxsize) goto no_space; /* increment packet count and length */ packet->count++; data[packet->offset]++; packet->length += 6; data[packet->offset + 2] = (packet->length) >> 8; data[packet->offset + 3] = (packet->length) & 0xff; packet->rtcp->map.size += 6 * 4; /* move to new report block offset */ data += offset; GST_WRITE_UINT32_BE (data, ssrc); data += 4; GST_WRITE_UINT32_BE (data, ((guint32) fractionlost << 24) | (packetslost & 0xffffff)); data += 4; GST_WRITE_UINT32_BE (data, exthighestseq); data += 4; GST_WRITE_UINT32_BE (data, jitter); data += 4; GST_WRITE_UINT32_BE (data, lsr); data += 4; GST_WRITE_UINT32_BE (data, dlsr); return TRUE; no_space: { return FALSE; } } /** * gst_rtcp_packet_set_rb: * @packet: a valid SR or RR #GstRTCPPacket * @nth: the nth report block to set * @ssrc: data source being reported * @fractionlost: fraction lost since last SR/RR * @packetslost: the cumululative number of packets lost * @exthighestseq: the extended last sequence number received * @jitter: the interarrival jitter * @lsr: the last SR packet from this source * @dlsr: the delay since last SR packet * * Set the @nth new report block in @packet with the given values. * * Note: Not implemented. */ void gst_rtcp_packet_set_rb (GstRTCPPacket * packet, guint nth, guint32 ssrc, guint8 fractionlost, gint32 packetslost, guint32 exthighestseq, guint32 jitter, guint32 lsr, guint32 dlsr) { g_return_if_fail (packet != NULL); g_return_if_fail (packet->type == GST_RTCP_TYPE_RR || packet->type == GST_RTCP_TYPE_SR); g_return_if_fail (packet->rtcp != NULL); g_return_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE); g_warning ("not implemented"); } /** * gst_rtcp_packet_add_profile_specific_ext: * @packet: a valid SR or RR #GstRTCPPacket * @data: (array length=len) (transfer none): profile-specific data * @len: length of the profile-specific data in bytes * * Add profile-specific extension @data to @packet. If @packet already * contains profile-specific extension @data will be appended to the existing * extension. * * Returns: %TRUE if the profile specific extension data was added. */ gboolean gst_rtcp_packet_add_profile_specific_ext (GstRTCPPacket * packet, const guint8 * data, guint len) { guint8 *bdata; guint maxsize, offset; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_RR || packet->type == GST_RTCP_TYPE_SR, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE, FALSE); g_return_val_if_fail ((len & 0x03) == 0, FALSE); bdata = packet->rtcp->map.data; maxsize = packet->rtcp->map.maxsize; /* skip to the end of the packet */ offset = packet->offset + (packet->length << 2) + 4; /* we need 'len' free bytes now */ if (G_UNLIKELY (offset + len > maxsize)) return FALSE; memcpy (&bdata[offset], data, len); packet->length += len >> 2; bdata[packet->offset + 2] = (packet->length) >> 8; bdata[packet->offset + 3] = (packet->length) & 0xff; packet->rtcp->map.size += len; return TRUE; } /** * gst_rtcp_packet_get_profile_specific_ext_length: * @packet: a valid SR or RR #GstRTCPPacket * * Returns: The number of 32-bit words containing profile-specific extension * data from @packet. */ guint16 gst_rtcp_packet_get_profile_specific_ext_length (GstRTCPPacket * packet) { guint pse_offset = 2; g_return_val_if_fail (packet != NULL, 0); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_RR || packet->type == GST_RTCP_TYPE_SR, 0); g_return_val_if_fail (packet->rtcp != NULL, 0); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, 0); if (packet->type == GST_RTCP_TYPE_SR) pse_offset += 5; pse_offset += (packet->count * 6); if (pse_offset <= (packet->length + 1)) return packet->length + 1 - pse_offset; /* This means that the packet is invalid! */ return 0; } /** * gst_rtcp_packet_get_profile_specific_ext: * @packet: a valid SR or RR #GstRTCPPacket * @data: (out) (array length=len) (transfer none): result profile-specific data * @len: (out): result length of the profile-specific data * * Returns: %TRUE if there was valid data. */ gboolean gst_rtcp_packet_get_profile_specific_ext (GstRTCPPacket * packet, guint8 ** data, guint * len) { guint16 pse_len; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_RR || packet->type == GST_RTCP_TYPE_SR, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, FALSE); pse_len = gst_rtcp_packet_get_profile_specific_ext_length (packet); if (pse_len > 0) { if (len != NULL) *len = pse_len * sizeof (guint32); if (data != NULL) { *data = packet->rtcp->map.data; *data += packet->offset; *data += ((packet->length + 1 - pse_len) * sizeof (guint32)); } return TRUE; } return FALSE; } /** * gst_rtcp_packet_copy_profile_specific_ext: * @packet: a valid SR or RR #GstRTCPPacket * @data: (out) (array length=len): result profile-specific data * @len: (out): length of the profile-specific extension data * * The profile-specific extension data is copied into a new allocated * memory area @data. This must be freed with g_free() after usage. * * Returns: %TRUE if there was valid data. */ gboolean gst_rtcp_packet_copy_profile_specific_ext (GstRTCPPacket * packet, guint8 ** data, guint * len) { guint16 pse_len; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_RR || packet->type == GST_RTCP_TYPE_SR, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, FALSE); pse_len = gst_rtcp_packet_get_profile_specific_ext_length (packet); if (pse_len > 0) { if (len != NULL) *len = pse_len * sizeof (guint32); if (data != NULL) { guint8 *ptr = packet->rtcp->map.data + packet->offset; ptr += ((packet->length + 1 - pse_len) * sizeof (guint32)); *data = g_memdup (ptr, pse_len * sizeof (guint32)); } return TRUE; } return FALSE; } /** * gst_rtcp_packet_sdes_get_item_count: * @packet: a valid SDES #GstRTCPPacket * * Get the number of items in the SDES packet @packet. * * Returns: The number of items in @packet. */ guint gst_rtcp_packet_sdes_get_item_count (GstRTCPPacket * packet) { g_return_val_if_fail (packet != NULL, 0); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_SDES, 0); return packet->count; } /** * gst_rtcp_packet_sdes_first_item: * @packet: a valid SDES #GstRTCPPacket * * Move to the first SDES item in @packet. * * Returns: TRUE if there was a first item. */ gboolean gst_rtcp_packet_sdes_first_item (GstRTCPPacket * packet) { g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_SDES, FALSE); packet->item_offset = 4; packet->item_count = 0; packet->entry_offset = 4; if (packet->count == 0) return FALSE; return TRUE; } /** * gst_rtcp_packet_sdes_next_item: * @packet: a valid SDES #GstRTCPPacket * * Move to the next SDES item in @packet. * * Returns: TRUE if there was a next item. */ gboolean gst_rtcp_packet_sdes_next_item (GstRTCPPacket * packet) { guint8 *data; guint offset; guint len; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_SDES, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, FALSE); /* if we are at the last item, we are done */ if (packet->item_count == packet->count) return FALSE; /* move to SDES */ data = packet->rtcp->map.data; data += packet->offset; /* move to item */ offset = packet->item_offset; /* skip SSRC */ offset += 4; /* don't overrun */ len = (packet->length << 2); while (offset < len) { if (data[offset] == 0) { /* end of list, round to next 32-bit word */ offset = (offset + 4) & ~3; break; } offset += data[offset + 1] + 2; } if (offset >= len) return FALSE; packet->item_offset = offset; packet->item_count++; packet->entry_offset = 4; return TRUE; } /** * gst_rtcp_packet_sdes_get_ssrc: * @packet: a valid SDES #GstRTCPPacket * * Get the SSRC of the current SDES item. * * Returns: the SSRC of the current item. */ guint32 gst_rtcp_packet_sdes_get_ssrc (GstRTCPPacket * packet) { guint32 ssrc; guint8 *data; g_return_val_if_fail (packet != NULL, 0); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_SDES, 0); g_return_val_if_fail (packet->rtcp != NULL, 0); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, 0); /* move to SDES */ data = packet->rtcp->map.data; data += packet->offset; /* move to item */ data += packet->item_offset; ssrc = GST_READ_UINT32_BE (data); return ssrc; } /** * gst_rtcp_packet_sdes_first_entry: * @packet: a valid SDES #GstRTCPPacket * * Move to the first SDES entry in the current item. * * Returns: %TRUE if there was a first entry. */ gboolean gst_rtcp_packet_sdes_first_entry (GstRTCPPacket * packet) { guint8 *data; guint len, offset; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_SDES, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, FALSE); /* move to SDES */ data = packet->rtcp->map.data; data += packet->offset; /* move to item */ offset = packet->item_offset; /* skip SSRC */ offset += 4; packet->entry_offset = 4; /* don't overrun */ len = (packet->length << 2); if (offset >= len) return FALSE; if (data[offset] == 0) return FALSE; return TRUE; } /** * gst_rtcp_packet_sdes_next_entry: * @packet: a valid SDES #GstRTCPPacket * * Move to the next SDES entry in the current item. * * Returns: %TRUE if there was a next entry. */ gboolean gst_rtcp_packet_sdes_next_entry (GstRTCPPacket * packet) { guint8 *data; guint len, offset, item_len; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_SDES, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, FALSE); /* move to SDES */ data = packet->rtcp->map.data; data += packet->offset; /* move to item */ offset = packet->item_offset; /* move to entry */ offset += packet->entry_offset; item_len = data[offset + 1] + 2; /* skip item */ offset += item_len; /* don't overrun */ len = (packet->length << 2); if (offset >= len) return FALSE; packet->entry_offset += item_len; /* check for end of list */ if (data[offset] == 0) return FALSE; return TRUE; } /** * gst_rtcp_packet_sdes_get_entry: * @packet: a valid SDES #GstRTCPPacket * @type: result of the entry type * @len: (out): result length of the entry data * @data: (out) (array length=len) (transfer none): result entry data * * Get the data of the current SDES item entry. @type (when not NULL) will * contain the type of the entry. @data (when not NULL) will point to @len * bytes. * * When @type refers to a text item, @data will point to a UTF8 string. Note * that this UTF8 string is NOT null-terminated. Use * gst_rtcp_packet_sdes_copy_entry() to get a null-terminated copy of the entry. * * Returns: %TRUE if there was valid data. */ gboolean gst_rtcp_packet_sdes_get_entry (GstRTCPPacket * packet, GstRTCPSDESType * type, guint8 * len, guint8 ** data) { guint8 *bdata; guint offset; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_SDES, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, FALSE); /* move to SDES */ bdata = packet->rtcp->map.data; bdata += packet->offset; /* move to item */ offset = packet->item_offset; /* move to entry */ offset += packet->entry_offset; if (bdata[offset] == 0) return FALSE; if (type) *type = bdata[offset]; if (len) *len = bdata[offset + 1]; if (data) *data = &bdata[offset + 2]; return TRUE; } /** * gst_rtcp_packet_sdes_copy_entry: * @packet: a valid SDES #GstRTCPPacket * @type: result of the entry type * @len: (out): result length of the entry data * @data: (out) (array length=len): result entry data * * This function is like gst_rtcp_packet_sdes_get_entry() but it returns a * null-terminated copy of the data instead. use g_free() after usage. * * Returns: %TRUE if there was valid data. */ gboolean gst_rtcp_packet_sdes_copy_entry (GstRTCPPacket * packet, GstRTCPSDESType * type, guint8 * len, guint8 ** data) { guint8 *tdata; guint8 tlen; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_SDES, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, FALSE); if (!gst_rtcp_packet_sdes_get_entry (packet, type, &tlen, &tdata)) return FALSE; if (len) *len = tlen; if (data) *data = (guint8 *) g_strndup ((gchar *) tdata, tlen); return TRUE; } /** * gst_rtcp_packet_sdes_add_item: * @packet: a valid SDES #GstRTCPPacket * @ssrc: the SSRC of the new item to add * * Add a new SDES item for @ssrc to @packet. * * Returns: %TRUE if the item could be added, %FALSE if the maximum amount of * items has been exceeded for the SDES packet or the MTU has been reached. */ gboolean gst_rtcp_packet_sdes_add_item (GstRTCPPacket * packet, guint32 ssrc) { guint8 *data; guint offset; gsize maxsize; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_SDES, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE, FALSE); /* increment item count when possible */ if (packet->count >= GST_RTCP_MAX_SDES_ITEM_COUNT) goto no_space; /* pretend there is a next packet for the next call */ packet->count++; /* jump over current item */ gst_rtcp_packet_sdes_next_item (packet); /* move to SDES */ data = packet->rtcp->map.data; maxsize = packet->rtcp->map.maxsize; data += packet->offset; /* move to current item */ offset = packet->item_offset; /* we need 2 free words now */ if (offset + 8 >= maxsize) goto no_next; /* write SSRC */ GST_WRITE_UINT32_BE (&data[offset], ssrc); /* write 0 entry with padding */ GST_WRITE_UINT32_BE (&data[offset + 4], 0); /* update count */ data[0] = (data[0] & 0xe0) | packet->count; /* update length, we added 2 words */ packet->length += 2; data[2] = (packet->length) >> 8; data[3] = (packet->length) & 0xff; packet->rtcp->map.size += 8; return TRUE; /* ERRORS */ no_space: { return FALSE; } no_next: { packet->count--; return FALSE; } } /** * gst_rtcp_packet_sdes_add_entry: * @packet: a valid SDES #GstRTCPPacket * @type: the #GstRTCPSDESType of the SDES entry * @len: the data length * @data: (array length=len): the data * * Add a new SDES entry to the current item in @packet. * * Returns: %TRUE if the item could be added, %FALSE if the MTU has been * reached. */ gboolean gst_rtcp_packet_sdes_add_entry (GstRTCPPacket * packet, GstRTCPSDESType type, guint8 len, const guint8 * data) { guint8 *bdata; guint offset, padded; gsize maxsize; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_SDES, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE, FALSE); /* move to SDES */ bdata = packet->rtcp->map.data; maxsize = packet->rtcp->map.maxsize; bdata += packet->offset; /* move to item */ offset = packet->item_offset; /* move to entry */ offset += packet->entry_offset; /* add 1 byte end and up to 3 bytes padding to fill a full 32 bit word */ padded = (offset + 2 + len + 1 + 3) & ~3; /* we need enough space for type, len, data and padding */ if (packet->offset + padded >= maxsize) goto no_space; packet->rtcp->map.size = packet->offset + padded; bdata[offset] = type; bdata[offset + 1] = len; memcpy (&bdata[offset + 2], data, len); bdata[offset + 2 + len] = 0; /* calculate new packet length */ packet->length = (padded - 4) >> 2; bdata[2] = (packet->length) >> 8; bdata[3] = (packet->length) & 0xff; /* position to new next entry */ packet->entry_offset += 2 + len; return TRUE; /* ERRORS */ no_space: { return FALSE; } } /** * gst_rtcp_packet_bye_get_ssrc_count: * @packet: a valid BYE #GstRTCPPacket * * Get the number of SSRC fields in @packet. * * Returns: The number of SSRC fields in @packet. */ guint gst_rtcp_packet_bye_get_ssrc_count (GstRTCPPacket * packet) { g_return_val_if_fail (packet != NULL, -1); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_BYE, -1); return packet->count; } /** * gst_rtcp_packet_bye_get_nth_ssrc: * @packet: a valid BYE #GstRTCPPacket * @nth: the nth SSRC to get * * Get the @nth SSRC of the BYE @packet. * * Returns: The @nth SSRC of @packet. */ guint32 gst_rtcp_packet_bye_get_nth_ssrc (GstRTCPPacket * packet, guint nth) { guint8 *data; guint offset; guint32 ssrc; g_return_val_if_fail (packet != NULL, 0); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_BYE, 0); g_return_val_if_fail (packet->rtcp != NULL, 0); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, 0); g_return_val_if_fail (nth < packet->count, 0); /* get offset in 32-bits words into packet, skip the header */ offset = 1 + nth; /* check that we don't go past the packet length */ if (offset > packet->length) return 0; /* scale to bytes */ offset <<= 2; offset += packet->offset; /* check if the packet is valid */ if (offset + 4 > packet->rtcp->map.size) return 0; data = packet->rtcp->map.data; data += offset; ssrc = GST_READ_UINT32_BE (data); return ssrc; } /** * gst_rtcp_packet_bye_add_ssrc: * @packet: a valid BYE #GstRTCPPacket * @ssrc: an SSRC to add * * Add @ssrc to the BYE @packet. * * Returns: %TRUE if the ssrc was added. This function can return %FALSE if * the max MTU is exceeded or the number of sources blocks is greater than * #GST_RTCP_MAX_BYE_SSRC_COUNT. */ gboolean gst_rtcp_packet_bye_add_ssrc (GstRTCPPacket * packet, guint32 ssrc) { guint8 *data; gsize maxsize; guint offset; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_BYE, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE, FALSE); if (packet->count >= GST_RTCP_MAX_BYE_SSRC_COUNT) goto no_space; data = packet->rtcp->map.data; maxsize = packet->rtcp->map.maxsize; /* skip header */ offset = packet->offset + 4; /* move to current index */ offset += (packet->count * 4); if (offset + 4 >= maxsize) goto no_space; /* increment packet count and length */ packet->count++; data[packet->offset]++; packet->length += 1; data[packet->offset + 2] = (packet->length) >> 8; data[packet->offset + 3] = (packet->length) & 0xff; packet->rtcp->map.size += 4; /* move to new SSRC offset and write ssrc */ data += offset; GST_WRITE_UINT32_BE (data, ssrc); return TRUE; /* ERRORS */ no_space: { return FALSE; } } /** * gst_rtcp_packet_bye_add_ssrcs: * @packet: a valid BYE #GstRTCPPacket * @ssrc: (array length=len) (transfer none): an array of SSRCs to add * @len: number of elements in @ssrc * * Adds @len SSRCs in @ssrc to BYE @packet. * * Returns: %TRUE if the all the SSRCs were added. This function can return %FALSE if * the max MTU is exceeded or the number of sources blocks is greater than * #GST_RTCP_MAX_BYE_SSRC_COUNT. */ gboolean gst_rtcp_packet_bye_add_ssrcs (GstRTCPPacket * packet, guint32 * ssrc, guint len) { guint i; gboolean res; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_BYE, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE, FALSE); res = TRUE; for (i = 0; i < len && res; i++) { res = gst_rtcp_packet_bye_add_ssrc (packet, ssrc[i]); } return res; } /* get the offset in packet of the reason length */ static guint get_reason_offset (GstRTCPPacket * packet) { guint offset; /* get amount of sources plus header */ offset = 1 + packet->count; /* check that we don't go past the packet length */ if (offset > packet->length) return 0; /* scale to bytes */ offset <<= 2; offset += packet->offset; /* check if the packet is valid */ if (offset + 1 > packet->rtcp->map.size) return 0; return offset; } /** * gst_rtcp_packet_bye_get_reason_len: * @packet: a valid BYE #GstRTCPPacket * * Get the length of the reason string. * * Returns: The length of the reason string or 0 when there is no reason string * present. */ guint8 gst_rtcp_packet_bye_get_reason_len (GstRTCPPacket * packet) { guint8 *data; guint roffset; g_return_val_if_fail (packet != NULL, 0); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_BYE, 0); g_return_val_if_fail (packet->rtcp != NULL, 0); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, 0); roffset = get_reason_offset (packet); if (roffset == 0) return 0; data = packet->rtcp->map.data; return data[roffset]; } /** * gst_rtcp_packet_bye_get_reason: * @packet: a valid BYE #GstRTCPPacket * * Get the reason in @packet. * * Returns: The reason for the BYE @packet or NULL if the packet did not contain * a reason string. The string must be freed with g_free() after usage. */ gchar * gst_rtcp_packet_bye_get_reason (GstRTCPPacket * packet) { guint8 *data; guint roffset; guint8 len; g_return_val_if_fail (packet != NULL, NULL); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_BYE, NULL); g_return_val_if_fail (packet->rtcp != NULL, NULL); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, NULL); roffset = get_reason_offset (packet); if (roffset == 0) return NULL; data = packet->rtcp->map.data; /* get length of reason string */ len = data[roffset]; if (len == 0) return NULL; /* move to string */ roffset += 1; /* check if enough data to copy */ if (roffset + len > packet->rtcp->map.size) return NULL; return g_strndup ((gconstpointer) (data + roffset), len); } /** * gst_rtcp_packet_bye_set_reason: * @packet: a valid BYE #GstRTCPPacket * @reason: a reason string * * Set the reason string to @reason in @packet. * * Returns: TRUE if the string could be set. */ gboolean gst_rtcp_packet_bye_set_reason (GstRTCPPacket * packet, const gchar * reason) { guint8 *data; guint roffset; gsize maxsize; guint8 len, padded; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_BYE, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE, FALSE); if (reason == NULL) return TRUE; len = strlen (reason); if (len == 0) return TRUE; /* make room for the string before we get the offset */ packet->length++; roffset = get_reason_offset (packet); if (roffset == 0) goto no_space; data = packet->rtcp->map.data; maxsize = packet->rtcp->map.maxsize; /* we have 1 byte length and we need to pad to 4 bytes */ padded = ((len + 1) + 3) & ~3; /* we need enough space for the padded length */ if (roffset + padded >= maxsize) goto no_space; data[roffset] = len; memcpy (&data[roffset + 1], reason, len); /* update packet length, we made room for 1 double word already */ packet->length += (padded >> 2) - 1; data[packet->offset + 2] = (packet->length) >> 8; data[packet->offset + 3] = (packet->length) & 0xff; packet->rtcp->map.size += padded; return TRUE; /* ERRORS */ no_space: { packet->length--; return FALSE; } } /** * gst_rtcp_packet_fb_get_sender_ssrc: * @packet: a valid RTPFB or PSFB #GstRTCPPacket * * Get the sender SSRC field of the RTPFB or PSFB @packet. * * Returns: the sender SSRC. */ guint32 gst_rtcp_packet_fb_get_sender_ssrc (GstRTCPPacket * packet) { guint8 *data; guint32 ssrc; g_return_val_if_fail (packet != NULL, 0); g_return_val_if_fail ((packet->type == GST_RTCP_TYPE_RTPFB || packet->type == GST_RTCP_TYPE_PSFB), 0); g_return_val_if_fail (packet->rtcp != NULL, 0); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, 0); data = packet->rtcp->map.data; /* skip header */ data += packet->offset + 4; ssrc = GST_READ_UINT32_BE (data); return ssrc; } /** * gst_rtcp_packet_fb_set_sender_ssrc: * @packet: a valid RTPFB or PSFB #GstRTCPPacket * @ssrc: a sender SSRC * * Set the sender SSRC field of the RTPFB or PSFB @packet. */ void gst_rtcp_packet_fb_set_sender_ssrc (GstRTCPPacket * packet, guint32 ssrc) { guint8 *data; g_return_if_fail (packet != NULL); g_return_if_fail (packet->type == GST_RTCP_TYPE_RTPFB || packet->type == GST_RTCP_TYPE_PSFB); g_return_if_fail (packet->rtcp != NULL); g_return_if_fail (packet->rtcp->map.flags & GST_MAP_READ); data = packet->rtcp->map.data; /* skip header */ data += packet->offset + 4; GST_WRITE_UINT32_BE (data, ssrc); } /** * gst_rtcp_packet_fb_get_media_ssrc: * @packet: a valid RTPFB or PSFB #GstRTCPPacket * * Get the media SSRC field of the RTPFB or PSFB @packet. * * Returns: the media SSRC. */ guint32 gst_rtcp_packet_fb_get_media_ssrc (GstRTCPPacket * packet) { guint8 *data; guint32 ssrc; g_return_val_if_fail (packet != NULL, 0); g_return_val_if_fail ((packet->type == GST_RTCP_TYPE_RTPFB || packet->type == GST_RTCP_TYPE_PSFB), 0); g_return_val_if_fail (packet->rtcp != NULL, 0); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, 0); data = packet->rtcp->map.data; /* skip header and sender ssrc */ data += packet->offset + 8; ssrc = GST_READ_UINT32_BE (data); return ssrc; } /** * gst_rtcp_packet_fb_set_media_ssrc: * @packet: a valid RTPFB or PSFB #GstRTCPPacket * @ssrc: a media SSRC * * Set the media SSRC field of the RTPFB or PSFB @packet. */ void gst_rtcp_packet_fb_set_media_ssrc (GstRTCPPacket * packet, guint32 ssrc) { guint8 *data; g_return_if_fail (packet != NULL); g_return_if_fail (packet->type == GST_RTCP_TYPE_RTPFB || packet->type == GST_RTCP_TYPE_PSFB); g_return_if_fail (packet->rtcp != NULL); g_return_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE); data = packet->rtcp->map.data; /* skip header and sender ssrc */ data += packet->offset + 8; GST_WRITE_UINT32_BE (data, ssrc); } /** * gst_rtcp_packet_fb_get_type: * @packet: a valid RTPFB or PSFB #GstRTCPPacket * * Get the feedback message type of the FB @packet. * * Returns: The feedback message type. */ GstRTCPFBType gst_rtcp_packet_fb_get_type (GstRTCPPacket * packet) { g_return_val_if_fail (packet != NULL, GST_RTCP_FB_TYPE_INVALID); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_RTPFB || packet->type == GST_RTCP_TYPE_PSFB, GST_RTCP_FB_TYPE_INVALID); return packet->count; } /** * gst_rtcp_packet_fb_set_type: * @packet: a valid RTPFB or PSFB #GstRTCPPacket * @type: the #GstRTCPFBType to set * * Set the feedback message type of the FB @packet. */ void gst_rtcp_packet_fb_set_type (GstRTCPPacket * packet, GstRTCPFBType type) { guint8 *data; g_return_if_fail (packet != NULL); g_return_if_fail (packet->type == GST_RTCP_TYPE_RTPFB || packet->type == GST_RTCP_TYPE_PSFB); g_return_if_fail (packet->rtcp != NULL); g_return_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE); data = packet->rtcp->map.data; data[packet->offset] = (data[packet->offset] & 0xe0) | type; packet->count = type; } /** * gst_rtcp_ntp_to_unix: * @ntptime: an NTP timestamp * * Converts an NTP time to UNIX nanoseconds. @ntptime can typically be * the NTP time of an SR RTCP message and contains, in the upper 32 bits, the * number of seconds since 1900 and, in the lower 32 bits, the fractional * seconds. The resulting value will be the number of nanoseconds since 1970. * * Returns: the UNIX time for @ntptime in nanoseconds. */ guint64 gst_rtcp_ntp_to_unix (guint64 ntptime) { guint64 unixtime; /* conversion from NTP timestamp (seconds since 1900) to seconds since * 1970. */ unixtime = ntptime - (G_GUINT64_CONSTANT (2208988800) << 32); /* conversion to nanoseconds */ unixtime = gst_util_uint64_scale (unixtime, GST_SECOND, (G_GINT64_CONSTANT (1) << 32)); return unixtime; } /** * gst_rtcp_unix_to_ntp: * @unixtime: an UNIX timestamp in nanoseconds * * Converts a UNIX timestamp in nanoseconds to an NTP time. The caller should * pass a value with nanoseconds since 1970. The NTP time will, in the upper * 32 bits, contain the number of seconds since 1900 and, in the lower 32 * bits, the fractional seconds. The resulting value can be used as an ntptime * for constructing SR RTCP packets. * * Returns: the NTP time for @unixtime. */ guint64 gst_rtcp_unix_to_ntp (guint64 unixtime) { guint64 ntptime; /* convert clock time to NTP time. upper 32 bits should contain the seconds * and the lower 32 bits, the fractions of a second. */ ntptime = gst_util_uint64_scale (unixtime, (G_GINT64_CONSTANT (1) << 32), GST_SECOND); /* conversion from UNIX timestamp (seconds since 1970) to NTP (seconds * since 1900). */ ntptime += (G_GUINT64_CONSTANT (2208988800) << 32); return ntptime; } /** * gst_rtcp_sdes_type_to_name: * @type: a #GstRTCPSDESType * * Converts @type to the string equivalent. The string is typically used as a * key in a #GstStructure containing SDES items. * * Returns: the string equivalent of @type */ const gchar * gst_rtcp_sdes_type_to_name (GstRTCPSDESType type) { const gchar *result; switch (type) { case GST_RTCP_SDES_CNAME: result = "cname"; break; case GST_RTCP_SDES_NAME: result = "name"; break; case GST_RTCP_SDES_EMAIL: result = "email"; break; case GST_RTCP_SDES_PHONE: result = "phone"; break; case GST_RTCP_SDES_LOC: result = "location"; break; case GST_RTCP_SDES_TOOL: result = "tool"; break; case GST_RTCP_SDES_NOTE: result = "note"; break; case GST_RTCP_SDES_PRIV: result = "priv"; break; default: result = NULL; break; } return result; } /** * gst_rtcp_sdes_name_to_type: * @name: a SDES name * * Convert @name into a @GstRTCPSDESType. @name is typically a key in a * #GstStructure containing SDES items. * * Returns: the #GstRTCPSDESType for @name or #GST_RTCP_SDES_PRIV when @name * is a private sdes item. */ GstRTCPSDESType gst_rtcp_sdes_name_to_type (const gchar * name) { if (name == NULL || strlen (name) == 0) return GST_RTCP_SDES_INVALID; if (strcmp ("cname", name) == 0) return GST_RTCP_SDES_CNAME; if (strcmp ("name", name) == 0) return GST_RTCP_SDES_NAME; if (strcmp ("email", name) == 0) return GST_RTCP_SDES_EMAIL; if (strcmp ("phone", name) == 0) return GST_RTCP_SDES_PHONE; if (strcmp ("location", name) == 0) return GST_RTCP_SDES_LOC; if (strcmp ("tool", name) == 0) return GST_RTCP_SDES_TOOL; if (strcmp ("note", name) == 0) return GST_RTCP_SDES_NOTE; return GST_RTCP_SDES_PRIV; } /** * gst_rtcp_packet_fb_get_fci_length: * @packet: a valid RTPFB or PSFB #GstRTCPPacket * * Get the length of the Feedback Control Information attached to a * RTPFB or PSFB @packet. * * Returns: The length of the FCI in 32-bit words. */ guint16 gst_rtcp_packet_fb_get_fci_length (GstRTCPPacket * packet) { guint8 *data; g_return_val_if_fail (packet != NULL, 0); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_RTPFB || packet->type == GST_RTCP_TYPE_PSFB, 0); g_return_val_if_fail (packet->rtcp != NULL, 0); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, 0); data = packet->rtcp->map.data + packet->offset + 2; return GST_READ_UINT16_BE (data) - 2; } /** * gst_rtcp_packet_fb_set_fci_length: * @packet: a valid RTPFB or PSFB #GstRTCPPacket * @wordlen: Length of the FCI in 32-bit words * * Set the length of the Feedback Control Information attached to a * RTPFB or PSFB @packet. * * Returns: %TRUE if there was enough space in the packet to add this much FCI */ gboolean gst_rtcp_packet_fb_set_fci_length (GstRTCPPacket * packet, guint16 wordlen) { guint8 *data; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_RTPFB || packet->type == GST_RTCP_TYPE_PSFB, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE, FALSE); if (packet->rtcp->map.maxsize < packet->offset + ((wordlen + 3) * 4)) return FALSE; data = packet->rtcp->map.data + packet->offset + 2; wordlen += 2; GST_WRITE_UINT16_BE (data, wordlen); packet->rtcp->map.size = packet->offset + ((wordlen + 1) * 4); return TRUE; } /** * gst_rtcp_packet_fb_get_fci: * @packet: a valid RTPFB or PSFB #GstRTCPPacket * * Get the Feedback Control Information attached to a RTPFB or PSFB @packet. * * Returns: a pointer to the FCI */ guint8 * gst_rtcp_packet_fb_get_fci (GstRTCPPacket * packet) { guint8 *data; g_return_val_if_fail (packet != NULL, NULL); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_RTPFB || packet->type == GST_RTCP_TYPE_PSFB, NULL); g_return_val_if_fail (packet->rtcp != NULL, NULL); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, NULL); data = packet->rtcp->map.data + packet->offset; if (GST_READ_UINT16_BE (data + 2) <= 2) return NULL; return data + 12; } /** * gst_rtcp_packet_app_set_subtype: * @packet: a valid APP #GstRTCPPacket * @subtype: subtype of the packet * * Set the subtype field of the APP @packet. * * Since: 1.10 **/ void gst_rtcp_packet_app_set_subtype (GstRTCPPacket * packet, guint8 subtype) { guint8 *data; g_return_if_fail (packet != NULL); g_return_if_fail (packet->type == GST_RTCP_TYPE_APP); g_return_if_fail (packet->rtcp != NULL); g_return_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE); data = packet->rtcp->map.data + packet->offset; data[0] = (data[0] & 0xe0) | subtype; } /** * gst_rtcp_packet_app_get_subtype: * @packet: a valid APP #GstRTCPPacket * * Get the subtype field of the APP @packet. * * Returns: The subtype. * * Since: 1.10 */ guint8 gst_rtcp_packet_app_get_subtype (GstRTCPPacket * packet) { guint8 *data; g_return_val_if_fail (packet != NULL, 0); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_APP, 0); g_return_val_if_fail (packet->rtcp != NULL, 0); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, 0); data = packet->rtcp->map.data + packet->offset; return data[0] & 0x1f; } /** * gst_rtcp_packet_app_set_ssrc: * @packet: a valid APP #GstRTCPPacket * @ssrc: SSRC/CSRC of the packet * * Set the SSRC/CSRC field of the APP @packet. * * Since: 1.10 */ void gst_rtcp_packet_app_set_ssrc (GstRTCPPacket * packet, guint32 ssrc) { guint8 *data; g_return_if_fail (packet != NULL); g_return_if_fail (packet->type == GST_RTCP_TYPE_APP); g_return_if_fail (packet->rtcp != NULL); g_return_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE); data = packet->rtcp->map.data + packet->offset + 4; GST_WRITE_UINT32_BE (data, ssrc); } /** * gst_rtcp_packet_app_get_ssrc: * @packet: a valid APP #GstRTCPPacket * * Get the SSRC/CSRC field of the APP @packet. * * Returns: The SSRC/CSRC. * * Since: 1.10 */ guint32 gst_rtcp_packet_app_get_ssrc (GstRTCPPacket * packet) { guint8 *data; g_return_val_if_fail (packet != NULL, 0); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_APP, 0); g_return_val_if_fail (packet->rtcp != NULL, 0); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, 0); data = packet->rtcp->map.data + packet->offset + 4; return GST_READ_UINT32_BE (data); } /** * gst_rtcp_packet_app_set_name: * @packet: a valid APP #GstRTCPPacket * @name: 4-byte ASCII name * * Set the name field of the APP @packet. * * Since: 1.10 */ void gst_rtcp_packet_app_set_name (GstRTCPPacket * packet, const gchar * name) { guint8 *data; g_return_if_fail (packet != NULL); g_return_if_fail (packet->type == GST_RTCP_TYPE_APP); g_return_if_fail (packet->rtcp != NULL); g_return_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE); data = packet->rtcp->map.data + packet->offset + 8; memcpy (data, name, 4); } /** * gst_rtcp_packet_app_get_name: * @packet: a valid APP #GstRTCPPacket * * Get the name field of the APP @packet. * * Returns: The 4-byte name field, not zero-terminated. * * Since: 1.10 */ const gchar * gst_rtcp_packet_app_get_name (GstRTCPPacket * packet) { g_return_val_if_fail (packet != NULL, NULL); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_APP, NULL); g_return_val_if_fail (packet->rtcp != NULL, NULL); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, NULL); return (const gchar *) &packet->rtcp->map.data[packet->offset + 8]; } /** * gst_rtcp_packet_app_get_data_length: * @packet: a valid APP #GstRTCPPacket * * Get the length of the application-dependent data attached to an APP * @packet. * * Returns: The length of data in 32-bit words. * * Since: 1.10 */ guint16 gst_rtcp_packet_app_get_data_length (GstRTCPPacket * packet) { guint8 *data; g_return_val_if_fail (packet != NULL, 0); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_APP, 0); g_return_val_if_fail (packet->rtcp != NULL, 0); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, 0); data = packet->rtcp->map.data + packet->offset + 2; return GST_READ_UINT16_BE (data) - 2; } /** * gst_rtcp_packet_app_set_data_length: * @packet: a valid APP #GstRTCPPacket * @wordlen: Length of the data in 32-bit words * * Set the length of the application-dependent data attached to an APP * @packet. * * Returns: %TRUE if there was enough space in the packet to add this much * data. * * Since: 1.10 */ gboolean gst_rtcp_packet_app_set_data_length (GstRTCPPacket * packet, guint16 wordlen) { guint8 *data; g_return_val_if_fail (packet != NULL, FALSE); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_APP, FALSE); g_return_val_if_fail (packet->rtcp != NULL, FALSE); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_WRITE, FALSE); if (packet->rtcp->map.maxsize < packet->offset + ((wordlen + 3) * 4)) return FALSE; data = packet->rtcp->map.data + packet->offset + 2; wordlen += 2; GST_WRITE_UINT16_BE (data, wordlen); packet->rtcp->map.size = packet->offset + ((wordlen + 1) * 4); return TRUE; } /** * gst_rtcp_packet_app_get_data: * @packet: a valid APP #GstRTCPPacket * * Get the application-dependent data attached to a RTPFB or PSFB @packet. * * Returns: A pointer to the data * * Since: 1.10 */ guint8 * gst_rtcp_packet_app_get_data (GstRTCPPacket * packet) { guint8 *data; g_return_val_if_fail (packet != NULL, NULL); g_return_val_if_fail (packet->type == GST_RTCP_TYPE_APP, NULL); g_return_val_if_fail (packet->rtcp != NULL, NULL); g_return_val_if_fail (packet->rtcp->map.flags & GST_MAP_READ, NULL); data = packet->rtcp->map.data + packet->offset; if (GST_READ_UINT16_BE (data + 2) <= 2) return NULL; return data + 12; }