| /* GStreamer |
| * Copyright (C) 2008-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-mxfdemux |
| * |
| * mxfdemux demuxes an MXF file into the different contained streams. |
| * |
| * <refsect2> |
| * <title>Example launch line</title> |
| * |[ |
| * gst-launch-1.0 -v filesrc location=/path/to/mxf ! mxfdemux ! audioconvert ! autoaudiosink |
| * ]| This pipeline demuxes an MXF file and outputs one of the contained raw audio streams. |
| * </refsect2> |
| */ |
| |
| /* TODO: |
| * - Handle timecode tracks correctly (where is this documented?) |
| * - Handle drop-frame field of timecode tracks |
| * - Handle Generic container system items |
| * - Implement correct support for clip-wrapped essence elements. |
| * - Post structural metadata and descriptive metadata trees as a message on the bus |
| * and send them downstream as event. |
| * - Multichannel audio needs channel layouts, define them (SMPTE S320M?). |
| * - Correctly handle the different rectangles and aspect-ratio for video |
| * - Add more support for non-standard MXF used by Avid (bug #561922). |
| * - Fix frame layout stuff, i.e. interlaced/progressive |
| * - In pull mode first find the first buffer for every pad before pushing |
| * to prevent jumpy playback in the beginning due to resynchronization. |
| * |
| * - Implement SMPTE D11 essence and the digital cinema/MXF specs |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "mxfdemux.h" |
| #include "mxfessence.h" |
| |
| #include <string.h> |
| |
| static GstStaticPadTemplate mxf_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("application/mxf") |
| ); |
| |
| static GstStaticPadTemplate mxf_src_template = |
| GST_STATIC_PAD_TEMPLATE ("track_%u", |
| GST_PAD_SRC, |
| GST_PAD_SOMETIMES, |
| GST_STATIC_CAPS_ANY); |
| |
| GST_DEBUG_CATEGORY_STATIC (mxfdemux_debug); |
| #define GST_CAT_DEFAULT mxfdemux_debug |
| |
| static GstFlowReturn |
| gst_mxf_demux_pull_klv_packet (GstMXFDemux * demux, guint64 offset, MXFUL * key, |
| GstBuffer ** outbuf, guint * read); |
| static GstFlowReturn |
| gst_mxf_demux_handle_index_table_segment (GstMXFDemux * demux, |
| const MXFUL * key, GstBuffer * buffer, guint64 offset); |
| |
| static void collect_index_table_segments (GstMXFDemux * demux); |
| |
| GType gst_mxf_demux_pad_get_type (void); |
| G_DEFINE_TYPE (GstMXFDemuxPad, gst_mxf_demux_pad, GST_TYPE_PAD); |
| |
| static void |
| gst_mxf_demux_pad_finalize (GObject * object) |
| { |
| GstMXFDemuxPad *pad = GST_MXF_DEMUX_PAD (object); |
| |
| if (pad->tags) { |
| gst_tag_list_unref (pad->tags); |
| pad->tags = NULL; |
| } |
| |
| G_OBJECT_CLASS (gst_mxf_demux_pad_parent_class)->finalize (object); |
| } |
| |
| static void |
| gst_mxf_demux_pad_class_init (GstMXFDemuxPadClass * klass) |
| { |
| GObjectClass *gobject_class = (GObjectClass *) klass; |
| |
| gobject_class->finalize = gst_mxf_demux_pad_finalize; |
| } |
| |
| static void |
| gst_mxf_demux_pad_init (GstMXFDemuxPad * pad) |
| { |
| pad->position = 0; |
| } |
| |
| enum |
| { |
| PROP_0, |
| PROP_PACKAGE, |
| PROP_MAX_DRIFT, |
| PROP_STRUCTURE |
| }; |
| |
| static gboolean gst_mxf_demux_sink_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static gboolean gst_mxf_demux_src_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static gboolean gst_mxf_demux_src_query (GstPad * pad, GstObject * parent, |
| GstQuery * query); |
| |
| #define gst_mxf_demux_parent_class parent_class |
| G_DEFINE_TYPE (GstMXFDemux, gst_mxf_demux, GST_TYPE_ELEMENT); |
| |
| static void |
| gst_mxf_demux_remove_pad (GstMXFDemuxPad * pad, GstMXFDemux * demux) |
| { |
| gst_flow_combiner_remove_pad (demux->flowcombiner, GST_PAD_CAST (pad)); |
| gst_element_remove_pad (GST_ELEMENT (demux), GST_PAD_CAST (pad)); |
| } |
| |
| static void |
| gst_mxf_demux_remove_pads (GstMXFDemux * demux) |
| { |
| g_ptr_array_foreach (demux->src, (GFunc) gst_mxf_demux_remove_pad, demux); |
| g_ptr_array_foreach (demux->src, (GFunc) gst_object_unref, NULL); |
| g_ptr_array_set_size (demux->src, 0); |
| } |
| |
| static void |
| gst_mxf_demux_partition_free (GstMXFDemuxPartition * partition) |
| { |
| mxf_partition_pack_reset (&partition->partition); |
| mxf_primer_pack_reset (&partition->primer); |
| |
| g_free (partition); |
| } |
| |
| static void |
| gst_mxf_demux_reset_mxf_state (GstMXFDemux * demux) |
| { |
| guint i; |
| |
| GST_DEBUG_OBJECT (demux, "Resetting MXF state"); |
| |
| g_list_foreach (demux->partitions, (GFunc) gst_mxf_demux_partition_free, |
| NULL); |
| g_list_free (demux->partitions); |
| demux->partitions = NULL; |
| |
| demux->current_partition = NULL; |
| |
| for (i = 0; i < demux->essence_tracks->len; i++) { |
| GstMXFDemuxEssenceTrack *t = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, i); |
| |
| if (t->offsets) |
| g_array_free (t->offsets, TRUE); |
| |
| g_free (t->mapping_data); |
| |
| if (t->tags) |
| gst_tag_list_unref (t->tags); |
| |
| if (t->caps) |
| gst_caps_unref (t->caps); |
| } |
| g_array_set_size (demux->essence_tracks, 0); |
| } |
| |
| static void |
| gst_mxf_demux_reset_linked_metadata (GstMXFDemux * demux) |
| { |
| guint i; |
| |
| for (i = 0; i < demux->src->len; i++) { |
| GstMXFDemuxPad *pad = g_ptr_array_index (demux->src, i); |
| |
| pad->material_track = NULL; |
| pad->material_package = NULL; |
| pad->current_component = NULL; |
| } |
| |
| for (i = 0; i < demux->essence_tracks->len; i++) { |
| GstMXFDemuxEssenceTrack *track = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, i); |
| |
| track->source_package = NULL; |
| track->source_track = NULL; |
| } |
| |
| demux->current_package = NULL; |
| } |
| |
| static void |
| gst_mxf_demux_reset_metadata (GstMXFDemux * demux) |
| { |
| GST_DEBUG_OBJECT (demux, "Resetting metadata"); |
| |
| g_rw_lock_writer_lock (&demux->metadata_lock); |
| |
| demux->update_metadata = TRUE; |
| demux->metadata_resolved = FALSE; |
| |
| gst_mxf_demux_reset_linked_metadata (demux); |
| |
| demux->preface = NULL; |
| |
| if (demux->metadata) { |
| g_hash_table_destroy (demux->metadata); |
| } |
| demux->metadata = mxf_metadata_hash_table_new (); |
| |
| if (demux->tags) { |
| gst_tag_list_unref (demux->tags); |
| demux->tags = NULL; |
| } |
| |
| g_rw_lock_writer_unlock (&demux->metadata_lock); |
| } |
| |
| static void |
| gst_mxf_demux_reset (GstMXFDemux * demux) |
| { |
| GST_DEBUG_OBJECT (demux, "cleaning up MXF demuxer"); |
| |
| demux->flushing = FALSE; |
| |
| demux->footer_partition_pack_offset = 0; |
| demux->offset = 0; |
| |
| demux->pull_footer_metadata = TRUE; |
| |
| demux->run_in = -1; |
| |
| memset (&demux->current_package_uid, 0, sizeof (MXFUMID)); |
| |
| gst_segment_init (&demux->segment, GST_FORMAT_TIME); |
| |
| if (demux->close_seg_event) { |
| gst_event_unref (demux->close_seg_event); |
| demux->close_seg_event = NULL; |
| } |
| |
| gst_adapter_clear (demux->adapter); |
| |
| gst_mxf_demux_remove_pads (demux); |
| |
| if (demux->random_index_pack) { |
| g_array_free (demux->random_index_pack, TRUE); |
| demux->random_index_pack = NULL; |
| } |
| |
| if (demux->pending_index_table_segments) { |
| GList *l; |
| |
| for (l = demux->pending_index_table_segments; l; l = l->next) { |
| MXFIndexTableSegment *s = l->data; |
| mxf_index_table_segment_reset (s); |
| g_free (s); |
| } |
| g_list_free (demux->pending_index_table_segments); |
| demux->pending_index_table_segments = NULL; |
| } |
| |
| if (demux->index_tables) { |
| GList *l; |
| |
| for (l = demux->index_tables; l; l = l->next) { |
| GstMXFDemuxIndexTable *t = l->data; |
| g_array_free (t->offsets, TRUE); |
| g_free (t); |
| } |
| g_list_free (demux->index_tables); |
| demux->index_tables = NULL; |
| } |
| |
| demux->index_table_segments_collected = FALSE; |
| |
| gst_mxf_demux_reset_mxf_state (demux); |
| gst_mxf_demux_reset_metadata (demux); |
| |
| demux->have_group_id = FALSE; |
| demux->group_id = G_MAXUINT; |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_pull_range (GstMXFDemux * demux, guint64 offset, |
| guint size, GstBuffer ** buffer) |
| { |
| GstFlowReturn ret; |
| |
| ret = gst_pad_pull_range (demux->sinkpad, offset, size, buffer); |
| if (G_UNLIKELY (ret != GST_FLOW_OK)) { |
| GST_WARNING_OBJECT (demux, |
| "failed when pulling %u bytes from offset %" G_GUINT64_FORMAT ": %s", |
| size, offset, gst_flow_get_name (ret)); |
| *buffer = NULL; |
| return ret; |
| } |
| |
| if (G_UNLIKELY (*buffer && gst_buffer_get_size (*buffer) != size)) { |
| GST_WARNING_OBJECT (demux, |
| "partial pull got %" G_GSIZE_FORMAT " when expecting %u from offset %" |
| G_GUINT64_FORMAT, gst_buffer_get_size (*buffer), size, offset); |
| gst_buffer_unref (*buffer); |
| ret = GST_FLOW_EOS; |
| *buffer = NULL; |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_mxf_demux_push_src_event (GstMXFDemux * demux, GstEvent * event) |
| { |
| gboolean ret = TRUE; |
| guint i; |
| |
| GST_DEBUG_OBJECT (demux, "Pushing '%s' event downstream", |
| GST_EVENT_TYPE_NAME (event)); |
| |
| for (i = 0; i < demux->src->len; i++) { |
| GstMXFDemuxPad *pad = GST_MXF_DEMUX_PAD (g_ptr_array_index (demux->src, i)); |
| |
| if (pad->eos && GST_EVENT_TYPE (event) == GST_EVENT_EOS) |
| continue; |
| |
| ret |= gst_pad_push_event (GST_PAD_CAST (pad), gst_event_ref (event)); |
| } |
| |
| gst_event_unref (event); |
| |
| return ret; |
| } |
| |
| static GstMXFDemuxPad * |
| gst_mxf_demux_get_earliest_pad (GstMXFDemux * demux) |
| { |
| guint i; |
| GstClockTime earliest = GST_CLOCK_TIME_NONE; |
| GstMXFDemuxPad *pad = NULL; |
| |
| for (i = 0; i < demux->src->len; i++) { |
| GstMXFDemuxPad *p = g_ptr_array_index (demux->src, i); |
| |
| if (!p->eos && p->position < earliest) { |
| earliest = p->position; |
| pad = p; |
| } |
| } |
| |
| return pad; |
| } |
| |
| static gint |
| gst_mxf_demux_partition_compare (GstMXFDemuxPartition * a, |
| GstMXFDemuxPartition * b) |
| { |
| return (a->partition.this_partition - b->partition.this_partition); |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_handle_partition_pack (GstMXFDemux * demux, const MXFUL * key, |
| GstBuffer * buffer) |
| { |
| MXFPartitionPack partition; |
| GList *l; |
| GstMXFDemuxPartition *p = NULL; |
| GstMapInfo map; |
| gboolean ret; |
| |
| GST_DEBUG_OBJECT (demux, |
| "Handling partition pack of size %" G_GSIZE_FORMAT " at offset %" |
| G_GUINT64_FORMAT, gst_buffer_get_size (buffer), demux->offset); |
| |
| for (l = demux->partitions; l; l = l->next) { |
| GstMXFDemuxPartition *tmp = l->data; |
| |
| if (tmp->partition.this_partition + demux->run_in == demux->offset && |
| tmp->partition.major_version == 0x0001) { |
| GST_DEBUG_OBJECT (demux, "Partition already parsed"); |
| p = tmp; |
| goto out; |
| } |
| } |
| |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| ret = mxf_partition_pack_parse (key, &partition, map.data, map.size); |
| gst_buffer_unmap (buffer, &map); |
| if (!ret) { |
| GST_ERROR_OBJECT (demux, "Parsing partition pack failed"); |
| return GST_FLOW_ERROR; |
| } |
| |
| if (partition.this_partition != demux->offset + demux->run_in) { |
| GST_WARNING_OBJECT (demux, "Partition with incorrect offset"); |
| partition.this_partition = demux->offset + demux->run_in; |
| } |
| |
| if (partition.type == MXF_PARTITION_PACK_HEADER) |
| demux->footer_partition_pack_offset = partition.footer_partition; |
| |
| for (l = demux->partitions; l; l = l->next) { |
| GstMXFDemuxPartition *tmp = l->data; |
| |
| if (tmp->partition.this_partition + demux->run_in == demux->offset) { |
| p = tmp; |
| break; |
| } |
| } |
| |
| if (p) { |
| mxf_partition_pack_reset (&p->partition); |
| memcpy (&p->partition, &partition, sizeof (MXFPartitionPack)); |
| } else { |
| p = g_new0 (GstMXFDemuxPartition, 1); |
| memcpy (&p->partition, &partition, sizeof (MXFPartitionPack)); |
| demux->partitions = |
| g_list_insert_sorted (demux->partitions, p, |
| (GCompareFunc) gst_mxf_demux_partition_compare); |
| } |
| |
| for (l = demux->partitions; l; l = l->next) { |
| GstMXFDemuxPartition *a, *b; |
| |
| if (l->next == NULL) |
| break; |
| |
| a = l->data; |
| b = l->next->data; |
| |
| b->partition.prev_partition = a->partition.this_partition; |
| } |
| |
| out: |
| demux->current_partition = p; |
| |
| return GST_FLOW_OK; |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_handle_primer_pack (GstMXFDemux * demux, const MXFUL * key, |
| GstBuffer * buffer) |
| { |
| GstMapInfo map; |
| gboolean ret; |
| |
| GST_DEBUG_OBJECT (demux, |
| "Handling primer pack of size %" G_GSIZE_FORMAT " at offset %" |
| G_GUINT64_FORMAT, gst_buffer_get_size (buffer), demux->offset); |
| |
| if (G_UNLIKELY (!demux->current_partition)) { |
| GST_ERROR_OBJECT (demux, "Primer pack before partition pack"); |
| return GST_FLOW_ERROR; |
| } |
| |
| if (G_UNLIKELY (demux->current_partition->primer.mappings)) { |
| GST_DEBUG_OBJECT (demux, "Primer pack already exists"); |
| return GST_FLOW_OK; |
| } |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| ret = mxf_primer_pack_parse (key, &demux->current_partition->primer, |
| map.data, map.size); |
| gst_buffer_unmap (buffer, &map); |
| if (!ret) { |
| GST_ERROR_OBJECT (demux, "Parsing primer pack failed"); |
| return GST_FLOW_ERROR; |
| } |
| |
| demux->current_partition->primer.offset = demux->offset; |
| |
| return GST_FLOW_OK; |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_resolve_references (GstMXFDemux * demux) |
| { |
| GstFlowReturn ret = GST_FLOW_OK; |
| GHashTableIter iter; |
| MXFMetadataBase *m = NULL; |
| GstStructure *structure; |
| |
| g_rw_lock_writer_lock (&demux->metadata_lock); |
| |
| GST_DEBUG_OBJECT (demux, "Resolve metadata references"); |
| demux->update_metadata = FALSE; |
| |
| if (!demux->metadata) { |
| GST_ERROR_OBJECT (demux, "No metadata yet"); |
| g_rw_lock_writer_unlock (&demux->metadata_lock); |
| return GST_FLOW_ERROR; |
| } |
| |
| g_hash_table_iter_init (&iter, demux->metadata); |
| while (g_hash_table_iter_next (&iter, NULL, (gpointer) & m)) { |
| m->resolved = MXF_METADATA_BASE_RESOLVE_STATE_NONE; |
| } |
| |
| g_hash_table_iter_init (&iter, demux->metadata); |
| while (g_hash_table_iter_next (&iter, NULL, (gpointer) & m)) { |
| gboolean resolved; |
| |
| resolved = mxf_metadata_base_resolve (m, demux->metadata); |
| |
| /* Resolving can fail for anything but the preface, as the preface |
| * will resolve everything required */ |
| if (!resolved && MXF_IS_METADATA_PREFACE (m)) { |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } |
| } |
| |
| demux->metadata_resolved = TRUE; |
| |
| structure = |
| mxf_metadata_base_to_structure (MXF_METADATA_BASE (demux->preface)); |
| if (!demux->tags) |
| demux->tags = gst_tag_list_new_empty (); |
| |
| gst_tag_list_add (demux->tags, GST_TAG_MERGE_REPLACE, GST_TAG_MXF_STRUCTURE, |
| structure, NULL); |
| |
| gst_structure_free (structure); |
| |
| g_rw_lock_writer_unlock (&demux->metadata_lock); |
| |
| return ret; |
| |
| error: |
| demux->metadata_resolved = FALSE; |
| g_rw_lock_writer_unlock (&demux->metadata_lock); |
| |
| return ret; |
| } |
| |
| static MXFMetadataGenericPackage * |
| gst_mxf_demux_find_package (GstMXFDemux * demux, const MXFUMID * umid) |
| { |
| MXFMetadataGenericPackage *ret = NULL; |
| guint i; |
| |
| if (demux->preface->content_storage |
| && demux->preface->content_storage->packages) { |
| for (i = 0; i < demux->preface->content_storage->n_packages; i++) { |
| MXFMetadataGenericPackage *p = |
| demux->preface->content_storage->packages[i]; |
| |
| if (!p) |
| continue; |
| |
| if (mxf_umid_is_equal (&p->package_uid, umid)) { |
| ret = p; |
| break; |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| static MXFMetadataGenericPackage * |
| gst_mxf_demux_choose_package (GstMXFDemux * demux) |
| { |
| MXFMetadataGenericPackage *ret = NULL; |
| guint i; |
| |
| if (demux->requested_package_string) { |
| MXFUMID umid = { {0,} |
| }; |
| |
| if (!mxf_umid_from_string (demux->requested_package_string, &umid)) { |
| GST_ERROR_OBJECT (demux, "Invalid requested package"); |
| } |
| g_free (demux->requested_package_string); |
| demux->requested_package_string = NULL; |
| |
| ret = gst_mxf_demux_find_package (demux, &umid); |
| } |
| |
| if (!ret && !mxf_umid_is_zero (&demux->current_package_uid)) |
| ret = gst_mxf_demux_find_package (demux, &demux->current_package_uid); |
| |
| if (ret && (MXF_IS_METADATA_MATERIAL_PACKAGE (ret) |
| || (MXF_IS_METADATA_SOURCE_PACKAGE (ret) |
| && MXF_METADATA_SOURCE_PACKAGE (ret)->top_level))) |
| goto done; |
| else if (ret) |
| GST_WARNING_OBJECT (demux, |
| "Current package is not a material package or top-level source package, choosing the first best"); |
| else if (!mxf_umid_is_zero (&demux->current_package_uid)) |
| GST_WARNING_OBJECT (demux, |
| "Current package not found, choosing the first best"); |
| |
| ret = demux->preface->primary_package; |
| if (ret && (MXF_IS_METADATA_MATERIAL_PACKAGE (ret) |
| || (MXF_IS_METADATA_SOURCE_PACKAGE (ret) |
| && MXF_METADATA_SOURCE_PACKAGE (ret)->top_level))) |
| goto done; |
| ret = NULL; |
| |
| for (i = 0; i < demux->preface->content_storage->n_packages; i++) { |
| if (demux->preface->content_storage->packages[i] && |
| MXF_IS_METADATA_MATERIAL_PACKAGE (demux->preface->content_storage-> |
| packages[i])) { |
| ret = |
| MXF_METADATA_GENERIC_PACKAGE (demux->preface->content_storage-> |
| packages[i]); |
| break; |
| } |
| } |
| |
| if (!ret) { |
| GST_ERROR_OBJECT (demux, "No material package"); |
| return NULL; |
| } |
| |
| done: |
| if (mxf_umid_is_equal (&ret->package_uid, &demux->current_package_uid)) { |
| gchar current_package_string[96]; |
| |
| gst_mxf_demux_remove_pads (demux); |
| memcpy (&demux->current_package_uid, &ret->package_uid, 32); |
| |
| mxf_umid_to_string (&ret->package_uid, current_package_string); |
| demux->current_package_string = g_strdup (current_package_string); |
| g_object_notify (G_OBJECT (demux), "package"); |
| |
| if (!demux->tags) |
| demux->tags = gst_tag_list_new_empty (); |
| gst_tag_list_add (demux->tags, GST_TAG_MERGE_REPLACE, GST_TAG_MXF_UMID, |
| demux->current_package_string, NULL); |
| } |
| demux->current_package = ret; |
| |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_update_essence_tracks (GstMXFDemux * demux) |
| { |
| guint i, j, k; |
| |
| g_return_val_if_fail (demux->preface->content_storage, GST_FLOW_ERROR); |
| g_return_val_if_fail (demux->preface->content_storage->essence_container_data, |
| GST_FLOW_ERROR); |
| |
| for (i = 0; i < demux->preface->content_storage->n_essence_container_data; |
| i++) { |
| MXFMetadataEssenceContainerData *edata; |
| MXFMetadataSourcePackage *package; |
| |
| if (demux->preface->content_storage->essence_container_data[i] == NULL) |
| continue; |
| |
| edata = demux->preface->content_storage->essence_container_data[i]; |
| |
| if (!edata->linked_package) { |
| GST_WARNING_OBJECT (demux, "Linked package not resolved"); |
| continue; |
| } |
| |
| package = edata->linked_package; |
| |
| if (!package->parent.tracks) { |
| GST_WARNING_OBJECT (demux, "Linked package with no resolved tracks"); |
| continue; |
| } |
| |
| for (j = 0; j < package->parent.n_tracks; j++) { |
| MXFMetadataTimelineTrack *track; |
| GstMXFDemuxEssenceTrack *etrack = NULL; |
| GstCaps *caps = NULL; |
| gboolean new = FALSE; |
| |
| if (!package->parent.tracks[j] |
| || !MXF_IS_METADATA_TIMELINE_TRACK (package->parent.tracks[j])) |
| continue; |
| |
| track = MXF_METADATA_TIMELINE_TRACK (package->parent.tracks[j]); |
| if ((track->parent.type & 0xf0) != 0x30) |
| continue; |
| |
| if (track->edit_rate.n <= 0 || track->edit_rate.d <= 0) { |
| GST_WARNING_OBJECT (demux, "Invalid edit rate"); |
| continue; |
| } |
| |
| for (k = 0; k < demux->essence_tracks->len; k++) { |
| GstMXFDemuxEssenceTrack *tmp = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, |
| k); |
| |
| if (tmp->track_number == track->parent.track_number && |
| tmp->body_sid == edata->body_sid) { |
| if (tmp->track_id != track->parent.track_id || |
| !mxf_umid_is_equal (&tmp->source_package_uid, |
| &package->parent.package_uid)) { |
| GST_ERROR_OBJECT (demux, "There already exists a different track " |
| "with this track number and body sid but a different source " |
| "or source track id -- ignoring"); |
| continue; |
| } |
| etrack = tmp; |
| break; |
| } |
| } |
| |
| if (!etrack) { |
| GstMXFDemuxEssenceTrack tmp; |
| |
| memset (&tmp, 0, sizeof (tmp)); |
| tmp.body_sid = edata->body_sid; |
| tmp.index_sid = edata->index_sid; |
| tmp.track_number = track->parent.track_number; |
| tmp.track_id = track->parent.track_id; |
| memcpy (&tmp.source_package_uid, &package->parent.package_uid, 32); |
| |
| if (demux->current_partition->partition.body_sid == edata->body_sid && |
| demux->current_partition->partition.body_offset == 0) |
| tmp.position = 0; |
| else |
| tmp.position = -1; |
| |
| g_array_append_val (demux->essence_tracks, tmp); |
| etrack = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, |
| demux->essence_tracks->len - 1); |
| new = TRUE; |
| } |
| |
| etrack->source_package = NULL; |
| etrack->source_track = NULL; |
| |
| if (!track->parent.sequence) { |
| GST_WARNING_OBJECT (demux, "Source track has no sequence"); |
| goto next; |
| } |
| |
| if (track->parent.n_descriptor == 0) { |
| GST_WARNING_OBJECT (demux, "Source track has no descriptors"); |
| goto next; |
| } |
| |
| if (track->parent.sequence->duration > etrack->duration) |
| etrack->duration = track->parent.sequence->duration; |
| |
| g_free (etrack->mapping_data); |
| etrack->mapping_data = NULL; |
| etrack->handler = NULL; |
| etrack->handle_func = NULL; |
| if (etrack->tags) |
| gst_tag_list_unref (etrack->tags); |
| etrack->tags = NULL; |
| |
| etrack->handler = mxf_essence_element_handler_find (track); |
| if (!etrack->handler) { |
| gchar essence_container[48]; |
| gchar essence_compression[48]; |
| gchar *name; |
| |
| GST_WARNING_OBJECT (demux, |
| "No essence element handler for track %u found", i); |
| |
| mxf_ul_to_string (&track->parent.descriptor[0]->essence_container, |
| essence_container); |
| |
| if (track->parent.type == MXF_METADATA_TRACK_PICTURE_ESSENCE) { |
| if (MXF_IS_METADATA_GENERIC_PICTURE_ESSENCE_DESCRIPTOR (track->parent. |
| descriptor[0])) |
| mxf_ul_to_string (&MXF_METADATA_GENERIC_PICTURE_ESSENCE_DESCRIPTOR |
| (track->parent.descriptor[0])->picture_essence_coding, |
| essence_compression); |
| |
| name = |
| g_strdup_printf ("video/x-mxf-%s-%s", essence_container, |
| essence_compression); |
| } else if (track->parent.type == MXF_METADATA_TRACK_SOUND_ESSENCE) { |
| if (MXF_IS_METADATA_GENERIC_SOUND_ESSENCE_DESCRIPTOR (track->parent. |
| descriptor[0])) |
| mxf_ul_to_string (&MXF_METADATA_GENERIC_SOUND_ESSENCE_DESCRIPTOR |
| (track->parent.descriptor[0])->sound_essence_compression, |
| essence_compression); |
| |
| name = |
| g_strdup_printf ("audio/x-mxf-%s-%s", essence_container, |
| essence_compression); |
| } else if (track->parent.type == MXF_METADATA_TRACK_DATA_ESSENCE) { |
| if (MXF_IS_METADATA_GENERIC_DATA_ESSENCE_DESCRIPTOR (track->parent. |
| descriptor[0])) |
| mxf_ul_to_string (&MXF_METADATA_GENERIC_DATA_ESSENCE_DESCRIPTOR |
| (track->parent.descriptor[0])->data_essence_coding, |
| essence_compression); |
| |
| name = |
| g_strdup_printf ("application/x-mxf-%s-%s", essence_container, |
| essence_compression); |
| } else { |
| name = NULL; |
| g_assert_not_reached (); |
| } |
| |
| caps = gst_caps_new_empty_simple (name); |
| g_free (name); |
| } else { |
| caps = |
| etrack->handler->create_caps (track, &etrack->tags, |
| &etrack->handle_func, &etrack->mapping_data); |
| } |
| |
| GST_DEBUG_OBJECT (demux, "Created caps %" GST_PTR_FORMAT, caps); |
| |
| if (!caps && new) { |
| GST_WARNING_OBJECT (demux, "No caps created, ignoring stream"); |
| g_free (etrack->mapping_data); |
| etrack->mapping_data = NULL; |
| if (etrack->tags) |
| gst_tag_list_unref (etrack->tags); |
| goto next; |
| } else if (!caps) { |
| GST_WARNING_OBJECT (demux, "Couldn't create updated caps for stream"); |
| } else if (!etrack->caps || !gst_caps_is_equal (etrack->caps, caps)) { |
| if (etrack->caps) |
| gst_caps_unref (etrack->caps); |
| etrack->caps = caps; |
| } else { |
| gst_caps_unref (caps); |
| caps = NULL; |
| } |
| |
| if (etrack->handler != NULL) { |
| MXFEssenceWrapping track_wrapping; |
| |
| track_wrapping = etrack->handler->get_track_wrapping (track); |
| if (track_wrapping == MXF_ESSENCE_WRAPPING_CLIP_WRAPPING) { |
| GST_ELEMENT_ERROR (demux, STREAM, NOT_IMPLEMENTED, (NULL), |
| ("Clip essence wrapping is not implemented yet.")); |
| return GST_FLOW_ERROR; |
| } else if (track_wrapping == MXF_ESSENCE_WRAPPING_CUSTOM_WRAPPING) { |
| GST_ELEMENT_ERROR (demux, STREAM, NOT_IMPLEMENTED, (NULL), |
| ("Custom essence wrappings are not supported.")); |
| return GST_FLOW_ERROR; |
| } |
| } |
| |
| etrack->source_package = package; |
| etrack->source_track = track; |
| continue; |
| |
| next: |
| if (new) { |
| g_free (etrack->mapping_data); |
| if (etrack->tags) |
| gst_tag_list_unref (etrack->tags); |
| if (etrack->caps) |
| gst_caps_unref (etrack->caps); |
| |
| g_array_remove_index (demux->essence_tracks, |
| demux->essence_tracks->len - 1); |
| } |
| } |
| } |
| |
| if (demux->essence_tracks->len == 0) { |
| GST_ERROR_OBJECT (demux, "No valid essence tracks in this file"); |
| return GST_FLOW_ERROR; |
| } |
| |
| for (i = 0; i < demux->essence_tracks->len; i++) { |
| GstMXFDemuxEssenceTrack *etrack = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, i); |
| |
| if (!etrack->source_package || !etrack->source_track || !etrack->caps) { |
| GST_ERROR_OBJECT (demux, "Failed to update essence track %u", i); |
| return GST_FLOW_ERROR; |
| } |
| |
| } |
| |
| return GST_FLOW_OK; |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_update_tracks (GstMXFDemux * demux) |
| { |
| MXFMetadataGenericPackage *current_package = NULL; |
| guint i, j, k; |
| gboolean first_run; |
| guint component_index; |
| GstFlowReturn ret; |
| GList *pads = NULL, *l; |
| |
| g_rw_lock_writer_lock (&demux->metadata_lock); |
| GST_DEBUG_OBJECT (demux, "Updating tracks"); |
| |
| if ((ret = gst_mxf_demux_update_essence_tracks (demux)) != GST_FLOW_OK) { |
| goto error; |
| } |
| |
| current_package = gst_mxf_demux_choose_package (demux); |
| |
| if (!current_package) { |
| GST_ERROR_OBJECT (demux, "Unable to find current package"); |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } else if (!current_package->tracks) { |
| GST_ERROR_OBJECT (demux, "Current package has no (resolved) tracks"); |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } else if (!current_package->n_essence_tracks) { |
| GST_ERROR_OBJECT (demux, "Current package has no essence tracks"); |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } |
| |
| first_run = (demux->src->len == 0); |
| |
| for (i = 0; i < current_package->n_tracks; i++) { |
| MXFMetadataTimelineTrack *track = NULL; |
| MXFMetadataSequence *sequence; |
| MXFMetadataSourceClip *component = NULL; |
| MXFMetadataSourcePackage *source_package = NULL; |
| MXFMetadataTimelineTrack *source_track = NULL; |
| GstMXFDemuxEssenceTrack *etrack = NULL; |
| GstMXFDemuxPad *pad = NULL; |
| GstCaps *pad_caps; |
| |
| GST_DEBUG_OBJECT (demux, "Handling track %u", i); |
| |
| if (!current_package->tracks[i]) { |
| GST_WARNING_OBJECT (demux, "Unresolved track"); |
| continue; |
| } |
| |
| if (!MXF_IS_METADATA_TIMELINE_TRACK (current_package->tracks[i])) { |
| GST_DEBUG_OBJECT (demux, "No timeline track"); |
| continue; |
| } |
| |
| track = MXF_METADATA_TIMELINE_TRACK (current_package->tracks[i]); |
| |
| if (!first_run) { |
| /* Find pad from track_id */ |
| for (j = 0; j < demux->src->len; j++) { |
| GstMXFDemuxPad *tmp = g_ptr_array_index (demux->src, j); |
| |
| if (tmp->track_id == track->parent.track_id) { |
| pad = tmp; |
| break; |
| } |
| } |
| } |
| |
| if (pad) |
| component_index = pad->current_component_index; |
| else |
| component_index = 0; |
| |
| if (!track->parent.sequence) { |
| GST_WARNING_OBJECT (demux, "Track with no sequence"); |
| if (!pad) { |
| continue; |
| } else { |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } |
| } |
| |
| sequence = track->parent.sequence; |
| |
| if (MXF_IS_METADATA_SOURCE_PACKAGE (current_package)) { |
| GST_DEBUG_OBJECT (demux, "Playing source package"); |
| |
| component = NULL; |
| source_package = MXF_METADATA_SOURCE_PACKAGE (current_package); |
| source_track = track; |
| } else if (sequence->structural_components |
| && |
| MXF_IS_METADATA_SOURCE_CLIP (sequence->structural_components |
| [component_index])) { |
| GST_DEBUG_OBJECT (demux, "Playing material package"); |
| |
| component = |
| MXF_METADATA_SOURCE_CLIP (sequence->structural_components |
| [component_index]); |
| if (!component) { |
| GST_WARNING_OBJECT (demux, "NULL conponent in non source package"); |
| if (!pad) { |
| continue; |
| } else { |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } |
| } |
| |
| if (component->source_package && component->source_package->top_level && |
| MXF_METADATA_GENERIC_PACKAGE (component->source_package)->tracks) { |
| MXFMetadataGenericPackage *tmp_pkg = |
| MXF_METADATA_GENERIC_PACKAGE (component->source_package); |
| |
| source_package = component->source_package; |
| |
| for (k = 0; k < tmp_pkg->n_tracks; k++) { |
| MXFMetadataTrack *tmp = tmp_pkg->tracks[k]; |
| |
| if (tmp->track_id == component->source_track_id) { |
| source_track = MXF_METADATA_TIMELINE_TRACK (tmp); |
| break; |
| } |
| } |
| } |
| } |
| |
| if (track->parent.type && (track->parent.type & 0xf0) != 0x30) { |
| GST_DEBUG_OBJECT (demux, "No essence track"); |
| if (!pad) { |
| continue; |
| } else { |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } |
| } |
| |
| if (!source_package || track->parent.type == MXF_METADATA_TRACK_UNKNOWN |
| || !source_track) { |
| GST_WARNING_OBJECT (demux, |
| "No source package or track type for track found"); |
| if (!pad) { |
| continue; |
| } else { |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } |
| } |
| |
| for (k = 0; k < demux->essence_tracks->len; k++) { |
| GstMXFDemuxEssenceTrack *tmp = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, k); |
| |
| if (tmp->source_package == source_package && |
| tmp->source_track == source_track) { |
| etrack = tmp; |
| break; |
| } |
| } |
| |
| if (!etrack) { |
| GST_WARNING_OBJECT (demux, "No essence track for this track found"); |
| if (!pad) { |
| continue; |
| } else { |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } |
| } |
| |
| if (track->edit_rate.n <= 0 || track->edit_rate.d <= 0 || |
| source_track->edit_rate.n <= 0 || source_track->edit_rate.d <= 0) { |
| GST_WARNING_OBJECT (demux, "Track has an invalid edit rate"); |
| if (!pad) { |
| continue; |
| } else { |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } |
| } |
| |
| if (MXF_IS_METADATA_MATERIAL_PACKAGE (current_package) && !component) { |
| GST_WARNING_OBJECT (demux, |
| "Playing material package but found no component for track"); |
| if (!pad) { |
| continue; |
| } else { |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } |
| } |
| |
| if (!source_package->descriptor) { |
| GST_WARNING_OBJECT (demux, "Source package has no descriptors"); |
| if (!pad) { |
| continue; |
| } else { |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } |
| } |
| |
| if (!source_track->parent.descriptor) { |
| GST_WARNING_OBJECT (demux, "No descriptor found for track"); |
| if (!pad) { |
| continue; |
| } else { |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } |
| } |
| |
| if (!pad && first_run) { |
| GstPadTemplate *templ; |
| gchar *pad_name; |
| |
| templ = |
| gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (demux), |
| "track_%u"); |
| pad_name = g_strdup_printf ("track_%u", track->parent.track_id); |
| |
| g_assert (templ != NULL); |
| |
| /* Create pad */ |
| pad = (GstMXFDemuxPad *) g_object_new (GST_TYPE_MXF_DEMUX_PAD, |
| "name", pad_name, "direction", GST_PAD_SRC, "template", templ, NULL); |
| pad->need_segment = TRUE; |
| pad->eos = FALSE; |
| g_free (pad_name); |
| |
| if (demux->tags) |
| pad->tags = gst_tag_list_copy (demux->tags); |
| } |
| |
| if (!pad) { |
| GST_WARNING_OBJECT (demux, |
| "Not the first pad addition run, ignoring new track"); |
| continue; |
| } |
| |
| /* Update pad */ |
| pad->track_id = track->parent.track_id; |
| |
| pad->material_package = current_package; |
| pad->material_track = track; |
| |
| /* If we just added the pad initialize for the current component */ |
| if (first_run && MXF_IS_METADATA_MATERIAL_PACKAGE (current_package)) { |
| pad->current_component_index = 0; |
| pad->current_component_start = source_track->origin; |
| |
| if (component->parent.duration >= -1) |
| pad->current_component_duration = component->parent.duration; |
| else |
| pad->current_component_duration = -1; |
| |
| if (track->edit_rate.n != source_track->edit_rate.n || |
| track->edit_rate.d != source_track->edit_rate.d) { |
| pad->current_component_start += |
| gst_util_uint64_scale (component->start_position, |
| source_track->edit_rate.n * track->edit_rate.d, |
| source_track->edit_rate.d * track->edit_rate.n); |
| |
| if (pad->current_component_duration != -1) |
| pad->current_component_duration = |
| gst_util_uint64_scale (pad->current_component_duration, |
| source_track->edit_rate.n * track->edit_rate.d, |
| source_track->edit_rate.d * track->edit_rate.n); |
| } else { |
| pad->current_component_start += component->start_position; |
| } |
| pad->current_essence_track_position = pad->current_component_start; |
| } |
| |
| /* NULL iff playing a source package */ |
| pad->current_component = component; |
| |
| pad->current_essence_track = etrack; |
| |
| if (etrack->tags) { |
| if (pad->tags) |
| gst_tag_list_insert (pad->tags, etrack->tags, GST_TAG_MERGE_REPLACE); |
| else |
| pad->tags = gst_tag_list_copy (etrack->tags); |
| } |
| |
| pad_caps = gst_pad_get_current_caps (GST_PAD_CAST (pad)); |
| if (pad_caps && !gst_caps_is_equal (pad_caps, etrack->caps)) { |
| gst_pad_set_caps (GST_PAD_CAST (pad), etrack->caps); |
| } else if (!pad_caps) { |
| GstEvent *event; |
| gchar *stream_id; |
| |
| gst_pad_set_event_function (GST_PAD_CAST (pad), |
| GST_DEBUG_FUNCPTR (gst_mxf_demux_src_event)); |
| |
| gst_pad_set_query_function (GST_PAD_CAST (pad), |
| GST_DEBUG_FUNCPTR (gst_mxf_demux_src_query)); |
| |
| gst_pad_use_fixed_caps (GST_PAD_CAST (pad)); |
| gst_pad_set_active (GST_PAD_CAST (pad), TRUE); |
| |
| stream_id = |
| gst_pad_create_stream_id_printf (GST_PAD_CAST (pad), |
| GST_ELEMENT_CAST (demux), "%03u", pad->track_id); |
| |
| event = |
| gst_pad_get_sticky_event (demux->sinkpad, GST_EVENT_STREAM_START, 0); |
| if (event) { |
| if (gst_event_parse_group_id (event, &demux->group_id)) |
| demux->have_group_id = TRUE; |
| else |
| demux->have_group_id = FALSE; |
| gst_event_unref (event); |
| } else if (!demux->have_group_id) { |
| demux->have_group_id = TRUE; |
| demux->group_id = gst_util_group_id_next (); |
| } |
| event = gst_event_new_stream_start (stream_id); |
| if (demux->have_group_id) |
| gst_event_set_group_id (event, demux->group_id); |
| |
| gst_pad_push_event (GST_PAD_CAST (pad), event); |
| g_free (stream_id); |
| |
| gst_pad_set_caps (GST_PAD_CAST (pad), etrack->caps); |
| |
| pads = g_list_prepend (pads, gst_object_ref (pad)); |
| |
| g_ptr_array_add (demux->src, pad); |
| pad->discont = TRUE; |
| } |
| if (pad_caps) |
| gst_caps_unref (pad_caps); |
| } |
| |
| if (demux->src->len > 0) { |
| for (i = 0; i < demux->src->len; i++) { |
| GstMXFDemuxPad *pad = g_ptr_array_index (demux->src, i); |
| |
| if (!pad->material_track || !pad->material_package) { |
| GST_ERROR_OBJECT (demux, "Unable to update existing pad"); |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } |
| } |
| } else { |
| GST_ERROR_OBJECT (demux, "Couldn't create any streams"); |
| ret = GST_FLOW_ERROR; |
| goto error; |
| } |
| |
| g_rw_lock_writer_unlock (&demux->metadata_lock); |
| |
| for (l = pads; l; l = l->next) { |
| gst_flow_combiner_add_pad (demux->flowcombiner, l->data); |
| gst_element_add_pad (GST_ELEMENT_CAST (demux), l->data); |
| } |
| g_list_free (pads); |
| |
| if (first_run) |
| gst_element_no_more_pads (GST_ELEMENT_CAST (demux)); |
| |
| return GST_FLOW_OK; |
| |
| error: |
| g_rw_lock_writer_unlock (&demux->metadata_lock); |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_handle_metadata (GstMXFDemux * demux, const MXFUL * key, |
| GstBuffer * buffer) |
| { |
| guint16 type; |
| MXFMetadata *metadata = NULL, *old = NULL; |
| GstMapInfo map; |
| GstFlowReturn ret = GST_FLOW_OK; |
| |
| type = GST_READ_UINT16_BE (key->u + 13); |
| |
| GST_DEBUG_OBJECT (demux, |
| "Handling metadata of size %" G_GSIZE_FORMAT " at offset %" |
| G_GUINT64_FORMAT " of type 0x%04x", gst_buffer_get_size (buffer), |
| demux->offset, type); |
| |
| if (G_UNLIKELY (!demux->current_partition)) { |
| GST_ERROR_OBJECT (demux, "Partition pack doesn't exist"); |
| return GST_FLOW_ERROR; |
| } |
| |
| if (G_UNLIKELY (!demux->current_partition->primer.mappings)) { |
| GST_ERROR_OBJECT (demux, "Primer pack doesn't exists"); |
| return GST_FLOW_ERROR; |
| } |
| |
| if (demux->current_partition->parsed_metadata) { |
| GST_DEBUG_OBJECT (demux, "Metadata of this partition was already parsed"); |
| return GST_FLOW_OK; |
| } |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| metadata = |
| mxf_metadata_new (type, &demux->current_partition->primer, demux->offset, |
| map.data, map.size); |
| gst_buffer_unmap (buffer, &map); |
| |
| if (!metadata) { |
| GST_WARNING_OBJECT (demux, |
| "Unknown or unhandled metadata of type 0x%04x", type); |
| return GST_FLOW_OK; |
| } |
| |
| old = |
| g_hash_table_lookup (demux->metadata, |
| &MXF_METADATA_BASE (metadata)->instance_uid); |
| |
| if (old && G_TYPE_FROM_INSTANCE (old) != G_TYPE_FROM_INSTANCE (metadata)) { |
| #ifndef GST_DISABLE_GST_DEBUG |
| gchar str[48]; |
| #endif |
| |
| GST_DEBUG_OBJECT (demux, |
| "Metadata with instance uid %s already exists and has different type '%s'," |
| " expected '%s'", |
| mxf_uuid_to_string (&MXF_METADATA_BASE (metadata)->instance_uid, str), |
| g_type_name (G_TYPE_FROM_INSTANCE (old)), |
| g_type_name (G_TYPE_FROM_INSTANCE (metadata))); |
| g_object_unref (metadata); |
| return GST_FLOW_ERROR; |
| } else if (old |
| && MXF_METADATA_BASE (old)->offset >= |
| MXF_METADATA_BASE (metadata)->offset) { |
| #ifndef GST_DISABLE_GST_DEBUG |
| gchar str[48]; |
| #endif |
| |
| GST_DEBUG_OBJECT (demux, |
| "Metadata with instance uid %s already exists and is newer", |
| mxf_uuid_to_string (&MXF_METADATA_BASE (metadata)->instance_uid, str)); |
| g_object_unref (metadata); |
| return GST_FLOW_OK; |
| } |
| |
| g_rw_lock_writer_lock (&demux->metadata_lock); |
| demux->update_metadata = TRUE; |
| |
| if (MXF_IS_METADATA_PREFACE (metadata)) { |
| demux->preface = MXF_METADATA_PREFACE (metadata); |
| } |
| |
| gst_mxf_demux_reset_linked_metadata (demux); |
| |
| g_hash_table_replace (demux->metadata, |
| &MXF_METADATA_BASE (metadata)->instance_uid, metadata); |
| g_rw_lock_writer_unlock (&demux->metadata_lock); |
| |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_handle_descriptive_metadata (GstMXFDemux * demux, |
| const MXFUL * key, GstBuffer * buffer) |
| { |
| guint32 type; |
| guint8 scheme; |
| GstMapInfo map; |
| GstFlowReturn ret = GST_FLOW_OK; |
| MXFDescriptiveMetadata *m = NULL, *old = NULL; |
| |
| scheme = GST_READ_UINT8 (key->u + 12); |
| type = GST_READ_UINT24_BE (key->u + 13); |
| |
| GST_DEBUG_OBJECT (demux, |
| "Handling descriptive metadata of size %" G_GSIZE_FORMAT " at offset %" |
| G_GUINT64_FORMAT " with scheme 0x%02x and type 0x%06x", |
| gst_buffer_get_size (buffer), demux->offset, scheme, type); |
| |
| if (G_UNLIKELY (!demux->current_partition)) { |
| GST_ERROR_OBJECT (demux, "Partition pack doesn't exist"); |
| return GST_FLOW_ERROR; |
| } |
| |
| if (G_UNLIKELY (!demux->current_partition->primer.mappings)) { |
| GST_ERROR_OBJECT (demux, "Primer pack doesn't exists"); |
| return GST_FLOW_ERROR; |
| } |
| |
| if (demux->current_partition->parsed_metadata) { |
| GST_DEBUG_OBJECT (demux, "Metadata of this partition was already parsed"); |
| return GST_FLOW_OK; |
| } |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| m = mxf_descriptive_metadata_new (scheme, type, |
| &demux->current_partition->primer, demux->offset, map.data, map.size); |
| gst_buffer_unmap (buffer, &map); |
| |
| if (!m) { |
| GST_WARNING_OBJECT (demux, |
| "Unknown or unhandled descriptive metadata of scheme 0x%02x and type 0x%06x", |
| scheme, type); |
| return GST_FLOW_OK; |
| } |
| |
| old = |
| g_hash_table_lookup (demux->metadata, |
| &MXF_METADATA_BASE (m)->instance_uid); |
| |
| if (old && G_TYPE_FROM_INSTANCE (old) != G_TYPE_FROM_INSTANCE (m)) { |
| #ifndef GST_DISABLE_GST_DEBUG |
| gchar str[48]; |
| #endif |
| |
| GST_DEBUG_OBJECT (demux, |
| "Metadata with instance uid %s already exists and has different type '%s'," |
| " expected '%s'", |
| mxf_uuid_to_string (&MXF_METADATA_BASE (m)->instance_uid, str), |
| g_type_name (G_TYPE_FROM_INSTANCE (old)), |
| g_type_name (G_TYPE_FROM_INSTANCE (m))); |
| g_object_unref (m); |
| return GST_FLOW_ERROR; |
| } else if (old |
| && MXF_METADATA_BASE (old)->offset >= MXF_METADATA_BASE (m)->offset) { |
| #ifndef GST_DISABLE_GST_DEBUG |
| gchar str[48]; |
| #endif |
| |
| GST_DEBUG_OBJECT (demux, |
| "Metadata with instance uid %s already exists and is newer", |
| mxf_uuid_to_string (&MXF_METADATA_BASE (m)->instance_uid, str)); |
| g_object_unref (m); |
| return GST_FLOW_OK; |
| } |
| |
| g_rw_lock_writer_lock (&demux->metadata_lock); |
| |
| demux->update_metadata = TRUE; |
| gst_mxf_demux_reset_linked_metadata (demux); |
| |
| g_hash_table_replace (demux->metadata, &MXF_METADATA_BASE (m)->instance_uid, |
| m); |
| |
| g_rw_lock_writer_unlock (&demux->metadata_lock); |
| |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_handle_generic_container_system_item (GstMXFDemux * demux, |
| const MXFUL * key, GstBuffer * buffer) |
| { |
| GST_DEBUG_OBJECT (demux, |
| "Handling generic container system item of size %" G_GSIZE_FORMAT |
| " at offset %" G_GUINT64_FORMAT, gst_buffer_get_size (buffer), |
| demux->offset); |
| |
| if (demux->current_partition->essence_container_offset == 0) |
| demux->current_partition->essence_container_offset = |
| demux->offset - demux->current_partition->partition.this_partition - |
| demux->run_in; |
| |
| /* TODO: parse this */ |
| return GST_FLOW_OK; |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_pad_set_component (GstMXFDemux * demux, GstMXFDemuxPad * pad, |
| guint i) |
| { |
| GstFlowReturn ret = GST_FLOW_OK; |
| GstCaps *pad_caps; |
| MXFMetadataSequence *sequence; |
| guint k; |
| MXFMetadataSourcePackage *source_package = NULL; |
| MXFMetadataTimelineTrack *source_track = NULL; |
| gboolean update = (pad->current_component_index != i); |
| |
| pad->current_component_index = i; |
| |
| sequence = pad->material_track->parent.sequence; |
| |
| if (pad->current_component_index >= sequence->n_structural_components) { |
| GST_DEBUG_OBJECT (demux, "After last structural component"); |
| pad->current_component_index = sequence->n_structural_components - 1; |
| ret = GST_FLOW_EOS; |
| } |
| |
| GST_DEBUG_OBJECT (demux, "Switching to component %u", |
| pad->current_component_index); |
| |
| pad->current_component = |
| MXF_METADATA_SOURCE_CLIP (sequence->structural_components[pad-> |
| current_component_index]); |
| if (pad->current_component == NULL) { |
| GST_ERROR_OBJECT (demux, "No such structural component"); |
| return GST_FLOW_ERROR; |
| } |
| |
| if (!pad->current_component->source_package |
| || !pad->current_component->source_package->top_level |
| || !MXF_METADATA_GENERIC_PACKAGE (pad->current_component-> |
| source_package)->tracks) { |
| GST_ERROR_OBJECT (demux, "Invalid component"); |
| return GST_FLOW_ERROR; |
| } |
| |
| source_package = pad->current_component->source_package; |
| |
| for (k = 0; k < source_package->parent.n_tracks; k++) { |
| MXFMetadataTrack *tmp = source_package->parent.tracks[k]; |
| |
| if (tmp->track_id == pad->current_component->source_track_id) { |
| source_track = MXF_METADATA_TIMELINE_TRACK (tmp); |
| break; |
| } |
| } |
| |
| if (!source_track) { |
| GST_ERROR_OBJECT (demux, "No source track found"); |
| return GST_FLOW_ERROR; |
| } |
| |
| pad->current_essence_track = NULL; |
| |
| for (k = 0; k < demux->essence_tracks->len; k++) { |
| GstMXFDemuxEssenceTrack *tmp = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, k); |
| |
| if (tmp->source_package == source_package && |
| tmp->source_track == source_track) { |
| pad->current_essence_track = tmp; |
| break; |
| } |
| } |
| |
| if (!pad->current_essence_track) { |
| GST_ERROR_OBJECT (demux, "No corresponding essence track found"); |
| return GST_FLOW_ERROR; |
| } |
| |
| if (!source_package->descriptor) { |
| GST_ERROR_OBJECT (demux, "Source package has no descriptors"); |
| return GST_FLOW_ERROR; |
| } |
| |
| if (!source_track->parent.descriptor) { |
| GST_ERROR_OBJECT (demux, "No descriptor found for track"); |
| return GST_FLOW_ERROR; |
| } |
| |
| if (source_track->edit_rate.n <= 0 || source_track->edit_rate.d <= 0) { |
| GST_ERROR_OBJECT (demux, "Source track has invalid edit rate"); |
| return GST_FLOW_ERROR; |
| } |
| |
| pad->current_component_start = source_track->origin; |
| if (pad->current_component->parent.duration >= -1) |
| pad->current_component_duration = pad->current_component->parent.duration; |
| else |
| pad->current_component_duration = -1; |
| |
| if (pad->material_track->edit_rate.n != source_track->edit_rate.n || |
| pad->material_track->edit_rate.d != source_track->edit_rate.d) { |
| pad->current_component_start += |
| gst_util_uint64_scale (pad->current_component->start_position, |
| source_track->edit_rate.n * pad->material_track->edit_rate.d, |
| source_track->edit_rate.d * pad->material_track->edit_rate.n); |
| |
| if (pad->current_component_duration != -1) |
| pad->current_component_duration = |
| gst_util_uint64_scale (pad->current_component_duration, |
| source_track->edit_rate.n * pad->material_track->edit_rate.d, |
| source_track->edit_rate.d * pad->material_track->edit_rate.n); |
| } else { |
| pad->current_component_start += pad->current_component->start_position; |
| } |
| pad->current_essence_track_position = pad->current_component_start; |
| |
| |
| pad_caps = gst_pad_get_current_caps (GST_PAD_CAST (pad)); |
| if (!gst_caps_is_equal (pad_caps, pad->current_essence_track->caps)) { |
| gst_pad_set_caps (GST_PAD_CAST (pad), pad->current_essence_track->caps); |
| } |
| gst_caps_unref (pad_caps); |
| |
| if (update) { |
| if (pad->tags) { |
| if (pad->current_essence_track->tags) |
| gst_tag_list_insert (pad->tags, pad->current_essence_track->tags, |
| GST_TAG_MERGE_REPLACE); |
| } else { |
| if (pad->current_essence_track->tags) |
| pad->tags = gst_tag_list_copy (pad->current_essence_track->tags); |
| } |
| } |
| |
| if (ret == GST_FLOW_EOS) { |
| pad->current_essence_track_position += pad->current_component_duration; |
| } |
| |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_handle_generic_container_essence_element (GstMXFDemux * demux, |
| const MXFUL * key, GstBuffer * buffer, gboolean peek) |
| { |
| GstFlowReturn ret = GST_FLOW_OK; |
| guint32 track_number; |
| guint i; |
| GstBuffer *inbuf = NULL; |
| GstBuffer *outbuf = NULL; |
| GstMXFDemuxEssenceTrack *etrack = NULL; |
| gboolean keyframe = TRUE; |
| |
| GST_DEBUG_OBJECT (demux, |
| "Handling generic container essence element of size %" G_GSIZE_FORMAT |
| " at offset %" G_GUINT64_FORMAT, gst_buffer_get_size (buffer), |
| demux->offset); |
| |
| GST_DEBUG_OBJECT (demux, " type = 0x%02x", key->u[12]); |
| GST_DEBUG_OBJECT (demux, " essence element count = 0x%02x", key->u[13]); |
| GST_DEBUG_OBJECT (demux, " essence element type = 0x%02x", key->u[14]); |
| GST_DEBUG_OBJECT (demux, " essence element number = 0x%02x", key->u[15]); |
| |
| if (demux->current_partition->essence_container_offset == 0) |
| demux->current_partition->essence_container_offset = |
| demux->offset - demux->current_partition->partition.this_partition - |
| demux->run_in; |
| |
| if (!demux->current_package) { |
| GST_ERROR_OBJECT (demux, "No package selected yet"); |
| return GST_FLOW_ERROR; |
| } |
| |
| if (demux->src->len == 0) { |
| GST_ERROR_OBJECT (demux, "No streams created yet"); |
| return GST_FLOW_ERROR; |
| } |
| |
| if (demux->essence_tracks->len == 0) { |
| GST_ERROR_OBJECT (demux, "No essence streams found in the metadata"); |
| return GST_FLOW_ERROR; |
| } |
| |
| track_number = GST_READ_UINT32_BE (&key->u[12]); |
| |
| for (i = 0; i < demux->essence_tracks->len; i++) { |
| GstMXFDemuxEssenceTrack *tmp = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, i); |
| |
| if (tmp->body_sid == demux->current_partition->partition.body_sid && |
| (tmp->track_number == track_number || tmp->track_number == 0)) { |
| etrack = tmp; |
| break; |
| } |
| } |
| |
| if (!etrack) { |
| GST_WARNING_OBJECT (demux, |
| "No essence track for this essence element found"); |
| return GST_FLOW_OK; |
| } |
| |
| if (etrack->position == -1) { |
| GST_DEBUG_OBJECT (demux, |
| "Unknown essence track position, looking into index"); |
| if (etrack->offsets) { |
| for (i = 0; i < etrack->offsets->len; i++) { |
| GstMXFDemuxIndex *idx = |
| &g_array_index (etrack->offsets, GstMXFDemuxIndex, i); |
| |
| if (idx->offset != 0 && idx->offset == demux->offset - demux->run_in) { |
| etrack->position = i; |
| break; |
| } |
| } |
| } |
| |
| if (etrack->position == -1) { |
| GST_WARNING_OBJECT (demux, "Essence track position not in index"); |
| return GST_FLOW_OK; |
| } |
| } |
| |
| if (etrack->offsets && etrack->offsets->len > etrack->position) { |
| GstMXFDemuxIndex *index = |
| &g_array_index (etrack->offsets, GstMXFDemuxIndex, etrack->position); |
| if (index->offset != 0) |
| keyframe = index->keyframe; |
| } |
| |
| /* Create subbuffer to be able to change metadata */ |
| inbuf = |
| gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL, 0, |
| gst_buffer_get_size (buffer)); |
| |
| if (!keyframe) |
| GST_BUFFER_FLAG_SET (inbuf, GST_BUFFER_FLAG_DELTA_UNIT); |
| |
| if (etrack->handle_func) { |
| /* Takes ownership of inbuf */ |
| ret = |
| etrack->handle_func (key, inbuf, etrack->caps, |
| etrack->source_track, etrack->mapping_data, &outbuf); |
| inbuf = NULL; |
| } else { |
| outbuf = inbuf; |
| inbuf = NULL; |
| ret = GST_FLOW_OK; |
| } |
| |
| if (ret != GST_FLOW_OK) { |
| GST_ERROR_OBJECT (demux, "Failed to handle essence element"); |
| if (outbuf) { |
| gst_buffer_unref (outbuf); |
| outbuf = NULL; |
| } |
| return ret; |
| } |
| |
| if (outbuf) |
| keyframe = !GST_BUFFER_FLAG_IS_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT); |
| |
| /* Prefer keyframe information from index tables over everything else */ |
| if (demux->index_tables && outbuf) { |
| GList *l; |
| GstMXFDemuxIndexTable *index_table = NULL; |
| |
| for (l = demux->index_tables; l; l = l->next) { |
| GstMXFDemuxIndexTable *tmp = l->data; |
| |
| if (tmp->body_sid == etrack->body_sid |
| && tmp->index_sid == etrack->index_sid) { |
| index_table = tmp; |
| break; |
| } |
| } |
| |
| if (index_table && index_table->offsets->len > etrack->position) { |
| GstMXFDemuxIndex *index = |
| &g_array_index (index_table->offsets, GstMXFDemuxIndex, |
| etrack->position); |
| if (index->offset != 0) { |
| keyframe = index->keyframe; |
| |
| if (keyframe) |
| GST_BUFFER_FLAG_UNSET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT); |
| else |
| GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT); |
| } |
| } |
| } |
| |
| if (!etrack->offsets) |
| etrack->offsets = g_array_new (FALSE, TRUE, sizeof (GstMXFDemuxIndex)); |
| |
| { |
| if (etrack->offsets->len > etrack->position) { |
| GstMXFDemuxIndex *index = |
| &g_array_index (etrack->offsets, GstMXFDemuxIndex, etrack->position); |
| |
| index->offset = demux->offset - demux->run_in; |
| index->keyframe = keyframe; |
| } else { |
| GstMXFDemuxIndex index; |
| |
| index.offset = demux->offset - demux->run_in; |
| index.keyframe = keyframe; |
| if (etrack->offsets->len < etrack->position) |
| g_array_set_size (etrack->offsets, etrack->position); |
| g_array_insert_val (etrack->offsets, etrack->position, index); |
| } |
| } |
| |
| if (peek) |
| goto out; |
| |
| if (!outbuf) { |
| GST_DEBUG_OBJECT (demux, "No output buffer created"); |
| goto out; |
| } |
| |
| inbuf = outbuf; |
| outbuf = NULL; |
| |
| for (i = 0; i < demux->src->len; i++) { |
| GstMXFDemuxPad *pad = g_ptr_array_index (demux->src, i); |
| |
| if (pad->current_essence_track != etrack) |
| continue; |
| |
| if (pad->eos) { |
| GST_DEBUG_OBJECT (demux, "Pad is already EOS"); |
| continue; |
| } |
| |
| if (etrack->position != pad->current_essence_track_position) { |
| GST_DEBUG_OBJECT (demux, "Not at current component's position"); |
| continue; |
| } |
| |
| { |
| GstMXFDemuxPad *earliest = gst_mxf_demux_get_earliest_pad (demux); |
| |
| if (earliest && earliest != pad && earliest->position < pad->position && |
| pad->position - earliest->position > demux->max_drift) { |
| GST_DEBUG_OBJECT (demux, "Pad is too far ahead of time"); |
| continue; |
| } |
| } |
| |
| /* Create another subbuffer to have writable metadata */ |
| outbuf = |
| gst_buffer_copy_region (inbuf, GST_BUFFER_COPY_ALL, 0, |
| gst_buffer_get_size (inbuf)); |
| |
| GST_BUFFER_DTS (outbuf) = pad->position; |
| GST_BUFFER_PTS (outbuf) = pad->position; |
| GST_BUFFER_DURATION (outbuf) = |
| gst_util_uint64_scale (GST_SECOND, |
| pad->current_essence_track->source_track->edit_rate.d, |
| pad->current_essence_track->source_track->edit_rate.n); |
| GST_BUFFER_OFFSET (outbuf) = GST_BUFFER_OFFSET_NONE; |
| GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET_NONE; |
| |
| /* Update accumulated error and compensate */ |
| { |
| guint64 abs_error = |
| (GST_SECOND * pad->current_essence_track->source_track->edit_rate.d) % |
| pad->current_essence_track->source_track->edit_rate.n; |
| pad->position_accumulated_error += |
| ((gdouble) abs_error) / |
| ((gdouble) pad->current_essence_track->source_track->edit_rate.n); |
| } |
| if (pad->position_accumulated_error >= 1.0) { |
| GST_BUFFER_DURATION (outbuf) += 1; |
| pad->position_accumulated_error -= 1.0; |
| } |
| |
| if (pad->need_segment) { |
| GstEvent *e; |
| |
| if (demux->close_seg_event) |
| gst_pad_push_event (GST_PAD_CAST (pad), |
| gst_event_ref (demux->close_seg_event)); |
| |
| e = gst_event_new_segment (&demux->segment); |
| gst_event_set_seqnum (e, demux->seqnum); |
| gst_pad_push_event (GST_PAD_CAST (pad), e); |
| pad->need_segment = FALSE; |
| } |
| |
| if (pad->tags) { |
| gst_pad_push_event (GST_PAD_CAST (pad), gst_event_new_tag (pad->tags)); |
| pad->tags = NULL; |
| } |
| |
| pad->position += GST_BUFFER_DURATION (outbuf); |
| |
| GST_DEBUG_OBJECT (demux, |
| "Pushing buffer of size %" G_GSIZE_FORMAT " for track %u: timestamp %" |
| GST_TIME_FORMAT " duration %" GST_TIME_FORMAT " position %" |
| G_GUINT64_FORMAT, gst_buffer_get_size (outbuf), |
| pad->material_track->parent.track_id, |
| GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), |
| GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)), |
| pad->current_essence_track_position); |
| |
| if (pad->discont) { |
| GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT); |
| pad->discont = FALSE; |
| } |
| |
| ret = gst_pad_push (GST_PAD_CAST (pad), outbuf); |
| outbuf = NULL; |
| ret = gst_flow_combiner_update_flow (demux->flowcombiner, ret); |
| GST_LOG_OBJECT (demux, "combined return %s", gst_flow_get_name (ret)); |
| |
| if (pad->position > demux->segment.position) |
| demux->segment.position = pad->position; |
| |
| if (ret != GST_FLOW_OK) |
| goto out; |
| |
| pad->current_essence_track_position++; |
| |
| if (pad->current_component) { |
| if (pad->current_component_duration > 0 && |
| pad->current_essence_track_position - pad->current_component_start |
| >= pad->current_component_duration) { |
| GST_DEBUG_OBJECT (demux, "Switching to next component"); |
| |
| ret = |
| gst_mxf_demux_pad_set_component (demux, pad, |
| pad->current_component_index + 1); |
| if (ret != GST_FLOW_OK && ret != GST_FLOW_EOS) { |
| GST_ERROR_OBJECT (demux, "Switching component failed"); |
| } |
| } else if (etrack->duration > 0 |
| && pad->current_essence_track_position >= etrack->duration) { |
| GST_DEBUG_OBJECT (demux, |
| "Current component position after end of essence track"); |
| ret = GST_FLOW_EOS; |
| } |
| } else if (etrack->duration > 0 |
| && pad->current_essence_track_position == etrack->duration) { |
| GST_DEBUG_OBJECT (demux, "At the end of the essence track"); |
| ret = GST_FLOW_EOS; |
| } |
| |
| if (ret == GST_FLOW_EOS) { |
| GstEvent *e; |
| |
| GST_DEBUG_OBJECT (demux, "EOS for track"); |
| pad->eos = TRUE; |
| e = gst_event_new_eos (); |
| gst_event_set_seqnum (e, demux->seqnum); |
| gst_pad_push_event (GST_PAD_CAST (pad), e); |
| ret = GST_FLOW_OK; |
| } |
| |
| if (ret != GST_FLOW_OK) |
| goto out; |
| } |
| |
| out: |
| if (inbuf) |
| gst_buffer_unref (inbuf); |
| |
| if (outbuf) |
| gst_buffer_unref (outbuf); |
| |
| etrack->position++; |
| |
| return ret; |
| } |
| |
| static void |
| read_partition_header (GstMXFDemux * demux) |
| { |
| GstBuffer *buf; |
| MXFUL key; |
| guint read; |
| |
| if (gst_mxf_demux_pull_klv_packet (demux, demux->offset, &key, &buf, &read) |
| != GST_FLOW_OK) |
| return; |
| |
| if (!mxf_is_partition_pack (&key)) { |
| gst_buffer_unref (buf); |
| return; |
| } |
| |
| if (gst_mxf_demux_handle_partition_pack (demux, &key, buf) != GST_FLOW_OK) { |
| gst_buffer_unref (buf); |
| return; |
| } |
| demux->offset += read; |
| gst_buffer_unref (buf); |
| |
| if (gst_mxf_demux_pull_klv_packet (demux, demux->offset, &key, &buf, &read) |
| != GST_FLOW_OK) |
| return; |
| |
| while (mxf_is_fill (&key)) { |
| demux->offset += read; |
| gst_buffer_unref (buf); |
| if (gst_mxf_demux_pull_klv_packet (demux, demux->offset, &key, &buf, &read) |
| != GST_FLOW_OK) |
| return; |
| } |
| |
| if (!mxf_is_index_table_segment (&key) |
| && demux->current_partition->partition.header_byte_count) { |
| gst_buffer_unref (buf); |
| demux->offset += demux->current_partition->partition.header_byte_count; |
| if (gst_mxf_demux_pull_klv_packet (demux, demux->offset, &key, &buf, &read) |
| != GST_FLOW_OK) |
| return; |
| } |
| |
| while (mxf_is_index_table_segment (&key)) { |
| gst_mxf_demux_handle_index_table_segment (demux, &key, buf, demux->offset); |
| demux->offset += read; |
| |
| gst_buffer_unref (buf); |
| if (gst_mxf_demux_pull_klv_packet (demux, demux->offset, &key, &buf, &read) |
| != GST_FLOW_OK) |
| return; |
| } |
| |
| while (mxf_is_fill (&key)) { |
| demux->offset += read; |
| gst_buffer_unref (buf); |
| if (gst_mxf_demux_pull_klv_packet (demux, demux->offset, &key, &buf, &read) |
| != GST_FLOW_OK) |
| return; |
| } |
| |
| if (mxf_is_generic_container_system_item (&key) || |
| mxf_is_generic_container_essence_element (&key) || |
| mxf_is_avid_essence_container_essence_element (&key)) { |
| if (demux->current_partition->essence_container_offset == 0) |
| demux->current_partition->essence_container_offset = |
| demux->offset - demux->current_partition->partition.this_partition - |
| demux->run_in; |
| } |
| |
| gst_buffer_unref (buf); |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_handle_random_index_pack (GstMXFDemux * demux, const MXFUL * key, |
| GstBuffer * buffer) |
| { |
| guint i; |
| GList *l; |
| GstMapInfo map; |
| gboolean ret; |
| |
| GST_DEBUG_OBJECT (demux, |
| "Handling random index pack of size %" G_GSIZE_FORMAT " at offset %" |
| G_GUINT64_FORMAT, gst_buffer_get_size (buffer), demux->offset); |
| |
| if (demux->random_index_pack) { |
| GST_DEBUG_OBJECT (demux, "Already parsed random index pack"); |
| return GST_FLOW_OK; |
| } |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| ret = |
| mxf_random_index_pack_parse (key, map.data, map.size, |
| &demux->random_index_pack); |
| gst_buffer_unmap (buffer, &map); |
| |
| if (!ret) { |
| GST_ERROR_OBJECT (demux, "Parsing random index pack failed"); |
| return GST_FLOW_ERROR; |
| } |
| |
| for (i = 0; i < demux->random_index_pack->len; i++) { |
| GstMXFDemuxPartition *p = NULL; |
| MXFRandomIndexPackEntry *e = |
| &g_array_index (demux->random_index_pack, MXFRandomIndexPackEntry, i); |
| |
| if (e->offset < demux->run_in) { |
| GST_ERROR_OBJECT (demux, "Invalid random index pack entry"); |
| return GST_FLOW_ERROR; |
| } |
| |
| for (l = demux->partitions; l; l = l->next) { |
| GstMXFDemuxPartition *tmp = l->data; |
| |
| if (tmp->partition.this_partition + demux->run_in == e->offset) { |
| p = tmp; |
| break; |
| } |
| } |
| |
| if (!p) { |
| p = g_new0 (GstMXFDemuxPartition, 1); |
| p->partition.this_partition = e->offset - demux->run_in; |
| p->partition.body_sid = e->body_sid; |
| demux->partitions = |
| g_list_insert_sorted (demux->partitions, p, |
| (GCompareFunc) gst_mxf_demux_partition_compare); |
| } |
| } |
| |
| for (l = demux->partitions; l; l = l->next) { |
| GstMXFDemuxPartition *a, *b; |
| |
| if (l->next == NULL) |
| break; |
| |
| a = l->data; |
| b = l->next->data; |
| |
| b->partition.prev_partition = a->partition.this_partition; |
| } |
| |
| return GST_FLOW_OK; |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_handle_index_table_segment (GstMXFDemux * demux, |
| const MXFUL * key, GstBuffer * buffer, guint64 offset) |
| { |
| MXFIndexTableSegment *segment; |
| GstMapInfo map; |
| gboolean ret; |
| |
| GST_DEBUG_OBJECT (demux, |
| "Handling index table segment of size %" G_GSIZE_FORMAT " at offset %" |
| G_GUINT64_FORMAT, gst_buffer_get_size (buffer), offset); |
| |
| segment = g_new0 (MXFIndexTableSegment, 1); |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| ret = mxf_index_table_segment_parse (key, segment, map.data, map.size); |
| gst_buffer_unmap (buffer, &map); |
| |
| if (!ret) { |
| GST_ERROR_OBJECT (demux, "Parsing index table segment failed"); |
| return GST_FLOW_ERROR; |
| } |
| |
| demux->pending_index_table_segments = |
| g_list_prepend (demux->pending_index_table_segments, segment); |
| |
| return GST_FLOW_OK; |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_pull_klv_packet (GstMXFDemux * demux, guint64 offset, MXFUL * key, |
| GstBuffer ** outbuf, guint * read) |
| { |
| GstBuffer *buffer = NULL; |
| const guint8 *data; |
| guint64 data_offset = 0; |
| guint64 length; |
| GstFlowReturn ret = GST_FLOW_OK; |
| GstMapInfo map; |
| #ifndef GST_DISABLE_GST_DEBUG |
| gchar str[48]; |
| #endif |
| |
| memset (key, 0, sizeof (MXFUL)); |
| |
| /* Pull 16 byte key and first byte of BER encoded length */ |
| if ((ret = |
| gst_mxf_demux_pull_range (demux, offset, 17, &buffer)) != GST_FLOW_OK) |
| goto beach; |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| |
| memcpy (key, map.data, 16); |
| |
| GST_DEBUG_OBJECT (demux, "Got KLV packet with key %s", mxf_ul_to_string (key, |
| str)); |
| |
| /* Decode BER encoded packet length */ |
| if ((map.data[16] & 0x80) == 0) { |
| length = map.data[16]; |
| data_offset = 17; |
| } else { |
| guint slen = map.data[16] & 0x7f; |
| |
| data_offset = 16 + 1 + slen; |
| |
| gst_buffer_unmap (buffer, &map); |
| gst_buffer_unref (buffer); |
| buffer = NULL; |
| |
| /* Must be at most 8 according to SMPTE-379M 5.3.4 */ |
| if (slen > 8) { |
| GST_ERROR_OBJECT (demux, "Invalid KLV packet length: %u", slen); |
| ret = GST_FLOW_ERROR; |
| goto beach; |
| } |
| |
| /* Now pull the length of the packet */ |
| if ((ret = gst_mxf_demux_pull_range (demux, offset + 17, slen, |
| &buffer)) != GST_FLOW_OK) |
| goto beach; |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| |
| data = map.data; |
| length = 0; |
| while (slen) { |
| length = (length << 8) | *data; |
| data++; |
| slen--; |
| } |
| } |
| |
| gst_buffer_unmap (buffer, &map); |
| gst_buffer_unref (buffer); |
| buffer = NULL; |
| |
| /* GStreamer's buffer sizes are stored in a guint so we |
| * limit ourself to G_MAXUINT large buffers */ |
| if (length > G_MAXUINT) { |
| GST_ERROR_OBJECT (demux, |
| "Unsupported KLV packet length: %" G_GUINT64_FORMAT, length); |
| ret = GST_FLOW_ERROR; |
| goto beach; |
| } |
| |
| GST_DEBUG_OBJECT (demux, "KLV packet with key %s has length " |
| "%" G_GUINT64_FORMAT, mxf_ul_to_string (key, str), length); |
| |
| /* Pull the complete KLV packet */ |
| if ((ret = gst_mxf_demux_pull_range (demux, offset + data_offset, length, |
| &buffer)) != GST_FLOW_OK) |
| goto beach; |
| |
| *outbuf = buffer; |
| buffer = NULL; |
| if (read) |
| *read = data_offset + length; |
| |
| beach: |
| if (buffer) |
| gst_buffer_unref (buffer); |
| |
| return ret; |
| } |
| |
| static void |
| gst_mxf_demux_pull_random_index_pack (GstMXFDemux * demux) |
| { |
| GstBuffer *buffer; |
| gint64 filesize = -1; |
| GstFormat fmt = GST_FORMAT_BYTES; |
| guint32 pack_size; |
| guint64 old_offset = demux->offset; |
| MXFUL key; |
| GstMapInfo map; |
| GstFlowReturn flow_ret; |
| |
| if (!gst_pad_peer_query_duration (demux->sinkpad, fmt, &filesize) || |
| fmt != GST_FORMAT_BYTES || filesize == -1) { |
| GST_DEBUG_OBJECT (demux, "Can't query upstream size"); |
| return; |
| } |
| |
| g_assert (filesize > 4); |
| |
| buffer = NULL; |
| if (gst_mxf_demux_pull_range (demux, filesize - 4, 4, &buffer) != GST_FLOW_OK) { |
| GST_DEBUG_OBJECT (demux, "Failed pulling last 4 bytes"); |
| return; |
| } |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| pack_size = GST_READ_UINT32_BE (map.data); |
| gst_buffer_unmap (buffer, &map); |
| |
| gst_buffer_unref (buffer); |
| |
| if (pack_size < 20) { |
| GST_DEBUG_OBJECT (demux, "Too small pack size (%u bytes)", pack_size); |
| return; |
| } else if (pack_size > filesize - 20) { |
| GST_DEBUG_OBJECT (demux, "Too large pack size (%u bytes)", pack_size); |
| return; |
| } |
| |
| buffer = NULL; |
| if (gst_mxf_demux_pull_range (demux, filesize - pack_size, 16, |
| &buffer) != GST_FLOW_OK) { |
| GST_DEBUG_OBJECT (demux, "Failed pulling random index pack key"); |
| return; |
| } |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| memcpy (&key, map.data, 16); |
| gst_buffer_unmap (buffer, &map); |
| gst_buffer_unref (buffer); |
| |
| if (!mxf_is_random_index_pack (&key)) { |
| GST_DEBUG_OBJECT (demux, "No random index pack"); |
| return; |
| } |
| |
| demux->offset = filesize - pack_size; |
| if (gst_mxf_demux_pull_klv_packet (demux, filesize - pack_size, &key, |
| &buffer, NULL) != GST_FLOW_OK) { |
| GST_DEBUG_OBJECT (demux, "Failed pulling random index pack"); |
| return; |
| } |
| |
| flow_ret = gst_mxf_demux_handle_random_index_pack (demux, &key, buffer); |
| gst_buffer_unref (buffer); |
| demux->offset = old_offset; |
| |
| if (flow_ret == GST_FLOW_OK && !demux->index_table_segments_collected) { |
| collect_index_table_segments (demux); |
| demux->index_table_segments_collected = TRUE; |
| } |
| } |
| |
| static void |
| gst_mxf_demux_parse_footer_metadata (GstMXFDemux * demux) |
| { |
| guint64 old_offset = demux->offset; |
| MXFUL key; |
| GstBuffer *buffer = NULL; |
| guint read = 0; |
| GstFlowReturn flow = GST_FLOW_OK; |
| GstMXFDemuxPartition *old_partition = demux->current_partition; |
| |
| demux->current_partition = NULL; |
| |
| gst_mxf_demux_reset_metadata (demux); |
| |
| if (demux->footer_partition_pack_offset != 0) { |
| demux->offset = demux->run_in + demux->footer_partition_pack_offset; |
| } else { |
| MXFRandomIndexPackEntry *entry = |
| &g_array_index (demux->random_index_pack, MXFRandomIndexPackEntry, |
| demux->random_index_pack->len - 1); |
| demux->offset = entry->offset; |
| } |
| |
| next_try: |
| flow = |
| gst_mxf_demux_pull_klv_packet (demux, demux->offset, &key, &buffer, |
| &read); |
| if (G_UNLIKELY (flow != GST_FLOW_OK)) |
| goto out; |
| |
| if (!mxf_is_partition_pack (&key)) |
| goto out; |
| |
| if (gst_mxf_demux_handle_partition_pack (demux, &key, buffer) != GST_FLOW_OK) |
| goto out; |
| |
| demux->offset += read; |
| gst_buffer_unref (buffer); |
| buffer = NULL; |
| |
| if (demux->current_partition->partition.header_byte_count == 0) { |
| if (demux->current_partition->partition.prev_partition == 0 |
| || demux->current_partition->partition.this_partition == 0) |
| goto out; |
| |
| demux->offset = |
| demux->run_in + demux->current_partition->partition.this_partition - |
| demux->current_partition->partition.prev_partition; |
| goto next_try; |
| } |
| |
| while (TRUE) { |
| flow = |
| gst_mxf_demux_pull_klv_packet (demux, demux->offset, &key, &buffer, |
| &read); |
| if (G_UNLIKELY (flow != GST_FLOW_OK)) { |
| demux->offset = |
| demux->run_in + |
| demux->current_partition->partition.this_partition - |
| demux->current_partition->partition.prev_partition; |
| goto next_try; |
| } |
| |
| if (mxf_is_fill (&key)) { |
| demux->offset += read; |
| gst_buffer_unref (buffer); |
| buffer = NULL; |
| } else if (mxf_is_primer_pack (&key)) { |
| if (!demux->current_partition->primer.mappings) { |
| if (gst_mxf_demux_handle_primer_pack (demux, &key, |
| buffer) != GST_FLOW_OK) { |
| demux->offset += read; |
| gst_buffer_unref (buffer); |
| buffer = NULL; |
| demux->offset = |
| demux->run_in + |
| demux->current_partition->partition.this_partition - |
| demux->current_partition->partition.prev_partition; |
| goto next_try; |
| } |
| } |
| demux->offset += read; |
| gst_buffer_unref (buffer); |
| buffer = NULL; |
| break; |
| } else { |
| gst_buffer_unref (buffer); |
| buffer = NULL; |
| demux->offset = |
| demux->run_in + |
| demux->current_partition->partition.this_partition - |
| demux->current_partition->partition.prev_partition; |
| goto next_try; |
| } |
| } |
| |
| /* parse metadata */ |
| while (demux->offset < |
| demux->run_in + demux->current_partition->primer.offset + |
| demux->current_partition->partition.header_byte_count) { |
| flow = |
| gst_mxf_demux_pull_klv_packet (demux, demux->offset, &key, &buffer, |
| &read); |
| if (G_UNLIKELY (flow != GST_FLOW_OK)) { |
| demux->offset = |
| demux->run_in + |
| demux->current_partition->partition.this_partition - |
| demux->current_partition->partition.prev_partition; |
| goto next_try; |
| } |
| |
| if (mxf_is_metadata (&key)) { |
| flow = gst_mxf_demux_handle_metadata (demux, &key, buffer); |
| demux->offset += read; |
| gst_buffer_unref (buffer); |
| buffer = NULL; |
| |
| if (G_UNLIKELY (flow != GST_FLOW_OK)) { |
| gst_mxf_demux_reset_metadata (demux); |
| demux->offset = |
| demux->run_in + |
| demux->current_partition->partition.this_partition - |
| demux->current_partition->partition.prev_partition; |
| goto next_try; |
| } |
| } else if (mxf_is_descriptive_metadata (&key)) { |
| gst_mxf_demux_handle_descriptive_metadata (demux, &key, buffer); |
| demux->offset += read; |
| gst_buffer_unref (buffer); |
| buffer = NULL; |
| } else if (mxf_is_fill (&key)) { |
| demux->offset += read; |
| gst_buffer_unref (buffer); |
| buffer = NULL; |
| } else if (mxf_is_generic_container_system_item (&key) || |
| mxf_is_generic_container_essence_element (&key) || |
| mxf_is_avid_essence_container_essence_element (&key)) { |
| demux->offset += read; |
| gst_buffer_unref (buffer); |
| buffer = NULL; |
| break; |
| } else { |
| demux->offset += read; |
| gst_buffer_unref (buffer); |
| buffer = NULL; |
| } |
| } |
| |
| /* resolve references etc */ |
| |
| if (gst_mxf_demux_resolve_references (demux) != |
| GST_FLOW_OK || gst_mxf_demux_update_tracks (demux) != GST_FLOW_OK) { |
| demux->current_partition->parsed_metadata = TRUE; |
| demux->offset = |
| demux->run_in + demux->current_partition->partition.this_partition - |
| demux->current_partition->partition.prev_partition; |
| goto next_try; |
| } |
| |
| out: |
| if (buffer) |
| gst_buffer_unref (buffer); |
| |
| demux->offset = old_offset; |
| demux->current_partition = old_partition; |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_handle_klv_packet (GstMXFDemux * demux, const MXFUL * key, |
| GstBuffer * buffer, gboolean peek) |
| { |
| #ifndef GST_DISABLE_GST_DEBUG |
| gchar key_str[48]; |
| #endif |
| GstFlowReturn ret = GST_FLOW_OK; |
| |
| if (demux->update_metadata |
| && demux->preface |
| && (demux->offset >= |
| demux->run_in + demux->current_partition->primer.offset + |
| demux->current_partition->partition.header_byte_count || |
| mxf_is_generic_container_system_item (key) || |
| mxf_is_generic_container_essence_element (key) || |
| mxf_is_avid_essence_container_essence_element (key))) { |
| demux->current_partition->parsed_metadata = TRUE; |
| if ((ret = gst_mxf_demux_resolve_references (demux)) != GST_FLOW_OK || |
| (ret = gst_mxf_demux_update_tracks (demux)) != GST_FLOW_OK) { |
| goto beach; |
| } |
| } else if (demux->metadata_resolved && demux->requested_package_string) { |
| if ((ret = gst_mxf_demux_update_tracks (demux)) != GST_FLOW_OK) { |
| goto beach; |
| } |
| } |
| |
| if (!mxf_is_mxf_packet (key)) { |
| GST_WARNING_OBJECT (demux, |
| "Skipping non-MXF packet of size %" G_GSIZE_FORMAT " at offset %" |
| G_GUINT64_FORMAT ", key: %s", gst_buffer_get_size (buffer), |
| demux->offset, mxf_ul_to_string (key, key_str)); |
| } else if (mxf_is_partition_pack (key)) { |
| ret = gst_mxf_demux_handle_partition_pack (demux, key, buffer); |
| |
| /* If this partition contains the start of an essence container |
| * set the positions of all essence streams to 0 |
| */ |
| if (ret == GST_FLOW_OK && demux->current_partition |
| && demux->current_partition->partition.body_sid != 0 |
| && demux->current_partition->partition.body_offset == 0) { |
| guint i; |
| |
| for (i = 0; i < demux->essence_tracks->len; i++) { |
| GstMXFDemuxEssenceTrack *etrack = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, i); |
| |
| if (etrack->body_sid != demux->current_partition->partition.body_sid) |
| continue; |
| |
| etrack->position = 0; |
| } |
| } |
| } else if (mxf_is_primer_pack (key)) { |
| ret = gst_mxf_demux_handle_primer_pack (demux, key, buffer); |
| } else if (mxf_is_metadata (key)) { |
| ret = gst_mxf_demux_handle_metadata (demux, key, buffer); |
| } else if (mxf_is_descriptive_metadata (key)) { |
| ret = gst_mxf_demux_handle_descriptive_metadata (demux, key, buffer); |
| } else if (mxf_is_generic_container_system_item (key)) { |
| ret = |
| gst_mxf_demux_handle_generic_container_system_item (demux, key, buffer); |
| } else if (mxf_is_generic_container_essence_element (key) || |
| mxf_is_avid_essence_container_essence_element (key)) { |
| ret = |
| gst_mxf_demux_handle_generic_container_essence_element (demux, key, |
| buffer, peek); |
| } else if (mxf_is_random_index_pack (key)) { |
| ret = gst_mxf_demux_handle_random_index_pack (demux, key, buffer); |
| |
| if (ret == GST_FLOW_OK && demux->random_access |
| && !demux->index_table_segments_collected) { |
| collect_index_table_segments (demux); |
| demux->index_table_segments_collected = TRUE; |
| } |
| } else if (mxf_is_index_table_segment (key)) { |
| ret = |
| gst_mxf_demux_handle_index_table_segment (demux, key, buffer, |
| demux->offset); |
| } else if (mxf_is_fill (key)) { |
| GST_DEBUG_OBJECT (demux, |
| "Skipping filler packet of size %" G_GSIZE_FORMAT " at offset %" |
| G_GUINT64_FORMAT, gst_buffer_get_size (buffer), demux->offset); |
| } else { |
| GST_DEBUG_OBJECT (demux, |
| "Skipping unknown packet of size %" G_GSIZE_FORMAT " at offset %" |
| G_GUINT64_FORMAT ", key: %s", gst_buffer_get_size (buffer), |
| demux->offset, mxf_ul_to_string (key, key_str)); |
| } |
| |
| /* In pull mode try to get the last metadata */ |
| if (mxf_is_partition_pack (key) && ret == GST_FLOW_OK |
| && demux->pull_footer_metadata |
| && demux->random_access && demux->current_partition |
| && demux->current_partition->partition.type == MXF_PARTITION_PACK_HEADER |
| && (!demux->current_partition->partition.closed |
| || !demux->current_partition->partition.complete) |
| && (demux->footer_partition_pack_offset != 0 || demux->random_index_pack)) { |
| GST_DEBUG_OBJECT (demux, |
| "Open or incomplete header partition, trying to get final metadata from the last partitions"); |
| gst_mxf_demux_parse_footer_metadata (demux); |
| demux->pull_footer_metadata = FALSE; |
| |
| if (demux->current_partition->partition.body_sid != 0 && |
| demux->current_partition->partition.body_offset == 0) { |
| guint i; |
| for (i = 0; i < demux->essence_tracks->len; i++) { |
| GstMXFDemuxEssenceTrack *etrack = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, i); |
| |
| if (etrack->body_sid != demux->current_partition->partition.body_sid) |
| continue; |
| |
| etrack->position = 0; |
| } |
| } |
| } |
| |
| beach: |
| return ret; |
| } |
| |
| static void |
| gst_mxf_demux_set_partition_for_offset (GstMXFDemux * demux, guint64 offset) |
| { |
| GList *l; |
| |
| /* This partition will already be parsed, otherwise |
| * the position wouldn't be in the index */ |
| for (l = demux->partitions; l; l = l->next) { |
| GstMXFDemuxPartition *p = l->data; |
| |
| if (p->partition.this_partition + demux->run_in <= offset) |
| demux->current_partition = p; |
| } |
| } |
| |
| static guint64 |
| find_offset (GArray * offsets, gint64 * position, gboolean keyframe) |
| { |
| GstMXFDemuxIndex *idx; |
| guint64 current_offset = -1; |
| gint64 current_position = *position; |
| |
| if (!offsets || offsets->len <= *position) |
| return -1; |
| |
| idx = &g_array_index (offsets, GstMXFDemuxIndex, *position); |
| if (idx->offset != 0 && (!keyframe || idx->keyframe)) { |
| current_offset = idx->offset; |
| } else if (idx->offset != 0) { |
| current_position--; |
| while (current_position >= 0) { |
| idx = &g_array_index (offsets, GstMXFDemuxIndex, current_position); |
| if (idx->offset == 0) { |
| break; |
| } else if (!idx->keyframe) { |
| current_position--; |
| continue; |
| } else { |
| current_offset = idx->offset; |
| break; |
| } |
| } |
| } |
| |
| if (current_offset == -1) |
| return -1; |
| |
| *position = current_position; |
| return current_offset; |
| } |
| |
| static guint64 |
| find_closest_offset (GArray * offsets, gint64 * position, gboolean keyframe) |
| { |
| GstMXFDemuxIndex *idx; |
| gint64 current_position = *position; |
| |
| if (!offsets || offsets->len == 0) |
| return -1; |
| |
| current_position = MIN (current_position, offsets->len - 1); |
| |
| idx = &g_array_index (offsets, GstMXFDemuxIndex, current_position); |
| while (idx->offset == 0 || (keyframe && !idx->keyframe)) { |
| current_position--; |
| if (current_position < 0) |
| break; |
| idx = &g_array_index (offsets, GstMXFDemuxIndex, current_position); |
| } |
| |
| if (idx->offset != 0 && (!keyframe || idx->keyframe)) { |
| *position = current_position; |
| return idx->offset; |
| } |
| |
| return -1; |
| } |
| |
| static guint64 |
| gst_mxf_demux_find_essence_element (GstMXFDemux * demux, |
| GstMXFDemuxEssenceTrack * etrack, gint64 * position, gboolean keyframe) |
| { |
| GstFlowReturn ret = GST_FLOW_OK; |
| guint64 old_offset = demux->offset; |
| GstMXFDemuxPartition *old_partition = demux->current_partition; |
| gint i; |
| guint64 offset; |
| gint64 requested_position = *position; |
| GstMXFDemuxIndexTable *index_table = NULL; |
| |
| GST_DEBUG_OBJECT (demux, "Trying to find essence element %" G_GINT64_FORMAT |
| " of track %u with body_sid %u (keyframe %d)", *position, |
| etrack->track_number, etrack->body_sid, keyframe); |
| |
| if (demux->index_tables) { |
| GList *l; |
| |
| for (l = demux->index_tables; l; l = l->next) { |
| GstMXFDemuxIndexTable *tmp = l->data; |
| |
| if (tmp->body_sid == etrack->body_sid |
| && tmp->index_sid == etrack->index_sid) { |
| index_table = tmp; |
| break; |
| } |
| } |
| } |
| |
| from_index: |
| |
| if (etrack->duration > 0 && *position >= etrack->duration) { |
| GST_WARNING_OBJECT (demux, "Position after end of essence track"); |
| return -1; |
| } |
| |
| /* First try to find an offset in our index */ |
| offset = find_offset (etrack->offsets, position, keyframe); |
| if (offset != -1) { |
| GST_DEBUG_OBJECT (demux, |
| "Found edit unit %" G_GINT64_FORMAT " for %" G_GINT64_FORMAT |
| " in generated index at offset %" G_GUINT64_FORMAT, *position, |
| requested_position, offset); |
| return offset; |
| } |
| |
| GST_DEBUG_OBJECT (demux, "Not found in index"); |
| if (!demux->random_access) { |
| offset = find_closest_offset (etrack->offsets, position, keyframe); |
| if (offset != -1) { |
| GST_DEBUG_OBJECT (demux, |
| "Starting with edit unit %" G_GINT64_FORMAT " for %" G_GINT64_FORMAT |
| " in generated index at offset %" G_GUINT64_FORMAT, *position, |
| requested_position, offset); |
| return offset; |
| } |
| |
| if (index_table) { |
| offset = find_closest_offset (index_table->offsets, position, keyframe); |
| if (offset != -1) { |
| GST_DEBUG_OBJECT (demux, |
| "Starting with edit unit %" G_GINT64_FORMAT " for %" G_GINT64_FORMAT |
| " in index at offset %" G_GUINT64_FORMAT, *position, |
| requested_position, offset); |
| return offset; |
| } |
| } |
| } else if (demux->random_access) { |
| gint64 index_start_position = *position; |
| |
| demux->offset = demux->run_in; |
| |
| offset = |
| find_closest_offset (etrack->offsets, &index_start_position, FALSE); |
| if (offset != -1) { |
| demux->offset = offset + demux->run_in; |
| GST_DEBUG_OBJECT (demux, |
| "Starting with edit unit %" G_GINT64_FORMAT " for %" G_GINT64_FORMAT |
| " in generated index at offset %" G_GUINT64_FORMAT, |
| index_start_position, requested_position, offset); |
| } else { |
| index_start_position = -1; |
| } |
| |
| if (index_table) { |
| gint64 tmp_position = *position; |
| |
| offset = find_closest_offset (index_table->offsets, &tmp_position, TRUE); |
| if (offset != -1 && tmp_position > index_start_position) { |
| demux->offset = offset + demux->run_in; |
| index_start_position = tmp_position; |
| GST_DEBUG_OBJECT (demux, |
| "Starting with edit unit %" G_GINT64_FORMAT " for %" G_GINT64_FORMAT |
| " in index at offset %" G_GUINT64_FORMAT, index_start_position, |
| requested_position, offset); |
| } |
| } |
| |
| gst_mxf_demux_set_partition_for_offset (demux, demux->offset); |
| |
| for (i = 0; i < demux->essence_tracks->len; i++) { |
| GstMXFDemuxEssenceTrack *t = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, i); |
| |
| if (index_start_position != -1 && t == etrack) |
| t->position = index_start_position; |
| else |
| t->position = (demux->offset == demux->run_in) ? 0 : -1; |
| } |
| |
| /* Else peek at all essence elements and complete our |
| * index until we find the requested element |
| */ |
| while (ret == GST_FLOW_OK) { |
| GstBuffer *buffer = NULL; |
| MXFUL key; |
| guint read = 0; |
| |
| ret = |
| gst_mxf_demux_pull_klv_packet (demux, demux->offset, &key, &buffer, |
| &read); |
| |
| if (ret == GST_FLOW_EOS) { |
| for (i = 0; i < demux->essence_tracks->len; i++) { |
| GstMXFDemuxEssenceTrack *t = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, |
| i); |
| |
| if (t->position > 0) |
| t->duration = t->position; |
| } |
| /* For the searched track this is really our position */ |
| etrack->duration = etrack->position; |
| |
| for (i = 0; i < demux->src->len; i++) { |
| GstMXFDemuxPad *p = g_ptr_array_index (demux->src, i); |
| |
| if (!p->eos |
| && p->current_essence_track_position >= |
| p->current_essence_track->duration) { |
| GstEvent *e; |
| |
| p->eos = TRUE; |
| e = gst_event_new_eos (); |
| gst_event_set_seqnum (e, demux->seqnum); |
| gst_pad_push_event (GST_PAD_CAST (p), e); |
| } |
| } |
| } |
| |
| if (G_UNLIKELY (ret != GST_FLOW_OK) && etrack->position <= *position) { |
| demux->offset = old_offset; |
| demux->current_partition = old_partition; |
| break; |
| } else if (G_UNLIKELY (ret == GST_FLOW_OK)) { |
| ret = gst_mxf_demux_handle_klv_packet (demux, &key, buffer, TRUE); |
| gst_buffer_unref (buffer); |
| } |
| |
| /* If we found the position read it from the index again */ |
| if (((ret == GST_FLOW_OK && etrack->position == *position + 2) || |
| (ret == GST_FLOW_EOS && etrack->position == *position + 1)) |
| && etrack->offsets && etrack->offsets->len > *position |
| && g_array_index (etrack->offsets, GstMXFDemuxIndex, |
| *position).offset != 0) { |
| GST_DEBUG_OBJECT (demux, "Found at offset %" G_GUINT64_FORMAT, |
| demux->offset); |
| demux->offset = old_offset; |
| demux->current_partition = old_partition; |
| goto from_index; |
| } |
| demux->offset += read; |
| } |
| demux->offset = old_offset; |
| demux->current_partition = old_partition; |
| |
| GST_DEBUG_OBJECT (demux, "Not found in this file"); |
| } |
| |
| return -1; |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_pull_and_handle_klv_packet (GstMXFDemux * demux) |
| { |
| GstBuffer *buffer = NULL; |
| MXFUL key; |
| GstFlowReturn ret = GST_FLOW_OK; |
| guint read = 0; |
| |
| if (demux->src->len > 0) { |
| if (!gst_mxf_demux_get_earliest_pad (demux)) { |
| ret = GST_FLOW_EOS; |
| GST_DEBUG_OBJECT (demux, "All tracks are EOS"); |
| goto beach; |
| } |
| } |
| |
| ret = |
| gst_mxf_demux_pull_klv_packet (demux, demux->offset, &key, &buffer, |
| &read); |
| |
| if (ret == GST_FLOW_EOS && demux->src->len > 0) { |
| guint i; |
| GstMXFDemuxPad *p = NULL; |
| |
| for (i = 0; i < demux->essence_tracks->len; i++) { |
| GstMXFDemuxEssenceTrack *t = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, i); |
| |
| if (t->position > 0) |
| t->duration = t->position; |
| } |
| |
| for (i = 0; i < demux->src->len; i++) { |
| GstMXFDemuxPad *p = g_ptr_array_index (demux->src, i); |
| |
| if (!p->eos |
| && p->current_essence_track_position >= |
| p->current_essence_track->duration) { |
| GstEvent *e; |
| |
| p->eos = TRUE; |
| e = gst_event_new_eos (); |
| gst_event_set_seqnum (e, demux->seqnum); |
| gst_pad_push_event (GST_PAD_CAST (p), e); |
| } |
| } |
| |
| while ((p = gst_mxf_demux_get_earliest_pad (demux))) { |
| guint64 offset; |
| gint64 position; |
| |
| position = p->current_essence_track_position; |
| |
| offset = |
| gst_mxf_demux_find_essence_element (demux, p->current_essence_track, |
| &position, FALSE); |
| if (offset == -1) { |
| GstEvent *e; |
| |
| GST_ERROR_OBJECT (demux, "Failed to find offset for essence track"); |
| p->eos = TRUE; |
| e = gst_event_new_eos (); |
| gst_event_set_seqnum (e, demux->seqnum); |
| gst_pad_push_event (GST_PAD_CAST (p), e); |
| continue; |
| } |
| |
| demux->offset = offset + demux->run_in; |
| gst_mxf_demux_set_partition_for_offset (demux, demux->offset); |
| |
| p->current_essence_track->position = position; |
| |
| ret = GST_FLOW_OK; |
| goto beach; |
| } |
| } |
| |
| if (G_UNLIKELY (ret != GST_FLOW_OK)) |
| goto beach; |
| |
| ret = gst_mxf_demux_handle_klv_packet (demux, &key, buffer, FALSE); |
| demux->offset += read; |
| |
| if (ret == GST_FLOW_OK && demux->src->len > 0 |
| && demux->essence_tracks->len > 0) { |
| GstMXFDemuxPad *earliest = NULL; |
| /* We allow time drifts of at most 500ms */ |
| while ((earliest = gst_mxf_demux_get_earliest_pad (demux)) && |
| demux->segment.position - earliest->position > demux->max_drift) { |
| guint64 offset; |
| gint64 position; |
| |
| GST_WARNING_OBJECT (demux, |
| "Found synchronization issue -- trying to solve"); |
| |
| position = earliest->current_essence_track_position; |
| |
| /* FIXME: This can probably be improved by using the |
| * offset of position-1 if it's in the same partition |
| * or the start of the position otherwise. |
| * This way we won't skip elements from the same essence |
| * container as etrack->position |
| */ |
| offset = |
| gst_mxf_demux_find_essence_element (demux, |
| earliest->current_essence_track, &position, FALSE); |
| if (offset == -1) { |
| GstEvent *e; |
| |
| GST_WARNING_OBJECT (demux, |
| "Failed to find offset for late essence track"); |
| earliest->eos = TRUE; |
| e = gst_event_new_eos (); |
| gst_event_set_seqnum (e, demux->seqnum); |
| gst_pad_push_event (GST_PAD_CAST (earliest), e); |
| continue; |
| } |
| |
| demux->offset = offset + demux->run_in; |
| gst_mxf_demux_set_partition_for_offset (demux, demux->offset); |
| |
| earliest->current_essence_track->position = position; |
| break; |
| } |
| } |
| |
| beach: |
| if (buffer) |
| gst_buffer_unref (buffer); |
| |
| return ret; |
| } |
| |
| static void |
| gst_mxf_demux_loop (GstPad * pad) |
| { |
| GstMXFDemux *demux = NULL; |
| GstFlowReturn flow = GST_FLOW_OK; |
| GstMapInfo map; |
| gboolean res; |
| |
| demux = GST_MXF_DEMUX (gst_pad_get_parent (pad)); |
| |
| if (demux->run_in == -1) { |
| /* Skip run-in, which is at most 64K and is finished |
| * by a header partition pack */ |
| while (demux->offset < 64 * 1024) { |
| GstBuffer *buffer = NULL; |
| |
| if ((flow = |
| gst_mxf_demux_pull_range (demux, demux->offset, 16, |
| &buffer)) != GST_FLOW_OK) |
| break; |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| res = mxf_is_header_partition_pack ((const MXFUL *) map.data); |
| gst_buffer_unmap (buffer, &map); |
| |
| if (res) { |
| GST_DEBUG_OBJECT (demux, |
| "Found header partition pack at offset %" G_GUINT64_FORMAT, |
| demux->offset); |
| demux->run_in = demux->offset; |
| gst_buffer_unref (buffer); |
| break; |
| } |
| |
| demux->offset++; |
| gst_buffer_unref (buffer); |
| } |
| |
| if (G_UNLIKELY (flow != GST_FLOW_OK)) |
| goto pause; |
| |
| if (G_UNLIKELY (demux->run_in == -1)) { |
| GST_ERROR_OBJECT (demux, "No valid header partition pack found"); |
| flow = GST_FLOW_ERROR; |
| goto pause; |
| } |
| |
| /* First of all pull&parse the random index pack at EOF */ |
| gst_mxf_demux_pull_random_index_pack (demux); |
| } |
| |
| /* Now actually do something */ |
| flow = gst_mxf_demux_pull_and_handle_klv_packet (demux); |
| |
| /* pause if something went wrong */ |
| if (G_UNLIKELY (flow != GST_FLOW_OK)) |
| goto pause; |
| |
| /* check EOS condition */ |
| if ((demux->segment.flags & GST_SEEK_FLAG_SEGMENT) && |
| (demux->segment.stop != -1) && |
| (demux->segment.position >= demux->segment.stop)) { |
| guint i; |
| gboolean eos = TRUE; |
| |
| for (i = 0; i < demux->src->len; i++) { |
| GstMXFDemuxPad *p = g_ptr_array_index (demux->src, i); |
| |
| if (!p->eos && p->position < demux->segment.stop) { |
| eos = FALSE; |
| break; |
| } |
| } |
| |
| if (eos) { |
| flow = GST_FLOW_EOS; |
| goto pause; |
| } |
| } |
| |
| gst_object_unref (demux); |
| |
| return; |
| |
| pause: |
| { |
| const gchar *reason = gst_flow_get_name (flow); |
| |
| GST_LOG_OBJECT (demux, "pausing task, reason %s", reason); |
| gst_pad_pause_task (pad); |
| |
| if (flow == GST_FLOW_EOS) { |
| /* perform EOS logic */ |
| if (demux->segment.flags & GST_SEEK_FLAG_SEGMENT) { |
| gint64 stop; |
| GstMessage *m; |
| GstEvent *e; |
| |
| /* for segment playback we need to post when (in stream time) |
| * we stopped, this is either stop (when set) or the duration. */ |
| if ((stop = demux->segment.stop) == -1) |
| stop = demux->segment.duration; |
| |
| GST_LOG_OBJECT (demux, "Sending segment done, at end of segment"); |
| m = gst_message_new_segment_done (GST_OBJECT_CAST (demux), |
| GST_FORMAT_TIME, stop); |
| gst_message_set_seqnum (m, demux->seqnum); |
| gst_element_post_message (GST_ELEMENT_CAST (demux), m); |
| e = gst_event_new_segment_done (GST_FORMAT_TIME, stop); |
| gst_event_set_seqnum (e, demux->seqnum); |
| gst_mxf_demux_push_src_event (demux, e); |
| } else { |
| GstEvent *e; |
| |
| /* normal playback, send EOS to all linked pads */ |
| GST_LOG_OBJECT (demux, "Sending EOS, at end of stream"); |
| e = gst_event_new_eos (); |
| gst_event_set_seqnum (e, demux->seqnum); |
| if (!gst_mxf_demux_push_src_event (demux, e)) { |
| GST_WARNING_OBJECT (demux, "failed pushing EOS on streams"); |
| } |
| } |
| } else if (flow == GST_FLOW_NOT_LINKED || flow < GST_FLOW_EOS) { |
| GstEvent *e; |
| |
| GST_ELEMENT_ERROR (demux, STREAM, FAILED, |
| ("Internal data stream error."), |
| ("stream stopped, reason %s", reason)); |
| e = gst_event_new_eos (); |
| gst_event_set_seqnum (e, demux->seqnum); |
| gst_mxf_demux_push_src_event (demux, e); |
| } |
| gst_object_unref (demux); |
| return; |
| } |
| } |
| |
| static GstFlowReturn |
| gst_mxf_demux_chain (GstPad * pad, GstObject * parent, GstBuffer * inbuf) |
| { |
| GstFlowReturn ret = GST_FLOW_OK; |
| GstMXFDemux *demux = NULL; |
| MXFUL key; |
| const guint8 *data = NULL; |
| guint64 length = 0; |
| guint64 offset = 0; |
| GstBuffer *buffer = NULL; |
| gboolean res; |
| #ifndef GST_DISABLE_GST_DEBUG |
| gchar str[48]; |
| #endif |
| |
| demux = GST_MXF_DEMUX (parent); |
| |
| GST_LOG_OBJECT (demux, |
| "received buffer of %" G_GSIZE_FORMAT " bytes at offset %" |
| G_GUINT64_FORMAT, gst_buffer_get_size (inbuf), GST_BUFFER_OFFSET (inbuf)); |
| |
| if (demux->src->len > 0) { |
| if (!gst_mxf_demux_get_earliest_pad (demux)) { |
| ret = GST_FLOW_EOS; |
| GST_DEBUG_OBJECT (demux, "All tracks are EOS"); |
| return ret; |
| } |
| } |
| |
| if (G_UNLIKELY (GST_BUFFER_OFFSET (inbuf) == 0)) { |
| GST_DEBUG_OBJECT (demux, "beginning of file, expect header"); |
| demux->run_in = -1; |
| demux->offset = 0; |
| } |
| |
| if (G_UNLIKELY (demux->offset == 0 && GST_BUFFER_OFFSET (inbuf) != 0)) { |
| GST_DEBUG_OBJECT (demux, "offset was zero, synchronizing with buffer's"); |
| if (GST_BUFFER_OFFSET_IS_VALID (inbuf)) |
| demux->offset = GST_BUFFER_OFFSET (inbuf); |
| gst_mxf_demux_set_partition_for_offset (demux, demux->offset); |
| } else if (demux->current_partition == NULL) { |
| gst_mxf_demux_set_partition_for_offset (demux, demux->offset); |
| } |
| |
| gst_adapter_push (demux->adapter, inbuf); |
| inbuf = NULL; |
| |
| while (ret == GST_FLOW_OK) { |
| if (G_UNLIKELY (demux->flushing)) { |
| GST_DEBUG_OBJECT (demux, "we are now flushing, exiting parser loop"); |
| ret = GST_FLOW_FLUSHING; |
| break; |
| } |
| |
| if (gst_adapter_available (demux->adapter) < 16) |
| break; |
| |
| if (demux->run_in == -1) { |
| /* Skip run-in, which is at most 64K and is finished |
| * by a header partition pack */ |
| |
| while (demux->offset < 64 * 1024 |
| && gst_adapter_available (demux->adapter) >= 16) { |
| data = gst_adapter_map (demux->adapter, 16); |
| res = mxf_is_header_partition_pack ((const MXFUL *) data); |
| gst_adapter_unmap (demux->adapter); |
| |
| if (res) { |
| GST_DEBUG_OBJECT (demux, |
| "Found header partition pack at offset %" G_GUINT64_FORMAT, |
| demux->offset); |
| demux->run_in = demux->offset; |
| break; |
| } |
| gst_adapter_flush (demux->adapter, 1); |
| demux->offset++; |
| } |
| } else if (demux->offset < demux->run_in) { |
| guint64 flush = MIN (gst_adapter_available (demux->adapter), |
| demux->run_in - demux->offset); |
| gst_adapter_flush (demux->adapter, flush); |
| demux->offset += flush; |
| continue; |
| } |
| |
| if (G_UNLIKELY (ret != GST_FLOW_OK)) |
| break; |
| |
| /* Need more data */ |
| if (demux->run_in == -1 && demux->offset < 64 * 1024) |
| break; |
| |
| if (G_UNLIKELY (demux->run_in == -1)) { |
| GST_ERROR_OBJECT (demux, "No valid header partition pack found"); |
| ret = GST_FLOW_ERROR; |
| break; |
| } |
| |
| if (gst_adapter_available (demux->adapter) < 17) |
| break; |
| |
| /* Now actually do something */ |
| memset (&key, 0, sizeof (MXFUL)); |
| |
| /* Pull 16 byte key and first byte of BER encoded length */ |
| data = gst_adapter_map (demux->adapter, 17); |
| |
| memcpy (&key, data, 16); |
| |
| GST_DEBUG_OBJECT (demux, "Got KLV packet with key %s", |
| mxf_ul_to_string (&key, str)); |
| |
| /* Decode BER encoded packet length */ |
| if ((data[16] & 0x80) == 0) { |
| length = data[16]; |
| offset = 17; |
| } else { |
| guint slen = data[16] & 0x7f; |
| |
| offset = 16 + 1 + slen; |
| |
| gst_adapter_unmap (demux->adapter); |
| |
| /* Must be at most 8 according to SMPTE-379M 5.3.4 and |
| * GStreamer buffers can only have a 4 bytes length */ |
| if (slen > 8) { |
| GST_ERROR_OBJECT (demux, "Invalid KLV packet length: %u", slen); |
| ret = GST_FLOW_ERROR; |
| break; |
| } |
| |
| if (gst_adapter_available (demux->adapter) < 17 + slen) |
| break; |
| |
| data = gst_adapter_map (demux->adapter, 17 + slen); |
| data += 17; |
| |
| length = 0; |
| while (slen) { |
| length = (length << 8) | *data; |
| data++; |
| slen--; |
| } |
| } |
| |
| gst_adapter_unmap (demux->adapter); |
| |
| /* GStreamer's buffer sizes are stored in a guint so we |
| * limit ourself to G_MAXUINT large buffers */ |
| if (length > G_MAXUINT) { |
| GST_ERROR_OBJECT (demux, |
| "Unsupported KLV packet length: %" G_GUINT64_FORMAT, length); |
| ret = GST_FLOW_ERROR; |
| break; |
| } |
| |
| GST_DEBUG_OBJECT (demux, "KLV packet with key %s has length " |
| "%" G_GUINT64_FORMAT, mxf_ul_to_string (&key, str), length); |
| |
| if (gst_adapter_available (demux->adapter) < offset + length) |
| break; |
| |
| gst_adapter_flush (demux->adapter, offset); |
| |
| if (length > 0) { |
| buffer = gst_adapter_take_buffer (demux->adapter, length); |
| |
| ret = gst_mxf_demux_handle_klv_packet (demux, &key, buffer, FALSE); |
| gst_buffer_unref (buffer); |
| } |
| |
| demux->offset += offset + length; |
| } |
| |
| return ret; |
| } |
| |
| static void |
| gst_mxf_demux_pad_set_position (GstMXFDemux * demux, GstMXFDemuxPad * p, |
| GstClockTime start) |
| { |
| guint i; |
| GstClockTime sum = 0; |
| MXFMetadataSourceClip *clip = NULL; |
| |
| if (!p->current_component) { |
| p->current_essence_track_position = |
| gst_util_uint64_scale (start, p->material_track->edit_rate.n, |
| p->material_track->edit_rate.d * GST_SECOND); |
| |
| if (p->current_essence_track_position >= p->current_essence_track->duration |
| && p->current_essence_track->duration > 0) { |
| p->current_essence_track_position = p->current_essence_track->duration; |
| p->position = |
| gst_util_uint64_scale (p->current_essence_track->duration, |
| p->material_track->edit_rate.d * GST_SECOND, |
| p->material_track->edit_rate.n); |
| } else { |
| p->position = start; |
| } |
| p->position_accumulated_error = 0.0; |
| |
| return; |
| } |
| |
| for (i = 0; i < p->material_track->parent.sequence->n_structural_components; |
| i++) { |
| clip = |
| MXF_METADATA_SOURCE_CLIP (p->material_track->parent.sequence-> |
| structural_components[i]); |
| |
| if (clip->parent.duration <= 0) |
| break; |
| |
| sum += |
| gst_util_uint64_scale (clip->parent.duration, |
| p->material_track->edit_rate.d * GST_SECOND, |
| p->material_track->edit_rate.n); |
| |
| if (sum > start) |
| break; |
| } |
| |
| if (i == p->material_track->parent.sequence->n_structural_components) { |
| p->position = sum; |
| p->position_accumulated_error = 0.0; |
| |
| gst_mxf_demux_pad_set_component (demux, p, i); |
| return; |
| } |
| |
| if (clip->parent.duration > 0) |
| sum -= |
| gst_util_uint64_scale (clip->parent.duration, |
| p->material_track->edit_rate.d * GST_SECOND, |
| p->material_track->edit_rate.n); |
| |
| start -= sum; |
| |
| gst_mxf_demux_pad_set_component (demux, p, i); |
| |
| { |
| gint64 essence_offset = gst_util_uint64_scale (start, |
| p->current_essence_track->source_track->edit_rate.n, |
| p->current_essence_track->source_track->edit_rate.d * GST_SECOND); |
| |
| p->current_essence_track_position += essence_offset; |
| |
| p->position = sum + gst_util_uint64_scale (essence_offset, |
| GST_SECOND * p->material_track->edit_rate.d, |
| p->material_track->edit_rate.n); |
| p->position_accumulated_error = 0.0; |
| } |
| |
| if (p->current_essence_track_position >= p->current_essence_track->duration |
| && p->current_essence_track->duration > 0) { |
| p->current_essence_track_position = p->current_essence_track->duration; |
| p->position = |
| sum + gst_util_uint64_scale (p->current_component->parent.duration, |
| p->material_track->edit_rate.d * GST_SECOND, |
| p->material_track->edit_rate.n); |
| } |
| } |
| |
| static gboolean |
| gst_mxf_demux_seek_push (GstMXFDemux * demux, GstEvent * event) |
| { |
| GstFormat format; |
| GstSeekFlags flags; |
| GstSeekType start_type, stop_type; |
| gint64 start, stop; |
| gdouble rate; |
| gboolean update, flush, keyframe; |
| GstSegment seeksegment; |
| guint i; |
| guint32 seqnum; |
| |
| gst_event_parse_seek (event, &rate, &format, &flags, |
| &start_type, &start, &stop_type, &stop); |
| seqnum = gst_event_get_seqnum (event); |
| |
| if (rate <= 0.0) |
| goto wrong_rate; |
| |
| if (format != GST_FORMAT_TIME) |
| goto wrong_format; |
| |
| flush = ! !(flags & GST_SEEK_FLAG_FLUSH); |
| keyframe = ! !(flags & GST_SEEK_FLAG_KEY_UNIT); |
| |
| /* Work on a copy until we are sure the seek succeeded. */ |
| memcpy (&seeksegment, &demux->segment, sizeof (GstSegment)); |
| |
| GST_DEBUG_OBJECT (demux, "segment before configure %" GST_SEGMENT_FORMAT, |
| &demux->segment); |
| |
| /* Apply the seek to our segment */ |
| gst_segment_do_seek (&seeksegment, rate, format, flags, |
| start_type, start, stop_type, stop, &update); |
| |
| GST_DEBUG_OBJECT (demux, "segment configured %" GST_SEGMENT_FORMAT, |
| &seeksegment); |
| |
| if (flush || seeksegment.position != demux->segment.position) { |
| gboolean ret; |
| guint64 new_offset = -1; |
| GstEvent *e; |
| |
| if (!demux->metadata_resolved || demux->update_metadata) { |
| if (gst_mxf_demux_resolve_references (demux) != GST_FLOW_OK || |
| gst_mxf_demux_update_tracks (demux) != GST_FLOW_OK) { |
| goto unresolved_metadata; |
| } |
| } |
| |
| /* Do the actual seeking */ |
| for (i = 0; i < demux->src->len; i++) { |
| GstMXFDemuxPad *p = g_ptr_array_index (demux->src, i); |
| gint64 position; |
| guint64 off; |
| |
| /* Reset EOS flag on all pads */ |
| p->eos = FALSE; |
| gst_mxf_demux_pad_set_position (demux, p, start); |
| |
| position = p->current_essence_track_position; |
| off = gst_mxf_demux_find_essence_element (demux, p->current_essence_track, |
| &position, keyframe); |
| new_offset = MIN (off, new_offset); |
| p->discont = TRUE; |
| } |
| |
| if (new_offset == -1) |
| goto no_new_offset; |
| |
| new_offset += demux->run_in; |
| |
| GST_DEBUG_OBJECT (demux, "generating an upstream seek at position %" |
| G_GUINT64_FORMAT, new_offset); |
| e = gst_event_new_seek (seeksegment.rate, GST_FORMAT_BYTES, |
| seeksegment.flags | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, |
| new_offset, GST_SEEK_TYPE_NONE, 0); |
| gst_event_set_seqnum (e, seqnum); |
| ret = gst_pad_push_event (demux->sinkpad, e); |
| |
| if (G_UNLIKELY (!ret)) { |
| goto seek_failed; |
| } |
| } |
| |
| /* Tell all the stream a new segment is needed */ |
| for (i = 0; i < demux->src->len; i++) { |
| GstMXFDemuxPad *p = g_ptr_array_index (demux->src, i); |
| p->need_segment = TRUE; |
| } |
| |
| for (i = 0; i < demux->essence_tracks->len; i++) { |
| GstMXFDemuxEssenceTrack *t = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, i); |
| t->position = -1; |
| } |
| |
| /* Ok seek succeeded, take the newly configured segment */ |
| memcpy (&demux->segment, &seeksegment, sizeof (GstSegment)); |
| |
| return TRUE; |
| |
| /* ERRORS */ |
| wrong_format: |
| { |
| GST_WARNING_OBJECT (demux, "seeking only supported in TIME format"); |
| return gst_pad_push_event (demux->sinkpad, gst_event_ref (event)); |
| } |
| wrong_rate: |
| { |
| GST_WARNING_OBJECT (demux, "only rates > 0.0 are allowed"); |
| return FALSE; |
| } |
| unresolved_metadata: |
| { |
| GST_WARNING_OBJECT (demux, "metadata can't be resolved"); |
| return gst_pad_push_event (demux->sinkpad, gst_event_ref (event)); |
| } |
| seek_failed: |
| { |
| GST_WARNING_OBJECT (demux, "upstream seek failed"); |
| return gst_pad_push_event (demux->sinkpad, gst_event_ref (event)); |
| } |
| no_new_offset: |
| { |
| GST_WARNING_OBJECT (demux, "can't find new offset"); |
| return gst_pad_push_event (demux->sinkpad, gst_event_ref (event)); |
| } |
| } |
| |
| static void |
| collect_index_table_segments (GstMXFDemux * demux) |
| { |
| GList *l; |
| guint i; |
| guint64 old_offset = demux->offset; |
| GstMXFDemuxPartition *old_partition = demux->current_partition; |
| |
| if (!demux->random_index_pack) |
| return; |
| |
| for (i = 0; i < demux->random_index_pack->len; i++) { |
| MXFRandomIndexPackEntry *e = |
| &g_array_index (demux->random_index_pack, MXFRandomIndexPackEntry, i); |
| |
| if (e->offset < demux->run_in) { |
| GST_ERROR_OBJECT (demux, "Invalid random index pack entry"); |
| return; |
| } |
| |
| demux->offset = e->offset; |
| read_partition_header (demux); |
| } |
| |
| demux->offset = old_offset; |
| demux->current_partition = old_partition; |
| |
| for (l = demux->pending_index_table_segments; l; l = l->next) { |
| MXFIndexTableSegment *segment = l->data; |
| GstMXFDemuxIndexTable *t = NULL; |
| GList *k; |
| guint64 start, end; |
| |
| for (k = demux->index_tables; k; k = k->next) { |
| GstMXFDemuxIndexTable *tmp = k->data; |
| |
| if (tmp->body_sid == segment->body_sid |
| && tmp->index_sid == segment->index_sid) { |
| t = tmp; |
| break; |
| } |
| } |
| |
| if (!t) { |
| t = g_new0 (GstMXFDemuxIndexTable, 1); |
| t->body_sid = segment->body_sid; |
| t->index_sid = segment->index_sid; |
| t->offsets = g_array_new (FALSE, TRUE, sizeof (GstMXFDemuxIndex)); |
| demux->index_tables = g_list_prepend (demux->index_tables, t); |
| } |
| |
| start = segment->index_start_position; |
| end = start + segment->index_duration; |
| |
| if (t->offsets->len < end) |
| g_array_set_size (t->offsets, end); |
| |
| for (i = 0; i < segment->n_index_entries; i++) { |
| GstMXFDemuxIndex *index = |
| &g_array_index (t->offsets, GstMXFDemuxIndex, start + i); |
| guint64 offset = segment->index_entries[i].stream_offset; |
| GList *m; |
| GstMXFDemuxPartition *offset_partition = NULL, *next_partition = NULL; |
| |
| for (m = demux->partitions; m; m = m->next) { |
| GstMXFDemuxPartition *partition = m->data; |
| |
| if (!next_partition && offset_partition) |
| next_partition = partition; |
| |
| if (partition->partition.body_sid != t->body_sid) |
| continue; |
| if (partition->partition.body_offset > offset) |
| break; |
| |
| offset_partition = partition; |
| next_partition = NULL; |
| } |
| |
| if (offset_partition && offset >= offset_partition->partition.body_offset |
| && (offset - offset_partition->partition.body_offset)) { |
| offset = |
| offset_partition->partition.this_partition + |
| offset_partition->essence_container_offset + (offset - |
| offset_partition->partition.body_offset); |
| |
| if (next_partition |
| && offset >= next_partition->partition.this_partition) { |
| GST_ERROR_OBJECT (demux, |
| "Invalid index table segment going into next unrelated partition"); |
| } else { |
| index->offset = offset; |
| index->keyframe = ! !(segment->index_entries[i].flags & 0x80) |
| || (segment->index_entries[i].key_frame_offset == 0); |
| } |
| } |
| } |
| } |
| |
| for (l = demux->pending_index_table_segments; l; l = l->next) { |
| MXFIndexTableSegment *s = l->data; |
| mxf_index_table_segment_reset (s); |
| g_free (s); |
| } |
| g_list_free (demux->pending_index_table_segments); |
| demux->pending_index_table_segments = NULL; |
| } |
| |
| static gboolean |
| gst_mxf_demux_seek_pull (GstMXFDemux * demux, GstEvent * event) |
| { |
| GstClockTime keyunit_ts; |
| GstFormat format; |
| GstSeekFlags flags; |
| GstSeekType start_type, stop_type; |
| gint64 start, stop; |
| gdouble rate; |
| gboolean update, flush, keyframe; |
| GstSegment seeksegment; |
| guint i; |
| gboolean ret = TRUE; |
| guint32 seqnum; |
| |
| gst_event_parse_seek (event, &rate, &format, &flags, |
| &start_type, &start, &stop_type, &stop); |
| seqnum = gst_event_get_seqnum (event); |
| |
| if (format != GST_FORMAT_TIME) |
| goto wrong_format; |
| |
| if (rate <= 0.0) |
| goto wrong_rate; |
| |
| flush = ! !(flags & GST_SEEK_FLAG_FLUSH); |
| keyframe = ! !(flags & GST_SEEK_FLAG_KEY_UNIT); |
| |
| keyunit_ts = start; |
| |
| if (!demux->index_table_segments_collected) { |
| collect_index_table_segments (demux); |
| demux->index_table_segments_collected = TRUE; |
| } |
| |
| if (flush) { |
| GstEvent *e; |
| |
| /* Flush start up and downstream to make sure data flow and loops are |
| idle */ |
| e = gst_event_new_flush_start (); |
| gst_event_set_seqnum (e, seqnum); |
| gst_mxf_demux_push_src_event (demux, gst_event_ref (e)); |
| gst_pad_push_event (demux->sinkpad, e); |
| } else { |
| /* Pause the pulling task */ |
| gst_pad_pause_task (demux->sinkpad); |
| } |
| |
| /* Take the stream lock */ |
| GST_PAD_STREAM_LOCK (demux->sinkpad); |
| |
| if (flush) { |
| GstEvent *e; |
| |
| /* Stop flushing upstream we need to pull */ |
| e = gst_event_new_flush_stop (TRUE); |
| gst_event_set_seqnum (e, seqnum); |
| gst_pad_push_event (demux->sinkpad, e); |
| } |
| |
| /* Work on a copy until we are sure the seek succeeded. */ |
| memcpy (&seeksegment, &demux->segment, sizeof (GstSegment)); |
| |
| GST_DEBUG_OBJECT (demux, "segment before configure %" GST_SEGMENT_FORMAT, |
| &demux->segment); |
| |
| /* Apply the seek to our segment */ |
| gst_segment_do_seek (&seeksegment, rate, format, flags, |
| start_type, start, stop_type, stop, &update); |
| |
| GST_DEBUG_OBJECT (demux, "segment configured %" GST_SEGMENT_FORMAT, |
| &seeksegment); |
| |
| if (flush || seeksegment.position != demux->segment.position) { |
| guint64 new_offset = -1; |
| |
| if (!demux->metadata_resolved || demux->update_metadata) { |
| if (gst_mxf_demux_resolve_references (demux) != GST_FLOW_OK || |
| gst_mxf_demux_update_tracks (demux) != GST_FLOW_OK) { |
| goto unresolved_metadata; |
| } |
| } |
| |
| /* Do the actual seeking */ |
| for (i = 0; i < demux->src->len; i++) { |
| MXFMetadataTrackType track_type = MXF_METADATA_TRACK_UNKNOWN; |
| GstMXFDemuxPad *p = g_ptr_array_index (demux->src, i); |
| gint64 position; |
| guint64 off; |
| |
| if (p->material_track != NULL) |
| track_type = p->material_track->parent.type; |
| |
| /* Reset EOS flag on all pads */ |
| p->eos = FALSE; |
| gst_mxf_demux_pad_set_position (demux, p, start); |
| |
| /* we always want to send data starting with a key unit */ |
| position = p->current_essence_track_position; |
| off = |
| gst_mxf_demux_find_essence_element (demux, p->current_essence_track, |
| &position, TRUE); |
| if (off == -1) { |
| GST_DEBUG_OBJECT (demux, "Unable to find offset for pad %s", |
| GST_PAD_NAME (p)); |
| p->current_essence_track_position = p->current_essence_track->duration; |
| } else { |
| new_offset = MIN (off, new_offset); |
| if (position != p->current_essence_track_position) { |
| p->position -= |
| gst_util_uint64_scale (p->current_essence_track_position - |
| position, |
| GST_SECOND * p->current_essence_track->source_track->edit_rate.d, |
| p->current_essence_track->source_track->edit_rate.n); |
| } |
| p->current_essence_track_position = position; |
| |
| /* FIXME: what about DV + MPEG-TS container essence tracks? */ |
| if (track_type == MXF_METADATA_TRACK_PICTURE_ESSENCE) { |
| keyunit_ts = MIN (p->position, keyunit_ts); |
| } |
| } |
| p->discont = TRUE; |
| } |
| gst_flow_combiner_reset (demux->flowcombiner); |
| if (new_offset == -1) { |
| GST_WARNING_OBJECT (demux, "No new offset found"); |
| ret = FALSE; |
| } else { |
| demux->offset = new_offset + demux->run_in; |
| } |
| gst_mxf_demux_set_partition_for_offset (demux, demux->offset); |
| } |
| |
| if (G_UNLIKELY (demux->close_seg_event)) { |
| gst_event_unref (demux->close_seg_event); |
| demux->close_seg_event = NULL; |
| } |
| |
| if (flush) { |
| GstEvent *e; |
| |
| /* Stop flushing, the sinks are at time 0 now */ |
| e = gst_event_new_flush_stop (TRUE); |
| gst_event_set_seqnum (e, seqnum); |
| gst_mxf_demux_push_src_event (demux, e); |
| } else { |
| GST_DEBUG_OBJECT (demux, "closing running segment %" GST_SEGMENT_FORMAT, |
| &demux->segment); |
| |
| /* Close the current segment for a linear playback */ |
| demux->close_seg_event = gst_event_new_segment (&demux->segment); |
| gst_event_set_seqnum (demux->close_seg_event, demux->seqnum); |
| } |
| |
| if (keyframe && keyunit_ts != start) { |
| GST_INFO_OBJECT (demux, "key unit seek, adjusting segment start to " |
| "%" GST_TIME_FORMAT, GST_TIME_ARGS (keyunit_ts)); |
| gst_segment_do_seek (&seeksegment, rate, format, flags, |
| start_type, keyunit_ts, stop_type, stop, &update); |
| } |
| |
| /* Ok seek succeeded, take the newly configured segment */ |
| memcpy (&demux->segment, &seeksegment, sizeof (GstSegment)); |
| |
| /* Notify about the start of a new segment */ |
| if (demux->segment.flags & GST_SEEK_FLAG_SEGMENT) { |
| GstMessage *m; |
| |
| m = gst_message_new_segment_start (GST_OBJECT (demux), |
| demux->segment.format, demux->segment.position); |
| gst_message_set_seqnum (m, seqnum); |
| gst_element_post_message (GST_ELEMENT (demux), m); |
| } |
| |
| /* Tell all the stream a new segment is needed */ |
| for (i = 0; i < demux->src->len; i++) { |
| GstMXFDemuxPad *p = g_ptr_array_index (demux->src, i); |
| p->need_segment = TRUE; |
| } |
| |
| for (i = 0; i < demux->essence_tracks->len; i++) { |
| GstMXFDemuxEssenceTrack *t = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, i); |
| t->position = -1; |
| } |
| |
| demux->seqnum = seqnum; |
| |
| gst_pad_start_task (demux->sinkpad, |
| (GstTaskFunction) gst_mxf_demux_loop, demux->sinkpad, NULL); |
| |
| GST_PAD_STREAM_UNLOCK (demux->sinkpad); |
| |
| return ret; |
| |
| /* ERRORS */ |
| wrong_format: |
| { |
| GST_WARNING_OBJECT (demux, "seeking only supported in TIME format"); |
| return FALSE; |
| } |
| wrong_rate: |
| { |
| GST_WARNING_OBJECT (demux, "only rates > 0.0 are allowed"); |
| return FALSE; |
| } |
| unresolved_metadata: |
| { |
| gst_pad_start_task (demux->sinkpad, |
| (GstTaskFunction) gst_mxf_demux_loop, demux->sinkpad, NULL); |
| GST_PAD_STREAM_UNLOCK (demux->sinkpad); |
| GST_WARNING_OBJECT (demux, "metadata can't be resolved"); |
| return FALSE; |
| } |
| } |
| |
| static gboolean |
| gst_mxf_demux_src_event (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| GstMXFDemux *demux = GST_MXF_DEMUX (parent); |
| gboolean ret; |
| |
| GST_DEBUG_OBJECT (pad, "handling event %s", GST_EVENT_TYPE_NAME (event)); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_SEEK: |
| if (demux->random_access) |
| ret = gst_mxf_demux_seek_pull (demux, event); |
| else |
| ret = gst_mxf_demux_seek_push (demux, event); |
| gst_event_unref (event); |
| break; |
| default: |
| ret = gst_pad_push_event (demux->sinkpad, event); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_mxf_demux_src_query (GstPad * pad, GstObject * parent, GstQuery * query) |
| { |
| GstMXFDemux *demux = GST_MXF_DEMUX (parent); |
| gboolean ret = FALSE; |
| GstMXFDemuxPad *mxfpad = GST_MXF_DEMUX_PAD (pad); |
| |
| GST_DEBUG_OBJECT (pad, "handling query %s", |
| gst_query_type_get_name (GST_QUERY_TYPE (query))); |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_POSITION: |
| { |
| GstFormat format; |
| gint64 pos; |
| |
| gst_query_parse_position (query, &format, NULL); |
| if (format != GST_FORMAT_TIME && format != GST_FORMAT_DEFAULT) |
| goto error; |
| |
| pos = mxfpad->position; |
| |
| g_rw_lock_reader_lock (&demux->metadata_lock); |
| if (format == GST_FORMAT_DEFAULT && pos != GST_CLOCK_TIME_NONE) { |
| if (!mxfpad->material_track || mxfpad->material_track->edit_rate.n == 0 |
| || mxfpad->material_track->edit_rate.d == 0) { |
| g_rw_lock_reader_unlock (&demux->metadata_lock); |
| goto error; |
| } |
| |
| pos = |
| gst_util_uint64_scale (pos, |
| mxfpad->material_track->edit_rate.n, |
| mxfpad->material_track->edit_rate.d * GST_SECOND); |
| } |
| g_rw_lock_reader_unlock (&demux->metadata_lock); |
| |
| GST_DEBUG_OBJECT (pad, |
| "Returning position %" G_GINT64_FORMAT " in format %s", pos, |
| gst_format_get_name (format)); |
| |
| gst_query_set_position (query, format, pos); |
| ret = TRUE; |
| |
| break; |
| } |
| case GST_QUERY_DURATION:{ |
| gint64 duration; |
| GstFormat format; |
| |
| gst_query_parse_duration (query, &format, NULL); |
| if (format != GST_FORMAT_TIME && format != GST_FORMAT_DEFAULT) |
| goto error; |
| |
| g_rw_lock_reader_lock (&demux->metadata_lock); |
| if (!mxfpad->material_track || !mxfpad->material_track->parent.sequence) { |
| g_rw_lock_reader_unlock (&demux->metadata_lock); |
| goto error; |
| } |
| |
| duration = mxfpad->material_track->parent.sequence->duration; |
| if (duration <= -1) |
| duration = -1; |
| |
| if (duration != -1 && format == GST_FORMAT_TIME) { |
| if (mxfpad->material_track->edit_rate.n == 0 || |
| mxfpad->material_track->edit_rate.d == 0) { |
| g_rw_lock_reader_unlock (&demux->metadata_lock); |
| goto error; |
| } |
| |
| duration = |
| gst_util_uint64_scale (duration, |
| GST_SECOND * mxfpad->material_track->edit_rate.d, |
| mxfpad->material_track->edit_rate.n); |
| } |
| g_rw_lock_reader_unlock (&demux->metadata_lock); |
| |
| GST_DEBUG_OBJECT (pad, |
| "Returning duration %" G_GINT64_FORMAT " in format %s", duration, |
| gst_format_get_name (format)); |
| |
| gst_query_set_duration (query, format, duration); |
| ret = TRUE; |
| break; |
| } |
| case GST_QUERY_SEEKING:{ |
| GstFormat fmt; |
| |
| ret = TRUE; |
| gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); |
| if (fmt != GST_FORMAT_TIME) { |
| gst_query_set_seeking (query, fmt, FALSE, -1, -1); |
| goto done; |
| } |
| |
| if (demux->random_access) { |
| gst_query_set_seeking (query, GST_FORMAT_TIME, TRUE, 0, -1); |
| } else { |
| GstQuery *peerquery = gst_query_new_seeking (GST_FORMAT_BYTES); |
| gboolean seekable; |
| |
| seekable = gst_pad_peer_query (demux->sinkpad, peerquery); |
| if (seekable) |
| gst_query_parse_seeking (peerquery, NULL, &seekable, NULL, NULL); |
| if (seekable) |
| gst_query_set_seeking (query, GST_FORMAT_TIME, TRUE, 0, -1); |
| else |
| gst_query_set_seeking (query, GST_FORMAT_TIME, FALSE, -1, -1); |
| } |
| |
| break; |
| } |
| case GST_QUERY_SEGMENT:{ |
| GstFormat format; |
| gint64 start, stop; |
| |
| format = demux->segment.format; |
| |
| start = |
| gst_segment_to_stream_time (&demux->segment, format, |
| demux->segment.start); |
| if ((stop = demux->segment.stop) == -1) |
| stop = demux->segment.duration; |
| else |
| stop = gst_segment_to_stream_time (&demux->segment, format, stop); |
| |
| gst_query_set_segment (query, demux->segment.rate, format, start, stop); |
| ret = TRUE; |
| break; |
| } |
| default: |
| ret = gst_pad_query_default (pad, parent, query); |
| break; |
| } |
| |
| done: |
| return ret; |
| |
| /* ERRORS */ |
| error: |
| { |
| GST_DEBUG_OBJECT (pad, "query failed"); |
| goto done; |
| } |
| } |
| |
| static gboolean |
| gst_mxf_demux_sink_activate (GstPad * sinkpad, GstObject * parent) |
| { |
| GstQuery *query; |
| GstPadMode mode = GST_PAD_MODE_PUSH; |
| |
| query = gst_query_new_scheduling (); |
| |
| if (gst_pad_peer_query (sinkpad, query)) { |
| if (gst_query_has_scheduling_mode_with_flags (query, |
| GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE)) { |
| GstSchedulingFlags flags; |
| gst_query_parse_scheduling (query, &flags, NULL, NULL, NULL); |
| if (!(flags & GST_SCHEDULING_FLAG_SEQUENTIAL)) |
| mode = GST_PAD_MODE_PULL; |
| } |
| } |
| gst_query_unref (query); |
| |
| return gst_pad_activate_mode (sinkpad, mode, TRUE); |
| } |
| |
| static gboolean |
| gst_mxf_demux_sink_activate_mode (GstPad * sinkpad, GstObject * parent, |
| GstPadMode mode, gboolean active) |
| { |
| GstMXFDemux *demux; |
| |
| demux = GST_MXF_DEMUX (parent); |
| |
| if (mode == GST_PAD_MODE_PUSH) { |
| demux->random_access = FALSE; |
| } else { |
| if (active) { |
| demux->random_access = TRUE; |
| return gst_pad_start_task (sinkpad, (GstTaskFunction) gst_mxf_demux_loop, |
| sinkpad, NULL); |
| } else { |
| demux->random_access = FALSE; |
| return gst_pad_stop_task (sinkpad); |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_mxf_demux_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| GstMXFDemux *demux; |
| gboolean ret = FALSE; |
| |
| demux = GST_MXF_DEMUX (parent); |
| |
| GST_DEBUG_OBJECT (pad, "handling event %s", GST_EVENT_TYPE_NAME (event)); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_FLUSH_START: |
| demux->flushing = TRUE; |
| ret = gst_pad_event_default (pad, parent, event); |
| break; |
| case GST_EVENT_FLUSH_STOP: |
| GST_DEBUG_OBJECT (demux, "flushing queued data in the MXF demuxer"); |
| |
| gst_adapter_clear (demux->adapter); |
| demux->flushing = FALSE; |
| demux->offset = 0; |
| ret = gst_pad_event_default (pad, parent, event); |
| break; |
| case GST_EVENT_EOS:{ |
| GstMXFDemuxPad *p = NULL; |
| guint i; |
| |
| for (i = 0; i < demux->essence_tracks->len; i++) { |
| GstMXFDemuxEssenceTrack *t = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, i); |
| |
| if (t->position > 0) |
| t->duration = t->position; |
| } |
| |
| for (i = 0; i < demux->src->len; i++) { |
| GstMXFDemuxPad *p = g_ptr_array_index (demux->src, i); |
| |
| if (!p->eos |
| && p->current_essence_track_position >= |
| p->current_essence_track->duration) { |
| p->eos = TRUE; |
| gst_pad_push_event (GST_PAD_CAST (p), gst_event_new_eos ()); |
| } |
| } |
| |
| while ((p = gst_mxf_demux_get_earliest_pad (demux))) { |
| guint64 offset; |
| gint64 position; |
| |
| position = p->current_essence_track_position; |
| |
| offset = |
| gst_mxf_demux_find_essence_element (demux, p->current_essence_track, |
| &position, FALSE); |
| if (offset == -1) { |
| GST_ERROR_OBJECT (demux, "Failed to find offset for essence track"); |
| p->eos = TRUE; |
| gst_pad_push_event (GST_PAD_CAST (p), gst_event_new_eos ()); |
| continue; |
| } |
| |
| if (gst_pad_push_event (demux->sinkpad, |
| gst_event_new_seek (demux->segment.rate, GST_FORMAT_BYTES, |
| demux->segment.flags | GST_SEEK_FLAG_ACCURATE, |
| GST_SEEK_TYPE_SET, offset + demux->run_in, |
| GST_SEEK_TYPE_NONE, 0))) { |
| |
| for (i = 0; i < demux->essence_tracks->len; i++) { |
| GstMXFDemuxEssenceTrack *etrack = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, |
| i); |
| etrack->position = -1; |
| } |
| ret = TRUE; |
| goto out; |
| } else { |
| GST_WARNING_OBJECT (demux, |
| "Seek to remaining part of the file failed"); |
| p->eos = TRUE; |
| gst_pad_push_event (GST_PAD_CAST (p), gst_event_new_eos ()); |
| continue; |
| } |
| } |
| |
| /* and one more time for good measure apparently? */ |
| gst_pad_event_default (pad, parent, event); |
| ret = (demux->src->len > 0); |
| break; |
| } |
| case GST_EVENT_SEGMENT:{ |
| guint i; |
| |
| for (i = 0; i < demux->essence_tracks->len; i++) { |
| GstMXFDemuxEssenceTrack *t = |
| &g_array_index (demux->essence_tracks, GstMXFDemuxEssenceTrack, |
| i); |
| t->position = -1; |
| } |
| demux->current_partition = NULL; |
| demux->seqnum = gst_event_get_seqnum (event); |
| gst_event_unref (event); |
| ret = TRUE; |
| break; |
| } |
| default: |
| ret = gst_pad_event_default (pad, parent, event); |
| break; |
| } |
| |
| out: |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_mxf_demux_query (GstElement * element, GstQuery * query) |
| { |
| GstMXFDemux *demux = GST_MXF_DEMUX (element); |
| gboolean ret = FALSE; |
| |
| GST_DEBUG_OBJECT (demux, "handling query %s", |
| gst_query_type_get_name (GST_QUERY_TYPE (query))); |
| |
| switch (GST_QUERY_TYPE (query)) { |
| case GST_QUERY_POSITION: |
| { |
| GstFormat format; |
| gint64 pos; |
| |
| gst_query_parse_position (query, &format, NULL); |
| if (format != GST_FORMAT_TIME) |
| goto error; |
| |
| pos = demux->segment.position; |
| |
| GST_DEBUG_OBJECT (demux, |
| "Returning position %" G_GINT64_FORMAT " in format %s", pos, |
| gst_format_get_name (format)); |
| |
| gst_query_set_position (query, format, pos); |
| ret = TRUE; |
| |
| break; |
| } |
| case GST_QUERY_DURATION:{ |
| gint64 duration = -1; |
| GstFormat format; |
| guint i; |
| |
| gst_query_parse_duration (query, &format, NULL); |
| if (format != GST_FORMAT_TIME) |
| goto error; |
| |
| if (demux->src->len == 0) |
| goto done; |
| |
| g_rw_lock_reader_lock (&demux->metadata_lock); |
| for (i = 0; i < demux->src->len; i++) { |
| GstMXFDemuxPad *pad = g_ptr_array_index (demux->src, i); |
| gint64 pdur = -1; |
| |
| if (!pad->material_track || !pad->material_track->parent.sequence) |
| continue; |
| |
| pdur = pad->material_track->parent.sequence->duration; |
| if (pad->material_track->edit_rate.n == 0 || |
| pad->material_track->edit_rate.d == 0 || pdur <= -1) |
| continue; |
| |
| pdur = |
| gst_util_uint64_scale (pdur, |
| GST_SECOND * pad->material_track->edit_rate.d, |
| pad->material_track->edit_rate.n); |
| duration = MAX (duration, pdur); |
| } |
| g_rw_lock_reader_unlock (&demux->metadata_lock); |
| |
| if (duration == -1) { |
| GST_DEBUG_OBJECT (demux, "No duration known (yet)"); |
| goto done; |
| } |
| |
| GST_DEBUG_OBJECT (demux, |
| "Returning duration %" G_GINT64_FORMAT " in format %s", duration, |
| gst_format_get_name (format)); |
| |
| gst_query_set_duration (query, format, duration); |
| ret = TRUE; |
| break; |
| } |
| case GST_QUERY_SEEKING:{ |
| GstFormat fmt; |
| |
| ret = TRUE; |
| gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL); |
| if (fmt != GST_FORMAT_TIME) { |
| gst_query_set_seeking (query, fmt, FALSE, -1, -1); |
| goto done; |
| } |
| |
| if (demux->random_access) { |
| gst_query_set_seeking (query, GST_FORMAT_TIME, TRUE, 0, -1); |
| } else { |
| GstQuery *peerquery = gst_query_new_seeking (GST_FORMAT_BYTES); |
| gboolean seekable; |
| |
| seekable = gst_pad_peer_query (demux->sinkpad, peerquery); |
| if (seekable) |
| gst_query_parse_seeking (peerquery, NULL, &seekable, NULL, NULL); |
| if (seekable) |
| gst_query_set_seeking (query, GST_FORMAT_TIME, TRUE, 0, -1); |
| else |
| gst_query_set_seeking (query, GST_FORMAT_TIME, FALSE, -1, -1); |
| } |
| |
| break; |
| } |
| case GST_QUERY_SEGMENT:{ |
| GstFormat format; |
| gint64 start, stop; |
| |
| format = demux->segment.format; |
| |
| start = |
| gst_segment_to_stream_time (&demux->segment, format, |
| demux->segment.start); |
| if ((stop = demux->segment.stop) == -1) |
| stop = demux->segment.duration; |
| else |
| stop = gst_segment_to_stream_time (&demux->segment, format, stop); |
| |
| gst_query_set_segment (query, demux->segment.rate, format, start, stop); |
| ret = TRUE; |
| break; |
| } |
| default: |
| /* else forward upstream */ |
| ret = gst_pad_peer_query (demux->sinkpad, query); |
| break; |
| } |
| |
| done: |
| return ret; |
| |
| /* ERRORS */ |
| error: |
| { |
| GST_DEBUG_OBJECT (demux, "query failed"); |
| goto done; |
| } |
| } |
| |
| static GstStateChangeReturn |
| gst_mxf_demux_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstMXFDemux *demux = GST_MXF_DEMUX (element); |
| GstStateChangeReturn ret; |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_READY_TO_PAUSED: |
| demux->seqnum = gst_util_seqnum_next (); |
| break; |
| default: |
| break; |
| } |
| |
| ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| if (ret == GST_STATE_CHANGE_FAILURE) |
| return ret; |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| gst_mxf_demux_reset (demux); |
| break; |
| default: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static void |
| gst_mxf_demux_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstMXFDemux *demux = GST_MXF_DEMUX (object); |
| |
| switch (prop_id) { |
| case PROP_PACKAGE: |
| g_free (demux->requested_package_string); |
| demux->requested_package_string = g_value_dup_string (value); |
| break; |
| case PROP_MAX_DRIFT: |
| demux->max_drift = g_value_get_uint64 (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_mxf_demux_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstMXFDemux *demux = GST_MXF_DEMUX (object); |
| |
| switch (prop_id) { |
| case PROP_PACKAGE: |
| g_value_set_string (value, demux->current_package_string); |
| break; |
| case PROP_MAX_DRIFT: |
| g_value_set_uint64 (value, demux->max_drift); |
| break; |
| case PROP_STRUCTURE:{ |
| GstStructure *s; |
| |
| g_rw_lock_reader_lock (&demux->metadata_lock); |
| if (demux->preface) |
| s = mxf_metadata_base_to_structure (MXF_METADATA_BASE (demux->preface)); |
| else |
| s = NULL; |
| |
| gst_value_set_structure (value, s); |
| |
| if (s) |
| gst_structure_free (s); |
| |
| g_rw_lock_reader_unlock (&demux->metadata_lock); |
| break; |
| } |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_mxf_demux_finalize (GObject * object) |
| { |
| GstMXFDemux *demux = GST_MXF_DEMUX (object); |
| |
| gst_mxf_demux_reset (demux); |
| |
| if (demux->adapter) { |
| g_object_unref (demux->adapter); |
| demux->adapter = NULL; |
| } |
| |
| if (demux->flowcombiner) { |
| gst_flow_combiner_free (demux->flowcombiner); |
| demux->flowcombiner = NULL; |
| } |
| |
| if (demux->close_seg_event) { |
| gst_event_unref (demux->close_seg_event); |
| demux->close_seg_event = NULL; |
| } |
| |
| g_free (demux->current_package_string); |
| demux->current_package_string = NULL; |
| g_free (demux->requested_package_string); |
| demux->requested_package_string = NULL; |
| |
| g_ptr_array_free (demux->src, TRUE); |
| demux->src = NULL; |
| g_array_free (demux->essence_tracks, TRUE); |
| demux->essence_tracks = NULL; |
| |
| g_hash_table_destroy (demux->metadata); |
| |
| g_rw_lock_clear (&demux->metadata_lock); |
| |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static void |
| gst_mxf_demux_class_init (GstMXFDemuxClass * klass) |
| { |
| GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| |
| GST_DEBUG_CATEGORY_INIT (mxfdemux_debug, "mxfdemux", 0, "MXF demuxer"); |
| |
| parent_class = g_type_class_peek_parent (klass); |
| |
| gobject_class->finalize = gst_mxf_demux_finalize; |
| gobject_class->set_property = gst_mxf_demux_set_property; |
| gobject_class->get_property = gst_mxf_demux_get_property; |
| |
| g_object_class_install_property (gobject_class, PROP_PACKAGE, |
| g_param_spec_string ("package", "Package", |
| "Material or Source package to use for playback", NULL, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_MAX_DRIFT, |
| g_param_spec_uint64 ("max-drift", "Maximum drift", |
| "Maximum number of nanoseconds by which tracks can differ", |
| 100 * GST_MSECOND, G_MAXUINT64, 500 * GST_MSECOND, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| g_object_class_install_property (gobject_class, PROP_STRUCTURE, |
| g_param_spec_boxed ("structure", "Structure", |
| "Structural metadata of the MXF file", |
| GST_TYPE_STRUCTURE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); |
| |
| gstelement_class->change_state = |
| GST_DEBUG_FUNCPTR (gst_mxf_demux_change_state); |
| gstelement_class->query = GST_DEBUG_FUNCPTR (gst_mxf_demux_query); |
| |
| gst_element_class_add_pad_template (gstelement_class, |
| gst_static_pad_template_get (&mxf_sink_template)); |
| gst_element_class_add_pad_template (gstelement_class, |
| gst_static_pad_template_get (&mxf_src_template)); |
| gst_element_class_set_static_metadata (gstelement_class, "MXF Demuxer", |
| "Codec/Demuxer", |
| "Demux MXF files", "Sebastian Dröge <sebastian.droege@collabora.co.uk>"); |
| } |
| |
| static void |
| gst_mxf_demux_init (GstMXFDemux * demux) |
| { |
| demux->sinkpad = |
| gst_pad_new_from_static_template (&mxf_sink_template, "sink"); |
| |
| gst_pad_set_event_function (demux->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_mxf_demux_sink_event)); |
| gst_pad_set_chain_function (demux->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_mxf_demux_chain)); |
| gst_pad_set_activate_function (demux->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_mxf_demux_sink_activate)); |
| gst_pad_set_activatemode_function (demux->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_mxf_demux_sink_activate_mode)); |
| |
| gst_element_add_pad (GST_ELEMENT (demux), demux->sinkpad); |
| |
| demux->max_drift = 500 * GST_MSECOND; |
| |
| demux->adapter = gst_adapter_new (); |
| demux->flowcombiner = gst_flow_combiner_new (); |
| g_rw_lock_init (&demux->metadata_lock); |
| |
| demux->src = g_ptr_array_new (); |
| demux->essence_tracks = |
| g_array_new (FALSE, FALSE, sizeof (GstMXFDemuxEssenceTrack)); |
| |
| gst_segment_init (&demux->segment, GST_FORMAT_TIME); |
| |
| gst_mxf_demux_reset (demux); |
| } |