gstreamer/gst/rtp/gstrtpsbcpay.c
Arun Raghavan 10a16a6321 rtpsbcpay: Fix timestamping
We were just picking the timestamp of the last buffer pushed into our
adapter before we had enough data to push out.

This fixes things to figure out how large each frame is and what
duration it covers, so we can set both the timestamp and duration
correctly.

Also adds some DISCONT handling.
2016-09-25 01:20:14 +05:30

377 lines
11 KiB
C

/* GStreamer RTP SBC payloader
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <gst/audio/audio.h>
#include "gstrtpsbcpay.h"
#include <math.h>
#include <string.h>
#include "gstrtputils.h"
#define RTP_SBC_PAYLOAD_HEADER_SIZE 1
#define DEFAULT_MIN_FRAMES 0
#define RTP_SBC_HEADER_TOTAL (12 + RTP_SBC_PAYLOAD_HEADER_SIZE)
/* BEGIN: Packing for rtp_payload */
#ifdef _MSC_VER
#pragma pack(push, 1)
#endif
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
/* FIXME: this seems all a bit over the top for a single byte.. */
struct rtp_payload
{
guint8 frame_count:4;
guint8 rfa0:1;
guint8 is_last_fragment:1;
guint8 is_first_fragment:1;
guint8 is_fragmented:1;
}
#elif G_BYTE_ORDER == G_BIG_ENDIAN
struct rtp_payload
{
guint8 is_fragmented:1;
guint8 is_first_fragment:1;
guint8 is_last_fragment:1;
guint8 rfa0:1;
guint8 frame_count:4;
}
#else
#error "Unknown byte order"
#endif
#ifdef _MSC_VER
;
#pragma pack(pop)
#else
__attribute__ ((packed));
#endif
/* END: Packing for rtp_payload */
enum
{
PROP_0,
PROP_MIN_FRAMES
};
GST_DEBUG_CATEGORY_STATIC (gst_rtp_sbc_pay_debug);
#define GST_CAT_DEFAULT gst_rtp_sbc_pay_debug
#define parent_class gst_rtp_sbc_pay_parent_class
G_DEFINE_TYPE (GstRtpSBCPay, gst_rtp_sbc_pay, GST_TYPE_RTP_BASE_PAYLOAD);
static GstStaticPadTemplate gst_rtp_sbc_pay_sink_factory =
GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-sbc, "
"rate = (int) { 16000, 32000, 44100, 48000 }, "
"channels = (int) [ 1, 2 ], "
"channel-mode = (string) { mono, dual, stereo, joint }, "
"blocks = (int) { 4, 8, 12, 16 }, "
"subbands = (int) { 4, 8 }, "
"allocation-method = (string) { snr, loudness }, "
"bitpool = (int) [ 2, 64 ]")
);
static GstStaticPadTemplate gst_rtp_sbc_pay_src_factory =
GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
GST_STATIC_CAPS ("application/x-rtp, "
"media = (string) audio,"
"payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
"clock-rate = (int) { 16000, 32000, 44100, 48000 },"
"encoding-name = (string) SBC")
);
static void gst_rtp_sbc_pay_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_rtp_sbc_pay_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static gint
gst_rtp_sbc_pay_get_frame_len (gint subbands, gint channels,
gint blocks, gint bitpool, const gchar * channel_mode)
{
gint len;
gint join;
len = 4 + (4 * subbands * channels) / 8;
if (strcmp (channel_mode, "mono") == 0 || strcmp (channel_mode, "dual") == 0)
len += ((blocks * channels * bitpool) + 7) / 8;
else {
join = strcmp (channel_mode, "joint") == 0 ? 1 : 0;
len += ((join * subbands + blocks * bitpool) + 7) / 8;
}
return len;
}
static gboolean
gst_rtp_sbc_pay_set_caps (GstRTPBasePayload * payload, GstCaps * caps)
{
GstRtpSBCPay *sbcpay;
gint rate, subbands, channels, blocks, bitpool;
gint frame_len;
const gchar *channel_mode;
GstStructure *structure;
sbcpay = GST_RTP_SBC_PAY (payload);
structure = gst_caps_get_structure (caps, 0);
if (!gst_structure_get_int (structure, "rate", &rate))
return FALSE;
if (!gst_structure_get_int (structure, "channels", &channels))
return FALSE;
if (!gst_structure_get_int (structure, "blocks", &blocks))
return FALSE;
if (!gst_structure_get_int (structure, "bitpool", &bitpool))
return FALSE;
if (!gst_structure_get_int (structure, "subbands", &subbands))
return FALSE;
channel_mode = gst_structure_get_string (structure, "channel-mode");
if (!channel_mode)
return FALSE;
frame_len = gst_rtp_sbc_pay_get_frame_len (subbands, channels, blocks,
bitpool, channel_mode);
sbcpay->frame_length = frame_len;
sbcpay->frame_duration = ((blocks * subbands) * GST_SECOND) / rate;
sbcpay->last_timestamp = GST_CLOCK_TIME_NONE;
gst_rtp_base_payload_set_options (payload, "audio", TRUE, "SBC", rate);
GST_DEBUG_OBJECT (payload, "calculated frame length: %d ", frame_len);
return gst_rtp_base_payload_set_outcaps (payload, NULL);
}
static GstFlowReturn
gst_rtp_sbc_pay_flush_buffers (GstRtpSBCPay * sbcpay)
{
GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
guint available;
guint max_payload;
GstBuffer *outbuf, *paybuf;
guint8 *payload_data;
guint frame_count;
guint payload_length;
struct rtp_payload *payload;
if (sbcpay->frame_length == 0) {
GST_ERROR_OBJECT (sbcpay, "Frame length is 0");
return GST_FLOW_ERROR;
}
available = gst_adapter_available (sbcpay->adapter);
max_payload =
gst_rtp_buffer_calc_payload_len (GST_RTP_BASE_PAYLOAD_MTU (sbcpay) -
RTP_SBC_PAYLOAD_HEADER_SIZE, 0, 0);
max_payload = MIN (max_payload, available);
frame_count = max_payload / sbcpay->frame_length;
payload_length = frame_count * sbcpay->frame_length;
if (payload_length == 0) /* Nothing to send */
return GST_FLOW_OK;
outbuf = gst_rtp_buffer_new_allocate (RTP_SBC_PAYLOAD_HEADER_SIZE, 0, 0);
/* get payload */
gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
gst_rtp_buffer_set_payload_type (&rtp, GST_RTP_BASE_PAYLOAD_PT (sbcpay));
/* write header and copy data into payload */
payload_data = gst_rtp_buffer_get_payload (&rtp);
payload = (struct rtp_payload *) payload_data;
memset (payload, 0, sizeof (struct rtp_payload));
payload->frame_count = frame_count;
gst_rtp_buffer_unmap (&rtp);
paybuf = gst_adapter_take_buffer_fast (sbcpay->adapter, payload_length);
gst_rtp_copy_meta (GST_ELEMENT_CAST (sbcpay), outbuf, paybuf,
g_quark_from_static_string (GST_META_TAG_AUDIO_STR));
outbuf = gst_buffer_append (outbuf, paybuf);
GST_BUFFER_PTS (outbuf) = sbcpay->last_timestamp;
GST_BUFFER_DURATION (outbuf) = frame_count * sbcpay->frame_duration;
GST_DEBUG_OBJECT (sbcpay, "Pushing %d bytes: %" GST_TIME_FORMAT,
payload_length, GST_TIME_ARGS (GST_BUFFER_PTS (outbuf)));
sbcpay->last_timestamp += frame_count * sbcpay->frame_duration;
return gst_rtp_base_payload_push (GST_RTP_BASE_PAYLOAD (sbcpay), outbuf);
}
static GstFlowReturn
gst_rtp_sbc_pay_handle_buffer (GstRTPBasePayload * payload, GstBuffer * buffer)
{
GstRtpSBCPay *sbcpay;
guint available;
/* FIXME check for negotiation */
sbcpay = GST_RTP_SBC_PAY (payload);
if (GST_BUFFER_IS_DISCONT (buffer)) {
/* Try to flush whatever's left */
gst_rtp_sbc_pay_flush_buffers (sbcpay);
/* Drop the rest */
gst_adapter_flush (sbcpay->adapter,
gst_adapter_available (sbcpay->adapter));
/* Reset timestamps */
sbcpay->last_timestamp = GST_CLOCK_TIME_NONE;
}
if (sbcpay->last_timestamp == GST_CLOCK_TIME_NONE)
sbcpay->last_timestamp = GST_BUFFER_PTS (buffer);
gst_adapter_push (sbcpay->adapter, buffer);
available = gst_adapter_available (sbcpay->adapter);
if (available + RTP_SBC_HEADER_TOTAL >=
GST_RTP_BASE_PAYLOAD_MTU (sbcpay) ||
(available > (sbcpay->min_frames * sbcpay->frame_length)))
return gst_rtp_sbc_pay_flush_buffers (sbcpay);
return GST_FLOW_OK;
}
static gboolean
gst_rtp_sbc_pay_sink_event (GstRTPBasePayload * payload, GstEvent * event)
{
GstRtpSBCPay *sbcpay = GST_RTP_SBC_PAY (payload);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS:
gst_rtp_sbc_pay_flush_buffers (sbcpay);
break;
default:
break;
}
return GST_RTP_BASE_PAYLOAD_CLASS (parent_class)->sink_event (payload, event);
}
static void
gst_rtp_sbc_pay_finalize (GObject * object)
{
GstRtpSBCPay *sbcpay = GST_RTP_SBC_PAY (object);
g_object_unref (sbcpay->adapter);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static void
gst_rtp_sbc_pay_class_init (GstRtpSBCPayClass * klass)
{
GstRTPBasePayloadClass *payload_class = GST_RTP_BASE_PAYLOAD_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = gst_rtp_sbc_pay_finalize;
gobject_class->set_property = gst_rtp_sbc_pay_set_property;
gobject_class->get_property = gst_rtp_sbc_pay_get_property;
payload_class->set_caps = GST_DEBUG_FUNCPTR (gst_rtp_sbc_pay_set_caps);
payload_class->handle_buffer =
GST_DEBUG_FUNCPTR (gst_rtp_sbc_pay_handle_buffer);
payload_class->sink_event = GST_DEBUG_FUNCPTR (gst_rtp_sbc_pay_sink_event);
/* properties */
g_object_class_install_property (G_OBJECT_CLASS (klass),
PROP_MIN_FRAMES,
g_param_spec_int ("min-frames", "minimum frame number",
"Minimum quantity of frames to send in one packet "
"(-1 for maximum allowed by the mtu)",
-1, G_MAXINT, DEFAULT_MIN_FRAMES, G_PARAM_READWRITE));
gst_element_class_add_static_pad_template (element_class,
&gst_rtp_sbc_pay_sink_factory);
gst_element_class_add_static_pad_template (element_class,
&gst_rtp_sbc_pay_src_factory);
gst_element_class_set_static_metadata (element_class, "RTP packet payloader",
"Codec/Payloader/Network", "Payload SBC audio as RTP packets",
"Thiago Sousa Santos <thiagoss@lcc.ufcg.edu.br>");
GST_DEBUG_CATEGORY_INIT (gst_rtp_sbc_pay_debug, "rtpsbcpay", 0,
"RTP SBC payloader");
}
static void
gst_rtp_sbc_pay_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstRtpSBCPay *sbcpay;
sbcpay = GST_RTP_SBC_PAY (object);
switch (prop_id) {
case PROP_MIN_FRAMES:
sbcpay->min_frames = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_rtp_sbc_pay_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstRtpSBCPay *sbcpay;
sbcpay = GST_RTP_SBC_PAY (object);
switch (prop_id) {
case PROP_MIN_FRAMES:
g_value_set_int (value, sbcpay->min_frames);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_rtp_sbc_pay_init (GstRtpSBCPay * self)
{
self->adapter = gst_adapter_new ();
self->frame_length = 0;
self->last_timestamp = GST_CLOCK_TIME_NONE;
self->min_frames = DEFAULT_MIN_FRAMES;
}
gboolean
gst_rtp_sbc_pay_plugin_init (GstPlugin * plugin)
{
return gst_element_register (plugin, "rtpsbcpay", GST_RANK_NONE,
GST_TYPE_RTP_SBC_PAY);
}