mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-25 11:11:08 +00:00
388 lines
12 KiB
C
388 lines
12 KiB
C
|
/*
|
||
|
* GStreamer AVTP Plugin
|
||
|
* Copyright (C) 2019 Intel Corporation
|
||
|
*
|
||
|
* 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 Street, Fifth Floor,
|
||
|
* Boston, MA 02110-1301 USA
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* SECTION:element-avtpaafpay
|
||
|
* @see_also: avtpaafdepay
|
||
|
*
|
||
|
* Payload raw audio into AVTPDUs according to IEEE 1722-2016. For detailed
|
||
|
* information see https://standards.ieee.org/standard/1722-2016.html.
|
||
|
*
|
||
|
* <refsect2>
|
||
|
* <title>Example pipeline</title>
|
||
|
* |[
|
||
|
* gst-launch-1.0 audiotestsrc ! audioconvert ! avtpaafpay ! avtpsink
|
||
|
* ]| This example pipeline will payload raw audio. Refer to the avtpaafdepay
|
||
|
* example to depayload and play the AVTP stream.
|
||
|
* </refsect2>
|
||
|
*/
|
||
|
|
||
|
#include <avtp.h>
|
||
|
#include <avtp_aaf.h>
|
||
|
#include <gst/audio/audio-format.h>
|
||
|
|
||
|
#include "gstavtpaafpay.h"
|
||
|
|
||
|
GST_DEBUG_CATEGORY_STATIC (avtpaafpay_debug);
|
||
|
#define GST_CAT_DEFAULT (avtpaafpay_debug)
|
||
|
|
||
|
#define DEFAULT_TSTAMP_MODE GST_AVTP_AAF_TSTAMP_MODE_NORMAL
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
PROP_0,
|
||
|
PROP_TSTAMP_MODE,
|
||
|
};
|
||
|
|
||
|
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
|
||
|
GST_PAD_SINK,
|
||
|
GST_PAD_ALWAYS,
|
||
|
GST_STATIC_CAPS ("audio/x-raw, "
|
||
|
"format = (string) { S16BE, S24BE, S32BE, F32BE }, "
|
||
|
"rate = (int) { 8000, 16000, 24000, 32000, 44100, 48000, 88200, 96000, 176400, 192000 }, "
|
||
|
"channels = " GST_AUDIO_CHANNELS_RANGE ", "
|
||
|
"layout = (string) interleaved")
|
||
|
);
|
||
|
|
||
|
#define GST_TYPE_AVTP_AAF_TSTAMP_MODE (gst_avtp_aaf_tstamp_mode_get_type())
|
||
|
static GType
|
||
|
gst_avtp_aaf_tstamp_mode_get_type (void)
|
||
|
{
|
||
|
static GType tstamp_mode_type = 0;
|
||
|
static const GEnumValue tstamp_mode_types[] = {
|
||
|
{GST_AVTP_AAF_TSTAMP_MODE_NORMAL, "Normal timestamping mode", "normal"},
|
||
|
{GST_AVTP_AAF_TSTAMP_MODE_SPARSE, "Sparse timestamping mode", "sparse"},
|
||
|
{0, NULL, NULL},
|
||
|
};
|
||
|
|
||
|
tstamp_mode_type =
|
||
|
g_enum_register_static ("GstAvtpAafTstampMode", tstamp_mode_types);
|
||
|
|
||
|
return tstamp_mode_type;
|
||
|
}
|
||
|
|
||
|
#define gst_avtp_aaf_pay_parent_class parent_class
|
||
|
G_DEFINE_TYPE (GstAvtpAafPay, gst_avtp_aaf_pay, GST_TYPE_AVTP_BASE_PAYLOAD);
|
||
|
|
||
|
static void gst_avtp_aaf_pay_set_property (GObject * object, guint prop_id,
|
||
|
const GValue * value, GParamSpec * pspec);
|
||
|
static void gst_avtp_aaf_pay_get_property (GObject * object, guint prop_id,
|
||
|
GValue * value, GParamSpec * pspec);
|
||
|
|
||
|
static GstStateChangeReturn gst_avtp_aaf_pay_change_state (GstElement * element,
|
||
|
GstStateChange transition);
|
||
|
|
||
|
static GstFlowReturn gst_avtp_aaf_pay_chain (GstPad * pad, GstObject * parent,
|
||
|
GstBuffer * buffer);
|
||
|
static gboolean gst_avtp_aaf_pay_sink_event (GstPad * pad, GstObject * parent,
|
||
|
GstEvent * event);
|
||
|
|
||
|
static void
|
||
|
gst_avtp_aaf_pay_class_init (GstAvtpAafPayClass * klass)
|
||
|
{
|
||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
||
|
GstAvtpBasePayloadClass *avtpbasepayload_class =
|
||
|
GST_AVTP_BASE_PAYLOAD_CLASS (klass);
|
||
|
|
||
|
object_class->set_property = gst_avtp_aaf_pay_set_property;
|
||
|
object_class->get_property = gst_avtp_aaf_pay_get_property;
|
||
|
|
||
|
g_object_class_install_property (object_class, PROP_TSTAMP_MODE,
|
||
|
g_param_spec_enum ("tstamp-mode", "Timestamping Mode",
|
||
|
"AAF timestamping mode", GST_TYPE_AVTP_AAF_TSTAMP_MODE,
|
||
|
DEFAULT_TSTAMP_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
||
|
GST_PARAM_MUTABLE_PAUSED));
|
||
|
|
||
|
element_class->change_state =
|
||
|
GST_DEBUG_FUNCPTR (gst_avtp_aaf_pay_change_state);
|
||
|
|
||
|
gst_element_class_add_static_pad_template (element_class, &sink_template);
|
||
|
|
||
|
gst_element_class_set_static_metadata (element_class,
|
||
|
"AVTP Audio Format (AAF) payloader",
|
||
|
"Codec/Payloader/Network/AVTP",
|
||
|
"Payload-encode Raw audio into AAF AVTPDU (IEEE 1722)",
|
||
|
"Andre Guedes <andre.guedes@intel.com>");
|
||
|
|
||
|
avtpbasepayload_class->chain = GST_DEBUG_FUNCPTR (gst_avtp_aaf_pay_chain);
|
||
|
avtpbasepayload_class->sink_event =
|
||
|
GST_DEBUG_FUNCPTR (gst_avtp_aaf_pay_sink_event);
|
||
|
|
||
|
GST_DEBUG_CATEGORY_INIT (avtpaafpay_debug, "avtpaafpay", 0,
|
||
|
"AAF AVTP Payloader");
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_avtp_aaf_pay_init (GstAvtpAafPay * avtpaafpay)
|
||
|
{
|
||
|
avtpaafpay->tstamp_mode = DEFAULT_TSTAMP_MODE;
|
||
|
|
||
|
avtpaafpay->header = NULL;
|
||
|
avtpaafpay->channels = 0;
|
||
|
avtpaafpay->depth = 0;
|
||
|
avtpaafpay->rate = 0;
|
||
|
avtpaafpay->format = 0;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_avtp_aaf_pay_set_property (GObject * object, guint prop_id,
|
||
|
const GValue * value, GParamSpec * pspec)
|
||
|
{
|
||
|
GstAvtpAafPay *avtpaafpay = GST_AVTP_AAF_PAY (object);
|
||
|
|
||
|
GST_DEBUG_OBJECT (avtpaafpay, "prop_id %u", prop_id);
|
||
|
|
||
|
switch (prop_id) {
|
||
|
case PROP_TSTAMP_MODE:
|
||
|
avtpaafpay->tstamp_mode = g_value_get_enum (value);
|
||
|
break;
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_avtp_aaf_pay_get_property (GObject * object, guint prop_id,
|
||
|
GValue * value, GParamSpec * pspec)
|
||
|
{
|
||
|
GstAvtpAafPay *avtpaafpay = GST_AVTP_AAF_PAY (object);
|
||
|
|
||
|
GST_DEBUG_OBJECT (avtpaafpay, "prop_id %u", prop_id);
|
||
|
|
||
|
switch (prop_id) {
|
||
|
case PROP_TSTAMP_MODE:
|
||
|
g_value_set_enum (value, avtpaafpay->tstamp_mode);
|
||
|
break;
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static GstStateChangeReturn
|
||
|
gst_avtp_aaf_pay_change_state (GstElement * element, GstStateChange transition)
|
||
|
{
|
||
|
GstStateChangeReturn ret;
|
||
|
GstAvtpAafPay *avtpaafpay = GST_AVTP_AAF_PAY (element);
|
||
|
|
||
|
GST_DEBUG_OBJECT (avtpaafpay, "transition %d", transition);
|
||
|
|
||
|
switch (transition) {
|
||
|
case GST_STATE_CHANGE_NULL_TO_READY:{
|
||
|
GstMemory *mem;
|
||
|
|
||
|
mem = gst_allocator_alloc (NULL, sizeof (struct avtp_stream_pdu), NULL);
|
||
|
if (!mem) {
|
||
|
GST_ERROR_OBJECT (avtpaafpay, "Failed to allocate GstMemory");
|
||
|
return GST_STATE_CHANGE_FAILURE;
|
||
|
}
|
||
|
avtpaafpay->header = gst_memory_ref (mem);
|
||
|
break;
|
||
|
}
|
||
|
case GST_STATE_CHANGE_READY_TO_PAUSED:{
|
||
|
int res;
|
||
|
GstMapInfo info;
|
||
|
struct avtp_stream_pdu *pdu;
|
||
|
GstMemory *mem = avtpaafpay->header;
|
||
|
GstAvtpBasePayload *avtpbasepayload = GST_AVTP_BASE_PAYLOAD (element);
|
||
|
|
||
|
if (!gst_memory_map (mem, &info, GST_MAP_WRITE)) {
|
||
|
GST_ERROR_OBJECT (avtpaafpay, "Failed to map GstMemory");
|
||
|
return GST_STATE_CHANGE_FAILURE;
|
||
|
}
|
||
|
pdu = (struct avtp_stream_pdu *) info.data;
|
||
|
res = avtp_aaf_pdu_init (pdu);
|
||
|
g_assert (res == 0);
|
||
|
res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_MR, 0);
|
||
|
g_assert (res == 0);
|
||
|
res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_TV, 1);
|
||
|
g_assert (res == 0);
|
||
|
res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_TU, 0);
|
||
|
g_assert (res == 0);
|
||
|
res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_STREAM_ID,
|
||
|
avtpbasepayload->streamid);
|
||
|
g_assert (res == 0);
|
||
|
res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_SP, avtpaafpay->tstamp_mode);
|
||
|
g_assert (res == 0);
|
||
|
gst_memory_unmap (mem, &info);
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
||
|
if (ret == GST_STATE_CHANGE_FAILURE) {
|
||
|
GST_ERROR_OBJECT (avtpaafpay, "Parent failed to handle state transition");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
switch (transition) {
|
||
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
||
|
gst_memory_unref (avtpaafpay->header);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static GstFlowReturn
|
||
|
gst_avtp_aaf_pay_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
|
||
|
{
|
||
|
int res;
|
||
|
GstMemory *mem;
|
||
|
GstMapInfo info;
|
||
|
gsize data_len;
|
||
|
GstClockTime ptime;
|
||
|
struct avtp_stream_pdu *pdu;
|
||
|
GstAvtpAafPay *avtpaafpay = GST_AVTP_AAF_PAY (parent);
|
||
|
GstAvtpBasePayload *avtpbasepayload = GST_AVTP_BASE_PAYLOAD (parent);
|
||
|
|
||
|
ptime = gst_avtp_base_payload_calc_ptime (avtpbasepayload, buffer);
|
||
|
data_len = gst_buffer_get_size (buffer);
|
||
|
|
||
|
mem = gst_memory_copy (avtpaafpay->header, 0, -1);
|
||
|
if (!gst_memory_map (mem, &info, GST_MAP_WRITE)) {
|
||
|
GST_ELEMENT_ERROR (avtpaafpay, RESOURCE, WRITE, ("Failed to map memory"),
|
||
|
(NULL));
|
||
|
gst_buffer_unref (buffer);
|
||
|
return GST_FLOW_ERROR;
|
||
|
}
|
||
|
pdu = (struct avtp_stream_pdu *) info.data;
|
||
|
res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_TIMESTAMP, ptime);
|
||
|
g_assert (res == 0);
|
||
|
res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_NSR, avtpaafpay->rate);
|
||
|
g_assert (res == 0);
|
||
|
res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_FORMAT, avtpaafpay->format);
|
||
|
g_assert (res == 0);
|
||
|
res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_BIT_DEPTH, avtpaafpay->depth);
|
||
|
g_assert (res == 0);
|
||
|
res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_STREAM_DATA_LEN, data_len);
|
||
|
g_assert (res == 0);
|
||
|
res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_CHAN_PER_FRAME,
|
||
|
avtpaafpay->channels);
|
||
|
g_assert (res == 0);
|
||
|
res = avtp_aaf_pdu_set (pdu, AVTP_AAF_FIELD_SEQ_NUM,
|
||
|
avtpbasepayload->seqnum++);
|
||
|
g_assert (res == 0);
|
||
|
gst_memory_unmap (mem, &info);
|
||
|
|
||
|
gst_buffer_prepend_memory (buffer, mem);
|
||
|
return gst_pad_push (avtpbasepayload->srcpad, buffer);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
gst_to_avtp_rate (gint rate)
|
||
|
{
|
||
|
switch (rate) {
|
||
|
case 8000:
|
||
|
return AVTP_AAF_PCM_NSR_8KHZ;
|
||
|
case 16000:
|
||
|
return AVTP_AAF_PCM_NSR_16KHZ;
|
||
|
case 24000:
|
||
|
return AVTP_AAF_PCM_NSR_24KHZ;
|
||
|
case 32000:
|
||
|
return AVTP_AAF_PCM_NSR_32KHZ;
|
||
|
case 44100:
|
||
|
return AVTP_AAF_PCM_NSR_44_1KHZ;
|
||
|
case 48000:
|
||
|
return AVTP_AAF_PCM_NSR_48KHZ;
|
||
|
case 88200:
|
||
|
return AVTP_AAF_PCM_NSR_88_2KHZ;
|
||
|
case 96000:
|
||
|
return AVTP_AAF_PCM_NSR_96KHZ;
|
||
|
case 176400:
|
||
|
return AVTP_AAF_PCM_NSR_176_4KHZ;
|
||
|
case 192000:
|
||
|
return AVTP_AAF_PCM_NSR_192KHZ;
|
||
|
default:
|
||
|
return AVTP_AAF_PCM_NSR_USER;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
gst_to_avtp_format (GstAudioFormat format)
|
||
|
{
|
||
|
switch (format) {
|
||
|
case GST_AUDIO_FORMAT_S16BE:
|
||
|
return AVTP_AAF_FORMAT_INT_16BIT;
|
||
|
case GST_AUDIO_FORMAT_S24BE:
|
||
|
return AVTP_AAF_FORMAT_INT_24BIT;
|
||
|
case GST_AUDIO_FORMAT_S32BE:
|
||
|
return AVTP_AAF_FORMAT_INT_32BIT;
|
||
|
case GST_AUDIO_FORMAT_F32BE:
|
||
|
return AVTP_AAF_FORMAT_FLOAT_32BIT;
|
||
|
default:
|
||
|
return AVTP_AAF_FORMAT_USER;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
gst_avtp_aaf_pay_new_caps (GstAvtpAafPay * avtpaafpay, GstCaps * caps)
|
||
|
{
|
||
|
GstAudioInfo info;
|
||
|
|
||
|
gst_audio_info_init (&info);
|
||
|
if (!gst_audio_info_from_caps (&info, caps)) {
|
||
|
GST_ERROR_OBJECT (avtpaafpay, "Failed to get info from caps");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
avtpaafpay->channels = info.channels;
|
||
|
avtpaafpay->depth = info.finfo->depth;
|
||
|
avtpaafpay->rate = gst_to_avtp_rate (info.rate);
|
||
|
avtpaafpay->format = gst_to_avtp_format (info.finfo->format);
|
||
|
|
||
|
GST_DEBUG_OBJECT (avtpaafpay, "channels %d, depth %d, rate %d, format %s",
|
||
|
info.channels, info.finfo->depth, info.rate,
|
||
|
gst_audio_format_to_string (info.finfo->format));
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
gst_avtp_aaf_pay_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
|
||
|
{
|
||
|
GstCaps *caps;
|
||
|
GstAvtpAafPay *avtpaafpay = GST_AVTP_AAF_PAY (parent);
|
||
|
|
||
|
GST_DEBUG_OBJECT (avtpaafpay, "event %s", GST_EVENT_TYPE_NAME (event));
|
||
|
|
||
|
switch (GST_EVENT_TYPE (event)) {
|
||
|
case GST_EVENT_CAPS:
|
||
|
gst_event_parse_caps (event, &caps);
|
||
|
return gst_avtp_aaf_pay_new_caps (avtpaafpay, caps);
|
||
|
default:
|
||
|
return GST_AVTP_BASE_PAYLOAD_CLASS (parent_class)->sink_event (pad,
|
||
|
parent, event);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
gboolean
|
||
|
gst_avtp_aaf_pay_plugin_init (GstPlugin * plugin)
|
||
|
{
|
||
|
return gst_element_register (plugin, "avtpaafpay", GST_RANK_NONE,
|
||
|
GST_TYPE_AVTP_AAF_PAY);
|
||
|
}
|