mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-22 16:26:39 +00:00
a0f1b964f1
The payloader didn't copy anything so far, the depayloader copied every possible meta. Let's make it consistent and just copy all metas without tags or with only the audio tag. https://bugzilla.gnome.org/show_bug.cgi?id=751774
1020 lines
31 KiB
C
1020 lines
31 KiB
C
/* GStreamer
|
|
* Copyright (C) <2006> Philippe Khalaf <philippe.kalaf@collabora.co.uk>
|
|
*
|
|
* 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:gstrtpbaseaudiopayload
|
|
* @short_description: Base class for audio RTP payloader
|
|
*
|
|
* Provides a base class for audio RTP payloaders for frame or sample based
|
|
* audio codecs (constant bitrate)
|
|
*
|
|
* This class derives from GstRTPBasePayload. It can be used for payloading
|
|
* audio codecs. It will only work with constant bitrate codecs. It supports
|
|
* both frame based and sample based codecs. It takes care of packing up the
|
|
* audio data into RTP packets and filling up the headers accordingly. The
|
|
* payloading is done based on the maximum MTU (mtu) and the maximum time per
|
|
* packet (max-ptime). The general idea is to divide large data buffers into
|
|
* smaller RTP packets. The RTP packet size is the minimum of either the MTU,
|
|
* max-ptime (if set) or available data. The RTP packet size is always larger or
|
|
* equal to min-ptime (if set). If min-ptime is not set, any residual data is
|
|
* sent in a last RTP packet. In the case of frame based codecs, the resulting
|
|
* RTP packets always contain full frames.
|
|
*
|
|
* <refsect2>
|
|
* <title>Usage</title>
|
|
* <para>
|
|
* To use this base class, your child element needs to call either
|
|
* gst_rtp_base_audio_payload_set_frame_based() or
|
|
* gst_rtp_base_audio_payload_set_sample_based(). This is usually done in the
|
|
* element's _init() function. Then, the child element must call either
|
|
* gst_rtp_base_audio_payload_set_frame_options(),
|
|
* gst_rtp_base_audio_payload_set_sample_options() or
|
|
* gst_rtp_base_audio_payload_set_samplebits_options. Since
|
|
* GstRTPBaseAudioPayload derives from GstRTPBasePayload, the child element
|
|
* must set any variables or call/override any functions required by that base
|
|
* class. The child element does not need to override any other functions
|
|
* specific to GstRTPBaseAudioPayload.
|
|
* </para>
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <gst/rtp/gstrtpbuffer.h>
|
|
#include <gst/base/gstadapter.h>
|
|
#include <gst/audio/audio.h>
|
|
|
|
#include "gstrtpbaseaudiopayload.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (rtpbaseaudiopayload_debug);
|
|
#define GST_CAT_DEFAULT (rtpbaseaudiopayload_debug)
|
|
|
|
#define DEFAULT_BUFFER_LIST FALSE
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_BUFFER_LIST,
|
|
PROP_LAST
|
|
};
|
|
|
|
/* function to convert bytes to a time */
|
|
typedef GstClockTime (*GetBytesToTimeFunc) (GstRTPBaseAudioPayload * payload,
|
|
guint64 bytes);
|
|
/* function to convert bytes to a RTP time */
|
|
typedef guint32 (*GetBytesToRTPTimeFunc) (GstRTPBaseAudioPayload * payload,
|
|
guint64 bytes);
|
|
/* function to convert time to bytes */
|
|
typedef guint64 (*GetTimeToBytesFunc) (GstRTPBaseAudioPayload * payload,
|
|
GstClockTime time);
|
|
|
|
struct _GstRTPBaseAudioPayloadPrivate
|
|
{
|
|
GetBytesToTimeFunc bytes_to_time;
|
|
GetBytesToRTPTimeFunc bytes_to_rtptime;
|
|
GetTimeToBytesFunc time_to_bytes;
|
|
|
|
GstAdapter *adapter;
|
|
guint fragment_size;
|
|
GstClockTime frame_duration_ns;
|
|
gboolean discont;
|
|
guint64 offset;
|
|
GstClockTime last_timestamp;
|
|
guint32 last_rtptime;
|
|
guint align;
|
|
|
|
guint cached_mtu;
|
|
guint cached_min_ptime;
|
|
guint cached_max_ptime;
|
|
guint cached_ptime;
|
|
guint cached_min_length;
|
|
guint cached_max_length;
|
|
guint cached_ptime_multiple;
|
|
guint cached_align;
|
|
|
|
gboolean buffer_list;
|
|
};
|
|
|
|
|
|
#define GST_RTP_BASE_AUDIO_PAYLOAD_GET_PRIVATE(o) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE ((o), GST_TYPE_RTP_BASE_AUDIO_PAYLOAD, \
|
|
GstRTPBaseAudioPayloadPrivate))
|
|
|
|
static void gst_rtp_base_audio_payload_finalize (GObject * object);
|
|
|
|
static void gst_rtp_base_audio_payload_set_property (GObject * object,
|
|
guint prop_id, const GValue * value, GParamSpec * pspec);
|
|
static void gst_rtp_base_audio_payload_get_property (GObject * object,
|
|
guint prop_id, GValue * value, GParamSpec * pspec);
|
|
|
|
/* bytes to time functions */
|
|
static GstClockTime
|
|
gst_rtp_base_audio_payload_frame_bytes_to_time (GstRTPBaseAudioPayload *
|
|
payload, guint64 bytes);
|
|
static GstClockTime
|
|
gst_rtp_base_audio_payload_sample_bytes_to_time (GstRTPBaseAudioPayload *
|
|
payload, guint64 bytes);
|
|
|
|
/* bytes to RTP time functions */
|
|
static guint32
|
|
gst_rtp_base_audio_payload_frame_bytes_to_rtptime (GstRTPBaseAudioPayload *
|
|
payload, guint64 bytes);
|
|
static guint32
|
|
gst_rtp_base_audio_payload_sample_bytes_to_rtptime (GstRTPBaseAudioPayload *
|
|
payload, guint64 bytes);
|
|
|
|
/* time to bytes functions */
|
|
static guint64
|
|
gst_rtp_base_audio_payload_frame_time_to_bytes (GstRTPBaseAudioPayload *
|
|
payload, GstClockTime time);
|
|
static guint64
|
|
gst_rtp_base_audio_payload_sample_time_to_bytes (GstRTPBaseAudioPayload *
|
|
payload, GstClockTime time);
|
|
|
|
static GstFlowReturn gst_rtp_base_audio_payload_handle_buffer (GstRTPBasePayload
|
|
* payload, GstBuffer * buffer);
|
|
static GstStateChangeReturn gst_rtp_base_payload_audio_change_state (GstElement
|
|
* element, GstStateChange transition);
|
|
static gboolean gst_rtp_base_payload_audio_sink_event (GstRTPBasePayload
|
|
* payload, GstEvent * event);
|
|
|
|
#define gst_rtp_base_audio_payload_parent_class parent_class
|
|
G_DEFINE_TYPE (GstRTPBaseAudioPayload, gst_rtp_base_audio_payload,
|
|
GST_TYPE_RTP_BASE_PAYLOAD);
|
|
|
|
static void
|
|
gst_rtp_base_audio_payload_class_init (GstRTPBaseAudioPayloadClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
GstRTPBasePayloadClass *gstrtpbasepayload_class;
|
|
|
|
g_type_class_add_private (klass, sizeof (GstRTPBaseAudioPayloadPrivate));
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstelement_class = (GstElementClass *) klass;
|
|
gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
|
|
|
|
gobject_class->finalize = gst_rtp_base_audio_payload_finalize;
|
|
gobject_class->set_property = gst_rtp_base_audio_payload_set_property;
|
|
gobject_class->get_property = gst_rtp_base_audio_payload_get_property;
|
|
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BUFFER_LIST,
|
|
g_param_spec_boolean ("buffer-list", "Buffer List",
|
|
"Use Buffer Lists",
|
|
DEFAULT_BUFFER_LIST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
gstelement_class->change_state =
|
|
GST_DEBUG_FUNCPTR (gst_rtp_base_payload_audio_change_state);
|
|
|
|
gstrtpbasepayload_class->handle_buffer =
|
|
GST_DEBUG_FUNCPTR (gst_rtp_base_audio_payload_handle_buffer);
|
|
gstrtpbasepayload_class->sink_event =
|
|
GST_DEBUG_FUNCPTR (gst_rtp_base_payload_audio_sink_event);
|
|
|
|
GST_DEBUG_CATEGORY_INIT (rtpbaseaudiopayload_debug, "rtpbaseaudiopayload", 0,
|
|
"base audio RTP payloader");
|
|
}
|
|
|
|
static void
|
|
gst_rtp_base_audio_payload_init (GstRTPBaseAudioPayload * payload)
|
|
{
|
|
payload->priv = GST_RTP_BASE_AUDIO_PAYLOAD_GET_PRIVATE (payload);
|
|
|
|
/* these need to be set by child object if frame based */
|
|
payload->frame_size = 0;
|
|
payload->frame_duration = 0;
|
|
|
|
/* these need to be set by child object if sample based */
|
|
payload->sample_size = 0;
|
|
|
|
payload->priv->adapter = gst_adapter_new ();
|
|
|
|
payload->priv->buffer_list = DEFAULT_BUFFER_LIST;
|
|
}
|
|
|
|
static void
|
|
gst_rtp_base_audio_payload_finalize (GObject * object)
|
|
{
|
|
GstRTPBaseAudioPayload *payload;
|
|
|
|
payload = GST_RTP_BASE_AUDIO_PAYLOAD (object);
|
|
|
|
g_object_unref (payload->priv->adapter);
|
|
|
|
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
|
|
}
|
|
|
|
static void
|
|
gst_rtp_base_audio_payload_set_property (GObject * object,
|
|
guint prop_id, const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstRTPBaseAudioPayload *payload;
|
|
|
|
payload = GST_RTP_BASE_AUDIO_PAYLOAD (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_BUFFER_LIST:
|
|
#if 0
|
|
payload->priv->buffer_list = g_value_get_boolean (value);
|
|
#endif
|
|
payload->priv->buffer_list = FALSE;
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_rtp_base_audio_payload_get_property (GObject * object,
|
|
guint prop_id, GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstRTPBaseAudioPayload *payload;
|
|
|
|
payload = GST_RTP_BASE_AUDIO_PAYLOAD (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_BUFFER_LIST:
|
|
g_value_set_boolean (value, payload->priv->buffer_list);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_rtp_base_audio_payload_set_frame_based:
|
|
* @rtpbaseaudiopayload: a pointer to the element.
|
|
*
|
|
* Tells #GstRTPBaseAudioPayload that the child element is for a frame based
|
|
* audio codec
|
|
*/
|
|
void
|
|
gst_rtp_base_audio_payload_set_frame_based (GstRTPBaseAudioPayload *
|
|
rtpbaseaudiopayload)
|
|
{
|
|
g_return_if_fail (rtpbaseaudiopayload != NULL);
|
|
g_return_if_fail (rtpbaseaudiopayload->priv->time_to_bytes == NULL);
|
|
g_return_if_fail (rtpbaseaudiopayload->priv->bytes_to_time == NULL);
|
|
g_return_if_fail (rtpbaseaudiopayload->priv->bytes_to_rtptime == NULL);
|
|
|
|
rtpbaseaudiopayload->priv->bytes_to_time =
|
|
gst_rtp_base_audio_payload_frame_bytes_to_time;
|
|
rtpbaseaudiopayload->priv->bytes_to_rtptime =
|
|
gst_rtp_base_audio_payload_frame_bytes_to_rtptime;
|
|
rtpbaseaudiopayload->priv->time_to_bytes =
|
|
gst_rtp_base_audio_payload_frame_time_to_bytes;
|
|
}
|
|
|
|
/**
|
|
* gst_rtp_base_audio_payload_set_sample_based:
|
|
* @rtpbaseaudiopayload: a pointer to the element.
|
|
*
|
|
* Tells #GstRTPBaseAudioPayload that the child element is for a sample based
|
|
* audio codec
|
|
*/
|
|
void
|
|
gst_rtp_base_audio_payload_set_sample_based (GstRTPBaseAudioPayload *
|
|
rtpbaseaudiopayload)
|
|
{
|
|
g_return_if_fail (rtpbaseaudiopayload != NULL);
|
|
g_return_if_fail (rtpbaseaudiopayload->priv->time_to_bytes == NULL);
|
|
g_return_if_fail (rtpbaseaudiopayload->priv->bytes_to_time == NULL);
|
|
g_return_if_fail (rtpbaseaudiopayload->priv->bytes_to_rtptime == NULL);
|
|
|
|
rtpbaseaudiopayload->priv->bytes_to_time =
|
|
gst_rtp_base_audio_payload_sample_bytes_to_time;
|
|
rtpbaseaudiopayload->priv->bytes_to_rtptime =
|
|
gst_rtp_base_audio_payload_sample_bytes_to_rtptime;
|
|
rtpbaseaudiopayload->priv->time_to_bytes =
|
|
gst_rtp_base_audio_payload_sample_time_to_bytes;
|
|
}
|
|
|
|
/**
|
|
* gst_rtp_base_audio_payload_set_frame_options:
|
|
* @rtpbaseaudiopayload: a pointer to the element.
|
|
* @frame_duration: The duraction of an audio frame in milliseconds.
|
|
* @frame_size: The size of an audio frame in bytes.
|
|
*
|
|
* Sets the options for frame based audio codecs.
|
|
*
|
|
*/
|
|
void
|
|
gst_rtp_base_audio_payload_set_frame_options (GstRTPBaseAudioPayload
|
|
* rtpbaseaudiopayload, gint frame_duration, gint frame_size)
|
|
{
|
|
GstRTPBaseAudioPayloadPrivate *priv;
|
|
|
|
g_return_if_fail (rtpbaseaudiopayload != NULL);
|
|
|
|
priv = rtpbaseaudiopayload->priv;
|
|
|
|
rtpbaseaudiopayload->frame_duration = frame_duration;
|
|
priv->frame_duration_ns = frame_duration * GST_MSECOND;
|
|
rtpbaseaudiopayload->frame_size = frame_size;
|
|
priv->align = frame_size;
|
|
|
|
gst_adapter_clear (priv->adapter);
|
|
|
|
GST_DEBUG_OBJECT (rtpbaseaudiopayload, "frame set to %d ms and size %d",
|
|
frame_duration, frame_size);
|
|
}
|
|
|
|
/**
|
|
* gst_rtp_base_audio_payload_set_sample_options:
|
|
* @rtpbaseaudiopayload: a pointer to the element.
|
|
* @sample_size: Size per sample in bytes.
|
|
*
|
|
* Sets the options for sample based audio codecs.
|
|
*/
|
|
void
|
|
gst_rtp_base_audio_payload_set_sample_options (GstRTPBaseAudioPayload
|
|
* rtpbaseaudiopayload, gint sample_size)
|
|
{
|
|
g_return_if_fail (rtpbaseaudiopayload != NULL);
|
|
|
|
/* sample_size is in bits internally */
|
|
gst_rtp_base_audio_payload_set_samplebits_options (rtpbaseaudiopayload,
|
|
sample_size * 8);
|
|
}
|
|
|
|
/**
|
|
* gst_rtp_base_audio_payload_set_samplebits_options:
|
|
* @rtpbaseaudiopayload: a pointer to the element.
|
|
* @sample_size: Size per sample in bits.
|
|
*
|
|
* Sets the options for sample based audio codecs.
|
|
*/
|
|
void
|
|
gst_rtp_base_audio_payload_set_samplebits_options (GstRTPBaseAudioPayload
|
|
* rtpbaseaudiopayload, gint sample_size)
|
|
{
|
|
guint fragment_size;
|
|
GstRTPBaseAudioPayloadPrivate *priv;
|
|
|
|
g_return_if_fail (rtpbaseaudiopayload != NULL);
|
|
|
|
priv = rtpbaseaudiopayload->priv;
|
|
|
|
rtpbaseaudiopayload->sample_size = sample_size;
|
|
|
|
/* sample_size is in bits and is converted into multiple bytes */
|
|
fragment_size = sample_size;
|
|
while ((fragment_size % 8) != 0)
|
|
fragment_size += fragment_size;
|
|
priv->fragment_size = fragment_size / 8;
|
|
priv->align = priv->fragment_size;
|
|
|
|
gst_adapter_clear (priv->adapter);
|
|
|
|
GST_DEBUG_OBJECT (rtpbaseaudiopayload,
|
|
"Samplebits set to sample size %d bits", sample_size);
|
|
}
|
|
|
|
static void
|
|
gst_rtp_base_audio_payload_set_meta (GstRTPBaseAudioPayload * payload,
|
|
GstBuffer * buffer, guint payload_len, GstClockTime timestamp)
|
|
{
|
|
GstRTPBasePayload *basepayload;
|
|
GstRTPBaseAudioPayloadPrivate *priv;
|
|
GstRTPBuffer rtp = { NULL };
|
|
|
|
basepayload = GST_RTP_BASE_PAYLOAD_CAST (payload);
|
|
priv = payload->priv;
|
|
|
|
/* set payload type */
|
|
gst_rtp_buffer_map (buffer, GST_MAP_WRITE, &rtp);
|
|
gst_rtp_buffer_set_payload_type (&rtp, basepayload->pt);
|
|
/* set marker bit for disconts */
|
|
if (priv->discont) {
|
|
GST_DEBUG_OBJECT (payload, "Setting marker and DISCONT");
|
|
gst_rtp_buffer_set_marker (&rtp, TRUE);
|
|
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
|
|
priv->discont = FALSE;
|
|
}
|
|
gst_rtp_buffer_unmap (&rtp);
|
|
|
|
GST_BUFFER_PTS (buffer) = timestamp;
|
|
|
|
/* get the offset in RTP time */
|
|
GST_BUFFER_OFFSET (buffer) = priv->bytes_to_rtptime (payload, priv->offset);
|
|
|
|
priv->offset += payload_len;
|
|
|
|
/* Set the duration from the size */
|
|
GST_BUFFER_DURATION (buffer) = priv->bytes_to_time (payload, payload_len);
|
|
|
|
/* remember the last rtptime/timestamp pair. We will use this to realign our
|
|
* RTP timestamp after a buffer discont */
|
|
priv->last_rtptime = GST_BUFFER_OFFSET (buffer);
|
|
priv->last_timestamp = timestamp;
|
|
}
|
|
|
|
/**
|
|
* gst_rtp_base_audio_payload_push:
|
|
* @baseaudiopayload: a #GstRTPBasePayload
|
|
* @data: data to set as payload
|
|
* @payload_len: length of payload
|
|
* @timestamp: a #GstClockTime
|
|
*
|
|
* Create an RTP buffer and store @payload_len bytes of @data as the
|
|
* payload. Set the timestamp on the new buffer to @timestamp before pushing
|
|
* the buffer downstream.
|
|
*
|
|
* Returns: a #GstFlowReturn
|
|
*/
|
|
GstFlowReturn
|
|
gst_rtp_base_audio_payload_push (GstRTPBaseAudioPayload * baseaudiopayload,
|
|
const guint8 * data, guint payload_len, GstClockTime timestamp)
|
|
{
|
|
GstRTPBasePayload *basepayload;
|
|
GstBuffer *outbuf;
|
|
guint8 *payload;
|
|
GstFlowReturn ret;
|
|
GstRTPBuffer rtp = { NULL };
|
|
|
|
basepayload = GST_RTP_BASE_PAYLOAD (baseaudiopayload);
|
|
|
|
GST_DEBUG_OBJECT (baseaudiopayload, "Pushing %d bytes ts %" GST_TIME_FORMAT,
|
|
payload_len, GST_TIME_ARGS (timestamp));
|
|
|
|
/* create buffer to hold the payload */
|
|
outbuf = gst_rtp_buffer_new_allocate (payload_len, 0, 0);
|
|
|
|
/* copy payload */
|
|
gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
|
|
payload = gst_rtp_buffer_get_payload (&rtp);
|
|
memcpy (payload, data, payload_len);
|
|
gst_rtp_buffer_unmap (&rtp);
|
|
|
|
/* set metadata */
|
|
gst_rtp_base_audio_payload_set_meta (baseaudiopayload, outbuf, payload_len,
|
|
timestamp);
|
|
|
|
ret = gst_rtp_base_payload_push (basepayload, outbuf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GstRTPBaseAudioPayload *pay;
|
|
GstBuffer *outbuf;
|
|
} CopyMetaData;
|
|
|
|
static gboolean
|
|
foreach_metadata (GstBuffer * inbuf, GstMeta ** meta, gpointer user_data)
|
|
{
|
|
CopyMetaData *data = user_data;
|
|
GstRTPBaseAudioPayload *pay = data->pay;
|
|
GstBuffer *outbuf = data->outbuf;
|
|
const GstMetaInfo *info = (*meta)->info;
|
|
const gchar *const *tags = gst_meta_api_type_get_tags (info->api);
|
|
|
|
if (!tags || (g_strv_length ((gchar **) tags) == 1
|
|
&& gst_meta_api_type_has_tag (info->api,
|
|
g_quark_from_string (GST_META_TAG_AUDIO_STR)))) {
|
|
GstMetaTransformCopy copy_data = { FALSE, 0, -1 };
|
|
GST_DEBUG_OBJECT (pay, "copy metadata %s", g_type_name (info->api));
|
|
/* simply copy then */
|
|
info->transform_func (outbuf, *meta, inbuf,
|
|
_gst_meta_transform_copy, ©_data);
|
|
} else {
|
|
GST_DEBUG_OBJECT (pay, "not copying metadata %s", g_type_name (info->api));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_rtp_base_audio_payload_push_buffer (GstRTPBaseAudioPayload *
|
|
baseaudiopayload, GstBuffer * buffer, GstClockTime timestamp)
|
|
{
|
|
GstRTPBasePayload *basepayload;
|
|
GstRTPBaseAudioPayloadPrivate *priv;
|
|
GstBuffer *outbuf;
|
|
guint payload_len;
|
|
GstFlowReturn ret;
|
|
|
|
priv = baseaudiopayload->priv;
|
|
basepayload = GST_RTP_BASE_PAYLOAD (baseaudiopayload);
|
|
|
|
payload_len = gst_buffer_get_size (buffer);
|
|
|
|
GST_DEBUG_OBJECT (baseaudiopayload, "Pushing %d bytes ts %" GST_TIME_FORMAT,
|
|
payload_len, GST_TIME_ARGS (timestamp));
|
|
|
|
/* create just the RTP header buffer */
|
|
outbuf = gst_rtp_buffer_new_allocate (0, 0, 0);
|
|
|
|
/* set metadata */
|
|
gst_rtp_base_audio_payload_set_meta (baseaudiopayload, outbuf, payload_len,
|
|
timestamp);
|
|
|
|
if (priv->buffer_list) {
|
|
GstBufferList *list;
|
|
guint i, len;
|
|
|
|
list = gst_buffer_list_new ();
|
|
len = gst_buffer_list_length (list);
|
|
|
|
for (i = 0; i < len; i++) {
|
|
/* FIXME */
|
|
g_warning ("bufferlist not implemented");
|
|
gst_buffer_list_add (list, outbuf);
|
|
gst_buffer_list_add (list, buffer);
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (baseaudiopayload, "Pushing list %p", list);
|
|
ret = gst_rtp_base_payload_push_list (basepayload, list);
|
|
} else {
|
|
CopyMetaData data;
|
|
|
|
/* copy payload */
|
|
data.pay = baseaudiopayload;
|
|
data.outbuf = outbuf;
|
|
gst_buffer_foreach_meta (buffer, foreach_metadata, &data);
|
|
outbuf = gst_buffer_append (outbuf, buffer);
|
|
|
|
GST_DEBUG_OBJECT (baseaudiopayload, "Pushing buffer %p", outbuf);
|
|
ret = gst_rtp_base_payload_push (basepayload, outbuf);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* gst_rtp_base_audio_payload_flush:
|
|
* @baseaudiopayload: a #GstRTPBasePayload
|
|
* @payload_len: length of payload
|
|
* @timestamp: a #GstClockTime
|
|
*
|
|
* Create an RTP buffer and store @payload_len bytes of the adapter as the
|
|
* payload. Set the timestamp on the new buffer to @timestamp before pushing
|
|
* the buffer downstream.
|
|
*
|
|
* If @payload_len is -1, all pending bytes will be flushed. If @timestamp is
|
|
* -1, the timestamp will be calculated automatically.
|
|
*
|
|
* Returns: a #GstFlowReturn
|
|
*/
|
|
GstFlowReturn
|
|
gst_rtp_base_audio_payload_flush (GstRTPBaseAudioPayload * baseaudiopayload,
|
|
guint payload_len, GstClockTime timestamp)
|
|
{
|
|
GstRTPBasePayload *basepayload;
|
|
GstRTPBaseAudioPayloadPrivate *priv;
|
|
GstBuffer *outbuf;
|
|
GstFlowReturn ret;
|
|
GstAdapter *adapter;
|
|
guint64 distance;
|
|
|
|
priv = baseaudiopayload->priv;
|
|
adapter = priv->adapter;
|
|
|
|
basepayload = GST_RTP_BASE_PAYLOAD (baseaudiopayload);
|
|
|
|
if (payload_len == -1)
|
|
payload_len = gst_adapter_available (adapter);
|
|
|
|
/* nothing to do, just return */
|
|
if (payload_len == 0)
|
|
return GST_FLOW_OK;
|
|
|
|
if (timestamp == -1) {
|
|
/* calculate the timestamp */
|
|
timestamp = gst_adapter_prev_pts (adapter, &distance);
|
|
|
|
GST_LOG_OBJECT (baseaudiopayload,
|
|
"last timestamp %" GST_TIME_FORMAT ", distance %" G_GUINT64_FORMAT,
|
|
GST_TIME_ARGS (timestamp), distance);
|
|
|
|
if (GST_CLOCK_TIME_IS_VALID (timestamp) && distance > 0) {
|
|
/* convert the number of bytes since the last timestamp to time and add to
|
|
* the last seen timestamp */
|
|
timestamp += priv->bytes_to_time (baseaudiopayload, distance);
|
|
}
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (baseaudiopayload, "Pushing %d bytes ts %" GST_TIME_FORMAT,
|
|
payload_len, GST_TIME_ARGS (timestamp));
|
|
|
|
if (priv->buffer_list && gst_adapter_available_fast (adapter) >= payload_len) {
|
|
GstBuffer *buffer;
|
|
/* we can quickly take a buffer out of the adapter without having to copy
|
|
* anything. */
|
|
buffer = gst_adapter_take_buffer (adapter, payload_len);
|
|
|
|
ret =
|
|
gst_rtp_base_audio_payload_push_buffer (baseaudiopayload, buffer,
|
|
timestamp);
|
|
} else {
|
|
GstBuffer *paybuf;
|
|
CopyMetaData data;
|
|
|
|
|
|
/* create buffer to hold the payload */
|
|
outbuf = gst_rtp_buffer_new_allocate (0, 0, 0);
|
|
|
|
paybuf = gst_adapter_take_buffer_fast (adapter, payload_len);
|
|
|
|
data.pay = baseaudiopayload;
|
|
data.outbuf = outbuf;
|
|
gst_buffer_foreach_meta (paybuf, foreach_metadata, &data);
|
|
outbuf = gst_buffer_append (outbuf, paybuf);
|
|
|
|
/* set metadata */
|
|
gst_rtp_base_audio_payload_set_meta (baseaudiopayload, outbuf, payload_len,
|
|
timestamp);
|
|
|
|
ret = gst_rtp_base_payload_push (basepayload, outbuf);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define ALIGN_DOWN(val,len) ((val) - ((val) % (len)))
|
|
|
|
/* calculate the min and max length of a packet. This depends on the configured
|
|
* mtu and min/max_ptime values. We cache those so that we don't have to redo
|
|
* all the calculations */
|
|
static gboolean
|
|
gst_rtp_base_audio_payload_get_lengths (GstRTPBasePayload *
|
|
basepayload, guint * min_payload_len, guint * max_payload_len,
|
|
guint * align)
|
|
{
|
|
GstRTPBaseAudioPayload *payload;
|
|
GstRTPBaseAudioPayloadPrivate *priv;
|
|
guint max_mtu, mtu;
|
|
guint maxptime_octets;
|
|
guint minptime_octets;
|
|
guint ptime_mult_octets;
|
|
|
|
payload = GST_RTP_BASE_AUDIO_PAYLOAD_CAST (basepayload);
|
|
priv = payload->priv;
|
|
|
|
if (priv->align == 0)
|
|
return FALSE;
|
|
|
|
mtu = GST_RTP_BASE_PAYLOAD_MTU (payload);
|
|
|
|
/* check cached values */
|
|
if (G_LIKELY (priv->cached_mtu == mtu
|
|
&& priv->cached_ptime_multiple ==
|
|
basepayload->ptime_multiple
|
|
&& priv->cached_ptime == basepayload->ptime
|
|
&& priv->cached_max_ptime == basepayload->max_ptime
|
|
&& priv->cached_min_ptime == basepayload->min_ptime)) {
|
|
/* if nothing changed, return cached values */
|
|
*min_payload_len = priv->cached_min_length;
|
|
*max_payload_len = priv->cached_max_length;
|
|
*align = priv->cached_align;
|
|
return TRUE;
|
|
}
|
|
|
|
ptime_mult_octets = priv->time_to_bytes (payload,
|
|
basepayload->ptime_multiple);
|
|
*align = ALIGN_DOWN (MAX (priv->align, ptime_mult_octets), priv->align);
|
|
|
|
/* ptime max */
|
|
if (basepayload->max_ptime != -1) {
|
|
maxptime_octets = priv->time_to_bytes (payload, basepayload->max_ptime);
|
|
} else {
|
|
maxptime_octets = G_MAXUINT;
|
|
}
|
|
/* MTU max */
|
|
max_mtu = gst_rtp_buffer_calc_payload_len (mtu, 0, 0);
|
|
/* round down to alignment */
|
|
max_mtu = ALIGN_DOWN (max_mtu, *align);
|
|
|
|
/* combine max ptime and max payload length */
|
|
*max_payload_len = MIN (max_mtu, maxptime_octets);
|
|
|
|
/* min number of bytes based on a given ptime */
|
|
minptime_octets = priv->time_to_bytes (payload, basepayload->min_ptime);
|
|
/* must be at least one frame size */
|
|
*min_payload_len = MAX (minptime_octets, *align);
|
|
|
|
if (*min_payload_len > *max_payload_len)
|
|
*min_payload_len = *max_payload_len;
|
|
|
|
/* If the ptime is specified in the caps, tried to adhere to it exactly */
|
|
if (basepayload->ptime) {
|
|
guint ptime_in_bytes = priv->time_to_bytes (payload,
|
|
basepayload->ptime);
|
|
|
|
/* clip to computed min and max lengths */
|
|
ptime_in_bytes = MAX (*min_payload_len, ptime_in_bytes);
|
|
ptime_in_bytes = MIN (*max_payload_len, ptime_in_bytes);
|
|
|
|
*min_payload_len = *max_payload_len = ptime_in_bytes;
|
|
}
|
|
|
|
/* cache values */
|
|
priv->cached_mtu = mtu;
|
|
priv->cached_ptime = basepayload->ptime;
|
|
priv->cached_min_ptime = basepayload->min_ptime;
|
|
priv->cached_max_ptime = basepayload->max_ptime;
|
|
priv->cached_ptime_multiple = basepayload->ptime_multiple;
|
|
priv->cached_min_length = *min_payload_len;
|
|
priv->cached_max_length = *max_payload_len;
|
|
priv->cached_align = *align;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* frame conversions functions */
|
|
static GstClockTime
|
|
gst_rtp_base_audio_payload_frame_bytes_to_time (GstRTPBaseAudioPayload *
|
|
payload, guint64 bytes)
|
|
{
|
|
guint64 framecount;
|
|
|
|
framecount = bytes / payload->frame_size;
|
|
if (G_UNLIKELY (bytes % payload->frame_size))
|
|
framecount++;
|
|
|
|
return framecount * payload->priv->frame_duration_ns;
|
|
}
|
|
|
|
static guint32
|
|
gst_rtp_base_audio_payload_frame_bytes_to_rtptime (GstRTPBaseAudioPayload *
|
|
payload, guint64 bytes)
|
|
{
|
|
guint64 framecount;
|
|
guint64 time;
|
|
|
|
framecount = bytes / payload->frame_size;
|
|
if (G_UNLIKELY (bytes % payload->frame_size))
|
|
framecount++;
|
|
|
|
time = framecount * payload->priv->frame_duration_ns;
|
|
|
|
return gst_util_uint64_scale_int (time,
|
|
GST_RTP_BASE_PAYLOAD (payload)->clock_rate, GST_SECOND);
|
|
}
|
|
|
|
static guint64
|
|
gst_rtp_base_audio_payload_frame_time_to_bytes (GstRTPBaseAudioPayload *
|
|
payload, GstClockTime time)
|
|
{
|
|
return gst_util_uint64_scale (time, payload->frame_size,
|
|
payload->priv->frame_duration_ns);
|
|
}
|
|
|
|
/* sample conversion functions */
|
|
static GstClockTime
|
|
gst_rtp_base_audio_payload_sample_bytes_to_time (GstRTPBaseAudioPayload *
|
|
payload, guint64 bytes)
|
|
{
|
|
guint64 rtptime;
|
|
|
|
/* avoid division when we can */
|
|
if (G_LIKELY (payload->sample_size != 8))
|
|
rtptime = gst_util_uint64_scale_int (bytes, 8, payload->sample_size);
|
|
else
|
|
rtptime = bytes;
|
|
|
|
return gst_util_uint64_scale_int (rtptime, GST_SECOND,
|
|
GST_RTP_BASE_PAYLOAD (payload)->clock_rate);
|
|
}
|
|
|
|
static guint32
|
|
gst_rtp_base_audio_payload_sample_bytes_to_rtptime (GstRTPBaseAudioPayload *
|
|
payload, guint64 bytes)
|
|
{
|
|
/* avoid division when we can */
|
|
if (G_LIKELY (payload->sample_size != 8))
|
|
return gst_util_uint64_scale_int (bytes, 8, payload->sample_size);
|
|
else
|
|
return bytes;
|
|
}
|
|
|
|
static guint64
|
|
gst_rtp_base_audio_payload_sample_time_to_bytes (GstRTPBaseAudioPayload *
|
|
payload, guint64 time)
|
|
{
|
|
guint64 samples;
|
|
|
|
samples = gst_util_uint64_scale_int (time,
|
|
GST_RTP_BASE_PAYLOAD (payload)->clock_rate, GST_SECOND);
|
|
|
|
/* avoid multiplication when we can */
|
|
if (G_LIKELY (payload->sample_size != 8))
|
|
return gst_util_uint64_scale_int (samples, payload->sample_size, 8);
|
|
else
|
|
return samples;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_rtp_base_audio_payload_handle_buffer (GstRTPBasePayload *
|
|
basepayload, GstBuffer * buffer)
|
|
{
|
|
GstRTPBaseAudioPayload *payload;
|
|
GstRTPBaseAudioPayloadPrivate *priv;
|
|
guint payload_len;
|
|
GstFlowReturn ret;
|
|
guint available;
|
|
guint min_payload_len;
|
|
guint max_payload_len;
|
|
guint align;
|
|
guint size;
|
|
gboolean discont;
|
|
GstClockTime timestamp;
|
|
|
|
ret = GST_FLOW_OK;
|
|
|
|
payload = GST_RTP_BASE_AUDIO_PAYLOAD_CAST (basepayload);
|
|
priv = payload->priv;
|
|
|
|
timestamp = GST_BUFFER_PTS (buffer);
|
|
discont = GST_BUFFER_IS_DISCONT (buffer);
|
|
if (discont) {
|
|
|
|
GST_DEBUG_OBJECT (payload, "Got DISCONT");
|
|
/* flush everything out of the adapter, mark DISCONT */
|
|
ret = gst_rtp_base_audio_payload_flush (payload, -1, -1);
|
|
priv->discont = TRUE;
|
|
|
|
/* get the distance between the timestamp gap and produce the same gap in
|
|
* the RTP timestamps */
|
|
if (priv->last_timestamp != -1 && timestamp != -1) {
|
|
/* we had a last timestamp, compare it to the new timestamp and update the
|
|
* offset counter for RTP timestamps. The effect is that we will produce
|
|
* output buffers containing the same RTP timestamp gap as the gap
|
|
* between the GST timestamps. */
|
|
if (timestamp > priv->last_timestamp) {
|
|
GstClockTime diff;
|
|
guint64 bytes;
|
|
/* we're only going to apply a positive gap, otherwise we let the marker
|
|
* bit do its thing. simply convert to bytes and add the current
|
|
* offset */
|
|
diff = timestamp - priv->last_timestamp;
|
|
bytes = priv->time_to_bytes (payload, diff);
|
|
priv->offset += bytes;
|
|
|
|
GST_DEBUG_OBJECT (payload,
|
|
"elapsed time %" GST_TIME_FORMAT ", bytes %" G_GUINT64_FORMAT
|
|
", new offset %" G_GUINT64_FORMAT, GST_TIME_ARGS (diff), bytes,
|
|
priv->offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!gst_rtp_base_audio_payload_get_lengths (basepayload, &min_payload_len,
|
|
&max_payload_len, &align))
|
|
goto config_error;
|
|
|
|
GST_DEBUG_OBJECT (payload,
|
|
"Calculated min_payload_len %u and max_payload_len %u",
|
|
min_payload_len, max_payload_len);
|
|
|
|
size = gst_buffer_get_size (buffer);
|
|
|
|
/* shortcut, we don't need to use the adapter when the packet can be pushed
|
|
* through directly. */
|
|
available = gst_adapter_available (priv->adapter);
|
|
|
|
GST_DEBUG_OBJECT (payload, "got buffer size %u, available %u",
|
|
size, available);
|
|
|
|
if (available == 0 && (size >= min_payload_len && size <= max_payload_len) &&
|
|
(size % align == 0)) {
|
|
/* If buffer fits on an RTP packet, let's just push it through
|
|
* this will check against max_ptime and max_mtu */
|
|
GST_DEBUG_OBJECT (payload, "Fast packet push");
|
|
ret = gst_rtp_base_audio_payload_push_buffer (payload, buffer, timestamp);
|
|
} else {
|
|
/* push the buffer in the adapter */
|
|
gst_adapter_push (priv->adapter, buffer);
|
|
available += size;
|
|
|
|
GST_DEBUG_OBJECT (payload, "available now %u", available);
|
|
|
|
/* as long as we have full frames */
|
|
/* TODO: Use buffer lists here */
|
|
while (available >= min_payload_len) {
|
|
/* get multiple of alignment */
|
|
payload_len = MIN (max_payload_len, available);
|
|
payload_len = ALIGN_DOWN (payload_len, align);
|
|
|
|
/* and flush out the bytes from the adapter, automatically set the
|
|
* timestamp. */
|
|
ret = gst_rtp_base_audio_payload_flush (payload, payload_len, -1);
|
|
|
|
available -= payload_len;
|
|
GST_DEBUG_OBJECT (payload, "available after push %u", available);
|
|
}
|
|
}
|
|
return ret;
|
|
|
|
/* ERRORS */
|
|
config_error:
|
|
{
|
|
GST_ELEMENT_ERROR (payload, STREAM, NOT_IMPLEMENTED, (NULL),
|
|
("subclass did not configure us properly"));
|
|
gst_buffer_unref (buffer);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_rtp_base_payload_audio_change_state (GstElement * element,
|
|
GstStateChange transition)
|
|
{
|
|
GstRTPBaseAudioPayload *rtpbasepayload;
|
|
GstStateChangeReturn ret;
|
|
|
|
rtpbasepayload = GST_RTP_BASE_AUDIO_PAYLOAD (element);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_READY_TO_PAUSED:
|
|
rtpbasepayload->priv->cached_mtu = -1;
|
|
rtpbasepayload->priv->last_rtptime = -1;
|
|
rtpbasepayload->priv->last_timestamp = -1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
gst_adapter_clear (rtpbasepayload->priv->adapter);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_rtp_base_payload_audio_sink_event (GstRTPBasePayload * basep,
|
|
GstEvent * event)
|
|
{
|
|
GstRTPBaseAudioPayload *payload;
|
|
gboolean res = FALSE;
|
|
|
|
payload = GST_RTP_BASE_AUDIO_PAYLOAD (basep);
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_EOS:
|
|
/* flush remaining bytes in the adapter */
|
|
gst_rtp_base_audio_payload_flush (payload, -1, -1);
|
|
break;
|
|
case GST_EVENT_FLUSH_STOP:
|
|
gst_adapter_clear (payload->priv->adapter);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* let parent handle the remainder of the event */
|
|
res = GST_RTP_BASE_PAYLOAD_CLASS (parent_class)->sink_event (basep, event);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gst_rtp_base_audio_payload_get_adapter:
|
|
* @rtpbaseaudiopayload: a #GstRTPBaseAudioPayload
|
|
*
|
|
* Gets the internal adapter used by the depayloader.
|
|
*
|
|
* Returns: (transfer full): a #GstAdapter.
|
|
*/
|
|
GstAdapter *
|
|
gst_rtp_base_audio_payload_get_adapter (GstRTPBaseAudioPayload
|
|
* rtpbaseaudiopayload)
|
|
{
|
|
GstAdapter *adapter;
|
|
|
|
if ((adapter = rtpbaseaudiopayload->priv->adapter))
|
|
g_object_ref (adapter);
|
|
|
|
return adapter;
|
|
}
|