mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-26 02:00:33 +00:00
b056297eea
Based on `mtu` property, the CVF payloader is now capable of properly fragmenting H.264 NAL units that are bigger than MTU in several AVTP packets. AVTP spec defines two methods for fragmenting H.264 packets, but this patch only generates non-interleaved FU-A fragments. Usually, only the last NAL unit from a group of NAL units in a single buffer will be big enough to be fragmented. Nevertheless, only the last AVTP packet sent for a group of NAL units will have the M bit set (this means that the AVTP packet for the last fragment will only have the M bit set if there's no more NAL units in the group).
601 lines
18 KiB
C
601 lines
18 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-avtpcvfpay
|
|
* @see_also: avtpcvfdepay
|
|
*
|
|
* Payload compressed video (currently, only H.264) 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 videotestsrc ! x264enc ! avtpcvfpay ! avtpsink
|
|
* ]| This example pipeline will payload H.264 video. Refer to the avtpcvfdepay
|
|
* example to depayload and play the AVTP stream.
|
|
* </refsect2>
|
|
*/
|
|
|
|
#include <avtp.h>
|
|
#include <avtp_cvf.h>
|
|
|
|
#include "gstavtpcvfpay.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (avtpcvfpay_debug);
|
|
#define GST_CAT_DEFAULT avtpcvfpay_debug
|
|
|
|
/* prototypes */
|
|
|
|
static GstFlowReturn gst_avtp_cvf_pay_chain (GstPad * pad, GstObject * parent,
|
|
GstBuffer * buffer);
|
|
static gboolean gst_avtp_cvf_pay_sink_event (GstPad * pad, GstObject * parent,
|
|
GstEvent * event);
|
|
|
|
static void gst_avtp_cvf_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_avtp_cvf_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
static GstStateChangeReturn gst_avtp_cvf_change_state (GstElement *
|
|
element, GstStateChange transition);
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_MTU
|
|
};
|
|
|
|
#define DEFAULT_MTU 1500
|
|
|
|
#define AVTP_CVF_H264_HEADER_SIZE (sizeof(struct avtp_stream_pdu) + sizeof(guint32))
|
|
#define FU_A_TYPE 28
|
|
#define FU_A_HEADER_SIZE (sizeof(guint16))
|
|
|
|
#define NRI_MASK 0x60
|
|
#define NRI_SHIFT 5
|
|
#define START_SHIFT 7
|
|
#define END_SHIFT 6
|
|
#define NAL_TYPE_MASK 0x1f
|
|
#define FIRST_NAL_VCL_TYPE 0x01
|
|
#define LAST_NAL_VCL_TYPE 0x05
|
|
#define NAL_LEN_SIZE_MASK 0x03
|
|
|
|
/* pad templates */
|
|
|
|
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("video/x-h264, "
|
|
"stream-format = (string) avc, alignment = (string) au")
|
|
);
|
|
|
|
/* class initialization */
|
|
|
|
#define gst_avtp_cvf_pay_parent_class parent_class
|
|
G_DEFINE_TYPE (GstAvtpCvfPay, gst_avtp_cvf_pay, GST_TYPE_AVTP_BASE_PAYLOAD);
|
|
|
|
static void
|
|
gst_avtp_cvf_pay_class_init (GstAvtpCvfPayClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
|
|
GstAvtpBasePayloadClass *avtpbasepayload_class =
|
|
GST_AVTP_BASE_PAYLOAD_CLASS (klass);
|
|
|
|
gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
|
|
|
|
gst_element_class_set_static_metadata (gstelement_class,
|
|
"AVTP Compressed Video Format (CVF) payloader",
|
|
"Codec/Payloader/Network/AVTP",
|
|
"Payload-encode compressed video into CVF AVTPDU (IEEE 1722)",
|
|
"Ederson de Souza <ederson.desouza@intel.com>");
|
|
|
|
gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_avtp_cvf_set_property);
|
|
gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_avtp_cvf_get_property);
|
|
|
|
gstelement_class->change_state =
|
|
GST_DEBUG_FUNCPTR (gst_avtp_cvf_change_state);
|
|
|
|
avtpbasepayload_class->chain = GST_DEBUG_FUNCPTR (gst_avtp_cvf_pay_chain);
|
|
avtpbasepayload_class->sink_event =
|
|
GST_DEBUG_FUNCPTR (gst_avtp_cvf_pay_sink_event);
|
|
|
|
g_object_class_install_property (gobject_class, PROP_MTU,
|
|
g_param_spec_uint ("mtu", "Maximum Transit Unit",
|
|
"Maximum Transit Unit (MTU) of underlying network in bytes", 0,
|
|
G_MAXUINT, DEFAULT_MTU, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
GST_DEBUG_CATEGORY_INIT (avtpcvfpay_debug, "avtpcvfpay",
|
|
0, "debug category for avtpcvfpay element");
|
|
}
|
|
|
|
static void
|
|
gst_avtp_cvf_pay_init (GstAvtpCvfPay * avtpcvfpay)
|
|
{
|
|
avtpcvfpay->mtu = DEFAULT_MTU;
|
|
avtpcvfpay->header = NULL;
|
|
avtpcvfpay->nal_length_size = 0;
|
|
}
|
|
|
|
static void
|
|
gst_avtp_cvf_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstAvtpCvfPay *avtpcvfpay = GST_AVTP_CVF_PAY (object);
|
|
|
|
GST_DEBUG_OBJECT (avtpcvfpay, "prop_id: %u", prop_id);
|
|
|
|
if (prop_id == PROP_MTU) {
|
|
avtpcvfpay->mtu = g_value_get_uint (value);
|
|
} else {
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_avtp_cvf_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstAvtpCvfPay *avtpcvfpay = GST_AVTP_CVF_PAY (object);
|
|
|
|
GST_DEBUG_OBJECT (avtpcvfpay, "prop_id: %u", prop_id);
|
|
|
|
if (prop_id == PROP_MTU) {
|
|
g_value_set_uint (value, avtpcvfpay->mtu);
|
|
} else {
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_avtp_cvf_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstAvtpCvfPay *avtpcvfpay = GST_AVTP_CVF_PAY (element);
|
|
GstAvtpBasePayload *avtpbasepayload = GST_AVTP_BASE_PAYLOAD (avtpcvfpay);
|
|
GstStateChangeReturn ret;
|
|
|
|
if (transition == GST_STATE_CHANGE_NULL_TO_READY) {
|
|
GstMapInfo map;
|
|
struct avtp_stream_pdu *pdu;
|
|
int res;
|
|
|
|
avtpcvfpay->header = gst_buffer_new_allocate (NULL,
|
|
AVTP_CVF_H264_HEADER_SIZE, NULL);
|
|
if (avtpcvfpay->header == NULL) {
|
|
GST_ERROR_OBJECT (avtpcvfpay, "Could not allocate buffer");
|
|
return GST_STATE_CHANGE_FAILURE;
|
|
}
|
|
|
|
gst_buffer_map (avtpcvfpay->header, &map, GST_MAP_WRITE);
|
|
pdu = (struct avtp_stream_pdu *) map.data;
|
|
|
|
res = avtp_cvf_pdu_init (pdu, AVTP_CVF_FORMAT_SUBTYPE_H264);
|
|
g_assert (res == 0);
|
|
|
|
res =
|
|
avtp_cvf_pdu_set (pdu, AVTP_CVF_FIELD_STREAM_ID,
|
|
avtpbasepayload->streamid);
|
|
g_assert (res == 0);
|
|
|
|
gst_buffer_unmap (avtpcvfpay->header, &map);
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
if (ret == GST_STATE_CHANGE_FAILURE) {
|
|
return ret;
|
|
}
|
|
|
|
if (transition == GST_STATE_CHANGE_READY_TO_NULL) {
|
|
gst_buffer_unref (avtpcvfpay->header);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
gst_avtp_cvf_pay_extract_nals (GstAvtpCvfPay * avtpcvfpay,
|
|
GstBuffer * buffer, GPtrArray * nals)
|
|
{
|
|
/* The buffer may have more than one NAL. They are grouped together, and before
|
|
* each NAL there are some bytes that indicate how big is the NAL */
|
|
|
|
gsize size, offset = 0;
|
|
GstMapInfo map;
|
|
guint8 *data;
|
|
gboolean res;
|
|
|
|
if (G_UNLIKELY (avtpcvfpay->nal_length_size == 0)) {
|
|
GST_ERROR_OBJECT (avtpcvfpay,
|
|
"Can't extract NAL units without nal length size. Missing codec_data caps?");
|
|
goto end;
|
|
}
|
|
|
|
res = gst_buffer_map (buffer, &map, GST_MAP_READ);
|
|
if (!res) {
|
|
GST_ERROR_OBJECT (avtpcvfpay, "Could map buffer");
|
|
goto end;
|
|
}
|
|
|
|
size = map.size;
|
|
data = map.data;
|
|
|
|
while (size > avtpcvfpay->nal_length_size) {
|
|
gint i;
|
|
guint nal_len = 0;
|
|
GstBuffer *nal;
|
|
|
|
/* Gets NAL length */
|
|
for (i = 0; i < avtpcvfpay->nal_length_size; i++) {
|
|
nal_len = (nal_len << 8) + data[i];
|
|
}
|
|
|
|
offset += avtpcvfpay->nal_length_size;
|
|
data += avtpcvfpay->nal_length_size;
|
|
size -= avtpcvfpay->nal_length_size;
|
|
|
|
if (G_UNLIKELY (size < nal_len)) {
|
|
GST_WARNING_OBJECT (avtpcvfpay,
|
|
"Got incomplete NAL: NAL len %u, buffer len %zu", nal_len, size);
|
|
nal_len = size;
|
|
}
|
|
|
|
nal = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL, offset, nal_len);
|
|
GST_BUFFER_PTS (nal) = GST_BUFFER_PTS (buffer);
|
|
GST_BUFFER_DTS (nal) = GST_BUFFER_DTS (buffer);
|
|
g_ptr_array_add (nals, nal);
|
|
|
|
offset += nal_len;
|
|
data += nal_len;
|
|
size -= nal_len;
|
|
}
|
|
|
|
gst_buffer_unmap (buffer, &map);
|
|
|
|
end:
|
|
/* This function consumes the buffer, and all references to it are in the
|
|
* extracted nals, so we can release the reference to the buffer itself */
|
|
gst_buffer_unref (buffer);
|
|
|
|
GST_LOG_OBJECT (avtpcvfpay, "Extracted %u NALu's from buffer", nals->len);
|
|
}
|
|
|
|
static gboolean
|
|
gst_avtp_cvf_pay_is_nal_vcl (GstAvtpCvfPay * avtpcvfpay, GstBuffer * nal)
|
|
{
|
|
guint8 nal_header, nal_type;
|
|
|
|
gst_buffer_extract (nal, 0, &nal_header, 1);
|
|
nal_type = nal_header & NAL_TYPE_MASK;
|
|
|
|
return nal_type >= FIRST_NAL_VCL_TYPE && nal_type <= LAST_NAL_VCL_TYPE;
|
|
}
|
|
|
|
static GstBuffer *
|
|
gst_avtpcvpay_fragment_nal (GstAvtpCvfPay * avtpcvfpay, GstBuffer * nal,
|
|
gsize * offset, gboolean * last_fragment)
|
|
{
|
|
GstBuffer *fragment_header, *fragment;
|
|
guint8 nal_header, nal_type, nal_nri, fu_indicator, fu_header;
|
|
gsize available, nal_size, fragment_size, remaining;
|
|
GstMapInfo map;
|
|
|
|
nal_size = gst_buffer_get_size (nal);
|
|
|
|
/* If NAL + header will be smaller than MTU, nothing to fragment */
|
|
if (*offset == 0 && (nal_size + AVTP_CVF_H264_HEADER_SIZE) <= avtpcvfpay->mtu) {
|
|
*last_fragment = TRUE;
|
|
*offset = nal_size;
|
|
GST_DEBUG_OBJECT (avtpcvfpay, "Generated fragment with size %lu", nal_size);
|
|
return gst_buffer_ref (nal);
|
|
}
|
|
|
|
/* We're done with this buffer */
|
|
if (*offset == nal_size) {
|
|
return NULL;
|
|
}
|
|
|
|
*last_fragment = FALSE;
|
|
|
|
/* Remaining size is smaller than MTU, so this is the last fragment */
|
|
remaining = nal_size - *offset + AVTP_CVF_H264_HEADER_SIZE + FU_A_HEADER_SIZE;
|
|
if (remaining <= avtpcvfpay->mtu) {
|
|
*last_fragment = TRUE;
|
|
}
|
|
|
|
fragment_header = gst_buffer_new_allocate (NULL, FU_A_HEADER_SIZE, NULL);
|
|
if (G_UNLIKELY (fragment_header == NULL)) {
|
|
GST_ERROR_OBJECT (avtpcvfpay, "Could not allocate memory for buffer");
|
|
return NULL;
|
|
}
|
|
|
|
/* NAL header info is spread to all FUs */
|
|
gst_buffer_extract (nal, 0, &nal_header, 1);
|
|
nal_type = nal_header & NAL_TYPE_MASK;
|
|
nal_nri = (nal_header & NRI_MASK) >> NRI_SHIFT;
|
|
|
|
fu_indicator = (nal_nri << NRI_SHIFT) | FU_A_TYPE;
|
|
fu_header = ((*offset == 0) << START_SHIFT) |
|
|
((*last_fragment == TRUE) << END_SHIFT) | nal_type;
|
|
|
|
gst_buffer_map (fragment_header, &map, GST_MAP_WRITE);
|
|
map.data[0] = fu_indicator;
|
|
map.data[1] = fu_header;
|
|
gst_buffer_unmap (fragment_header, &map);
|
|
|
|
available =
|
|
avtpcvfpay->mtu - AVTP_CVF_H264_HEADER_SIZE -
|
|
gst_buffer_get_size (fragment_header);
|
|
|
|
/* NAL unit header is not sent, but spread into FU indicator and header,
|
|
* and reconstructed on depayloader */
|
|
if (*offset == 0)
|
|
*offset = 1;
|
|
|
|
fragment_size =
|
|
available < (nal_size - *offset) ? available : (nal_size - *offset);
|
|
|
|
fragment =
|
|
gst_buffer_append (fragment_header, gst_buffer_copy_region (nal,
|
|
GST_BUFFER_COPY_MEMORY, *offset, fragment_size));
|
|
|
|
*offset += fragment_size;
|
|
|
|
GST_DEBUG_OBJECT (avtpcvfpay, "Generated fragment with size %lu",
|
|
fragment_size);
|
|
|
|
return fragment;
|
|
}
|
|
|
|
static gboolean
|
|
gst_avtp_cvf_pay_prepare_avtp_packets (GstAvtpCvfPay * avtpcvfpay,
|
|
GPtrArray * nals, GPtrArray * avtp_packets)
|
|
{
|
|
GstAvtpBasePayload *avtpbasepayload = GST_AVTP_BASE_PAYLOAD (avtpcvfpay);
|
|
GstBuffer *header, *nal;
|
|
GstMapInfo map;
|
|
gint i;
|
|
|
|
for (i = 0; i < nals->len; i++) {
|
|
guint64 avtp_time, h264_time;
|
|
gboolean last_fragment;
|
|
GstBuffer *fragment;
|
|
gsize offset;
|
|
|
|
nal = g_ptr_array_index (nals, i);
|
|
GST_LOG_OBJECT (avtpcvfpay,
|
|
"Preparing AVTP packets for NAL whose size is %lu",
|
|
gst_buffer_get_size (nal));
|
|
|
|
/* Calculate timestamps. Note that we do it twice, one using DTS as base,
|
|
* the other using PTS - using code inherited from avtpbasepayload.
|
|
* Also worth noting: `avtpbasepayload->latency` is updated after
|
|
* first call to gst_avtp_base_payload_calc_ptime, so we MUST call
|
|
* it before using the latency value */
|
|
h264_time = gst_avtp_base_payload_calc_ptime (avtpbasepayload, nal);
|
|
|
|
avtp_time =
|
|
gst_element_get_base_time (GST_ELEMENT (avtpcvfpay)) +
|
|
gst_segment_to_running_time (&avtpbasepayload->segment, GST_FORMAT_TIME,
|
|
GST_BUFFER_DTS_OR_PTS (nal)) + avtpbasepayload->mtt +
|
|
avtpbasepayload->tu + avtpbasepayload->processing_deadline +
|
|
avtpbasepayload->latency;
|
|
|
|
offset = 0;
|
|
while ((fragment =
|
|
gst_avtpcvpay_fragment_nal (avtpcvfpay, nal, &offset,
|
|
&last_fragment))) {
|
|
GstBuffer *packet;
|
|
struct avtp_stream_pdu *pdu;
|
|
gint res;
|
|
|
|
/* Copy header to reuse common fields and change what is needed */
|
|
header = gst_buffer_copy (avtpcvfpay->header);
|
|
gst_buffer_map (header, &map, GST_MAP_WRITE);
|
|
pdu = (struct avtp_stream_pdu *) map.data;
|
|
|
|
/* Stream data len includes AVTP H264 header len as this is part of
|
|
* the payload too. It's just the uint32_t with the h264 timestamp*/
|
|
res =
|
|
avtp_cvf_pdu_set (pdu, AVTP_CVF_FIELD_STREAM_DATA_LEN,
|
|
gst_buffer_get_size (fragment) + sizeof (uint32_t));
|
|
g_assert (res == 0);
|
|
|
|
res =
|
|
avtp_cvf_pdu_set (pdu, AVTP_CVF_FIELD_SEQ_NUM,
|
|
avtpbasepayload->seqnum++);
|
|
g_assert (res == 0);
|
|
|
|
/* Although AVTP_TIMESTAMP is only set on the very last fragment, IEEE 1722
|
|
* doesn't mention such need for H264_TIMESTAMP. So, we set it for all
|
|
* fragments */
|
|
res = avtp_cvf_pdu_set (pdu, AVTP_CVF_FIELD_H264_TIMESTAMP, h264_time);
|
|
g_assert (res == 0);
|
|
res = avtp_cvf_pdu_set (pdu, AVTP_CVF_FIELD_H264_PTV, 1);
|
|
g_assert (res == 0);
|
|
|
|
/* Only last fragment should have M, AVTP_TS and TV fields set */
|
|
if (last_fragment) {
|
|
gboolean M;
|
|
|
|
res = avtp_cvf_pdu_set (pdu, AVTP_CVF_FIELD_TV, 1);
|
|
g_assert (res == 0);
|
|
|
|
res = avtp_cvf_pdu_set (pdu, AVTP_CVF_FIELD_TIMESTAMP, avtp_time);
|
|
g_assert (res == 0);
|
|
|
|
/* Set M only if last NAL and it is a VCL NAL */
|
|
M = (i == nals->len - 1)
|
|
&& gst_avtp_cvf_pay_is_nal_vcl (avtpcvfpay, nal);
|
|
res = avtp_cvf_pdu_set (pdu, AVTP_CVF_FIELD_M, M);
|
|
g_assert (res == 0);
|
|
|
|
if (M) {
|
|
GST_LOG_OBJECT (avtpcvfpay, "M packet sent, PTS: %" GST_TIME_FORMAT
|
|
" DTS: %" GST_TIME_FORMAT " AVTP_TS: %" GST_TIME_FORMAT
|
|
" H264_TS: %" GST_TIME_FORMAT "\navtp_time: %lu h264_time: %lu",
|
|
GST_TIME_ARGS (h264_time),
|
|
GST_TIME_ARGS (avtp_time), GST_TIME_ARGS ((guint32) avtp_time),
|
|
GST_TIME_ARGS ((guint32) h264_time), avtp_time, h264_time);
|
|
}
|
|
}
|
|
|
|
packet = gst_buffer_append (header, fragment);
|
|
|
|
/* Keep original timestamps */
|
|
GST_BUFFER_PTS (packet) = GST_BUFFER_PTS (nal);
|
|
GST_BUFFER_DTS (packet) = GST_BUFFER_DTS (nal);
|
|
|
|
g_ptr_array_add (avtp_packets, packet);
|
|
|
|
gst_buffer_unmap (header, &map);
|
|
}
|
|
|
|
gst_buffer_unref (nal);
|
|
}
|
|
|
|
GST_LOG_OBJECT (avtpcvfpay, "Prepared %u AVTP packets", avtp_packets->len);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_avtp_cvf_pay_push_packets (GstAvtpCvfPay * avtpcvfpay,
|
|
GPtrArray * avtp_packets)
|
|
{
|
|
int i;
|
|
GstAvtpBasePayload *avtpbasepayload = GST_AVTP_BASE_PAYLOAD (avtpcvfpay);
|
|
|
|
for (i = 0; i < avtp_packets->len; i++) {
|
|
GstBuffer *packet;
|
|
|
|
packet = g_ptr_array_index (avtp_packets, i);
|
|
if (gst_pad_push (avtpbasepayload->srcpad, packet) != GST_FLOW_OK)
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_avtp_cvf_pay_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
|
|
{
|
|
GstAvtpBasePayload *avtpbasepayload = GST_AVTP_BASE_PAYLOAD (parent);
|
|
GstAvtpCvfPay *avtpcvfpay = GST_AVTP_CVF_PAY (avtpbasepayload);
|
|
GPtrArray *nals, *avtp_packets;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
GST_LOG_OBJECT (avtpcvfpay,
|
|
"Incoming buffer size: %lu PTS: %" GST_TIME_FORMAT " DTS: %"
|
|
GST_TIME_FORMAT, gst_buffer_get_size (buffer),
|
|
GST_TIME_ARGS (GST_BUFFER_PTS (buffer)),
|
|
GST_TIME_ARGS (GST_BUFFER_DTS (buffer)));
|
|
|
|
/* Get all NALs inside buffer */
|
|
nals = g_ptr_array_new ();
|
|
gst_avtp_cvf_pay_extract_nals (avtpcvfpay, buffer, nals);
|
|
|
|
/* Prepare a list of avtp_packets to send */
|
|
avtp_packets = g_ptr_array_new ();
|
|
gst_avtp_cvf_pay_prepare_avtp_packets (avtpcvfpay, nals, avtp_packets);
|
|
|
|
if (!gst_avtp_cvf_pay_push_packets (avtpcvfpay, avtp_packets))
|
|
ret = GST_FLOW_ERROR;
|
|
|
|
/* Contents of both ptr_arrays should be unref'd or transferred
|
|
* to rightful owner by this point, no need to unref them again */
|
|
g_ptr_array_free (nals, TRUE);
|
|
g_ptr_array_free (avtp_packets, TRUE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_avtp_cvf_pay_new_caps (GstAvtpCvfPay * avtpcvfpay, GstCaps * caps)
|
|
{
|
|
const GValue *value;
|
|
GstStructure *str;
|
|
GstBuffer *buffer;
|
|
GstMapInfo map;
|
|
|
|
str = gst_caps_get_structure (caps, 0);
|
|
|
|
if ((value = gst_structure_get_value (str, "codec_data"))) {
|
|
guint8 *data;
|
|
gsize size;
|
|
|
|
buffer = gst_value_get_buffer (value);
|
|
gst_buffer_map (buffer, &map, GST_MAP_READ);
|
|
data = map.data;
|
|
size = map.size;
|
|
|
|
if (G_UNLIKELY (size < 7)) {
|
|
GST_ERROR_OBJECT (avtpcvfpay, "avcC size %" G_GSIZE_FORMAT " < 7", size);
|
|
goto error;
|
|
}
|
|
if (G_UNLIKELY (data[0] != 1)) {
|
|
GST_ERROR_OBJECT (avtpcvfpay, "avcC version %u != 1", data[0]);
|
|
goto error;
|
|
}
|
|
|
|
/* Number of bytes in front of NAL units marking their size */
|
|
avtpcvfpay->nal_length_size = (data[4] & NAL_LEN_SIZE_MASK) + 1;
|
|
GST_DEBUG_OBJECT (avtpcvfpay, "Got NAL length from caps: %u",
|
|
avtpcvfpay->nal_length_size);
|
|
|
|
gst_buffer_unmap (buffer, &map);
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
error:
|
|
gst_buffer_unmap (buffer, &map);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_avtp_cvf_pay_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
|
|
{
|
|
GstCaps *caps;
|
|
GstAvtpBasePayload *avtpbasepayload = GST_AVTP_BASE_PAYLOAD (parent);
|
|
GstAvtpCvfPay *avtpcvfpay = GST_AVTP_CVF_PAY (avtpbasepayload);
|
|
|
|
GST_DEBUG_OBJECT (avtpcvfpay, "Sink 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_cvf_pay_new_caps (avtpcvfpay, caps);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return GST_AVTP_BASE_PAYLOAD_CLASS (parent_class)->sink_event (pad, parent,
|
|
event);
|
|
}
|
|
|
|
gboolean
|
|
gst_avtp_cvf_pay_plugin_init (GstPlugin * plugin)
|
|
{
|
|
return gst_element_register (plugin, "avtpcvfpay", GST_RANK_NONE,
|
|
GST_TYPE_AVTP_CVF_PAY);
|
|
}
|