mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-24 17:20:36 +00:00
10a16a6321
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.
377 lines
11 KiB
C
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);
|
|
}
|