mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-27 02:30:35 +00:00
7c42ba97d7
rename gst-launch --> gst-launch-1.0 replace old elements with new elements(ffmpegcolorspace -> videoconvert, ffenc_** -> avenc_**) fix caps in examples https://bugzilla.gnome.org/show_bug.cgi?id=759432
1624 lines
51 KiB
C
1624 lines
51 KiB
C
/* GStreamer
|
|
* Copyright (C) 2009 Sebastian Dröge <sebastian.droege@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:element-mxfmux
|
|
*
|
|
* mxfmux muxes different streams into an MXF file.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example launch line</title>
|
|
* |[
|
|
* gst-launch-1.0 -v filesrc location=/path/to/audio ! decodebin ! queue ! mxfmux name=m ! filesink location=file.mxf filesrc location=/path/to/video ! decodebin ! queue ! m.
|
|
* ]| This pipeline muxes an audio and video file into a single MXF file.
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <math.h>
|
|
#include <string.h>
|
|
|
|
#include "mxfmux.h"
|
|
|
|
#ifdef HAVE_SYS_UTSNAME_H
|
|
#include <sys/utsname.h>
|
|
#endif
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (mxfmux_debug);
|
|
#define GST_CAT_DEFAULT mxfmux_debug
|
|
|
|
#define GST_TYPE_MXF_MUX_PAD (gst_mxf_mux_pad_get_type())
|
|
#define GST_MXF_MUX_PAD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MXF_MUX_PAD, GstMXFMuxPad))
|
|
#define GST_MXF_MUX_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MXF_MUX_PAD, GstMXFMuxPadClass))
|
|
#define GST_MXF_MUX_PAD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),GST_TYPE_MXF_MUX_PAD, GstMXFMuxPadClass))
|
|
#define GST_IS_MXF_MUX_PAD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MXF_MUX_PAD))
|
|
#define GST_IS_MXF_MUX_PAD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MXF_MUX_PAD))
|
|
|
|
typedef struct
|
|
{
|
|
GstAggregatorPad parent;
|
|
|
|
guint64 pos;
|
|
GstClockTime last_timestamp;
|
|
|
|
MXFMetadataFileDescriptor *descriptor;
|
|
|
|
GstAdapter *adapter;
|
|
gboolean have_complete_edit_unit;
|
|
|
|
gpointer mapping_data;
|
|
const MXFEssenceElementWriter *writer;
|
|
MXFEssenceElementWriteFunc write_func;
|
|
|
|
MXFMetadataSourcePackage *source_package;
|
|
MXFMetadataTimelineTrack *source_track;
|
|
} GstMXFMuxPad;
|
|
|
|
typedef struct
|
|
{
|
|
GstAggregatorPadClass parent_class;
|
|
} GstMXFMuxPadClass;
|
|
|
|
GType gst_mxf_mux_pad_get_type (void);
|
|
|
|
G_DEFINE_TYPE (GstMXFMuxPad, gst_mxf_mux_pad, GST_TYPE_AGGREGATOR_PAD);
|
|
|
|
static void
|
|
gst_mxf_mux_pad_finalize (GObject * object)
|
|
{
|
|
GstMXFMuxPad *pad = GST_MXF_MUX_PAD (object);
|
|
|
|
g_object_unref (pad->adapter);
|
|
g_free (pad->mapping_data);
|
|
|
|
G_OBJECT_CLASS (gst_mxf_mux_pad_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_mxf_mux_pad_class_init (GstMXFMuxPadClass * klass)
|
|
{
|
|
GObjectClass *object_class = (GObjectClass *) klass;
|
|
|
|
object_class->finalize = gst_mxf_mux_pad_finalize;
|
|
}
|
|
|
|
static void
|
|
gst_mxf_mux_pad_init (GstMXFMuxPad * pad)
|
|
{
|
|
}
|
|
|
|
static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("application/mxf")
|
|
);
|
|
|
|
enum
|
|
{
|
|
PROP_0
|
|
};
|
|
|
|
#define gst_mxf_mux_parent_class parent_class
|
|
G_DEFINE_TYPE (GstMXFMux, gst_mxf_mux, GST_TYPE_AGGREGATOR);
|
|
|
|
static void gst_mxf_mux_finalize (GObject * object);
|
|
|
|
static GstFlowReturn gst_mxf_mux_aggregate (GstAggregator * aggregator,
|
|
gboolean timeout);
|
|
static gboolean gst_mxf_mux_stop (GstAggregator * aggregator);
|
|
|
|
static gboolean gst_mxf_mux_src_event (GstAggregator * aggregator,
|
|
GstEvent * event);
|
|
static gboolean gst_mxf_mux_sink_event (GstAggregator * aggregator,
|
|
GstAggregatorPad * aggpad, GstEvent * event);
|
|
static GstAggregatorPad *gst_mxf_mux_create_new_pad (GstAggregator * aggregator,
|
|
GstPadTemplate * templ, const gchar * name, const GstCaps * caps);
|
|
|
|
static void gst_mxf_mux_reset (GstMXFMux * mux);
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_mux_push (GstMXFMux * mux, GstBuffer * buf)
|
|
{
|
|
guint size = gst_buffer_get_size (buf);
|
|
GstFlowReturn ret;
|
|
|
|
ret = gst_aggregator_finish_buffer (GST_AGGREGATOR (mux), buf);
|
|
mux->offset += size;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
gst_mxf_mux_class_init (GstMXFMuxClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
GstAggregatorClass *gstaggregator_class;
|
|
const GstPadTemplate **p;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (mxfmux_debug, "mxfmux", 0, "MXF muxer");
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstelement_class = (GstElementClass *) klass;
|
|
gstaggregator_class = (GstAggregatorClass *) klass;
|
|
|
|
gobject_class->finalize = gst_mxf_mux_finalize;
|
|
|
|
gstaggregator_class->create_new_pad =
|
|
GST_DEBUG_FUNCPTR (gst_mxf_mux_create_new_pad);
|
|
gstaggregator_class->src_event = GST_DEBUG_FUNCPTR (gst_mxf_mux_src_event);
|
|
gstaggregator_class->sink_event = GST_DEBUG_FUNCPTR (gst_mxf_mux_sink_event);
|
|
gstaggregator_class->stop = GST_DEBUG_FUNCPTR (gst_mxf_mux_stop);
|
|
gstaggregator_class->aggregate = GST_DEBUG_FUNCPTR (gst_mxf_mux_aggregate);
|
|
gstaggregator_class->sinkpads_type = GST_TYPE_MXF_MUX_PAD;
|
|
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&src_templ));
|
|
|
|
p = mxf_essence_element_writer_get_pad_templates ();
|
|
while (p && *p) {
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
(GstPadTemplate *) gst_object_ref (GST_OBJECT (*p)));
|
|
p++;
|
|
}
|
|
|
|
gst_element_class_set_static_metadata (gstelement_class, "MXF muxer",
|
|
"Codec/Muxer",
|
|
"Muxes video/audio streams into a MXF stream",
|
|
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
|
|
}
|
|
|
|
static void
|
|
gst_mxf_mux_init (GstMXFMux * mux)
|
|
{
|
|
mux->index_table = g_array_new (FALSE, FALSE, sizeof (MXFIndexTableSegment));
|
|
gst_mxf_mux_reset (mux);
|
|
}
|
|
|
|
static void
|
|
gst_mxf_mux_finalize (GObject * object)
|
|
{
|
|
GstMXFMux *mux = GST_MXF_MUX (object);
|
|
|
|
gst_mxf_mux_reset (mux);
|
|
|
|
if (mux->metadata) {
|
|
g_hash_table_destroy (mux->metadata);
|
|
mux->metadata = NULL;
|
|
g_list_free (mux->metadata_list);
|
|
mux->metadata_list = NULL;
|
|
}
|
|
|
|
if (mux->index_table) {
|
|
g_array_free (mux->index_table, TRUE);
|
|
mux->index_table = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_mxf_mux_reset (GstMXFMux * mux)
|
|
{
|
|
GList *l;
|
|
|
|
GST_OBJECT_LOCK (mux);
|
|
while ((l = GST_ELEMENT_CAST (mux)->sinkpads) != NULL) {
|
|
GstPad *pad = (GstPad *) l->data;
|
|
|
|
gst_object_ref (pad);
|
|
GST_OBJECT_UNLOCK (mux);
|
|
gst_element_release_request_pad (GST_ELEMENT_CAST (mux), pad);
|
|
gst_object_unref (pad);
|
|
GST_OBJECT_LOCK (mux);
|
|
}
|
|
GST_OBJECT_UNLOCK (mux);
|
|
|
|
mux->state = GST_MXF_MUX_STATE_HEADER;
|
|
mux->n_pads = 0;
|
|
|
|
if (mux->metadata) {
|
|
g_hash_table_destroy (mux->metadata);
|
|
mux->preface = NULL;
|
|
g_list_free (mux->metadata_list);
|
|
mux->metadata_list = NULL;
|
|
}
|
|
mux->metadata = mxf_metadata_hash_table_new ();
|
|
|
|
mxf_partition_pack_reset (&mux->partition);
|
|
mxf_primer_pack_reset (&mux->primer);
|
|
memset (&mux->min_edit_rate, 0, sizeof (MXFFraction));
|
|
mux->last_gc_timestamp = 0;
|
|
mux->last_gc_position = 0;
|
|
mux->offset = 0;
|
|
|
|
g_array_set_size (mux->index_table, 0);
|
|
}
|
|
|
|
static gboolean
|
|
gst_mxf_mux_src_event (GstAggregator * aggregator, GstEvent * event)
|
|
{
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_SEEK:
|
|
/* disable seeking for now */
|
|
gst_event_unref (event);
|
|
return FALSE;
|
|
default:
|
|
return GST_AGGREGATOR_CLASS (parent_class)->src_event (aggregator, event);
|
|
break;
|
|
}
|
|
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static gboolean
|
|
gst_mxf_mux_set_caps (GstMXFMux * mux, GstMXFMuxPad * pad, GstCaps * caps)
|
|
{
|
|
gboolean ret = TRUE;
|
|
MXFUUID d_instance_uid = { {0,} };
|
|
MXFMetadataFileDescriptor *old_descriptor = pad->descriptor;
|
|
GList *l;
|
|
|
|
GST_DEBUG_OBJECT (pad, "Setting caps %" GST_PTR_FORMAT, caps);
|
|
|
|
if (old_descriptor) {
|
|
memcpy (&d_instance_uid, &MXF_METADATA_BASE (old_descriptor)->instance_uid,
|
|
16);
|
|
pad->descriptor = NULL;
|
|
g_free (pad->mapping_data);
|
|
pad->mapping_data = NULL;
|
|
}
|
|
|
|
pad->descriptor =
|
|
pad->writer->get_descriptor (GST_PAD_PAD_TEMPLATE (pad), caps,
|
|
&pad->write_func, &pad->mapping_data);
|
|
|
|
if (!pad->descriptor) {
|
|
GST_ERROR_OBJECT (mux,
|
|
"Couldn't get descriptor for pad '%s' with caps %" GST_PTR_FORMAT,
|
|
GST_PAD_NAME (pad), caps);
|
|
return FALSE;
|
|
}
|
|
|
|
if (mxf_uuid_is_zero (&d_instance_uid))
|
|
mxf_uuid_init (&d_instance_uid, mux->metadata);
|
|
|
|
memcpy (&MXF_METADATA_BASE (pad->descriptor)->instance_uid, &d_instance_uid,
|
|
16);
|
|
|
|
if (old_descriptor) {
|
|
for (l = mux->metadata_list; l; l = l->next) {
|
|
MXFMetadataBase *tmp = l->data;
|
|
|
|
if (mxf_uuid_is_equal (&d_instance_uid, &tmp->instance_uid)) {
|
|
l->data = pad->descriptor;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
mux->metadata_list = g_list_prepend (mux->metadata_list, pad->descriptor);
|
|
}
|
|
|
|
g_hash_table_replace (mux->metadata,
|
|
&MXF_METADATA_BASE (pad->descriptor)->instance_uid, pad->descriptor);
|
|
|
|
if (old_descriptor) {
|
|
if (mux->preface && mux->preface->content_storage &&
|
|
mux->preface->content_storage->packages) {
|
|
guint i, j;
|
|
|
|
for (i = 0; i < mux->preface->content_storage->n_packages; i++) {
|
|
MXFMetadataSourcePackage *package;
|
|
|
|
if (!MXF_IS_METADATA_SOURCE_PACKAGE (mux->preface->
|
|
content_storage->packages[i]))
|
|
continue;
|
|
|
|
package =
|
|
MXF_METADATA_SOURCE_PACKAGE (mux->preface->
|
|
content_storage->packages[i]);
|
|
|
|
if (!package->descriptor)
|
|
continue;
|
|
|
|
if (MXF_IS_METADATA_MULTIPLE_DESCRIPTOR (package->descriptor)) {
|
|
MXFMetadataMultipleDescriptor *tmp =
|
|
MXF_METADATA_MULTIPLE_DESCRIPTOR (package->descriptor);
|
|
|
|
for (j = 0; j < tmp->n_sub_descriptors; j++) {
|
|
if (tmp->sub_descriptors[j] ==
|
|
MXF_METADATA_GENERIC_DESCRIPTOR (old_descriptor)) {
|
|
tmp->sub_descriptors[j] =
|
|
MXF_METADATA_GENERIC_DESCRIPTOR (pad->descriptor);
|
|
memcpy (&tmp->sub_descriptors_uids[j], &d_instance_uid, 16);
|
|
}
|
|
}
|
|
} else if (package->descriptor ==
|
|
MXF_METADATA_GENERIC_DESCRIPTOR (old_descriptor)) {
|
|
package->descriptor =
|
|
MXF_METADATA_GENERIC_DESCRIPTOR (pad->descriptor);
|
|
memcpy (&package->descriptor_uid, &d_instance_uid, 16);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_mxf_mux_sink_event (GstAggregator * aggregator,
|
|
GstAggregatorPad * aggpad, GstEvent * event)
|
|
{
|
|
GstMXFMux *mux = GST_MXF_MUX (aggregator);
|
|
gboolean ret = TRUE;
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_TAG:
|
|
/* TODO: do something with the tags */
|
|
break;
|
|
case GST_EVENT_CAPS:{
|
|
GstCaps *caps;
|
|
|
|
gst_event_parse_caps (event, &caps);
|
|
|
|
ret = gst_mxf_mux_set_caps (mux, GST_MXF_MUX_PAD (aggpad), caps);
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* now GstAggregator can take care of the rest, e.g. EOS */
|
|
if (ret)
|
|
ret =
|
|
GST_AGGREGATOR_CLASS (parent_class)->sink_event (aggregator, aggpad,
|
|
event);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
gst_mxf_mux_create_pad_name (GstPadTemplate * templ, guint id)
|
|
{
|
|
GString *string;
|
|
|
|
string = g_string_new (GST_PAD_TEMPLATE_NAME_TEMPLATE (templ));
|
|
g_string_truncate (string, string->len - 2);
|
|
g_string_append_printf (string, "%u", id);
|
|
|
|
return g_string_free (string, FALSE);
|
|
}
|
|
|
|
static GstAggregatorPad *
|
|
gst_mxf_mux_create_new_pad (GstAggregator * aggregator,
|
|
GstPadTemplate * templ, const gchar * pad_name, const GstCaps * caps)
|
|
{
|
|
GstMXFMux *mux = GST_MXF_MUX (aggregator);
|
|
GstMXFMuxPad *pad;
|
|
guint pad_number;
|
|
gchar *name = NULL;
|
|
const MXFEssenceElementWriter *writer;
|
|
|
|
if (mux->state != GST_MXF_MUX_STATE_HEADER) {
|
|
GST_WARNING_OBJECT (mux, "Can't request pads after writing header");
|
|
return NULL;
|
|
}
|
|
|
|
writer = mxf_essence_element_writer_find (templ);
|
|
if (!writer) {
|
|
GST_ERROR_OBJECT (mux, "Not our template");
|
|
return NULL;
|
|
}
|
|
pad_number = g_atomic_int_add ((gint *) & mux->n_pads, 1);
|
|
name = gst_mxf_mux_create_pad_name (templ, pad_number);
|
|
|
|
GST_DEBUG_OBJECT (mux, "Creating pad '%s'", name);
|
|
pad =
|
|
g_object_new (GST_TYPE_MXF_MUX_PAD, "name", name, "direction",
|
|
GST_PAD_SINK, "template", templ, NULL);
|
|
g_free (name);
|
|
pad->last_timestamp = 0;
|
|
pad->adapter = gst_adapter_new ();
|
|
pad->writer = writer;
|
|
|
|
gst_pad_use_fixed_caps (GST_PAD_CAST (pad));
|
|
|
|
return GST_AGGREGATOR_PAD (pad);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_mux_create_metadata (GstMXFMux * mux)
|
|
{
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GList *l;
|
|
GArray *tmp;
|
|
|
|
GST_DEBUG_OBJECT (mux, "Creating MXF metadata");
|
|
|
|
GST_OBJECT_LOCK (mux);
|
|
|
|
for (l = GST_ELEMENT_CAST (mux)->sinkpads; l; l = l->next) {
|
|
GstMXFMuxPad *pad = l->data;
|
|
GstCaps *caps;
|
|
GstBuffer *buffer;
|
|
|
|
if (!pad || !pad->descriptor) {
|
|
GST_OBJECT_UNLOCK (mux);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
caps = gst_pad_get_current_caps (GST_PAD_CAST (pad));
|
|
if (!caps) {
|
|
GST_OBJECT_UNLOCK (mux);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
buffer = gst_aggregator_pad_get_buffer (GST_AGGREGATOR_PAD (pad));
|
|
if (pad->writer->update_descriptor)
|
|
pad->writer->update_descriptor (pad->descriptor,
|
|
caps, pad->mapping_data, buffer);
|
|
if (buffer)
|
|
gst_buffer_unref (buffer);
|
|
gst_caps_unref (caps);
|
|
}
|
|
|
|
/* Preface */
|
|
mux->preface =
|
|
(MXFMetadataPreface *) g_object_new (MXF_TYPE_METADATA_PREFACE, NULL);
|
|
mxf_uuid_init (&MXF_METADATA_BASE (mux->preface)->instance_uid,
|
|
mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (mux->preface)->instance_uid, mux->preface);
|
|
mux->metadata_list = g_list_prepend (mux->metadata_list, mux->preface);
|
|
|
|
mxf_timestamp_set_now (&mux->preface->last_modified_date);
|
|
mux->preface->version = 258;
|
|
mux->preface->object_model_version = 1;
|
|
|
|
mxf_op_set_generalized (&mux->preface->operational_pattern, MXF_OP_1a, TRUE,
|
|
TRUE, FALSE);
|
|
|
|
tmp = g_array_new (FALSE, FALSE, sizeof (MXFUL));
|
|
for (l = GST_ELEMENT_CAST (mux)->sinkpads; l; l = l->next) {
|
|
GstMXFMuxPad *pad = l->data;
|
|
guint i;
|
|
gboolean found = FALSE;
|
|
|
|
if (!pad || !pad->descriptor ||
|
|
mxf_ul_is_zero (&pad->descriptor->essence_container)) {
|
|
GST_OBJECT_UNLOCK (mux);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
for (i = 0; i < tmp->len; i++) {
|
|
if (mxf_ul_is_equal (&pad->descriptor->essence_container,
|
|
&g_array_index (tmp, MXFUL, i))) {
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found)
|
|
continue;
|
|
|
|
g_array_append_val (tmp, pad->descriptor->essence_container);
|
|
}
|
|
mux->preface->n_essence_containers = tmp->len;
|
|
mux->preface->essence_containers = (MXFUL *) g_array_free (tmp, FALSE);
|
|
|
|
/* This will later be used as UID for the material package */
|
|
mxf_uuid_init (&mux->preface->primary_package_uid, mux->metadata);
|
|
|
|
/* Identifications */
|
|
{
|
|
MXFMetadataIdentification *identification;
|
|
static const guint8 gst_uid[] = {
|
|
0xe5, 0xde, 0xcd, 0x04, 0x24, 0x90, 0x69, 0x18,
|
|
0x8a, 0xc9, 0xb5, 0xd7, 0x02, 0x58, 0x46, 0x78
|
|
};
|
|
guint major, minor, micro, nano;
|
|
|
|
mux->preface->n_identifications = 1;
|
|
mux->preface->identifications = g_new0 (MXFMetadataIdentification *, 1);
|
|
identification = mux->preface->identifications[0] =
|
|
(MXFMetadataIdentification *)
|
|
g_object_new (MXF_TYPE_METADATA_IDENTIFICATION, NULL);
|
|
|
|
mxf_uuid_init (&MXF_METADATA_BASE (identification)->instance_uid,
|
|
mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (identification)->instance_uid, identification);
|
|
mux->metadata_list = g_list_prepend (mux->metadata_list, identification);
|
|
|
|
mxf_uuid_init (&identification->this_generation_uid, NULL);
|
|
|
|
identification->company_name = g_strdup ("GStreamer");
|
|
identification->product_name = g_strdup ("GStreamer Multimedia Framework");
|
|
|
|
gst_version (&major, &minor, µ, &nano);
|
|
identification->product_version.major = major;
|
|
identification->product_version.minor = minor;
|
|
identification->product_version.patch = micro;
|
|
identification->product_version.build = nano;
|
|
identification->product_version.release =
|
|
(nano == 0) ? 1 : (nano == 1) ? 2 : 4;
|
|
|
|
identification->version_string =
|
|
g_strdup_printf ("%u.%u.%u.%u", major, minor, micro, nano);
|
|
memcpy (&identification->product_uid, &gst_uid, 16);
|
|
|
|
memcpy (&identification->modification_date,
|
|
&mux->preface->last_modified_date, sizeof (MXFTimestamp));
|
|
memcpy (&identification->toolkit_version, &identification->product_version,
|
|
sizeof (MXFProductVersion));
|
|
|
|
#ifdef HAVE_SYS_UTSNAME_H
|
|
{
|
|
struct utsname sys_details;
|
|
|
|
if (uname (&sys_details) == 0) {
|
|
identification->platform = g_strdup_printf ("%s %s %s",
|
|
sys_details.sysname, sys_details.release, sys_details.machine);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(G_OS_WIN32)
|
|
if (identification->platform == NULL)
|
|
identification->platform = g_strdup ("Microsoft Windows");
|
|
#elif defined(G_OS_BEOS)
|
|
if (identification->platform == NULL)
|
|
identification->platform = g_strdup ("BEOS");
|
|
#elif defined(G_OS_UNIX)
|
|
if (identification->platform == NULL)
|
|
identification->platform = g_strdup ("Unix");
|
|
#endif
|
|
}
|
|
|
|
/* Content storage */
|
|
{
|
|
MXFMetadataContentStorage *cstorage;
|
|
guint i;
|
|
|
|
cstorage = mux->preface->content_storage = (MXFMetadataContentStorage *)
|
|
g_object_new (MXF_TYPE_METADATA_CONTENT_STORAGE, NULL);
|
|
mxf_uuid_init (&MXF_METADATA_BASE (cstorage)->instance_uid, mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (cstorage)->instance_uid, cstorage);
|
|
mux->metadata_list = g_list_prepend (mux->metadata_list, cstorage);
|
|
|
|
cstorage->n_packages = 2;
|
|
cstorage->packages = g_new0 (MXFMetadataGenericPackage *, 2);
|
|
|
|
/* Source package */
|
|
{
|
|
MXFMetadataSourcePackage *p;
|
|
|
|
cstorage->packages[1] = (MXFMetadataGenericPackage *)
|
|
g_object_new (MXF_TYPE_METADATA_SOURCE_PACKAGE, NULL);
|
|
mxf_uuid_init (&MXF_METADATA_BASE (cstorage->packages[1])->instance_uid,
|
|
mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (cstorage->packages[1])->instance_uid,
|
|
cstorage->packages[1]);
|
|
mux->metadata_list =
|
|
g_list_prepend (mux->metadata_list, cstorage->packages[1]);
|
|
p = (MXFMetadataSourcePackage *) cstorage->packages[1];
|
|
|
|
mxf_umid_init (&p->parent.package_uid);
|
|
p->parent.name = g_strdup ("Source package");
|
|
memcpy (&p->parent.package_creation_date,
|
|
&mux->preface->last_modified_date, sizeof (MXFTimestamp));
|
|
memcpy (&p->parent.package_modified_date,
|
|
&mux->preface->last_modified_date, sizeof (MXFTimestamp));
|
|
|
|
p->parent.n_tracks = GST_ELEMENT_CAST (mux)->numsinkpads;
|
|
p->parent.tracks = g_new0 (MXFMetadataTrack *, p->parent.n_tracks);
|
|
|
|
if (p->parent.n_tracks > 1) {
|
|
MXFMetadataMultipleDescriptor *d;
|
|
|
|
p->descriptor = (MXFMetadataGenericDescriptor *)
|
|
g_object_new (MXF_TYPE_METADATA_MULTIPLE_DESCRIPTOR, NULL);
|
|
d = (MXFMetadataMultipleDescriptor *) p->descriptor;
|
|
d->n_sub_descriptors = p->parent.n_tracks;
|
|
d->sub_descriptors =
|
|
g_new0 (MXFMetadataGenericDescriptor *, p->parent.n_tracks);
|
|
|
|
mxf_uuid_init (&MXF_METADATA_BASE (d)->instance_uid, mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (d)->instance_uid, d);
|
|
mux->metadata_list = g_list_prepend (mux->metadata_list, d);
|
|
}
|
|
|
|
/* Tracks */
|
|
{
|
|
guint n = 0;
|
|
|
|
/* Essence tracks */
|
|
for (l = GST_ELEMENT_CAST (mux)->sinkpads; l; l = l->next) {
|
|
GstMXFMuxPad *pad = l->data;
|
|
MXFMetadataTimelineTrack *track;
|
|
MXFMetadataSequence *sequence;
|
|
MXFMetadataSourceClip *clip;
|
|
GstCaps *caps;
|
|
GstBuffer *buffer;
|
|
|
|
p->parent.tracks[n] = (MXFMetadataTrack *)
|
|
g_object_new (MXF_TYPE_METADATA_TIMELINE_TRACK, NULL);
|
|
track = (MXFMetadataTimelineTrack *) p->parent.tracks[n];
|
|
mxf_uuid_init (&MXF_METADATA_BASE (track)->instance_uid,
|
|
mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (track)->instance_uid, track);
|
|
mux->metadata_list = g_list_prepend (mux->metadata_list, track);
|
|
|
|
caps = gst_pad_get_current_caps (GST_PAD_CAST (pad));
|
|
buffer = gst_aggregator_pad_get_buffer (GST_AGGREGATOR_PAD (pad));
|
|
track->parent.track_id = n + 1;
|
|
track->parent.track_number =
|
|
pad->writer->get_track_number_template (pad->descriptor,
|
|
caps, pad->mapping_data);
|
|
|
|
/* FIXME: All tracks in a source package must have the same edit
|
|
* rate! This means that if we have different edit rates, we need to
|
|
* make them different source packages and essence containers with
|
|
* a different BodySID */
|
|
pad->writer->get_edit_rate (pad->descriptor,
|
|
caps, pad->mapping_data, buffer, p, track, &track->edit_rate);
|
|
if (buffer)
|
|
gst_buffer_unref (buffer);
|
|
gst_caps_unref (caps);
|
|
|
|
sequence = track->parent.sequence = (MXFMetadataSequence *)
|
|
g_object_new (MXF_TYPE_METADATA_SEQUENCE, NULL);
|
|
mxf_uuid_init (&MXF_METADATA_BASE (sequence)->instance_uid,
|
|
mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (sequence)->instance_uid, sequence);
|
|
mux->metadata_list = g_list_prepend (mux->metadata_list, sequence);
|
|
|
|
memcpy (&sequence->data_definition, &pad->writer->data_definition,
|
|
16);
|
|
|
|
sequence->n_structural_components = 1;
|
|
sequence->structural_components =
|
|
g_new0 (MXFMetadataStructuralComponent *, 1);
|
|
|
|
clip = (MXFMetadataSourceClip *)
|
|
g_object_new (MXF_TYPE_METADATA_SOURCE_CLIP, NULL);
|
|
sequence->structural_components[0] =
|
|
(MXFMetadataStructuralComponent *) clip;
|
|
mxf_uuid_init (&MXF_METADATA_BASE (clip)->instance_uid,
|
|
mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (clip)->instance_uid, clip);
|
|
mux->metadata_list = g_list_prepend (mux->metadata_list, clip);
|
|
|
|
memcpy (&clip->parent.data_definition, &sequence->data_definition,
|
|
16);
|
|
clip->start_position = 0;
|
|
|
|
pad->source_package = p;
|
|
pad->source_track = track;
|
|
pad->descriptor->linked_track_id = n + 1;
|
|
if (p->parent.n_tracks == 1) {
|
|
p->descriptor = (MXFMetadataGenericDescriptor *) pad->descriptor;
|
|
} else {
|
|
MXF_METADATA_MULTIPLE_DESCRIPTOR (p->
|
|
descriptor)->sub_descriptors[n] =
|
|
(MXFMetadataGenericDescriptor *) pad->descriptor;
|
|
}
|
|
|
|
n++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Material package */
|
|
{
|
|
MXFMetadataMaterialPackage *p;
|
|
MXFFraction min_edit_rate = { 0, 0 };
|
|
gdouble min_edit_rate_d = G_MAXDOUBLE;
|
|
|
|
cstorage->packages[0] = (MXFMetadataGenericPackage *)
|
|
g_object_new (MXF_TYPE_METADATA_MATERIAL_PACKAGE, NULL);
|
|
memcpy (&MXF_METADATA_BASE (cstorage->packages[0])->instance_uid,
|
|
&mux->preface->primary_package_uid, 16);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (cstorage->packages[0])->instance_uid,
|
|
cstorage->packages[0]);
|
|
mux->metadata_list =
|
|
g_list_prepend (mux->metadata_list, cstorage->packages[0]);
|
|
p = (MXFMetadataMaterialPackage *) cstorage->packages[0];
|
|
|
|
mxf_umid_init (&p->package_uid);
|
|
p->name = g_strdup ("Material package");
|
|
memcpy (&p->package_creation_date, &mux->preface->last_modified_date,
|
|
sizeof (MXFTimestamp));
|
|
memcpy (&p->package_modified_date, &mux->preface->last_modified_date,
|
|
sizeof (MXFTimestamp));
|
|
|
|
p->n_tracks = GST_ELEMENT_CAST (mux)->numsinkpads + 1;
|
|
p->tracks = g_new0 (MXFMetadataTrack *, p->n_tracks);
|
|
|
|
/* Tracks */
|
|
{
|
|
guint n;
|
|
|
|
n = 1;
|
|
/* Essence tracks */
|
|
for (l = GST_ELEMENT_CAST (mux)->sinkpads; l; l = l->next) {
|
|
GstMXFMuxPad *pad = l->data;
|
|
GstCaps *caps;
|
|
GstBuffer *buffer;
|
|
MXFMetadataSourcePackage *source_package;
|
|
MXFMetadataTimelineTrack *track, *source_track;
|
|
MXFMetadataSequence *sequence;
|
|
MXFMetadataSourceClip *clip;
|
|
|
|
source_package = MXF_METADATA_SOURCE_PACKAGE (cstorage->packages[1]);
|
|
source_track =
|
|
MXF_METADATA_TIMELINE_TRACK (source_package->parent.tracks[n -
|
|
1]);
|
|
|
|
p->tracks[n] = (MXFMetadataTrack *)
|
|
g_object_new (MXF_TYPE_METADATA_TIMELINE_TRACK, NULL);
|
|
track = (MXFMetadataTimelineTrack *) p->tracks[n];
|
|
mxf_uuid_init (&MXF_METADATA_BASE (track)->instance_uid,
|
|
mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (track)->instance_uid, track);
|
|
mux->metadata_list = g_list_prepend (mux->metadata_list, track);
|
|
|
|
track->parent.track_id = n + 1;
|
|
track->parent.track_number = 0;
|
|
|
|
caps = gst_pad_get_current_caps (GST_PAD_CAST (pad));
|
|
buffer = gst_aggregator_pad_get_buffer (GST_AGGREGATOR_PAD (pad));
|
|
pad->writer->get_edit_rate (pad->descriptor,
|
|
caps, pad->mapping_data,
|
|
buffer, source_package, source_track, &track->edit_rate);
|
|
if (buffer)
|
|
gst_buffer_unref (buffer);
|
|
gst_caps_unref (caps);
|
|
|
|
if (track->edit_rate.n != source_track->edit_rate.n ||
|
|
track->edit_rate.d != source_track->edit_rate.d) {
|
|
memcpy (&source_track->edit_rate, &track->edit_rate,
|
|
sizeof (MXFFraction));
|
|
}
|
|
|
|
if (track->edit_rate.d <= 0 || track->edit_rate.n <= 0) {
|
|
GST_ERROR_OBJECT (mux, "Invalid edit rate");
|
|
GST_OBJECT_UNLOCK (mux);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
if (min_edit_rate_d >
|
|
((gdouble) track->edit_rate.n) / ((gdouble) track->edit_rate.d)) {
|
|
min_edit_rate_d =
|
|
((gdouble) track->edit_rate.n) / ((gdouble) track->edit_rate.d);
|
|
memcpy (&min_edit_rate, &track->edit_rate, sizeof (MXFFraction));
|
|
}
|
|
|
|
sequence = track->parent.sequence = (MXFMetadataSequence *)
|
|
g_object_new (MXF_TYPE_METADATA_SEQUENCE, NULL);
|
|
mxf_uuid_init (&MXF_METADATA_BASE (sequence)->instance_uid,
|
|
mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (sequence)->instance_uid, sequence);
|
|
mux->metadata_list = g_list_prepend (mux->metadata_list, sequence);
|
|
|
|
memcpy (&sequence->data_definition, &pad->writer->data_definition,
|
|
16);
|
|
sequence->n_structural_components = 1;
|
|
sequence->structural_components =
|
|
g_new0 (MXFMetadataStructuralComponent *, 1);
|
|
|
|
clip = (MXFMetadataSourceClip *)
|
|
g_object_new (MXF_TYPE_METADATA_SOURCE_CLIP, NULL);
|
|
sequence->structural_components[0] =
|
|
(MXFMetadataStructuralComponent *) clip;
|
|
mxf_uuid_init (&MXF_METADATA_BASE (clip)->instance_uid,
|
|
mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (clip)->instance_uid, clip);
|
|
mux->metadata_list = g_list_prepend (mux->metadata_list, clip);
|
|
|
|
memcpy (&clip->parent.data_definition, &sequence->data_definition,
|
|
16);
|
|
clip->start_position = 0;
|
|
|
|
memcpy (&clip->source_package_id, &cstorage->packages[1]->package_uid,
|
|
32);
|
|
clip->source_track_id = n;
|
|
|
|
n++;
|
|
}
|
|
|
|
n = 0;
|
|
/* Timecode track */
|
|
{
|
|
MXFMetadataTimelineTrack *track;
|
|
MXFMetadataSequence *sequence;
|
|
MXFMetadataTimecodeComponent *component;
|
|
|
|
p->tracks[n] = (MXFMetadataTrack *)
|
|
g_object_new (MXF_TYPE_METADATA_TIMELINE_TRACK, NULL);
|
|
track = (MXFMetadataTimelineTrack *) p->tracks[n];
|
|
mxf_uuid_init (&MXF_METADATA_BASE (track)->instance_uid,
|
|
mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (track)->instance_uid, track);
|
|
mux->metadata_list = g_list_prepend (mux->metadata_list, track);
|
|
|
|
track->parent.track_id = n + 1;
|
|
track->parent.track_number = 0;
|
|
track->parent.track_name = g_strdup ("Timecode track");
|
|
/* FIXME: Is this correct? */
|
|
memcpy (&track->edit_rate, &min_edit_rate, sizeof (MXFFraction));
|
|
|
|
sequence = track->parent.sequence = (MXFMetadataSequence *)
|
|
g_object_new (MXF_TYPE_METADATA_SEQUENCE, NULL);
|
|
mxf_uuid_init (&MXF_METADATA_BASE (sequence)->instance_uid,
|
|
mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (sequence)->instance_uid, sequence);
|
|
mux->metadata_list = g_list_prepend (mux->metadata_list, sequence);
|
|
|
|
memcpy (&sequence->data_definition,
|
|
mxf_metadata_track_identifier_get
|
|
(MXF_METADATA_TRACK_TIMECODE_12M_INACTIVE), 16);
|
|
|
|
sequence->n_structural_components = 1;
|
|
sequence->structural_components =
|
|
g_new0 (MXFMetadataStructuralComponent *, 1);
|
|
|
|
component = (MXFMetadataTimecodeComponent *)
|
|
g_object_new (MXF_TYPE_METADATA_TIMECODE_COMPONENT, NULL);
|
|
sequence->structural_components[0] =
|
|
(MXFMetadataStructuralComponent *) component;
|
|
mxf_uuid_init (&MXF_METADATA_BASE (component)->instance_uid,
|
|
mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (component)->instance_uid, component);
|
|
mux->metadata_list = g_list_prepend (mux->metadata_list, component);
|
|
|
|
memcpy (&component->parent.data_definition,
|
|
&sequence->data_definition, 16);
|
|
|
|
component->start_timecode = 0;
|
|
if (track->edit_rate.d == 0)
|
|
component->rounded_timecode_base = 1;
|
|
else
|
|
component->rounded_timecode_base =
|
|
(((gdouble) track->edit_rate.n) /
|
|
((gdouble) track->edit_rate.d) + 0.5);
|
|
/* TODO: drop frame */
|
|
}
|
|
|
|
memcpy (&mux->min_edit_rate, &min_edit_rate, sizeof (MXFFraction));
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < cstorage->packages[1]->n_tracks; i++) {
|
|
MXFMetadataTrack *track = cstorage->packages[1]->tracks[i];
|
|
guint j;
|
|
guint32 templ;
|
|
guint8 n_type, n;
|
|
|
|
if ((track->track_number & 0x00ff00ff) != 0)
|
|
continue;
|
|
|
|
templ = track->track_number;
|
|
n_type = 0;
|
|
|
|
for (j = 0; j < cstorage->packages[1]->n_tracks; j++) {
|
|
MXFMetadataTrack *tmp = cstorage->packages[1]->tracks[j];
|
|
|
|
if (tmp->track_number == templ) {
|
|
n_type++;
|
|
}
|
|
}
|
|
|
|
n = 0;
|
|
for (j = 0; j < cstorage->packages[1]->n_tracks; j++) {
|
|
MXFMetadataTrack *tmp = cstorage->packages[1]->tracks[j];
|
|
|
|
if (tmp->track_number == templ) {
|
|
n++;
|
|
tmp->track_number |= (n_type << 16) | (n);
|
|
}
|
|
}
|
|
}
|
|
|
|
cstorage->n_essence_container_data = 1;
|
|
cstorage->essence_container_data =
|
|
g_new0 (MXFMetadataEssenceContainerData *, 1);
|
|
cstorage->essence_container_data[0] = (MXFMetadataEssenceContainerData *)
|
|
g_object_new (MXF_TYPE_METADATA_ESSENCE_CONTAINER_DATA, NULL);
|
|
mxf_uuid_init (&MXF_METADATA_BASE (cstorage->essence_container_data[0])->
|
|
instance_uid, mux->metadata);
|
|
g_hash_table_insert (mux->metadata,
|
|
&MXF_METADATA_BASE (cstorage->essence_container_data[0])->instance_uid,
|
|
cstorage->essence_container_data[0]);
|
|
mux->metadata_list =
|
|
g_list_prepend (mux->metadata_list,
|
|
cstorage->essence_container_data[0]);
|
|
|
|
cstorage->essence_container_data[0]->linked_package =
|
|
MXF_METADATA_SOURCE_PACKAGE (cstorage->packages[1]);
|
|
cstorage->essence_container_data[0]->index_sid = 1;
|
|
cstorage->essence_container_data[0]->body_sid = 1;
|
|
}
|
|
|
|
/* Sort descriptors at the correct places */
|
|
{
|
|
GList *l;
|
|
GList *descriptors = NULL;
|
|
|
|
for (l = mux->metadata_list; l; l = l->next) {
|
|
MXFMetadataBase *m = l->data;
|
|
|
|
if (MXF_IS_METADATA_GENERIC_DESCRIPTOR (m)
|
|
&& !MXF_IS_METADATA_MULTIPLE_DESCRIPTOR (m)) {
|
|
descriptors = l;
|
|
l->prev->next = NULL;
|
|
l->prev = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_assert (descriptors != NULL);
|
|
|
|
for (l = mux->metadata_list; l; l = l->next) {
|
|
MXFMetadataBase *m = l->data;
|
|
GList *s;
|
|
|
|
if (MXF_IS_METADATA_MULTIPLE_DESCRIPTOR (m) ||
|
|
MXF_IS_METADATA_SOURCE_PACKAGE (m)) {
|
|
s = l->prev;
|
|
l->prev = g_list_last (descriptors);
|
|
s->next = descriptors;
|
|
descriptors->prev = s;
|
|
l->prev->next = l;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
GST_OBJECT_UNLOCK (mux);
|
|
|
|
mux->metadata_list = g_list_reverse (mux->metadata_list);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_mux_init_partition_pack (GstMXFMux * mux)
|
|
{
|
|
GList *l;
|
|
guint i = 0;
|
|
|
|
mxf_partition_pack_reset (&mux->partition);
|
|
mux->partition.type = MXF_PARTITION_PACK_HEADER;
|
|
mux->partition.closed = mux->partition.complete = FALSE;
|
|
mux->partition.major_version = 0x0001;
|
|
mux->partition.minor_version = 0x0002;
|
|
mux->partition.kag_size = 1;
|
|
mux->partition.this_partition = 0;
|
|
mux->partition.prev_partition = 0;
|
|
mux->partition.footer_partition = 0;
|
|
mux->partition.header_byte_count = 0;
|
|
mux->partition.index_byte_count = 0;
|
|
mux->partition.index_sid = 0;
|
|
mux->partition.body_offset = 0;
|
|
mux->partition.body_sid = 0;
|
|
|
|
memcpy (&mux->partition.operational_pattern,
|
|
&mux->preface->operational_pattern, 16);
|
|
|
|
GST_OBJECT_LOCK (mux);
|
|
mux->partition.n_essence_containers = GST_ELEMENT_CAST (mux)->numsinkpads;
|
|
mux->partition.essence_containers =
|
|
g_new0 (MXFUL, mux->partition.n_essence_containers);
|
|
|
|
for (l = GST_ELEMENT_CAST (mux)->sinkpads; l; l = l->next) {
|
|
GstMXFMuxPad *pad = l->data;
|
|
guint j;
|
|
gboolean found = FALSE;
|
|
|
|
for (j = 0; j <= i; j++) {
|
|
if (mxf_ul_is_equal (&pad->descriptor->essence_container,
|
|
&mux->partition.essence_containers[j])) {
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found)
|
|
continue;
|
|
|
|
memcpy (&mux->partition.essence_containers[i],
|
|
&pad->descriptor->essence_container, 16);
|
|
i++;
|
|
}
|
|
mux->partition.n_essence_containers = i;
|
|
GST_OBJECT_UNLOCK (mux);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_mux_write_header_metadata (GstMXFMux * mux)
|
|
{
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GstBuffer *buf;
|
|
GList *buffers = NULL;
|
|
GList *l;
|
|
MXFMetadataBase *m;
|
|
guint64 header_byte_count = 0;
|
|
|
|
for (l = mux->metadata_list; l; l = l->next) {
|
|
m = l->data;
|
|
buf = mxf_metadata_base_to_buffer (m, &mux->primer);
|
|
header_byte_count += gst_buffer_get_size (buf);
|
|
buffers = g_list_prepend (buffers, buf);
|
|
}
|
|
|
|
buffers = g_list_reverse (buffers);
|
|
buf = mxf_primer_pack_to_buffer (&mux->primer);
|
|
header_byte_count += gst_buffer_get_size (buf);
|
|
buffers = g_list_prepend (buffers, buf);
|
|
|
|
mux->partition.header_byte_count = header_byte_count;
|
|
buf = mxf_partition_pack_to_buffer (&mux->partition);
|
|
if ((ret = gst_mxf_mux_push (mux, buf)) != GST_FLOW_OK) {
|
|
GST_ERROR_OBJECT (mux, "Failed pushing partition: %s",
|
|
gst_flow_get_name (ret));
|
|
g_list_foreach (buffers, (GFunc) gst_mini_object_unref, NULL);
|
|
g_list_free (buffers);
|
|
return ret;
|
|
}
|
|
|
|
for (l = buffers; l; l = l->next) {
|
|
buf = l->data;
|
|
l->data = NULL;
|
|
if ((ret = gst_mxf_mux_push (mux, buf)) != GST_FLOW_OK) {
|
|
GST_ERROR_OBJECT (mux, "Failed pushing buffer: %s",
|
|
gst_flow_get_name (ret));
|
|
g_list_foreach (l, (GFunc) gst_mini_object_unref, NULL);
|
|
g_list_free (buffers);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
g_list_free (buffers);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const guint8 _gc_essence_element_ul[] = {
|
|
0x06, 0x0e, 0x2b, 0x34, 0x01, 0x02, 0x01, 0x01,
|
|
0x0d, 0x01, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_mux_handle_buffer (GstMXFMux * mux, GstMXFMuxPad * pad)
|
|
{
|
|
GstBuffer *buf = gst_aggregator_pad_get_buffer (GST_AGGREGATOR_PAD (pad));
|
|
GstBuffer *outbuf = NULL;
|
|
GstMapInfo map;
|
|
gsize buf_size;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
guint8 slen, ber[9];
|
|
gboolean flush = gst_aggregator_pad_is_eos (GST_AGGREGATOR_PAD (pad))
|
|
&& !pad->have_complete_edit_unit && buf == NULL;
|
|
gboolean is_keyframe = buf ?
|
|
!GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) : TRUE;
|
|
|
|
if (pad->have_complete_edit_unit) {
|
|
GST_DEBUG_OBJECT (pad,
|
|
"Handling remaining buffer for track %u at position %" G_GINT64_FORMAT,
|
|
pad->source_track->parent.track_id, pad->pos);
|
|
if (buf)
|
|
gst_buffer_unref (buf);
|
|
buf = NULL;
|
|
} else if (!flush) {
|
|
if (buf)
|
|
gst_buffer_unref (buf);
|
|
buf = gst_aggregator_pad_steal_buffer (GST_AGGREGATOR_PAD (pad));
|
|
}
|
|
|
|
if (buf) {
|
|
GST_DEBUG_OBJECT (pad,
|
|
"Handling buffer of size %" G_GSIZE_FORMAT " for track %u at position %"
|
|
G_GINT64_FORMAT, gst_buffer_get_size (buf),
|
|
pad->source_track->parent.track_id, pad->pos);
|
|
} else {
|
|
flush = TRUE;
|
|
GST_DEBUG_OBJECT (pad,
|
|
"Flushing for track %u at position %" G_GINT64_FORMAT,
|
|
pad->source_track->parent.track_id, pad->pos);
|
|
}
|
|
|
|
ret = pad->write_func (buf, pad->mapping_data, pad->adapter, &outbuf, flush);
|
|
if (ret != GST_FLOW_OK && ret != GST_FLOW_CUSTOM_SUCCESS) {
|
|
GST_ERROR_OBJECT (pad,
|
|
"Failed handling buffer for track %u, reason %s",
|
|
pad->source_track->parent.track_id, gst_flow_get_name (ret));
|
|
return ret;
|
|
}
|
|
|
|
if (ret == GST_FLOW_CUSTOM_SUCCESS) {
|
|
pad->have_complete_edit_unit = TRUE;
|
|
ret = GST_FLOW_OK;
|
|
} else {
|
|
pad->have_complete_edit_unit = FALSE;
|
|
}
|
|
|
|
buf = outbuf;
|
|
if (buf == NULL)
|
|
return ret;
|
|
|
|
/* We currently only index the first essence stream */
|
|
if (pad == (GstMXFMuxPad *) GST_ELEMENT_CAST (mux)->sinkpads->data) {
|
|
MXFIndexTableSegment *segment;
|
|
const gint max_segment_size = G_MAXUINT16 / 11;
|
|
|
|
if (mux->index_table->len == 0 ||
|
|
g_array_index (mux->index_table, MXFIndexTableSegment,
|
|
mux->index_table->len - 1).index_duration >= max_segment_size) {
|
|
MXFIndexTableSegment s;
|
|
|
|
memset (&segment, 0, sizeof (segment));
|
|
|
|
mxf_uuid_init (&s.instance_id, mux->metadata);
|
|
memcpy (&s.index_edit_rate, &pad->source_track->edit_rate,
|
|
sizeof (s.index_edit_rate));
|
|
s.index_start_position = pad->pos;
|
|
s.index_duration = 0;
|
|
s.edit_unit_byte_count = 0;
|
|
s.index_sid =
|
|
mux->preface->content_storage->essence_container_data[0]->index_sid;
|
|
s.body_sid =
|
|
mux->preface->content_storage->essence_container_data[0]->body_sid;
|
|
s.slice_count = 0;
|
|
s.pos_table_count = 0;
|
|
s.n_delta_entries = 0;
|
|
s.delta_entries = NULL;
|
|
s.n_index_entries = 0;
|
|
s.index_entries = g_new0 (MXFIndexEntry, max_segment_size);
|
|
g_array_append_val (mux->index_table, s);
|
|
}
|
|
segment =
|
|
&g_array_index (mux->index_table, MXFIndexTableSegment,
|
|
mux->index_table->len - 1);
|
|
|
|
segment->index_entries[segment->n_index_entries].temporal_offset = 0;
|
|
segment->index_entries[segment->n_index_entries].key_frame_offset = 0;
|
|
segment->index_entries[segment->n_index_entries].flags = is_keyframe ? 0x80 : 0x20; /* FIXME: Need to distinguish all the cases */
|
|
segment->index_entries[segment->n_index_entries].stream_offset =
|
|
mux->partition.body_offset;
|
|
|
|
segment->n_index_entries++;
|
|
segment->index_duration++;
|
|
}
|
|
|
|
buf_size = gst_buffer_get_size (buf);
|
|
slen = mxf_ber_encode_size (buf_size, ber);
|
|
outbuf = gst_buffer_new_and_alloc (16 + slen);
|
|
gst_buffer_map (outbuf, &map, GST_MAP_WRITE);
|
|
memcpy (map.data, _gc_essence_element_ul, 16);
|
|
GST_WRITE_UINT32_BE (map.data + 12, pad->source_track->parent.track_number);
|
|
memcpy (map.data + 16, ber, slen);
|
|
gst_buffer_unmap (outbuf, &map);
|
|
outbuf = gst_buffer_append (outbuf, buf);
|
|
|
|
GST_DEBUG_OBJECT (pad,
|
|
"Pushing buffer of size %" G_GSIZE_FORMAT " for track %u",
|
|
gst_buffer_get_size (outbuf), pad->source_track->parent.track_id);
|
|
|
|
mux->partition.body_offset += gst_buffer_get_size (outbuf);
|
|
if ((ret = gst_mxf_mux_push (mux, outbuf)) != GST_FLOW_OK) {
|
|
GST_ERROR_OBJECT (pad,
|
|
"Failed pushing buffer for track %u, reason %s",
|
|
pad->source_track->parent.track_id, gst_flow_get_name (ret));
|
|
return ret;
|
|
}
|
|
|
|
pad->pos++;
|
|
pad->last_timestamp =
|
|
gst_util_uint64_scale (GST_SECOND * pad->pos,
|
|
pad->source_track->edit_rate.d, pad->source_track->edit_rate.n);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_mux_write_body_partition (GstMXFMux * mux)
|
|
{
|
|
GstBuffer *buf;
|
|
|
|
mux->partition.type = MXF_PARTITION_PACK_BODY;
|
|
mux->partition.this_partition = mux->offset;
|
|
mux->partition.prev_partition = 0;
|
|
mux->partition.footer_partition = 0;
|
|
mux->partition.header_byte_count = 0;
|
|
mux->partition.index_byte_count = 0;
|
|
mux->partition.index_sid = 0;
|
|
mux->partition.body_offset = 0;
|
|
mux->partition.body_sid =
|
|
mux->preface->content_storage->essence_container_data[0]->body_sid;
|
|
|
|
buf = mxf_partition_pack_to_buffer (&mux->partition);
|
|
return gst_mxf_mux_push (mux, buf);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_mux_handle_eos (GstMXFMux * mux)
|
|
{
|
|
GList *l;
|
|
gboolean have_data = FALSE;
|
|
GstBuffer *packet;
|
|
|
|
do {
|
|
GstMXFMuxPad *best = NULL;
|
|
|
|
have_data = FALSE;
|
|
|
|
GST_OBJECT_LOCK (mux);
|
|
for (l = GST_ELEMENT_CAST (mux)->sinkpads; l; l = l->next) {
|
|
GstMXFMuxPad *pad = l->data;
|
|
GstBuffer *buffer =
|
|
gst_aggregator_pad_get_buffer (GST_AGGREGATOR_PAD (pad));
|
|
|
|
GstClockTime next_gc_timestamp =
|
|
gst_util_uint64_scale ((mux->last_gc_position + 1) * GST_SECOND,
|
|
mux->min_edit_rate.d, mux->min_edit_rate.n);
|
|
|
|
if (pad->have_complete_edit_unit ||
|
|
gst_adapter_available (pad->adapter) > 0 || buffer) {
|
|
have_data = TRUE;
|
|
if (pad->last_timestamp < next_gc_timestamp) {
|
|
best = gst_object_ref (pad);
|
|
if (buffer)
|
|
gst_buffer_unref (buffer);
|
|
break;
|
|
}
|
|
}
|
|
if (buffer)
|
|
gst_buffer_unref (buffer);
|
|
|
|
if (have_data && !l->next) {
|
|
mux->last_gc_position++;
|
|
mux->last_gc_timestamp = next_gc_timestamp;
|
|
break;
|
|
}
|
|
}
|
|
GST_OBJECT_UNLOCK (mux);
|
|
|
|
if (best) {
|
|
gst_mxf_mux_handle_buffer (mux, best);
|
|
gst_object_unref (best);
|
|
have_data = TRUE;
|
|
}
|
|
} while (have_data);
|
|
|
|
mux->last_gc_position++;
|
|
mux->last_gc_timestamp =
|
|
gst_util_uint64_scale (mux->last_gc_position * GST_SECOND,
|
|
mux->min_edit_rate.d, mux->min_edit_rate.n);
|
|
|
|
/* Update essence track durations */
|
|
GST_OBJECT_LOCK (mux);
|
|
for (l = GST_ELEMENT_CAST (mux)->sinkpads; l; l = l->next) {
|
|
GstMXFMuxPad *pad = l->data;
|
|
guint i;
|
|
|
|
/* Update durations */
|
|
pad->source_track->parent.sequence->duration = pad->pos;
|
|
MXF_METADATA_SOURCE_CLIP (pad->source_track->parent.
|
|
sequence->structural_components[0])->parent.duration = pad->pos;
|
|
for (i = 0; i < mux->preface->content_storage->packages[0]->n_tracks; i++) {
|
|
MXFMetadataTimelineTrack *track;
|
|
|
|
if (!MXF_IS_METADATA_TIMELINE_TRACK (mux->preface->
|
|
content_storage->packages[0]->tracks[i])
|
|
|| !MXF_IS_METADATA_SOURCE_CLIP (mux->preface->
|
|
content_storage->packages[0]->tracks[i]->sequence->
|
|
structural_components[0]))
|
|
continue;
|
|
|
|
track =
|
|
MXF_METADATA_TIMELINE_TRACK (mux->preface->
|
|
content_storage->packages[0]->tracks[i]);
|
|
if (MXF_METADATA_SOURCE_CLIP (track->parent.
|
|
sequence->structural_components[0])->source_track_id ==
|
|
pad->source_track->parent.track_id) {
|
|
track->parent.sequence->structural_components[0]->duration = pad->pos;
|
|
track->parent.sequence->duration = pad->pos;
|
|
}
|
|
}
|
|
}
|
|
GST_OBJECT_UNLOCK (mux);
|
|
|
|
/* Update timecode track duration */
|
|
{
|
|
MXFMetadataTimelineTrack *track =
|
|
MXF_METADATA_TIMELINE_TRACK (mux->preface->
|
|
content_storage->packages[0]->tracks[0]);
|
|
MXFMetadataSequence *sequence = track->parent.sequence;
|
|
MXFMetadataTimecodeComponent *component =
|
|
MXF_METADATA_TIMECODE_COMPONENT (sequence->structural_components[0]);
|
|
|
|
sequence->duration = mux->last_gc_position;
|
|
component->parent.duration = mux->last_gc_position;
|
|
}
|
|
|
|
{
|
|
guint64 body_partition = mux->partition.this_partition;
|
|
guint32 body_sid = mux->partition.body_sid;
|
|
guint64 footer_partition = mux->offset;
|
|
GArray *rip;
|
|
GstFlowReturn ret;
|
|
GstSegment segment;
|
|
MXFRandomIndexPackEntry entry;
|
|
GList *index_entries = NULL, *l;
|
|
guint index_byte_count = 0;
|
|
guint i;
|
|
|
|
for (i = 0; i < mux->index_table->len; i++) {
|
|
MXFIndexTableSegment *segment =
|
|
&g_array_index (mux->index_table, MXFIndexTableSegment, i);
|
|
GstBuffer *segment_buffer = mxf_index_table_segment_to_buffer (segment);
|
|
|
|
index_byte_count += gst_buffer_get_size (segment_buffer);
|
|
index_entries = g_list_prepend (index_entries, segment_buffer);
|
|
}
|
|
|
|
mux->partition.type = MXF_PARTITION_PACK_FOOTER;
|
|
mux->partition.closed = TRUE;
|
|
mux->partition.complete = TRUE;
|
|
mux->partition.this_partition = mux->offset;
|
|
mux->partition.prev_partition = body_partition;
|
|
mux->partition.footer_partition = mux->offset;
|
|
mux->partition.header_byte_count = 0;
|
|
mux->partition.index_byte_count = index_byte_count;
|
|
mux->partition.index_sid =
|
|
mux->preface->content_storage->essence_container_data[0]->index_sid;
|
|
mux->partition.body_offset = 0;
|
|
mux->partition.body_sid = 0;
|
|
|
|
gst_mxf_mux_write_header_metadata (mux);
|
|
|
|
index_entries = g_list_reverse (index_entries);
|
|
for (l = index_entries; l; l = l->next) {
|
|
if ((ret = gst_mxf_mux_push (mux, l->data)) != GST_FLOW_OK) {
|
|
GST_ERROR_OBJECT (mux, "Failed pushing index table segment");
|
|
}
|
|
}
|
|
g_list_free (index_entries);
|
|
|
|
rip = g_array_sized_new (FALSE, FALSE, sizeof (MXFRandomIndexPackEntry), 3);
|
|
entry.offset = 0;
|
|
entry.body_sid = 0;
|
|
g_array_append_val (rip, entry);
|
|
entry.offset = body_partition;
|
|
entry.body_sid = body_sid;
|
|
g_array_append_val (rip, entry);
|
|
entry.offset = footer_partition;
|
|
entry.body_sid = 0;
|
|
g_array_append_val (rip, entry);
|
|
|
|
packet = mxf_random_index_pack_to_buffer (rip);
|
|
if ((ret = gst_mxf_mux_push (mux, packet)) != GST_FLOW_OK) {
|
|
GST_ERROR_OBJECT (mux, "Failed pushing random index pack");
|
|
}
|
|
g_array_free (rip, TRUE);
|
|
|
|
/* Rewrite header partition with updated values */
|
|
gst_segment_init (&segment, GST_FORMAT_BYTES);
|
|
if (gst_pad_push_event (GST_AGGREGATOR_SRC_PAD (mux),
|
|
gst_event_new_segment (&segment))) {
|
|
mux->offset = 0;
|
|
mux->partition.type = MXF_PARTITION_PACK_HEADER;
|
|
mux->partition.closed = TRUE;
|
|
mux->partition.complete = TRUE;
|
|
mux->partition.this_partition = 0;
|
|
mux->partition.prev_partition = 0;
|
|
mux->partition.footer_partition = footer_partition;
|
|
mux->partition.header_byte_count = 0;
|
|
mux->partition.index_byte_count = 0;
|
|
mux->partition.index_sid = 0;
|
|
mux->partition.body_offset = 0;
|
|
mux->partition.body_sid = 0;
|
|
|
|
ret = gst_mxf_mux_write_header_metadata (mux);
|
|
if (ret != GST_FLOW_OK) {
|
|
GST_ERROR_OBJECT (mux, "Rewriting header partition failed");
|
|
return ret;
|
|
}
|
|
} else {
|
|
GST_WARNING_OBJECT (mux, "Can't rewrite header partition");
|
|
}
|
|
}
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static gint
|
|
_sort_mux_pads (gconstpointer a, gconstpointer b)
|
|
{
|
|
const GstMXFMuxPad *pa = a, *pb = b;
|
|
MXFMetadataTrackType ta =
|
|
mxf_metadata_track_identifier_parse (&pa->writer->data_definition);
|
|
MXFMetadataTrackType tb =
|
|
mxf_metadata_track_identifier_parse (&pb->writer->data_definition);
|
|
|
|
if (ta != tb)
|
|
return ta - tb;
|
|
|
|
return pa->source_track->parent.track_number -
|
|
pa->source_track->parent.track_number;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
gst_mxf_mux_stop (GstAggregator * aggregator)
|
|
{
|
|
GstMXFMux *mux = GST_MXF_MUX (aggregator);
|
|
|
|
gst_mxf_mux_reset (mux);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_mux_aggregate (GstAggregator * aggregator, gboolean timeout)
|
|
{
|
|
GstMXFMux *mux = GST_MXF_MUX (aggregator);
|
|
GstMXFMuxPad *best = NULL;
|
|
GstFlowReturn ret;
|
|
GList *l;
|
|
gboolean eos = TRUE;
|
|
|
|
if (timeout) {
|
|
GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
|
|
("Live mixing and got a timeout. This is not supported yet"));
|
|
ret = GST_FLOW_ERROR;
|
|
goto error;
|
|
}
|
|
|
|
if (mux->state == GST_MXF_MUX_STATE_ERROR) {
|
|
GST_ERROR_OBJECT (mux, "Had an error before -- returning");
|
|
return GST_FLOW_ERROR;
|
|
} else if (mux->state == GST_MXF_MUX_STATE_EOS) {
|
|
GST_WARNING_OBJECT (mux, "EOS");
|
|
return GST_FLOW_EOS;
|
|
}
|
|
|
|
if (mux->state == GST_MXF_MUX_STATE_HEADER) {
|
|
GstCaps *caps;
|
|
|
|
if (GST_ELEMENT_CAST (mux)->sinkpads == NULL) {
|
|
GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
|
|
("No input streams configured"));
|
|
ret = GST_FLOW_ERROR;
|
|
goto error;
|
|
}
|
|
|
|
caps = gst_caps_new_empty_simple ("application/mxf");
|
|
gst_aggregator_set_src_caps (GST_AGGREGATOR (mux), caps);
|
|
gst_caps_unref (caps);
|
|
|
|
if ((ret = gst_mxf_mux_create_metadata (mux)) != GST_FLOW_OK)
|
|
goto error;
|
|
|
|
if ((ret = gst_mxf_mux_init_partition_pack (mux)) != GST_FLOW_OK)
|
|
goto error;
|
|
|
|
if ((ret = gst_mxf_mux_write_header_metadata (mux)) != GST_FLOW_OK)
|
|
goto error;
|
|
|
|
/* Sort pads, we will always write in that order */
|
|
GST_OBJECT_LOCK (mux);
|
|
GST_ELEMENT_CAST (mux)->sinkpads =
|
|
g_list_sort (GST_ELEMENT_CAST (mux)->sinkpads, _sort_mux_pads);
|
|
GST_OBJECT_UNLOCK (mux);
|
|
|
|
/* Write body partition */
|
|
ret = gst_mxf_mux_write_body_partition (mux);
|
|
if (ret != GST_FLOW_OK)
|
|
goto error;
|
|
mux->state = GST_MXF_MUX_STATE_DATA;
|
|
}
|
|
|
|
g_return_val_if_fail (g_hash_table_size (mux->metadata) > 0, GST_FLOW_ERROR);
|
|
|
|
do {
|
|
GST_OBJECT_LOCK (mux);
|
|
for (l = GST_ELEMENT_CAST (mux)->sinkpads; l; l = l->next) {
|
|
gboolean pad_eos;
|
|
GstMXFMuxPad *pad = l->data;
|
|
GstBuffer *buffer;
|
|
GstClockTime next_gc_timestamp =
|
|
gst_util_uint64_scale ((mux->last_gc_position + 1) * GST_SECOND,
|
|
mux->min_edit_rate.d, mux->min_edit_rate.n);
|
|
|
|
pad_eos = gst_aggregator_pad_is_eos (GST_AGGREGATOR_PAD (pad));
|
|
if (!pad_eos)
|
|
eos = FALSE;
|
|
|
|
buffer = gst_aggregator_pad_get_buffer (GST_AGGREGATOR_PAD (pad));
|
|
|
|
if ((!pad_eos || pad->have_complete_edit_unit ||
|
|
gst_adapter_available (pad->adapter) > 0 || buffer)
|
|
&& pad->last_timestamp < next_gc_timestamp) {
|
|
if (buffer)
|
|
gst_buffer_unref (buffer);
|
|
best = gst_object_ref (pad);
|
|
break;
|
|
} else if (!eos && !l->next) {
|
|
mux->last_gc_position++;
|
|
mux->last_gc_timestamp = next_gc_timestamp;
|
|
eos = FALSE;
|
|
if (buffer)
|
|
gst_buffer_unref (buffer);
|
|
best = NULL;
|
|
break;
|
|
}
|
|
if (buffer)
|
|
gst_buffer_unref (buffer);
|
|
}
|
|
GST_OBJECT_UNLOCK (mux);
|
|
} while (!eos && best == NULL);
|
|
|
|
if (!eos && best) {
|
|
ret = gst_mxf_mux_handle_buffer (mux, best);
|
|
gst_object_unref (best);
|
|
if (ret != GST_FLOW_OK)
|
|
goto error;
|
|
} else if (eos) {
|
|
GST_DEBUG_OBJECT (mux, "Handling EOS");
|
|
|
|
if (best)
|
|
gst_object_unref (best);
|
|
|
|
gst_mxf_mux_handle_eos (mux);
|
|
mux->state = GST_MXF_MUX_STATE_EOS;
|
|
return GST_FLOW_EOS;
|
|
} else {
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
return GST_FLOW_OK;
|
|
|
|
error:
|
|
{
|
|
mux->state = GST_MXF_MUX_STATE_ERROR;
|
|
return ret;
|
|
}
|
|
}
|