| /* 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 + 1; |
| p->parent.tracks = g_new0 (MXFMetadataTrack *, p->parent.n_tracks); |
| |
| if (p->parent.n_tracks > 2) { |
| 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 - 1; |
| d->sub_descriptors = |
| g_new0 (MXFMetadataGenericDescriptor *, p->parent.n_tracks - 1); |
| |
| 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; |
| |
| n = 1; |
| |
| /* 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 == 2) { |
| p->descriptor = (MXFMetadataGenericDescriptor *) pad->descriptor; |
| } else { |
| MXF_METADATA_MULTIPLE_DESCRIPTOR (p-> |
| descriptor)->sub_descriptors[n - 1] = |
| (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]); |
| |
| 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 + 1; |
| |
| 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)); |
| } |
| } |
| |
| /* Timecode track */ |
| { |
| MXFMetadataSourcePackage *p; |
| MXFMetadataTimelineTrack *track; |
| MXFMetadataSequence *sequence; |
| MXFMetadataTimecodeComponent *component; |
| guint n = 0; |
| |
| p = (MXFMetadataSourcePackage *) cstorage->packages[1]; |
| |
| 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); |
| |
| 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, &mux->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 */ |
| } |
| |
| |
| for (i = 1; 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 = 1; 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 = 1; 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 = 2; |
| 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.closed = TRUE; |
| mux->partition.complete = TRUE; |
| 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; |
| } |
| |
| { |
| MXFMetadataTimelineTrack *track = |
| MXF_METADATA_TIMELINE_TRACK (mux->preface-> |
| content_storage->packages[1]->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; |
| GstBuffer *buf; |
| |
| 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; |
| } |
| |
| g_assert (mux->offset == body_partition); |
| |
| mux->partition.type = MXF_PARTITION_PACK_BODY; |
| mux->partition.closed = TRUE; |
| mux->partition.complete = TRUE; |
| mux->partition.this_partition = mux->offset; |
| 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 = |
| mux->preface->content_storage->essence_container_data[0]->body_sid; |
| |
| buf = mxf_partition_pack_to_buffer (&mux->partition); |
| ret = gst_mxf_mux_push (mux, buf); |
| if (ret != GST_FLOW_OK) { |
| GST_ERROR_OBJECT (mux, "Rewriting body 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; |
| } |
| } |