mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-30 12:10:37 +00:00
274551d450
Previously they were stored inline inside a GArray, but as references to the tracks were stored in various other places although the array could still be updated (and reallocated!), this could lead to dangling references in various places. Instead now store them in a GPtrArray in their own allocation so each track's memory position stays fixed. Fixes ZDI-CAN-22299 Fixes https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/3055 Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5635>
6062 lines
194 KiB
C
6062 lines
194 KiB
C
/* 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
|
|
* @title: mxfdemux
|
|
*
|
|
* mxfdemux demuxes an MXF file into the different contained streams.
|
|
*
|
|
* ## Example launch line
|
|
* |[
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
/* TODO:
|
|
* - Handle timecode tracks correctly (where is this documented?)
|
|
* - Handle drop-frame field of timecode tracks
|
|
* - Handle Generic container system items
|
|
* - 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 "gstmxfelements.h"
|
|
#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
|
|
|
|
/* Fill klv for the given offset, does not download the data */
|
|
static GstFlowReturn
|
|
gst_mxf_demux_peek_klv_packet (GstMXFDemux * demux, guint64 offset,
|
|
GstMXFKLV * klv);
|
|
|
|
/* Ensures the klv data is present. Pulls it if needed */
|
|
static GstFlowReturn
|
|
gst_mxf_demux_fill_klv (GstMXFDemux * demux, GstMXFKLV * klv);
|
|
|
|
/* Call when done with a klv. Will release the buffer (if any) and will update
|
|
* the demuxer offset position */
|
|
static void gst_mxf_demux_consume_klv (GstMXFDemux * demux, GstMXFKLV * klv);
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_demux_handle_index_table_segment (GstMXFDemux * demux, GstMXFKLV * klv);
|
|
|
|
static void collect_index_table_segments (GstMXFDemux * demux);
|
|
static gboolean find_entry_for_offset (GstMXFDemux * demux,
|
|
GstMXFDemuxEssenceTrack * etrack, guint64 offset,
|
|
GstMXFDemuxIndex * retentry);
|
|
|
|
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;
|
|
pad->current_material_track_position = 0;
|
|
}
|
|
|
|
#define DEFAULT_MAX_DRIFT 100 * GST_MSECOND
|
|
|
|
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);
|
|
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (mxfdemux, "mxfdemux", GST_RANK_PRIMARY,
|
|
GST_TYPE_MXF_DEMUX, mxf_element_init (plugin));
|
|
|
|
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_essence_track_free (GstMXFDemuxEssenceTrack * t)
|
|
{
|
|
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_free (t);
|
|
}
|
|
|
|
static void
|
|
gst_mxf_demux_reset_mxf_state (GstMXFDemux * demux)
|
|
{
|
|
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;
|
|
g_ptr_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_ptr_array_index (demux->essence_tracks, i);
|
|
|
|
track->source_package = NULL;
|
|
track->delta_id = -1;
|
|
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->state = GST_MXF_DEMUX_STATE_UNKNOWN;
|
|
|
|
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->segments, TRUE);
|
|
g_array_free (t->reverse_temporal_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)
|
|
{
|
|
if (a->partition.this_partition < b->partition.this_partition)
|
|
return -1;
|
|
else if (a->partition.this_partition > b->partition.this_partition)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/* Final checks and variable calculation for tracks and partition. This function
|
|
* can be called repeatedly without any side-effect.
|
|
*/
|
|
static void
|
|
gst_mxf_demux_partition_postcheck (GstMXFDemux * demux,
|
|
GstMXFDemuxPartition * partition)
|
|
{
|
|
guint i;
|
|
GstMXFDemuxPartition *old_partition = demux->current_partition;
|
|
|
|
/* If we already handled this partition or it doesn't contain any essence, skip */
|
|
if (partition->single_track || !partition->partition.body_sid)
|
|
return;
|
|
|
|
for (i = 0; i < demux->essence_tracks->len; i++) {
|
|
GstMXFDemuxEssenceTrack *cand =
|
|
g_ptr_array_index (demux->essence_tracks, i);
|
|
|
|
if (cand->body_sid != partition->partition.body_sid)
|
|
continue;
|
|
|
|
if (!cand->source_package->is_interleaved) {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Assigning single track %d (0x%08x) to partition at offset %"
|
|
G_GUINT64_FORMAT, cand->track_id, cand->track_number,
|
|
partition->partition.this_partition);
|
|
|
|
partition->single_track = cand;
|
|
|
|
if (partition->essence_container_offset != 0
|
|
&& cand->wrapping != MXF_ESSENCE_WRAPPING_FRAME_WRAPPING) {
|
|
GstMXFKLV essence_klv;
|
|
GstMXFDemuxIndex entry;
|
|
/* Update the essence container offset for the fact that the index
|
|
* stream offset is relative to the essence position and not to the
|
|
* KLV position */
|
|
if (gst_mxf_demux_peek_klv_packet (demux,
|
|
partition->partition.this_partition +
|
|
partition->essence_container_offset,
|
|
&essence_klv) == GST_FLOW_OK) {
|
|
partition->essence_container_offset += essence_klv.data_offset;
|
|
/* And keep a copy of the clip/custom klv for this partition */
|
|
partition->clip_klv = essence_klv;
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Non-frame wrapping, updated essence_container_offset to %"
|
|
G_GUINT64_FORMAT, partition->essence_container_offset);
|
|
|
|
/* And match it against index table, this will also update the track delta_id (if needed) */
|
|
demux->current_partition = partition;
|
|
find_entry_for_offset (demux, cand,
|
|
essence_klv.offset + essence_klv.data_offset, &entry);
|
|
demux->current_partition = old_partition;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_demux_handle_partition_pack (GstMXFDemux * demux, GstMXFKLV * klv)
|
|
{
|
|
MXFPartitionPack partition;
|
|
GList *l;
|
|
GstMXFDemuxPartition *p = NULL;
|
|
GstMapInfo map;
|
|
gboolean ret;
|
|
GstFlowReturn flowret;
|
|
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Handling partition pack of size %" G_GSIZE_FORMAT " at offset %"
|
|
G_GUINT64_FORMAT, klv->length, klv->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;
|
|
}
|
|
}
|
|
|
|
flowret = gst_mxf_demux_fill_klv (demux, klv);
|
|
if (flowret != GST_FLOW_OK)
|
|
return flowret;
|
|
|
|
gst_buffer_map (klv->data, &map, GST_MAP_READ);
|
|
ret = mxf_partition_pack_parse (&klv->key, &partition, map.data, map.size);
|
|
gst_buffer_unmap (klv->data, &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 (this %" G_GUINT64_FORMAT
|
|
" demux offset %" G_GUINT64_FORMAT " run_in:%" G_GUINT64_FORMAT ")",
|
|
partition.this_partition, demux->offset, demux->run_in);
|
|
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);
|
|
}
|
|
|
|
gst_mxf_demux_partition_postcheck (demux, p);
|
|
|
|
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:
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Current partition now %p (body_sid:%d index_sid:%d this_partition:%"
|
|
G_GUINT64_FORMAT ")", p, p->partition.body_sid, p->partition.index_sid,
|
|
p->partition.this_partition);
|
|
demux->current_partition = p;
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_demux_handle_primer_pack (GstMXFDemux * demux, GstMXFKLV * klv)
|
|
{
|
|
GstMapInfo map;
|
|
gboolean ret;
|
|
GstFlowReturn flowret;
|
|
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Handling primer pack of size %" G_GSIZE_FORMAT " at offset %"
|
|
G_GUINT64_FORMAT, klv->length, klv->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;
|
|
}
|
|
|
|
flowret = gst_mxf_demux_fill_klv (demux, klv);
|
|
if (flowret != GST_FLOW_OK)
|
|
return flowret;
|
|
|
|
gst_buffer_map (klv->data, &map, GST_MAP_READ);
|
|
ret = mxf_primer_pack_parse (&klv->key, &demux->current_partition->primer,
|
|
map.data, map.size);
|
|
gst_buffer_unmap (klv->data, &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;
|
|
guint i;
|
|
|
|
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);
|
|
|
|
/* Check for quirks */
|
|
for (i = 0; i < demux->preface->n_identifications; i++) {
|
|
MXFMetadataIdentification *identification =
|
|
demux->preface->identifications[i];
|
|
|
|
GST_DEBUG_OBJECT (demux, "product:'%s' company:'%s'",
|
|
identification->product_name, identification->company_name);
|
|
if (!g_strcmp0 (identification->product_name, "MXFTk Advanced") &&
|
|
!g_strcmp0 (identification->company_name, "OpenCube") &&
|
|
identification->product_version.major <= 2 &&
|
|
identification->product_version.minor <= 0) {
|
|
GST_WARNING_OBJECT (demux,
|
|
"Setting up quirk for misuse of temporal_order field");
|
|
demux->temporal_order_misuse = TRUE;
|
|
}
|
|
}
|
|
|
|
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;
|
|
MXFFraction common_rate = { 0, 0 };
|
|
|
|
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) {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Skipping track of type 0x%02x (id:%d number:0x%08x)",
|
|
track->parent.type, track->parent.track_id,
|
|
track->parent.track_number);
|
|
continue;
|
|
}
|
|
|
|
if (track->edit_rate.n <= 0 || track->edit_rate.d <= 0) {
|
|
GST_WARNING_OBJECT (demux, "Invalid edit rate");
|
|
continue;
|
|
}
|
|
|
|
if (package->is_interleaved) {
|
|
/*
|
|
* S377-1:2019 "9.4.2 The MXF timing model"
|
|
*
|
|
* The value of Edit Rate shall be identical for every timeline Essence
|
|
* Track of the Top-Level File Package.
|
|
*
|
|
* The value of Edit Rate of the timeline Essence Tracks of one
|
|
* Top-Level File Package need not match the Edit Rate of the Essence
|
|
* Tracks of the other Top-Level File Packages.
|
|
*
|
|
* S377-1:2019 "9.5.5 Top-Level File Packages"
|
|
*
|
|
*12. All Essence Tracks of a Top-Level File Package **shall** have the
|
|
* same value of Edit Rate. All other Tracks of a Top-Level File
|
|
* Package **should** have the same value of Edit Rate as the
|
|
* Essence Tracks.
|
|
*/
|
|
if (common_rate.n == 0 && common_rate.d == 0) {
|
|
common_rate = track->edit_rate;
|
|
} else if (common_rate.n * track->edit_rate.d !=
|
|
common_rate.d * track->edit_rate.n) {
|
|
GST_ELEMENT_ERROR (demux, STREAM, WRONG_TYPE, (NULL),
|
|
("Interleaved File Package doesn't have identical edit rate on all tracks."));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
for (k = 0; k < demux->essence_tracks->len; k++) {
|
|
GstMXFDemuxEssenceTrack *tmp =
|
|
g_ptr_array_index (demux->essence_tracks, 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 = g_new0 (GstMXFDemuxEssenceTrack, 1);
|
|
|
|
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_ptr_array_add (demux->essence_tracks, tmp);
|
|
etrack =
|
|
g_ptr_array_index (demux->essence_tracks,
|
|
demux->essence_tracks->len - 1);
|
|
new = TRUE;
|
|
}
|
|
|
|
etrack->source_package = NULL;
|
|
etrack->source_track = NULL;
|
|
etrack->delta_id = -1;
|
|
|
|
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);
|
|
etrack->intra_only = FALSE;
|
|
} else {
|
|
caps =
|
|
etrack->handler->create_caps (track, &etrack->tags,
|
|
&etrack->intra_only, &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);
|
|
etrack->tags = NULL;
|
|
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;
|
|
}
|
|
|
|
etrack->min_edit_units = 1;
|
|
/* Ensure we don't output one buffer per sample for audio */
|
|
if (gst_util_uint64_scale (GST_SECOND, track->edit_rate.d,
|
|
track->edit_rate.n) < 10 * GST_MSECOND) {
|
|
GstStructure *s = gst_caps_get_structure (etrack->caps, 0);
|
|
const gchar *name = gst_structure_get_name (s);
|
|
if (g_str_has_prefix (name, "audio/x-raw")) {
|
|
etrack->min_edit_units =
|
|
gst_util_uint64_scale (25 * GST_MSECOND, track->edit_rate.n,
|
|
track->edit_rate.d * GST_SECOND);
|
|
GST_DEBUG_OBJECT (demux, "Seting miminum number of edit units to %u",
|
|
etrack->min_edit_units);
|
|
}
|
|
}
|
|
|
|
/* FIXME : We really should just abort/ignore the stream completely if we
|
|
* don't have a handler for it */
|
|
if (etrack->handler != NULL)
|
|
etrack->wrapping = etrack->handler->get_track_wrapping (track);
|
|
else
|
|
etrack->wrapping = MXF_ESSENCE_WRAPPING_UNKNOWN_WRAPPING;
|
|
|
|
if (package->is_interleaved) {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"track comes from interleaved source package with %d track(s), setting delta_id to -1",
|
|
package->parent.n_tracks);
|
|
if (etrack->wrapping != MXF_ESSENCE_WRAPPING_FRAME_WRAPPING) {
|
|
GST_ELEMENT_ERROR (demux, STREAM, WRONG_TYPE, (NULL),
|
|
("Non-frame-wrapping is not allowed in interleaved File Package."));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
etrack->delta_id = MXF_INDEX_DELTA_ID_UNKNOWN;
|
|
} else {
|
|
etrack->delta_id = MXF_INDEX_DELTA_ID_UNKNOWN;
|
|
}
|
|
etrack->source_package = package;
|
|
etrack->source_track = track;
|
|
continue;
|
|
|
|
next:
|
|
if (new) {
|
|
g_ptr_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_ptr_array_index (demux->essence_tracks, 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 MXFMetadataEssenceContainerData *
|
|
essence_container_for_source_package (MXFMetadataContentStorage * storage,
|
|
MXFMetadataSourcePackage * package)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < storage->n_essence_container_data; i++) {
|
|
MXFMetadataEssenceContainerData *cont = storage->essence_container_data[i];
|
|
if (cont && cont->linked_package == package)
|
|
return cont;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
gst_mxf_demux_show_topology (GstMXFDemux * demux)
|
|
{
|
|
GList *material_packages = NULL;
|
|
GList *file_packages = NULL;
|
|
GList *tmp;
|
|
MXFMetadataContentStorage *storage = demux->preface->content_storage;
|
|
guint i;
|
|
gchar str[96];
|
|
|
|
/* Show the topology starting from the preface */
|
|
GST_DEBUG_OBJECT (demux, "Topology");
|
|
|
|
for (i = 0; i < storage->n_packages; i++) {
|
|
MXFMetadataGenericPackage *pack = storage->packages[i];
|
|
if (MXF_IS_METADATA_MATERIAL_PACKAGE (pack))
|
|
material_packages = g_list_append (material_packages, pack);
|
|
else if (MXF_IS_METADATA_SOURCE_PACKAGE (pack))
|
|
file_packages = g_list_append (file_packages, pack);
|
|
else
|
|
GST_DEBUG_OBJECT (demux, "Unknown package type");
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (demux, "Number of Material Package (i.e. output) : %d",
|
|
g_list_length (material_packages));
|
|
for (tmp = material_packages; tmp; tmp = tmp->next) {
|
|
MXFMetadataMaterialPackage *pack = (MXFMetadataMaterialPackage *) tmp->data;
|
|
GST_DEBUG_OBJECT (demux, " Package with %d tracks , UID:%s",
|
|
pack->n_tracks, mxf_umid_to_string (&pack->package_uid, str));
|
|
for (i = 0; i < pack->n_tracks; i++) {
|
|
MXFMetadataTrack *track = pack->tracks[i];
|
|
if (track == NULL) {
|
|
GST_DEBUG_OBJECT (demux, " Unknown/Unhandled track UUID %s",
|
|
mxf_uuid_to_string (&pack->tracks_uids[i], str));
|
|
} else if (MXF_IS_METADATA_TIMELINE_TRACK (track)) {
|
|
MXFMetadataTimelineTrack *mtrack = (MXFMetadataTimelineTrack *) track;
|
|
GST_DEBUG_OBJECT (demux,
|
|
" Timeline Track id:%d number:0x%08x name:`%s` edit_rate:%d/%d origin:%"
|
|
G_GINT64_FORMAT, track->track_id, track->track_number,
|
|
track->track_name, mtrack->edit_rate.n, mtrack->edit_rate.d,
|
|
mtrack->origin);
|
|
} else {
|
|
GST_DEBUG_OBJECT (demux,
|
|
" Non-Timeline-Track id:%d number:0x%08x name:`%s`",
|
|
track->track_id, track->track_number, track->track_name);
|
|
}
|
|
if (track) {
|
|
MXFMetadataSequence *sequence = track->sequence;
|
|
guint si;
|
|
GST_DEBUG_OBJECT (demux,
|
|
" Sequence duration:%" G_GINT64_FORMAT
|
|
" n_structural_components:%d", sequence->duration,
|
|
sequence->n_structural_components);
|
|
for (si = 0; si < sequence->n_structural_components; si++) {
|
|
MXFMetadataStructuralComponent *comp =
|
|
sequence->structural_components[si];
|
|
GST_DEBUG_OBJECT (demux,
|
|
" Component #%d duration:%" G_GINT64_FORMAT, si,
|
|
comp->duration);
|
|
if (MXF_IS_METADATA_SOURCE_CLIP (comp)) {
|
|
MXFMetadataSourceClip *clip = (MXFMetadataSourceClip *) comp;
|
|
GST_DEBUG_OBJECT (demux,
|
|
" Clip start_position:%" G_GINT64_FORMAT
|
|
" source_track_id:%d source_package_id:%s",
|
|
clip->start_position, clip->source_track_id,
|
|
mxf_umid_to_string (&clip->source_package_id, str));
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (demux, "Number of File Packages (i.e. input) : %d",
|
|
g_list_length (file_packages));
|
|
for (tmp = file_packages; tmp; tmp = tmp->next) {
|
|
MXFMetadataMaterialPackage *pack = (MXFMetadataMaterialPackage *) tmp->data;
|
|
MXFMetadataSourcePackage *src = (MXFMetadataSourcePackage *) pack;
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
MXFMetadataEssenceContainerData *econt =
|
|
essence_container_for_source_package (storage, src);
|
|
GST_DEBUG_OBJECT (demux,
|
|
" Package (body_sid:%d index_sid:%d top_level:%d) with %d tracks , UID:%s",
|
|
econt ? econt->body_sid : 0, econt ? econt->index_sid : 0,
|
|
src->top_level, pack->n_tracks, mxf_umid_to_string (&pack->package_uid,
|
|
str));
|
|
#endif
|
|
GST_DEBUG_OBJECT (demux, " Package descriptor : %s",
|
|
g_type_name (G_OBJECT_TYPE (src->descriptor)));
|
|
for (i = 0; i < pack->n_tracks; i++) {
|
|
MXFMetadataTrack *track = pack->tracks[i];
|
|
if (!track)
|
|
continue;
|
|
MXFMetadataSequence *sequence = track->sequence;
|
|
guint di, si;
|
|
if (MXF_IS_METADATA_TIMELINE_TRACK (track)) {
|
|
MXFMetadataTimelineTrack *mtrack = (MXFMetadataTimelineTrack *) track;
|
|
GST_DEBUG_OBJECT (demux,
|
|
" Timeline Track id:%d number:0x%08x name:`%s` edit_rate:%d/%d origin:%"
|
|
G_GINT64_FORMAT, track->track_id, track->track_number,
|
|
track->track_name, mtrack->edit_rate.n, mtrack->edit_rate.d,
|
|
mtrack->origin);
|
|
} else {
|
|
GST_DEBUG_OBJECT (demux,
|
|
" Non-Timeline-Track id:%d number:0x%08x name:`%s` type:0x%x",
|
|
track->track_id, track->track_number, track->track_name,
|
|
track->type);
|
|
}
|
|
for (di = 0; di < track->n_descriptor; di++) {
|
|
MXFMetadataFileDescriptor *desc = track->descriptor[di];
|
|
MXFMetadataGenericDescriptor *generic =
|
|
(MXFMetadataGenericDescriptor *) desc;
|
|
guint subdi;
|
|
|
|
GST_DEBUG_OBJECT (demux, " Descriptor %s %s",
|
|
g_type_name (G_OBJECT_TYPE (desc)),
|
|
mxf_ul_to_string (&desc->essence_container, str));
|
|
for (subdi = 0; subdi < generic->n_sub_descriptors; subdi++) {
|
|
MXFMetadataGenericDescriptor *subdesc =
|
|
generic->sub_descriptors[subdi];
|
|
if (subdesc) {
|
|
GST_DEBUG_OBJECT (demux, " Sub-Descriptor %s",
|
|
g_type_name (G_OBJECT_TYPE (subdesc)));
|
|
}
|
|
}
|
|
}
|
|
GST_DEBUG_OBJECT (demux,
|
|
" Sequence duration:%" G_GINT64_FORMAT
|
|
" n_structural_components:%d", sequence->duration,
|
|
sequence->n_structural_components);
|
|
for (si = 0; si < sequence->n_structural_components; si++) {
|
|
MXFMetadataStructuralComponent *comp =
|
|
sequence->structural_components[si];
|
|
GST_DEBUG_OBJECT (demux,
|
|
" Component #%d duration:%" G_GINT64_FORMAT, si,
|
|
comp->duration);
|
|
}
|
|
}
|
|
}
|
|
|
|
g_list_free (material_packages);
|
|
g_list_free (file_packages);
|
|
}
|
|
|
|
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;
|
|
GstVideoTimeCode start_timecode = GST_VIDEO_TIME_CODE_INIT;
|
|
|
|
g_rw_lock_writer_lock (&demux->metadata_lock);
|
|
GST_DEBUG_OBJECT (demux, "Updating tracks");
|
|
|
|
gst_mxf_demux_show_topology (demux);
|
|
|
|
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 material packages, there must be one timecode track with one
|
|
* continuous timecode. For source packages there might be multiple,
|
|
* discontinuous timecode components.
|
|
* TODO: Support multiple timecode components
|
|
*/
|
|
for (i = 0; i < current_package->n_tracks; i++) {
|
|
MXFMetadataTimelineTrack *track = NULL;
|
|
MXFMetadataSequence *sequence = NULL;
|
|
MXFMetadataTimecodeComponent *component = NULL;
|
|
|
|
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, "Skipping Non-timeline track");
|
|
continue;
|
|
}
|
|
|
|
|
|
track = MXF_METADATA_TIMELINE_TRACK (current_package->tracks[i]);
|
|
|
|
if (!track->parent.sequence)
|
|
continue;
|
|
sequence = track->parent.sequence;
|
|
if (sequence->n_structural_components != 1 ||
|
|
!sequence->structural_components[0]
|
|
||
|
|
!MXF_IS_METADATA_TIMECODE_COMPONENT (sequence->structural_components
|
|
[0]))
|
|
continue;
|
|
|
|
component =
|
|
MXF_METADATA_TIMECODE_COMPONENT (sequence->structural_components[0]);
|
|
|
|
/* Not a timecode track */
|
|
if (track->parent.type && (track->parent.type & 0xf0) != 0x10)
|
|
continue;
|
|
|
|
/* Main timecode track must have id 1, all others must be 0 */
|
|
if (track->parent.track_id != 1)
|
|
continue;
|
|
|
|
gst_video_time_code_init (&start_timecode, track->edit_rate.n,
|
|
track->edit_rate.d, NULL, (component->drop_frame
|
|
?
|
|
GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME
|
|
: GST_VIDEO_TIME_CODE_FLAGS_NONE), 0, 0, 0, 0, 0);
|
|
gst_video_time_code_add_frames (&start_timecode, track->origin);
|
|
gst_video_time_code_add_frames (&start_timecode, component->start_timecode);
|
|
break;
|
|
}
|
|
|
|
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 component 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. type:0x%02x track_id:%d track_number:0x%08x",
|
|
track->parent.type, track->parent.track_id,
|
|
track->parent.track_number);
|
|
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_ptr_array_index (demux->essence_tracks, 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;
|
|
|
|
pad->start_timecode = start_timecode;
|
|
|
|
/* 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;
|
|
pad->current_component_start_position = 0;
|
|
|
|
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));
|
|
|
|
/* Re-check all existing partitions for source package linking in case the
|
|
* header partition contains data (allowed in early MXF versions) */
|
|
for (l = demux->partitions; l; l = l->next)
|
|
gst_mxf_demux_partition_postcheck (demux, (GstMXFDemuxPartition *) l->data);
|
|
|
|
return GST_FLOW_OK;
|
|
|
|
error:
|
|
g_rw_lock_writer_unlock (&demux->metadata_lock);
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_demux_handle_metadata (GstMXFDemux * demux, GstMXFKLV * klv)
|
|
{
|
|
guint16 type;
|
|
MXFMetadata *metadata = NULL, *old = NULL;
|
|
GstMapInfo map;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
|
|
type = GST_READ_UINT16_BE (&klv->key.u[13]);
|
|
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Handling metadata of size %" G_GSIZE_FORMAT " at offset %"
|
|
G_GUINT64_FORMAT " of type 0x%04x", klv->length, klv->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;
|
|
}
|
|
|
|
if (klv->length == 0)
|
|
return GST_FLOW_OK;
|
|
ret = gst_mxf_demux_fill_klv (demux, klv);
|
|
if (ret != GST_FLOW_OK)
|
|
return ret;
|
|
|
|
gst_buffer_map (klv->data, &map, GST_MAP_READ);
|
|
metadata =
|
|
mxf_metadata_new (type, &demux->current_partition->primer, demux->offset,
|
|
map.data, map.size);
|
|
gst_buffer_unmap (klv->data, &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, GstMXFKLV * klv)
|
|
{
|
|
guint32 type;
|
|
guint8 scheme;
|
|
GstMapInfo map;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
MXFDescriptiveMetadata *m = NULL, *old = NULL;
|
|
|
|
scheme = GST_READ_UINT8 (&klv->key.u[12]);
|
|
type = GST_READ_UINT24_BE (&klv->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",
|
|
klv->length, klv->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;
|
|
}
|
|
|
|
ret = gst_mxf_demux_fill_klv (demux, klv);
|
|
if (ret != GST_FLOW_OK)
|
|
return ret;
|
|
|
|
gst_buffer_map (klv->data, &map, GST_MAP_READ);
|
|
m = mxf_descriptive_metadata_new (scheme, type,
|
|
&demux->current_partition->primer, demux->offset, map.data, map.size);
|
|
gst_buffer_unmap (klv->data, &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,
|
|
GstMXFKLV * klv)
|
|
{
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Handling generic container system item of size %" G_GSIZE_FORMAT
|
|
" at offset %" G_GUINT64_FORMAT, klv->length, klv->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_ptr_array_index (demux->essence_tracks, 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_position = 0;
|
|
for (k = 0; k < i; k++) {
|
|
pad->current_component_start_position +=
|
|
MXF_METADATA_SOURCE_CLIP (sequence->structural_components[k])->
|
|
parent.duration;
|
|
}
|
|
|
|
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 (!pad_caps
|
|
|| !gst_caps_is_equal (pad_caps, pad->current_essence_track->caps)) {
|
|
gst_pad_set_caps (GST_PAD_CAST (pad), pad->current_essence_track->caps);
|
|
}
|
|
if (pad_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;
|
|
}
|
|
|
|
/*
|
|
* Find the partition containing the stream offset of the given track
|
|
* */
|
|
static GstMXFDemuxPartition *
|
|
get_partition_for_stream_offset (GstMXFDemux * demux,
|
|
GstMXFDemuxEssenceTrack * etrack, guint64 stream_offset)
|
|
{
|
|
GList *tmp;
|
|
GstMXFDemuxPartition *offset_partition = NULL, *next_partition = NULL;
|
|
|
|
for (tmp = demux->partitions; tmp; tmp = tmp->next) {
|
|
GstMXFDemuxPartition *partition = tmp->data;
|
|
|
|
if (!next_partition && offset_partition)
|
|
next_partition = partition;
|
|
|
|
if (partition->partition.body_sid != etrack->body_sid)
|
|
continue;
|
|
if (partition->partition.body_offset > stream_offset)
|
|
break;
|
|
|
|
offset_partition = partition;
|
|
next_partition = NULL;
|
|
}
|
|
|
|
if (offset_partition
|
|
&& stream_offset < offset_partition->partition.body_offset)
|
|
return NULL;
|
|
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Found this_partition:%" G_GUINT64_FORMAT " body_offset:%"
|
|
G_GUINT64_FORMAT, offset_partition->partition.this_partition,
|
|
offset_partition->partition.body_offset);
|
|
|
|
/* Are we overriding into the next partition ? */
|
|
if (next_partition) {
|
|
guint64 partition_essence_size =
|
|
next_partition->partition.this_partition -
|
|
offset_partition->partition.this_partition +
|
|
offset_partition->essence_container_offset;
|
|
guint64 in_partition =
|
|
stream_offset - offset_partition->partition.body_offset;
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Followed by this_partition:%" G_GUINT64_FORMAT " body_offset:%"
|
|
G_GUINT64_FORMAT, next_partition->partition.this_partition,
|
|
next_partition->partition.body_offset);
|
|
|
|
if (in_partition >= partition_essence_size) {
|
|
GST_WARNING_OBJECT (demux,
|
|
"stream_offset %" G_GUINT64_FORMAT
|
|
" in track body_sid:% index_sid:%d leaks into next unrelated partition (body_sid:%d / index_sid:%d)",
|
|
stream_offset, etrack->body_sid, etrack->index_sid,
|
|
next_partition->partition.body_sid,
|
|
next_partition->partition.index_sid);
|
|
return NULL;
|
|
}
|
|
}
|
|
return offset_partition;
|
|
}
|
|
|
|
static GstMXFDemuxIndexTable *
|
|
get_track_index_table (GstMXFDemux * demux, GstMXFDemuxEssenceTrack * etrack)
|
|
{
|
|
GList *l;
|
|
|
|
/* Look in the indextables */
|
|
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) {
|
|
return tmp;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static guint32
|
|
get_track_max_temporal_offset (GstMXFDemux * demux,
|
|
GstMXFDemuxEssenceTrack * etrack)
|
|
{
|
|
GstMXFDemuxIndexTable *table;
|
|
|
|
if (etrack->intra_only)
|
|
return 0;
|
|
|
|
table = get_track_index_table (demux, etrack);
|
|
|
|
if (table)
|
|
return table->max_temporal_offset;
|
|
return 0;
|
|
}
|
|
|
|
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) {
|
|
GST_LOG ("current_position %" G_GINT64_FORMAT, current_position);
|
|
idx = &g_array_index (offsets, GstMXFDemuxIndex, current_position);
|
|
if (idx->offset == 0) {
|
|
GST_LOG ("breaking offset 0");
|
|
break;
|
|
} else if (!idx->keyframe) {
|
|
current_position--;
|
|
continue;
|
|
} else {
|
|
GST_LOG ("Breaking found offset");
|
|
current_offset = idx->offset;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (current_offset == -1)
|
|
return -1;
|
|
|
|
*position = current_position;
|
|
return current_offset;
|
|
}
|
|
|
|
/**
|
|
* find_edit_entry:
|
|
* @demux: The demuxer
|
|
* @etrack: The target essence track
|
|
* @position: An edit unit position
|
|
* @keyframe: if TRUE search for supporting keyframe
|
|
* @entry: (out): Will be filled with the matching entry information
|
|
*
|
|
* Finds the edit entry of @etrack for the given edit unit @position and fill
|
|
* @entry with the information about that edit entry. If @keyframe is TRUE, the
|
|
* supporting entry (i.e. keyframe) for the given position will be searched for.
|
|
*
|
|
* For frame-wrapped contents, the returned offset will be the position of the
|
|
* KLV of the content. For clip-wrapped content, the returned offset will be the
|
|
* position of the essence (i.e. without KLV header) and the entry will specify
|
|
* the size (in bytes).
|
|
*
|
|
* The returned entry will also specify the duration (in edit units) of the
|
|
* content, which can be different from 1 for special cases (such as raw audio
|
|
* where multiple samples could be aggregated).
|
|
*
|
|
* Returns: TRUE if the entry was found and @entry was properly filled, else
|
|
* FALSE.
|
|
*/
|
|
static gboolean
|
|
find_edit_entry (GstMXFDemux * demux, GstMXFDemuxEssenceTrack * etrack,
|
|
gint64 position, gboolean keyframe, GstMXFDemuxIndex * entry)
|
|
{
|
|
GstMXFDemuxIndexTable *index_table = NULL;
|
|
guint i;
|
|
MXFIndexTableSegment *segment = NULL;
|
|
GstMXFDemuxPartition *offset_partition = NULL;
|
|
guint64 stream_offset = G_MAXUINT64, absolute_offset;
|
|
|
|
GST_DEBUG_OBJECT (demux,
|
|
"track %d body_sid:%d index_sid:%d delta_id:%d position:%" G_GINT64_FORMAT
|
|
" keyframe:%d", etrack->track_id, etrack->body_sid,
|
|
etrack->index_sid, etrack->delta_id, position, keyframe);
|
|
|
|
/* Default values */
|
|
entry->duration = 1;
|
|
/* By default every entry is a keyframe unless specified otherwise */
|
|
entry->keyframe = TRUE;
|
|
|
|
/* Look in the track offsets */
|
|
if (etrack->offsets && etrack->offsets->len > position) {
|
|
if (find_offset (etrack->offsets, &position, keyframe) != -1) {
|
|
*entry = g_array_index (etrack->offsets, GstMXFDemuxIndex, position);
|
|
GST_LOG_OBJECT (demux, "Found entry in track offsets");
|
|
return TRUE;
|
|
} else
|
|
GST_LOG_OBJECT (demux, "Didn't find entry in track offsets");
|
|
}
|
|
|
|
/* Look in the indextables */
|
|
index_table = get_track_index_table (demux, etrack);
|
|
|
|
if (!index_table) {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Couldn't find index table for body_sid:%d index_sid:%d",
|
|
etrack->body_sid, etrack->index_sid);
|
|
return FALSE;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Looking for position %" G_GINT64_FORMAT
|
|
" in index table (max temporal offset %u)",
|
|
etrack->position, index_table->max_temporal_offset);
|
|
|
|
/* Searching for a position in index tables works in 3 steps:
|
|
*
|
|
* 1. Figure out the table segment containing that position
|
|
* 2. Figure out the "stream offset" (and additional flags/timing) of that
|
|
* position from the table segment.
|
|
* 3. Figure out the "absolute offset" of that "stream offset" using partitions
|
|
*/
|
|
|
|
search_in_segment:
|
|
|
|
/* Find matching index segment */
|
|
GST_DEBUG_OBJECT (demux, "Look for entry in %d segments",
|
|
index_table->segments->len);
|
|
for (i = 0; i < index_table->segments->len; i++) {
|
|
MXFIndexTableSegment *cand =
|
|
&g_array_index (index_table->segments, MXFIndexTableSegment, i);
|
|
if (position >= cand->index_start_position && (cand->index_duration == 0
|
|
|| position <
|
|
(cand->index_start_position + cand->index_duration))) {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Entry is in Segment #%d , start: %" G_GINT64_FORMAT " , duration: %"
|
|
G_GINT64_FORMAT, i, cand->index_start_position, cand->index_duration);
|
|
segment = cand;
|
|
break;
|
|
}
|
|
}
|
|
if (!segment) {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Didn't find index table segment for position %" G_GINT64_FORMAT,
|
|
position);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Were we asked for a keyframe ? */
|
|
if (keyframe) {
|
|
if (segment->edit_unit_byte_count && !segment->n_index_entries) {
|
|
GST_LOG_OBJECT (demux,
|
|
"Index table without entries, directly using requested position for keyframe search");
|
|
} else {
|
|
gint64 candidate;
|
|
GST_LOG_OBJECT (demux, "keyframe search");
|
|
/* Search backwards for keyframe */
|
|
for (candidate = position; candidate >= segment->index_start_position;
|
|
candidate--) {
|
|
MXFIndexEntry *segment_index_entry =
|
|
&segment->index_entries[candidate - segment->index_start_position];
|
|
|
|
/* Match */
|
|
if (segment_index_entry->flags & 0x80) {
|
|
GST_LOG_OBJECT (demux, "Found keyframe at position %" G_GINT64_FORMAT,
|
|
candidate);
|
|
position = candidate;
|
|
break;
|
|
}
|
|
|
|
/* If a keyframe offset is specified and valid, use that */
|
|
if (segment_index_entry->key_frame_offset
|
|
&& !(segment_index_entry->flags & 0x08)) {
|
|
GST_DEBUG_OBJECT (demux, "Using keyframe offset %d",
|
|
segment_index_entry->key_frame_offset);
|
|
position = candidate + segment_index_entry->key_frame_offset;
|
|
if (position < segment->index_start_position) {
|
|
GST_DEBUG_OBJECT (demux, "keyframe info is in previous segment");
|
|
goto search_in_segment;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* If we reached the beginning, use that */
|
|
if (candidate == 0) {
|
|
GST_LOG_OBJECT (demux,
|
|
"Reached position 0 while searching for keyframe");
|
|
position = 0;
|
|
break;
|
|
}
|
|
|
|
/* If we looped past the beginning of this segment, go to the previous one */
|
|
if (candidate == segment->index_start_position) {
|
|
position = candidate - 1;
|
|
GST_LOG_OBJECT (demux, "Looping with new position %" G_GINT64_FORMAT,
|
|
position);
|
|
goto search_in_segment;
|
|
}
|
|
|
|
/* loop back to check previous entry */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Figure out the stream offset (also called "body offset" in specification) */
|
|
if (segment->edit_unit_byte_count && !segment->n_index_entries) {
|
|
/* Constant entry table. */
|
|
stream_offset = position * segment->edit_unit_byte_count;
|
|
if (etrack->delta_id >= 0) {
|
|
MXFDeltaEntry *delta_entry = &segment->delta_entries[etrack->delta_id];
|
|
GST_LOG_OBJECT (demux,
|
|
"Using delta %d pos_table_index:%d slice:%u element_delta:%u",
|
|
etrack->delta_id, delta_entry->pos_table_index, delta_entry->slice,
|
|
delta_entry->element_delta);
|
|
stream_offset += delta_entry->element_delta;
|
|
} else if (etrack->min_edit_units != 1) {
|
|
GST_LOG_OBJECT (demux, "Handling minimum edit unit %u",
|
|
etrack->min_edit_units);
|
|
entry->duration =
|
|
MIN (etrack->min_edit_units,
|
|
(segment->index_start_position + segment->index_duration) - position);
|
|
entry->size = segment->edit_unit_byte_count * entry->duration;
|
|
} else {
|
|
entry->size = segment->edit_unit_byte_count;
|
|
}
|
|
} else if (segment->n_index_entries) {
|
|
MXFIndexEntry *segment_index_entry;
|
|
MXFDeltaEntry *delta_entry = NULL;
|
|
g_assert (position <=
|
|
segment->index_start_position + segment->n_index_entries);
|
|
segment_index_entry =
|
|
&segment->index_entries[position - segment->index_start_position];
|
|
stream_offset = segment_index_entry->stream_offset;
|
|
|
|
if (segment->n_delta_entries > 0)
|
|
delta_entry = &segment->delta_entries[etrack->delta_id];
|
|
|
|
if (delta_entry) {
|
|
GST_LOG_OBJECT (demux,
|
|
"Using delta %d pos_table_index:%d slice:%u element_delta:%u",
|
|
etrack->delta_id, delta_entry->pos_table_index, delta_entry->slice,
|
|
delta_entry->element_delta);
|
|
|
|
/* Apply offset from slice/delta if needed */
|
|
if (delta_entry->slice)
|
|
stream_offset +=
|
|
segment_index_entry->slice_offset[delta_entry->slice - 1];
|
|
stream_offset += delta_entry->element_delta;
|
|
if (delta_entry->pos_table_index == -1) {
|
|
entry->keyframe = (segment_index_entry->flags & 0x80) == 0x80;
|
|
}
|
|
/* FIXME : Handle fractional offset position (delta_entry->pos_table_offset > 0) */
|
|
}
|
|
|
|
/* Apply reverse temporal reordering if present */
|
|
if (index_table->reordered_delta_entry == etrack->delta_id) {
|
|
if (position >= index_table->reverse_temporal_offsets->len) {
|
|
GST_WARNING_OBJECT (demux,
|
|
"Can't apply temporal offset for position %" G_GINT64_FORMAT
|
|
" (max:%d)", position, index_table->reverse_temporal_offsets->len);
|
|
}
|
|
if (demux->temporal_order_misuse) {
|
|
GST_DEBUG_OBJECT (demux, "Handling temporal order misuse");
|
|
entry->pts = position + segment_index_entry->temporal_offset;
|
|
} else {
|
|
entry->pts =
|
|
position + g_array_index (index_table->reverse_temporal_offsets,
|
|
gint8, position);
|
|
GST_LOG_OBJECT (demux,
|
|
"Applied temporal offset. dts:%" G_GINT64_FORMAT " pts:%"
|
|
G_GINT64_FORMAT, position, entry->pts);
|
|
}
|
|
} else
|
|
entry->pts = position;
|
|
} else {
|
|
/* Note : This should have been handled in the parser */
|
|
GST_WARNING_OBJECT (demux,
|
|
"Can't handle index tables without entries nor constant edit unit byte count");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Find the partition containing the stream offset for this track */
|
|
offset_partition =
|
|
get_partition_for_stream_offset (demux, etrack, stream_offset);
|
|
|
|
if (!offset_partition) {
|
|
GST_WARNING_OBJECT (demux,
|
|
"Couldn't find matching partition for stream offset %" G_GUINT64_FORMAT,
|
|
stream_offset);
|
|
return FALSE;
|
|
} else {
|
|
GST_DEBUG_OBJECT (demux, "Entry is in partition %" G_GUINT64_FORMAT,
|
|
offset_partition->partition.this_partition);
|
|
}
|
|
|
|
/* Convert stream offset to absolute offset using matching partition */
|
|
absolute_offset =
|
|
offset_partition->partition.this_partition +
|
|
offset_partition->essence_container_offset + (stream_offset -
|
|
offset_partition->partition.body_offset);
|
|
|
|
GST_LOG_OBJECT (demux,
|
|
"track %d position:%" G_GINT64_FORMAT " stream_offset %" G_GUINT64_FORMAT
|
|
" matches to absolute offset %" G_GUINT64_FORMAT, etrack->track_id,
|
|
position, stream_offset, absolute_offset);
|
|
entry->initialized = TRUE;
|
|
entry->offset = absolute_offset;
|
|
entry->dts = position;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* find_entry_for_offset:
|
|
* @demux: The demuxer
|
|
* @etrack: The target essence track
|
|
* @offset: An absolute byte offset (excluding run_in)
|
|
* @entry: (out): Will be filled with the matching entry information
|
|
*
|
|
* Find the entry located at the given absolute byte offset.
|
|
*
|
|
* Note: the offset requested should be in the current partition !
|
|
*
|
|
* Returns: TRUE if the entry was found and @entry was properly filled, else
|
|
* FALSE.
|
|
*/
|
|
static gboolean
|
|
find_entry_for_offset (GstMXFDemux * demux, GstMXFDemuxEssenceTrack * etrack,
|
|
guint64 offset, GstMXFDemuxIndex * retentry)
|
|
{
|
|
GstMXFDemuxIndexTable *index_table = get_track_index_table (demux, etrack);
|
|
guint i;
|
|
MXFIndexTableSegment *index_segment = NULL;
|
|
GstMXFDemuxPartition *partition = demux->current_partition;
|
|
guint64 original_offset = offset;
|
|
guint64 cp_offset = 0; /* Offset in Content Package */
|
|
MXFIndexEntry *index_entry = NULL;
|
|
MXFDeltaEntry *delta_entry = NULL;
|
|
gint64 position = 0;
|
|
|
|
GST_DEBUG_OBJECT (demux,
|
|
"track %d body_sid:%d index_sid:%d offset:%" G_GUINT64_FORMAT,
|
|
etrack->track_id, etrack->body_sid, etrack->index_sid, offset);
|
|
|
|
/* Default value */
|
|
retentry->duration = 1;
|
|
retentry->keyframe = TRUE;
|
|
|
|
/* Index-less search */
|
|
if (etrack->offsets) {
|
|
for (i = 0; i < etrack->offsets->len; i++) {
|
|
GstMXFDemuxIndex *idx =
|
|
&g_array_index (etrack->offsets, GstMXFDemuxIndex, i);
|
|
|
|
if (idx->initialized && idx->offset != 0 && idx->offset == offset) {
|
|
*retentry = *idx;
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Found in track index. Position:%" G_GINT64_FORMAT, idx->dts);
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Actual index search */
|
|
if (!index_table || !index_table->segments->len) {
|
|
GST_WARNING_OBJECT (demux, "No index table or entries to search in");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!partition) {
|
|
GST_WARNING_OBJECT (demux, "No current partition for search");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Searching for a stream position from an absolute offset works in 3 steps:
|
|
*
|
|
* 1. Convert the absolute offset to a "stream offset" based on the partition
|
|
* information.
|
|
* 2. Find the segment for that "stream offset"
|
|
* 3. Match the entry within that segment
|
|
*/
|
|
|
|
/* Convert to stream offset */
|
|
GST_LOG_OBJECT (demux,
|
|
"offset %" G_GUINT64_FORMAT " this_partition:%" G_GUINT64_FORMAT
|
|
" essence_container_offset:%" G_GINT64_FORMAT " partition body offset %"
|
|
G_GINT64_FORMAT, offset, partition->partition.this_partition,
|
|
partition->essence_container_offset, partition->partition.body_offset);
|
|
offset =
|
|
offset - partition->partition.this_partition -
|
|
partition->essence_container_offset + partition->partition.body_offset;
|
|
|
|
GST_LOG_OBJECT (demux, "stream offset %" G_GUINT64_FORMAT, offset);
|
|
|
|
/* Find the segment that covers the given stream offset (the highest one that
|
|
* covers that offset) */
|
|
for (i = index_table->segments->len - 1; i >= 0; i--) {
|
|
index_segment =
|
|
&g_array_index (index_table->segments, MXFIndexTableSegment, i);
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Checking segment #%d (essence_offset %" G_GUINT64_FORMAT ")", i,
|
|
index_segment->segment_start_offset);
|
|
/* Not in the right segment yet */
|
|
if (offset >= index_segment->segment_start_offset) {
|
|
GST_LOG_OBJECT (demux, "Found");
|
|
break;
|
|
}
|
|
}
|
|
if (!index_segment) {
|
|
GST_WARNING_OBJECT (demux,
|
|
"Couldn't find index table segment for given offset");
|
|
return FALSE;
|
|
}
|
|
|
|
/* In the right segment, figure out:
|
|
* * the offset in the content package,
|
|
* * the position in edit units
|
|
* * the matching entry (if the table has entries)
|
|
*/
|
|
if (index_segment->edit_unit_byte_count) {
|
|
cp_offset = offset % index_segment->edit_unit_byte_count;
|
|
position = offset / index_segment->edit_unit_byte_count;
|
|
/* Boundary check */
|
|
if ((position < index_segment->index_start_position)
|
|
|| (index_segment->index_duration
|
|
&& position >
|
|
(index_segment->index_start_position +
|
|
index_segment->index_duration))) {
|
|
GST_WARNING_OBJECT (demux,
|
|
"Invalid offset, exceeds table segment limits");
|
|
return FALSE;
|
|
}
|
|
if (etrack->min_edit_units != 1) {
|
|
retentry->duration = MIN (etrack->min_edit_units,
|
|
(index_segment->index_start_position +
|
|
index_segment->index_duration) - position);
|
|
retentry->size = index_segment->edit_unit_byte_count * retentry->duration;
|
|
} else {
|
|
retentry->size = index_segment->edit_unit_byte_count;
|
|
}
|
|
} else {
|
|
/* Find the content package entry containing this offset */
|
|
guint cpidx;
|
|
for (cpidx = 0; cpidx < index_segment->n_index_entries; cpidx++) {
|
|
index_entry = &index_segment->index_entries[cpidx];
|
|
GST_DEBUG_OBJECT (demux,
|
|
"entry #%u offset:%" G_GUINT64_FORMAT " stream_offset:%"
|
|
G_GUINT64_FORMAT, cpidx, offset, index_entry->stream_offset);
|
|
if (index_entry->stream_offset == offset) {
|
|
index_entry = &index_segment->index_entries[cpidx];
|
|
/* exactly on the entry */
|
|
cp_offset = offset - index_entry->stream_offset;
|
|
position = index_segment->index_start_position + cpidx;
|
|
break;
|
|
}
|
|
if (index_entry->stream_offset > offset && cpidx > 0) {
|
|
index_entry = &index_segment->index_entries[cpidx - 1];
|
|
/* One too far, result is in previous entry */
|
|
cp_offset = offset - index_entry->stream_offset;
|
|
position = index_segment->index_start_position + cpidx - 1;
|
|
break;
|
|
}
|
|
}
|
|
if (cpidx == index_segment->n_index_entries) {
|
|
GST_WARNING_OBJECT (demux,
|
|
"offset exceeds maximum number of entries in table segment");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* If the track comes from an interleaved essence container and doesn't have a
|
|
* delta_id set, figure it out now */
|
|
if (G_UNLIKELY (etrack->delta_id == MXF_INDEX_DELTA_ID_UNKNOWN)) {
|
|
guint delta;
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Unknown delta_id for track. Attempting to resolve it");
|
|
|
|
if (index_segment->n_delta_entries == 0) {
|
|
/* No delta entries, nothing we can do about this */
|
|
GST_DEBUG_OBJECT (demux, "Index table has no delta entries, ignoring");
|
|
etrack->delta_id = MXF_INDEX_DELTA_ID_IGNORE;
|
|
} else if (!index_entry) {
|
|
for (delta = 0; delta < index_segment->n_delta_entries; delta++) {
|
|
/* No entry, therefore no slices */
|
|
GST_LOG_OBJECT (demux,
|
|
"delta #%d offset %" G_GUINT64_FORMAT " cp_offs:%" G_GUINT64_FORMAT
|
|
" element_delta:%u", delta, offset, cp_offset,
|
|
index_segment->delta_entries[delta].element_delta);
|
|
if (cp_offset == index_segment->delta_entries[delta].element_delta) {
|
|
GST_DEBUG_OBJECT (demux, "Matched to delta %d", delta);
|
|
etrack->delta_id = delta;
|
|
delta_entry = &index_segment->delta_entries[delta];
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for (delta = 0; delta < index_segment->n_delta_entries; delta++) {
|
|
guint64 delta_offs = 0;
|
|
/* If we are not in the first slice, take that offset into account */
|
|
if (index_segment->delta_entries[delta].slice)
|
|
delta_offs =
|
|
index_entry->slice_offset[index_segment->
|
|
delta_entries[delta].slice - 1];
|
|
/* Add the offset for this delta */
|
|
delta_offs += index_segment->delta_entries[delta].element_delta;
|
|
if (cp_offset == delta_offs) {
|
|
GST_DEBUG_OBJECT (demux, "Matched to delta %d", delta);
|
|
etrack->delta_id = delta;
|
|
delta_entry = &index_segment->delta_entries[delta];
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
/* If we didn't managed to match, ignore it from now on */
|
|
if (etrack->delta_id == MXF_INDEX_DELTA_ID_UNKNOWN) {
|
|
GST_WARNING_OBJECT (demux,
|
|
"Couldn't match delta id, ignoring it from now on");
|
|
etrack->delta_id = MXF_INDEX_DELTA_ID_IGNORE;
|
|
}
|
|
} else if (index_segment->n_delta_entries > 0) {
|
|
delta_entry = &index_segment->delta_entries[etrack->delta_id];
|
|
}
|
|
|
|
if (index_entry && delta_entry && delta_entry->pos_table_index == -1) {
|
|
retentry->keyframe = (index_entry->flags & 0x80) == 0x80;
|
|
if (!demux->temporal_order_misuse)
|
|
retentry->pts =
|
|
position + g_array_index (index_table->reverse_temporal_offsets,
|
|
gint8, position);
|
|
else
|
|
retentry->pts = position + index_entry->temporal_offset;
|
|
GST_LOG_OBJECT (demux,
|
|
"Applied temporal offset. dts:%" G_GINT64_FORMAT " pts:%"
|
|
G_GINT64_FORMAT, position, retentry->pts);
|
|
} else
|
|
retentry->pts = position;
|
|
|
|
/* FIXME : check if position and cp_offs matches the table */
|
|
GST_LOG_OBJECT (demux, "Found in index table. position:%" G_GINT64_FORMAT,
|
|
position);
|
|
retentry->initialized = TRUE;
|
|
retentry->offset = original_offset;
|
|
retentry->dts = position;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_demux_handle_generic_container_essence_element (GstMXFDemux * demux,
|
|
GstMXFKLV * klv, gboolean peek)
|
|
{
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
guint32 track_number;
|
|
guint i;
|
|
GstBuffer *inbuf = NULL;
|
|
GstBuffer *outbuf = NULL;
|
|
GstMXFDemuxEssenceTrack *etrack = NULL;
|
|
/* As in GstMXFDemuxIndex */
|
|
guint64 pts = G_MAXUINT64;
|
|
gint32 max_temporal_offset = 0;
|
|
GstMXFDemuxIndex index_entry = { 0, };
|
|
guint64 offset;
|
|
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Handling generic container essence element of size %" G_GSIZE_FORMAT
|
|
" at offset %" G_GUINT64_FORMAT, klv->length,
|
|
klv->offset + klv->consumed);
|
|
|
|
GST_DEBUG_OBJECT (demux, " type = 0x%02x", klv->key.u[12]);
|
|
GST_DEBUG_OBJECT (demux, " essence element count = 0x%02x", klv->key.u[13]);
|
|
GST_DEBUG_OBJECT (demux, " essence element type = 0x%02x", klv->key.u[14]);
|
|
GST_DEBUG_OBJECT (demux, " essence element number = 0x%02x", klv->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_partition->single_track
|
|
&& demux->current_partition->single_track->wrapping !=
|
|
MXF_ESSENCE_WRAPPING_FRAME_WRAPPING) {
|
|
demux->current_partition->essence_container_offset += klv->data_offset;
|
|
demux->current_partition->clip_klv = *klv;
|
|
/* "consume" the initial bytes of the KLV */
|
|
klv->consumed = klv->data_offset;
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Non-frame wrapping, updated essence_container_offset to %"
|
|
G_GUINT64_FORMAT, demux->current_partition->essence_container_offset);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* Identify and fetch the essence track */
|
|
track_number = GST_READ_UINT32_BE (&klv->key.u[12]);
|
|
|
|
etrack = demux->current_partition->single_track;
|
|
if (!etrack) {
|
|
for (i = 0; i < demux->essence_tracks->len; i++) {
|
|
GstMXFDemuxEssenceTrack *tmp =
|
|
g_ptr_array_index (demux->essence_tracks, 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_DEBUG_OBJECT (demux,
|
|
"No essence track for this essence element found");
|
|
return GST_FLOW_OK;
|
|
}
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Handling generic container essence (track %d , position:%"
|
|
G_GINT64_FORMAT ", number: 0x%08x , frame-wrapped:%d)", etrack->track_id,
|
|
etrack->position, track_number,
|
|
etrack->wrapping == MXF_ESSENCE_WRAPPING_FRAME_WRAPPING);
|
|
|
|
/* Fetch the current entry.
|
|
*
|
|
* 1. If we don't have a current position, use find_entry_for_offset()
|
|
* 2. If we do have a position, use find_edit_entry()
|
|
*
|
|
* 3. If we are dealing with frame-wrapped content, pull the corresponding
|
|
* data from upstream (because it wasn't provided). If we didn't find an
|
|
* entry, error out because we can't deal with a frame-wrapped stream
|
|
* without index.
|
|
*/
|
|
|
|
offset = klv->offset + klv->consumed;
|
|
|
|
/* Update the track position (in case of resyncs) */
|
|
if (etrack->position == -1) {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Unknown essence track position, looking into index");
|
|
if (!find_entry_for_offset (demux, etrack, offset - demux->run_in,
|
|
&index_entry)) {
|
|
GST_WARNING_OBJECT (demux, "Essence track position not in index");
|
|
return GST_FLOW_OK;
|
|
}
|
|
/* Update track position */
|
|
etrack->position = index_entry.dts;
|
|
} else if (etrack->delta_id == MXF_INDEX_DELTA_ID_UNKNOWN) {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Unknown essence track delta_id, looking into index");
|
|
if (!find_entry_for_offset (demux, etrack, offset - demux->run_in,
|
|
&index_entry)) {
|
|
/* Non-fatal, fallback to legacy mode */
|
|
GST_WARNING_OBJECT (demux, "Essence track position not in index");
|
|
} else if (etrack->position != index_entry.dts) {
|
|
GST_ERROR_OBJECT (demux,
|
|
"track position doesn't match %" G_GINT64_FORMAT " entry dts %"
|
|
G_GINT64_FORMAT, etrack->position, index_entry.dts);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
} else {
|
|
if (!find_edit_entry (demux, etrack, etrack->position, FALSE, &index_entry)) {
|
|
GST_DEBUG_OBJECT (demux, "Couldn't find entry");
|
|
} else if (etrack->wrapping == MXF_ESSENCE_WRAPPING_FRAME_WRAPPING) {
|
|
if (etrack->delta_id != MXF_INDEX_DELTA_ID_IGNORE
|
|
&& index_entry.offset != offset) {
|
|
GST_ERROR_OBJECT (demux,
|
|
"demux offset doesn't match %" G_GINT64_FORMAT " entry offset %"
|
|
G_GUINT64_FORMAT, offset, index_entry.offset);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
} else if (index_entry.offset != klv->offset + klv->consumed &&
|
|
index_entry.offset != klv->offset + klv->data_offset) {
|
|
GST_ERROR_OBJECT (demux,
|
|
"KLV offset doesn't match %" G_GINT64_FORMAT " entry offset %"
|
|
G_GUINT64_FORMAT, klv->offset + klv->consumed, index_entry.offset);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
if (etrack->wrapping != MXF_ESSENCE_WRAPPING_FRAME_WRAPPING) {
|
|
/* We need entry information to deal with non-frame-wrapped content */
|
|
if (!index_entry.initialized) {
|
|
GST_ELEMENT_ERROR (demux, STREAM, WRONG_TYPE, (NULL),
|
|
("Essence with non-frame-wrapping require an index table to be present"));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
/* We cannot deal with non-frame-wrapping in push mode for now */
|
|
if (!demux->random_access) {
|
|
GST_ELEMENT_ERROR (demux, STREAM, WRONG_TYPE, (NULL),
|
|
("Non-frame-wrapping is not support in push mode"));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
/* FIXME : If we're peeking and don't need to actually parse the data, we
|
|
* should avoid pulling the content from upstream */
|
|
if (etrack->wrapping != MXF_ESSENCE_WRAPPING_FRAME_WRAPPING) {
|
|
g_assert (index_entry.size);
|
|
GST_DEBUG_OBJECT (demux, "Should only grab %" G_GUINT64_FORMAT " bytes",
|
|
index_entry.size);
|
|
ret =
|
|
gst_mxf_demux_pull_range (demux, index_entry.offset, index_entry.size,
|
|
&inbuf);
|
|
if (ret != GST_FLOW_OK)
|
|
return ret;
|
|
if (klv->consumed == 0)
|
|
klv->consumed = klv->data_offset + index_entry.size;
|
|
else
|
|
klv->consumed += index_entry.size;
|
|
if (klv != &demux->current_partition->clip_klv)
|
|
demux->current_partition->clip_klv = *klv;
|
|
GST_LOG_OBJECT (demux,
|
|
"klv data_offset:%" G_GUINT64_FORMAT " length:%" G_GSIZE_FORMAT
|
|
" consumed:%" G_GUINT64_FORMAT, klv->data_offset, klv->length,
|
|
klv->consumed);
|
|
/* Switch back to KLV mode if we're done with this one */
|
|
if (klv->length + klv->data_offset == klv->consumed)
|
|
demux->state = GST_MXF_DEMUX_STATE_KLV;
|
|
else
|
|
demux->state = GST_MXF_DEMUX_STATE_ESSENCE;
|
|
} else {
|
|
|
|
ret = gst_mxf_demux_fill_klv (demux, klv);
|
|
if (ret != GST_FLOW_OK)
|
|
return ret;
|
|
|
|
/* Create subbuffer to be able to change metadata */
|
|
inbuf =
|
|
gst_buffer_copy_region (klv->data, GST_BUFFER_COPY_ALL, 0,
|
|
gst_buffer_get_size (klv->data));
|
|
|
|
}
|
|
|
|
if (index_entry.initialized) {
|
|
GST_DEBUG_OBJECT (demux, "Got entry dts:%" G_GINT64_FORMAT " keyframe:%d",
|
|
index_entry.dts, index_entry.keyframe);
|
|
}
|
|
if (index_entry.initialized && !index_entry.keyframe)
|
|
GST_BUFFER_FLAG_SET (inbuf, GST_BUFFER_FLAG_DELTA_UNIT);
|
|
|
|
if (etrack->handle_func) {
|
|
/* Takes ownership of inbuf */
|
|
ret =
|
|
etrack->handle_func (&klv->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 (!index_entry.initialized) {
|
|
/* This can happen when doing scanning without entry tables */
|
|
index_entry.duration = 1;
|
|
index_entry.offset = demux->offset - demux->run_in;
|
|
index_entry.dts = etrack->position;
|
|
index_entry.pts = etrack->intra_only ? etrack->position : G_MAXUINT64;
|
|
index_entry.keyframe =
|
|
!GST_BUFFER_FLAG_IS_SET (outbuf, GST_BUFFER_FLAG_DELTA_UNIT);
|
|
index_entry.initialized = TRUE;
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Storing newly discovered information on track %d. dts: %"
|
|
G_GINT64_FORMAT " offset:%" G_GUINT64_FORMAT " keyframe:%d",
|
|
etrack->track_id, index_entry.dts, index_entry.offset,
|
|
index_entry.keyframe);
|
|
|
|
if (!etrack->offsets)
|
|
etrack->offsets = g_array_new (FALSE, TRUE, sizeof (GstMXFDemuxIndex));
|
|
|
|
/* We only ever append to the track offset entry. */
|
|
g_assert (etrack->position <= etrack->offsets->len);
|
|
g_array_insert_val (etrack->offsets, etrack->position, index_entry);
|
|
}
|
|
|
|
if (peek)
|
|
goto out;
|
|
|
|
if (!outbuf) {
|
|
GST_DEBUG_OBJECT (demux, "No output buffer created");
|
|
goto out;
|
|
}
|
|
|
|
inbuf = outbuf;
|
|
outbuf = NULL;
|
|
|
|
max_temporal_offset = get_track_max_temporal_offset (demux, etrack);
|
|
|
|
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 (pad, "Pad is already EOS");
|
|
continue;
|
|
}
|
|
|
|
if (etrack->position < pad->current_essence_track_position) {
|
|
GST_DEBUG_OBJECT (pad,
|
|
"Not at current component's position (track:%" G_GINT64_FORMAT
|
|
" essence:%" G_GINT64_FORMAT ")", etrack->position,
|
|
pad->current_essence_track_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 (earliest,
|
|
"Pad is too far ahead of time (%" GST_TIME_FORMAT " vs earliest:%"
|
|
GST_TIME_FORMAT ")", GST_TIME_ARGS (earliest->position),
|
|
GST_TIME_ARGS (pad->position));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Create another subbuffer to have writable metadata */
|
|
outbuf =
|
|
gst_buffer_copy_region (inbuf, GST_BUFFER_COPY_ALL, 0,
|
|
gst_buffer_get_size (inbuf));
|
|
|
|
pts = index_entry.pts;
|
|
|
|
GST_BUFFER_DTS (outbuf) = pad->position;
|
|
if (etrack->intra_only) {
|
|
GST_BUFFER_PTS (outbuf) = pad->position;
|
|
} else if (pts != G_MAXUINT64) {
|
|
GST_BUFFER_PTS (outbuf) = gst_util_uint64_scale (pts * GST_SECOND,
|
|
pad->current_essence_track->source_track->edit_rate.d,
|
|
pad->current_essence_track->source_track->edit_rate.n);
|
|
GST_BUFFER_PTS (outbuf) +=
|
|
gst_util_uint64_scale (pad->current_component_start_position *
|
|
GST_SECOND, pad->material_track->edit_rate.d,
|
|
pad->material_track->edit_rate.n);
|
|
/* We are dealing with reordered data, the PTS is shifted forward by the
|
|
* maximum temporal reordering (the DTS remain as-is). */
|
|
if (max_temporal_offset > 0)
|
|
GST_BUFFER_PTS (outbuf) +=
|
|
gst_util_uint64_scale (max_temporal_offset * GST_SECOND,
|
|
pad->current_essence_track->source_track->edit_rate.d,
|
|
pad->current_essence_track->source_track->edit_rate.n);
|
|
|
|
} else {
|
|
GST_BUFFER_PTS (outbuf) = GST_CLOCK_TIME_NONE;
|
|
}
|
|
|
|
GST_BUFFER_DURATION (outbuf) =
|
|
gst_util_uint64_scale (GST_SECOND,
|
|
index_entry.duration *
|
|
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;
|
|
|
|
if (pad->material_track->parent.type == MXF_METADATA_TRACK_PICTURE_ESSENCE
|
|
&& pad->start_timecode.config.fps_n != 0
|
|
&& pad->start_timecode.config.fps_d != 0) {
|
|
if (etrack->intra_only) {
|
|
GstVideoTimeCode timecode = pad->start_timecode;
|
|
|
|
gst_video_time_code_add_frames (&timecode,
|
|
pad->current_material_track_position);
|
|
gst_buffer_add_video_time_code_meta (outbuf, &timecode);
|
|
} else if (pts != G_MAXUINT64) {
|
|
GstVideoTimeCode timecode = pad->start_timecode;
|
|
|
|
gst_video_time_code_add_frames (&timecode,
|
|
pad->current_component_start_position);
|
|
gst_video_time_code_add_frames (&timecode,
|
|
gst_util_uint64_scale (pts,
|
|
pad->material_track->edit_rate.n *
|
|
pad->current_essence_track->source_track->edit_rate.d,
|
|
pad->material_track->edit_rate.d *
|
|
pad->current_essence_track->source_track->edit_rate.n));
|
|
gst_buffer_add_video_time_code_meta (outbuf, &timecode);
|
|
}
|
|
|
|
}
|
|
|
|
/* 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));
|
|
|
|
if (max_temporal_offset > 0) {
|
|
GstSegment shift_segment;
|
|
/* Handle maximum temporal offset. We are shifting all output PTS for
|
|
* this stream by the greatest temporal reordering that can occur. In
|
|
* order not to change the stream/running time we shift the segment
|
|
* start and stop values accordingly */
|
|
gst_segment_copy_into (&demux->segment, &shift_segment);
|
|
if (GST_CLOCK_TIME_IS_VALID (shift_segment.start))
|
|
shift_segment.start +=
|
|
gst_util_uint64_scale (max_temporal_offset * GST_SECOND,
|
|
pad->current_essence_track->source_track->edit_rate.d,
|
|
pad->current_essence_track->source_track->edit_rate.n);
|
|
if (GST_CLOCK_TIME_IS_VALID (shift_segment.stop))
|
|
shift_segment.stop +=
|
|
gst_util_uint64_scale (max_temporal_offset * GST_SECOND,
|
|
pad->current_essence_track->source_track->edit_rate.d,
|
|
pad->current_essence_track->source_track->edit_rate.n);
|
|
e = gst_event_new_segment (&shift_segment);
|
|
} else
|
|
e = gst_event_new_segment (&demux->segment);
|
|
GST_DEBUG_OBJECT (pad, "Sending segment %" GST_PTR_FORMAT, e);
|
|
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);
|
|
pad->current_material_track_position += index_entry.duration;
|
|
|
|
if (pad->discont) {
|
|
GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
|
|
pad->discont = FALSE;
|
|
}
|
|
|
|
/* Handlers can provide empty GAP buffers to indicate that the parsed
|
|
* content was valid but that nothing meaningful needs to be outputted. In
|
|
* such cases we send out a GAP event instead */
|
|
if (GST_BUFFER_FLAG_IS_SET (outbuf, GST_BUFFER_FLAG_GAP) &&
|
|
gst_buffer_get_size (outbuf) == 0) {
|
|
GstEvent *gap = gst_event_new_gap (GST_BUFFER_DTS (outbuf),
|
|
GST_BUFFER_DURATION (outbuf));
|
|
gst_buffer_unref (outbuf);
|
|
GST_DEBUG_OBJECT (pad,
|
|
"Replacing empty gap buffer with gap event %" GST_PTR_FORMAT, gap);
|
|
gst_pad_push_event (GST_PAD_CAST (pad), gap);
|
|
} else {
|
|
GST_DEBUG_OBJECT (pad,
|
|
"Pushing buffer of size %" G_GSIZE_FORMAT " for track %u: pts %"
|
|
GST_TIME_FORMAT " dts %" 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_PTS (outbuf)),
|
|
GST_TIME_ARGS (GST_BUFFER_DTS (outbuf)),
|
|
GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)),
|
|
pad->current_essence_track_position);
|
|
|
|
ret = gst_pad_push (GST_PAD_CAST (pad), outbuf);
|
|
}
|
|
outbuf = NULL;
|
|
ret = gst_flow_combiner_update_flow (demux->flowcombiner, ret);
|
|
GST_LOG_OBJECT (pad, "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 += index_entry.duration;
|
|
|
|
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) {
|
|
pad->current_essence_track->position =
|
|
pad->current_essence_track_position;
|
|
} else if (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 (pad, "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 += index_entry.duration;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Called when analyzing the (RIP) Random Index Pack.
|
|
*
|
|
* FIXME : If a file doesn't have a RIP, we should iterate the partition headers
|
|
* to collect as much information as possible.
|
|
*
|
|
* This function collects as much information as possible from the partition headers:
|
|
* * Store partition information in the list of partitions
|
|
* * Handle any index table segment present
|
|
*/
|
|
static void
|
|
read_partition_header (GstMXFDemux * demux)
|
|
{
|
|
GstMXFKLV klv;
|
|
|
|
if (gst_mxf_demux_peek_klv_packet (demux, demux->offset, &klv) != GST_FLOW_OK
|
|
|| !mxf_is_partition_pack (&klv.key)) {
|
|
return;
|
|
}
|
|
|
|
if (gst_mxf_demux_handle_partition_pack (demux, &klv) != GST_FLOW_OK) {
|
|
if (klv.data)
|
|
gst_buffer_unref (klv.data);
|
|
return;
|
|
}
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
|
|
if (gst_mxf_demux_peek_klv_packet (demux, demux->offset, &klv) != GST_FLOW_OK)
|
|
return;
|
|
|
|
while (mxf_is_fill (&klv.key)) {
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
if (gst_mxf_demux_peek_klv_packet (demux, demux->offset,
|
|
&klv) != GST_FLOW_OK)
|
|
return;
|
|
}
|
|
|
|
if (!mxf_is_index_table_segment (&klv.key)
|
|
&& demux->current_partition->partition.header_byte_count) {
|
|
demux->offset += demux->current_partition->partition.header_byte_count;
|
|
if (gst_mxf_demux_peek_klv_packet (demux, demux->offset,
|
|
&klv) != GST_FLOW_OK)
|
|
return;
|
|
}
|
|
|
|
while (mxf_is_fill (&klv.key)) {
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
if (gst_mxf_demux_peek_klv_packet (demux, demux->offset,
|
|
&klv) != GST_FLOW_OK)
|
|
return;
|
|
}
|
|
|
|
if (demux->current_partition->partition.index_byte_count
|
|
&& mxf_is_index_table_segment (&klv.key)) {
|
|
guint64 index_end_offset =
|
|
demux->offset + demux->current_partition->partition.index_byte_count;
|
|
|
|
while (demux->offset < index_end_offset) {
|
|
if (mxf_is_index_table_segment (&klv.key))
|
|
gst_mxf_demux_handle_index_table_segment (demux, &klv);
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
|
|
if (gst_mxf_demux_peek_klv_packet (demux, demux->offset,
|
|
&klv) != GST_FLOW_OK)
|
|
return;
|
|
}
|
|
}
|
|
|
|
while (mxf_is_fill (&klv.key)) {
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
if (gst_mxf_demux_peek_klv_packet (demux, demux->offset,
|
|
&klv) != GST_FLOW_OK)
|
|
return;
|
|
}
|
|
|
|
if (mxf_is_generic_container_system_item (&klv.key) ||
|
|
mxf_is_generic_container_essence_element (&klv.key) ||
|
|
mxf_is_avid_essence_container_essence_element (&klv.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;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_demux_handle_random_index_pack (GstMXFDemux * demux, GstMXFKLV * klv)
|
|
{
|
|
guint i;
|
|
GList *l;
|
|
GstMapInfo map;
|
|
gboolean ret;
|
|
GstFlowReturn flowret;
|
|
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Handling random index pack of size %" G_GSIZE_FORMAT " at offset %"
|
|
G_GUINT64_FORMAT, klv->length, klv->offset);
|
|
|
|
if (demux->random_index_pack) {
|
|
GST_DEBUG_OBJECT (demux, "Already parsed random index pack");
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
flowret = gst_mxf_demux_fill_klv (demux, klv);
|
|
if (flowret != GST_FLOW_OK)
|
|
return flowret;
|
|
|
|
gst_buffer_map (klv->data, &map, GST_MAP_READ);
|
|
ret =
|
|
mxf_random_index_pack_parse (&klv->key, map.data, map.size,
|
|
&demux->random_index_pack);
|
|
gst_buffer_unmap (klv->data, &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 gint
|
|
compare_index_table_segment (MXFIndexTableSegment * sa,
|
|
MXFIndexTableSegment * sb)
|
|
{
|
|
if (sa->body_sid != sb->body_sid)
|
|
return (sa->body_sid < sb->body_sid) ? -1 : 1;
|
|
if (sa->index_sid != sb->index_sid)
|
|
return (sa->index_sid < sb->index_sid) ? -1 : 1;
|
|
if (sa->index_start_position != sb->index_start_position)
|
|
return (sa->index_start_position < sb->index_start_position) ? -1 : 1;
|
|
|
|
/* If all the above are equal ... the index table segments are only equal if
|
|
* their instance ID are equal. Until March 2022 the FFmpeg MXF muxer would
|
|
* write the same instance id for the various (different) index table
|
|
* segments, we therefore only check instance ID *after* all the above
|
|
* properties to make sure they are really different. */
|
|
if (mxf_uuid_is_equal (&sa->instance_id, &sb->instance_id))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_demux_handle_index_table_segment (GstMXFDemux * demux, GstMXFKLV * klv)
|
|
{
|
|
MXFIndexTableSegment *segment;
|
|
GstMapInfo map;
|
|
gboolean ret;
|
|
GList *tmp;
|
|
GstFlowReturn flowret;
|
|
|
|
flowret = gst_mxf_demux_fill_klv (demux, klv);
|
|
if (flowret != GST_FLOW_OK)
|
|
return flowret;
|
|
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Handling index table segment of size %" G_GSIZE_FORMAT " at offset %"
|
|
G_GUINT64_FORMAT, klv->length, klv->offset);
|
|
|
|
segment = g_new0 (MXFIndexTableSegment, 1);
|
|
|
|
gst_buffer_map (klv->data, &map, GST_MAP_READ);
|
|
ret = mxf_index_table_segment_parse (&klv->key, segment, map.data, map.size);
|
|
gst_buffer_unmap (klv->data, &map);
|
|
|
|
if (!ret) {
|
|
GST_ERROR_OBJECT (demux, "Parsing index table segment failed");
|
|
g_free (segment);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
/* Drop it if we already saw it. Ideally we should be able to do this before
|
|
parsing (by checking instance UID) */
|
|
if (g_list_find_custom (demux->pending_index_table_segments, segment,
|
|
(GCompareFunc) compare_index_table_segment)) {
|
|
GST_DEBUG_OBJECT (demux, "Already in pending list");
|
|
mxf_index_table_segment_reset (segment);
|
|
g_free (segment);
|
|
return GST_FLOW_OK;
|
|
}
|
|
for (tmp = demux->index_tables; tmp; tmp = tmp->next) {
|
|
GstMXFDemuxIndexTable *table = (GstMXFDemuxIndexTable *) tmp->data;
|
|
if (g_array_binary_search (table->segments, segment,
|
|
(GCompareFunc) compare_index_table_segment, NULL)) {
|
|
GST_DEBUG_OBJECT (demux, "Already handled");
|
|
mxf_index_table_segment_reset (segment);
|
|
g_free (segment);
|
|
return GST_FLOW_OK;
|
|
}
|
|
}
|
|
|
|
demux->pending_index_table_segments =
|
|
g_list_insert_sorted (demux->pending_index_table_segments, segment,
|
|
(GCompareFunc) compare_index_table_segment);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_demux_peek_klv_packet (GstMXFDemux * demux, guint64 offset,
|
|
GstMXFKLV * klv)
|
|
{
|
|
GstBuffer *buffer = NULL;
|
|
const guint8 *data;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GstMapInfo map;
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
gchar str[48];
|
|
#endif
|
|
|
|
memset (klv, 0, sizeof (GstMXFKLV));
|
|
klv->offset = offset;
|
|
|
|
/* 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 (&klv->key, map.data, 16);
|
|
|
|
/* Decode BER encoded packet length */
|
|
if ((map.data[16] & 0x80) == 0) {
|
|
klv->length = map.data[16];
|
|
klv->data_offset = 17;
|
|
} else {
|
|
guint slen = map.data[16] & 0x7f;
|
|
|
|
klv->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;
|
|
klv->length = 0;
|
|
while (slen) {
|
|
klv->length = (klv->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 (klv->length > G_MAXUINT) {
|
|
GST_ERROR_OBJECT (demux,
|
|
"Unsupported KLV packet length: %" G_GSIZE_FORMAT, klv->length);
|
|
ret = GST_FLOW_ERROR;
|
|
goto beach;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Found KLV packet at offset %" G_GUINT64_FORMAT " with key %s and length "
|
|
"%" G_GSIZE_FORMAT, offset, mxf_ul_to_string (&klv->key, str),
|
|
klv->length);
|
|
|
|
beach:
|
|
if (buffer)
|
|
gst_buffer_unref (buffer);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_demux_fill_klv (GstMXFDemux * demux, GstMXFKLV * klv)
|
|
{
|
|
if (klv->data)
|
|
return GST_FLOW_OK;
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Pulling %" G_GSIZE_FORMAT " bytes from offset %" G_GUINT64_FORMAT,
|
|
klv->length, klv->offset + klv->data_offset);
|
|
return gst_mxf_demux_pull_range (demux, klv->offset + klv->data_offset,
|
|
klv->length, &klv->data);
|
|
}
|
|
|
|
/* Call when done with a klv. Will release the buffer (if any) and will update
|
|
* the demuxer offset position. Do *NOT* call if you do not want the demuxer
|
|
* offset to be updated */
|
|
static void
|
|
gst_mxf_demux_consume_klv (GstMXFDemux * demux, GstMXFKLV * klv)
|
|
{
|
|
if (klv->data) {
|
|
gst_buffer_unref (klv->data);
|
|
klv->data = NULL;
|
|
}
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Consuming KLV offset:%" G_GUINT64_FORMAT " data_offset:%"
|
|
G_GUINT64_FORMAT " length:%" G_GSIZE_FORMAT " consumed:%"
|
|
G_GUINT64_FORMAT, klv->offset, klv->data_offset, klv->length,
|
|
klv->consumed);
|
|
if (klv->consumed)
|
|
demux->offset = klv->offset + klv->consumed;
|
|
else
|
|
demux->offset += klv->data_offset + klv->length;
|
|
}
|
|
|
|
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;
|
|
GstMapInfo map;
|
|
GstFlowReturn flow_ret;
|
|
GstMXFKLV klv;
|
|
|
|
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;
|
|
}
|
|
|
|
/* Peek for klv at filesize - pack_size */
|
|
if (gst_mxf_demux_peek_klv_packet (demux, filesize - pack_size,
|
|
&klv) != GST_FLOW_OK) {
|
|
GST_DEBUG_OBJECT (demux, "Failed pulling random index pack key");
|
|
return;
|
|
}
|
|
|
|
if (!mxf_is_random_index_pack (&klv.key)) {
|
|
GST_DEBUG_OBJECT (demux, "No random index pack");
|
|
return;
|
|
}
|
|
|
|
demux->offset = filesize - pack_size;
|
|
flow_ret = gst_mxf_demux_handle_random_index_pack (demux, &klv);
|
|
if (klv.data)
|
|
gst_buffer_unref (klv.data);
|
|
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;
|
|
GstMXFKLV klv;
|
|
GstFlowReturn flow = GST_FLOW_OK;
|
|
GstMXFDemuxPartition *old_partition = demux->current_partition;
|
|
|
|
GST_DEBUG_OBJECT (demux, "Parsing footer metadata");
|
|
|
|
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:
|
|
GST_LOG_OBJECT (demux, "Peeking partition pack at offset %" G_GUINT64_FORMAT,
|
|
demux->offset);
|
|
|
|
/* Process Partition Pack */
|
|
flow = gst_mxf_demux_peek_klv_packet (demux, demux->offset, &klv);
|
|
if (G_UNLIKELY (flow != GST_FLOW_OK))
|
|
goto out;
|
|
|
|
if (!mxf_is_partition_pack (&klv.key))
|
|
goto out;
|
|
|
|
if (gst_mxf_demux_handle_partition_pack (demux, &klv) != GST_FLOW_OK) {
|
|
if (klv.data)
|
|
gst_buffer_unref (klv.data);
|
|
goto out;
|
|
}
|
|
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
|
|
/* If there's no Header Metadata in this partition, jump to the previous
|
|
* one */
|
|
if (demux->current_partition->partition.header_byte_count == 0) {
|
|
/* Reached the first partition, bail out */
|
|
if (demux->current_partition->partition.this_partition == 0)
|
|
goto out;
|
|
|
|
demux->offset =
|
|
demux->run_in + demux->current_partition->partition.prev_partition;
|
|
goto next_try;
|
|
}
|
|
|
|
/* Next up should be an optional fill pack followed by a primer pack */
|
|
while (TRUE) {
|
|
flow = gst_mxf_demux_peek_klv_packet (demux, demux->offset, &klv);
|
|
if (G_UNLIKELY (flow != GST_FLOW_OK)) {
|
|
/* If ever we can't get the next KLV, jump to the previous partition */
|
|
if (!demux->current_partition->partition.prev_partition)
|
|
goto out;
|
|
demux->offset =
|
|
demux->run_in + demux->current_partition->partition.prev_partition;
|
|
goto next_try;
|
|
}
|
|
|
|
if (mxf_is_fill (&klv.key)) {
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
} else if (mxf_is_primer_pack (&klv.key)) {
|
|
/* Update primer mapping if present (jump to previous if it failed) */
|
|
if (!demux->current_partition->primer.mappings) {
|
|
if (gst_mxf_demux_handle_primer_pack (demux, &klv) != GST_FLOW_OK) {
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
if (!demux->current_partition->partition.prev_partition)
|
|
goto out;
|
|
demux->offset =
|
|
demux->run_in +
|
|
demux->current_partition->partition.prev_partition;
|
|
goto next_try;
|
|
}
|
|
}
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
break;
|
|
} else {
|
|
if (!demux->current_partition->partition.prev_partition)
|
|
goto out;
|
|
demux->offset =
|
|
demux->run_in + demux->current_partition->partition.prev_partition;
|
|
goto next_try;
|
|
}
|
|
}
|
|
|
|
/* parse metadata for this partition */
|
|
while (demux->offset <
|
|
demux->run_in + demux->current_partition->primer.offset +
|
|
demux->current_partition->partition.header_byte_count) {
|
|
flow = gst_mxf_demux_peek_klv_packet (demux, demux->offset, &klv);
|
|
if (G_UNLIKELY (flow != GST_FLOW_OK)) {
|
|
if (!demux->current_partition->partition.prev_partition)
|
|
goto out;
|
|
demux->offset =
|
|
demux->run_in + demux->current_partition->partition.prev_partition;
|
|
goto next_try;
|
|
}
|
|
|
|
if (mxf_is_metadata (&klv.key)) {
|
|
flow = gst_mxf_demux_handle_metadata (demux, &klv);
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
|
|
if (G_UNLIKELY (flow != GST_FLOW_OK)) {
|
|
gst_mxf_demux_reset_metadata (demux);
|
|
if (!demux->current_partition->partition.prev_partition)
|
|
goto out;
|
|
demux->offset =
|
|
demux->run_in + demux->current_partition->partition.prev_partition;
|
|
goto next_try;
|
|
}
|
|
} else if (mxf_is_descriptive_metadata (&klv.key)) {
|
|
gst_mxf_demux_handle_descriptive_metadata (demux, &klv);
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
} else {
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
}
|
|
}
|
|
|
|
/* resolve references etc */
|
|
if (!demux->preface || gst_mxf_demux_resolve_references (demux) !=
|
|
GST_FLOW_OK || gst_mxf_demux_update_tracks (demux) != GST_FLOW_OK) {
|
|
/* Don't attempt to parse metadata from this partition again */
|
|
demux->current_partition->parsed_metadata = TRUE;
|
|
/* Skip to previous partition or bail out */
|
|
if (!demux->current_partition->partition.prev_partition)
|
|
goto out;
|
|
demux->offset =
|
|
demux->run_in + demux->current_partition->partition.prev_partition;
|
|
goto next_try;
|
|
}
|
|
|
|
out:
|
|
demux->offset = old_offset;
|
|
demux->current_partition = old_partition;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_mxf_demux_handle_klv_packet (GstMXFDemux * demux, GstMXFKLV * klv,
|
|
gboolean peek)
|
|
{
|
|
MXFUL *key = &klv->key;
|
|
#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", klv->length,
|
|
demux->offset, mxf_ul_to_string (key, key_str));
|
|
} else if (mxf_is_partition_pack (key)) {
|
|
ret = gst_mxf_demux_handle_partition_pack (demux, klv);
|
|
} else if (mxf_is_primer_pack (key)) {
|
|
ret = gst_mxf_demux_handle_primer_pack (demux, klv);
|
|
} else if (mxf_is_metadata (key)) {
|
|
ret = gst_mxf_demux_handle_metadata (demux, klv);
|
|
} else if (mxf_is_descriptive_metadata (key)) {
|
|
ret = gst_mxf_demux_handle_descriptive_metadata (demux, klv);
|
|
} else if (mxf_is_generic_container_system_item (key)) {
|
|
if (demux->pending_index_table_segments)
|
|
collect_index_table_segments (demux);
|
|
ret = gst_mxf_demux_handle_generic_container_system_item (demux, klv);
|
|
} else if (mxf_is_generic_container_essence_element (key) ||
|
|
mxf_is_avid_essence_container_essence_element (key)) {
|
|
if (demux->pending_index_table_segments)
|
|
collect_index_table_segments (demux);
|
|
ret =
|
|
gst_mxf_demux_handle_generic_container_essence_element (demux, klv,
|
|
peek);
|
|
} else if (mxf_is_random_index_pack (key)) {
|
|
ret = gst_mxf_demux_handle_random_index_pack (demux, klv);
|
|
|
|
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, klv);
|
|
} else if (mxf_is_fill (key)) {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Skipping filler packet of size %" G_GSIZE_FORMAT " at offset %"
|
|
G_GUINT64_FORMAT, klv->length, demux->offset);
|
|
} else {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Skipping unknown packet of size %" G_GSIZE_FORMAT " at offset %"
|
|
G_GUINT64_FORMAT ", key: %s", klv->length,
|
|
demux->offset, mxf_ul_to_string (key, key_str));
|
|
}
|
|
|
|
beach:
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
gst_mxf_demux_set_partition_for_offset (GstMXFDemux * demux, guint64 offset)
|
|
{
|
|
GList *l;
|
|
|
|
GST_LOG_OBJECT (demux, "offset %" G_GUINT64_FORMAT, offset);
|
|
|
|
/* 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;
|
|
}
|
|
if (demux->current_partition)
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Current partition now %p (body_sid:%d index_sid:%d this_partition:%"
|
|
G_GUINT64_FORMAT ")", demux->current_partition,
|
|
demux->current_partition->partition.body_sid,
|
|
demux->current_partition->partition.index_sid,
|
|
demux->current_partition->partition.this_partition);
|
|
else
|
|
GST_DEBUG_OBJECT (demux, "Haven't found partition for offset yet");
|
|
}
|
|
|
|
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, index_start_position;
|
|
GstMXFDemuxIndex index_entry = { 0, };
|
|
|
|
GST_DEBUG_OBJECT (demux, "Trying to find essence element %" G_GINT64_FORMAT
|
|
" of track 0x%08x with body_sid %u (keyframe %d)", *position,
|
|
etrack->track_number, etrack->body_sid, keyframe);
|
|
|
|
/* Get entry from index table if present */
|
|
if (find_edit_entry (demux, etrack, *position, keyframe, &index_entry)) {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Got position %" G_GINT64_FORMAT " at offset %" G_GUINT64_FORMAT,
|
|
index_entry.dts, index_entry.offset);
|
|
*position = index_entry.dts;
|
|
return index_entry.offset;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (demux, "Not found in index table");
|
|
|
|
/* Fallback to track offsets */
|
|
|
|
if (!demux->random_access) {
|
|
/* Best effort for push mode */
|
|
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 (etrack->duration > 0 && *position >= etrack->duration) {
|
|
GST_WARNING_OBJECT (demux, "Position after end of essence track");
|
|
return -1;
|
|
}
|
|
|
|
from_track_offset:
|
|
|
|
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;
|
|
}
|
|
|
|
gst_mxf_demux_set_partition_for_offset (demux, demux->offset);
|
|
|
|
for (i = 0; i < demux->essence_tracks->len; i++) {
|
|
GstMXFDemuxEssenceTrack *t = g_ptr_array_index (demux->essence_tracks, i);
|
|
|
|
if (index_start_position != -1 && t == etrack)
|
|
t->position = index_start_position;
|
|
else
|
|
t->position = (demux->offset == demux->run_in) ? 0 : -1;
|
|
GST_LOG_OBJECT (demux, "Setting track %d position to %" G_GINT64_FORMAT,
|
|
t->track_id, t->position);
|
|
}
|
|
|
|
/* Else peek at all essence elements and complete our
|
|
* index until we find the requested element
|
|
*/
|
|
while (ret == GST_FLOW_OK) {
|
|
GstMXFKLV klv;
|
|
|
|
GST_LOG_OBJECT (demux, "Pulling from offset %" G_GINT64_FORMAT,
|
|
demux->offset);
|
|
ret = gst_mxf_demux_peek_klv_packet (demux, demux->offset, &klv);
|
|
|
|
if (ret == GST_FLOW_EOS) {
|
|
/* Handle EOS */
|
|
for (i = 0; i < demux->essence_tracks->len; i++) {
|
|
GstMXFDemuxEssenceTrack *t =
|
|
g_ptr_array_index (demux->essence_tracks, 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
GST_LOG_OBJECT (demux,
|
|
"pulling gave flow:%s track->position:%" G_GINT64_FORMAT,
|
|
gst_flow_get_name (ret), etrack->position);
|
|
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, &klv, TRUE);
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
}
|
|
|
|
GST_LOG_OBJECT (demux,
|
|
"Handling gave flow:%s track->position:%" G_GINT64_FORMAT
|
|
" looking for %" G_GINT64_FORMAT, gst_flow_get_name (ret),
|
|
etrack->position, *position);
|
|
|
|
/* If we found the position read it from the index again */
|
|
if (((ret == GST_FLOW_OK && etrack->position == *position + 1) ||
|
|
(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;
|
|
if (find_edit_entry (demux, etrack, *position, keyframe, &index_entry)) {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Got position %" G_GINT64_FORMAT " at offset %" G_GUINT64_FORMAT,
|
|
index_entry.dts, index_entry.offset);
|
|
*position = index_entry.dts;
|
|
return index_entry.offset;
|
|
}
|
|
goto from_track_offset;
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
GstMXFKLV klv;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
gboolean force_switch = FALSE;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (demux->state == GST_MXF_DEMUX_STATE_ESSENCE) {
|
|
g_assert (demux->current_partition->single_track
|
|
&& demux->current_partition->single_track->wrapping !=
|
|
MXF_ESSENCE_WRAPPING_FRAME_WRAPPING);
|
|
/* Feeding essence directly (i.e. in the middle of a custom/clip KLV) */
|
|
ret =
|
|
gst_mxf_demux_handle_generic_container_essence_element (demux,
|
|
&demux->current_partition->clip_klv, FALSE);
|
|
gst_mxf_demux_consume_klv (demux, &demux->current_partition->clip_klv);
|
|
if (ret == GST_FLOW_OK
|
|
&& demux->current_partition->single_track->position >=
|
|
demux->current_partition->single_track->duration) {
|
|
/* We are done with the contents of this clip/custom wrapping, force the
|
|
* switch to the next non-EOS track */
|
|
GST_DEBUG_OBJECT (demux, "Single track EOS, switch");
|
|
force_switch = TRUE;
|
|
}
|
|
|
|
} else {
|
|
|
|
ret = gst_mxf_demux_peek_klv_packet (demux, demux->offset, &klv);
|
|
|
|
/* FIXME
|
|
*
|
|
* Move this EOS handling to a separate function
|
|
*/
|
|
if (ret == GST_FLOW_EOS && demux->src->len > 0) {
|
|
guint i;
|
|
GstMXFDemuxPad *p = NULL;
|
|
|
|
GST_DEBUG_OBJECT (demux, "EOS HANDLING");
|
|
|
|
for (i = 0; i < demux->src->len; i++) {
|
|
GstMXFDemuxPad *p = g_ptr_array_index (demux->src, i);
|
|
|
|
GST_DEBUG_OBJECT (p,
|
|
"eos:%d current_essence_track_position:%" G_GINT64_FORMAT
|
|
" position:%" G_GINT64_FORMAT " duration:%" G_GINT64_FORMAT, p->eos,
|
|
p->current_essence_track_position,
|
|
p->current_essence_track->position,
|
|
p->current_essence_track->duration);
|
|
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;
|
|
|
|
GST_DEBUG_OBJECT (p, "Trying on earliest");
|
|
|
|
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);
|
|
if (p->current_essence_track->wrapping !=
|
|
MXF_ESSENCE_WRAPPING_FRAME_WRAPPING) {
|
|
demux->state = GST_MXF_DEMUX_STATE_ESSENCE;
|
|
demux->current_partition->clip_klv.consumed =
|
|
offset - demux->current_partition->clip_klv.offset;
|
|
} else
|
|
demux->state = GST_MXF_DEMUX_STATE_KLV;
|
|
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, &klv, FALSE);
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
|
|
/* We entered a new partition */
|
|
if (ret == GST_FLOW_OK && mxf_is_partition_pack (&klv.key)) {
|
|
GstMXFDemuxPartition *partition = demux->current_partition;
|
|
gboolean partition_done = FALSE;
|
|
|
|
/* Grab footer metadata if needed */
|
|
if (demux->pull_footer_metadata
|
|
&& partition->partition.type == MXF_PARTITION_PACK_HEADER
|
|
&& (!partition->partition.closed || !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 the partition has some content, do post-checks */
|
|
if (partition->partition.body_sid != 0) {
|
|
guint64 lowest_offset = G_MAXUINT64;
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Entered partition (body_sid:%d index_sid:%d body_offset:%"
|
|
G_GUINT64_FORMAT "), checking positions",
|
|
partition->partition.body_sid, partition->partition.index_sid,
|
|
partition->partition.body_offset);
|
|
|
|
if (partition->single_track) {
|
|
/* Fast-path for single track partition */
|
|
if (partition->single_track->position == -1
|
|
&& partition->partition.body_offset == 0) {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"First time in partition, setting track position to 0");
|
|
partition->single_track->position = 0;
|
|
} else if (partition->single_track->position == -1) {
|
|
GST_ERROR_OBJECT (demux,
|
|
"Unknown track position, consuming data from first partition entry");
|
|
lowest_offset =
|
|
partition->partition.this_partition +
|
|
partition->essence_container_offset;
|
|
partition->clip_klv.consumed = 0;
|
|
} else if (partition->single_track->position != 0) {
|
|
GstMXFDemuxIndex entry;
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Track already at another position : %" G_GINT64_FORMAT,
|
|
partition->single_track->position);
|
|
if (find_edit_entry (demux, partition->single_track,
|
|
partition->single_track->position, FALSE, &entry)) {
|
|
lowest_offset = entry.offset;
|
|
} else if (partition->single_track->position >=
|
|
partition->single_track->duration) {
|
|
GST_DEBUG_OBJECT (demux, "Track fully consumed, partition done");
|
|
partition_done = TRUE;
|
|
}
|
|
}
|
|
} else {
|
|
guint i;
|
|
for (i = 0; i < demux->essence_tracks->len; i++) {
|
|
GstMXFDemuxEssenceTrack *etrack =
|
|
g_ptr_array_index (demux->essence_tracks, i);
|
|
|
|
if (etrack->body_sid != partition->partition.body_sid)
|
|
continue;
|
|
if (etrack->position == -1 && partition->partition.body_offset == 0) {
|
|
GST_DEBUG_OBJECT (demux, "Resetting track %d to position 0",
|
|
etrack->track_id);
|
|
|
|
etrack->position = 0;
|
|
} else if (etrack->position != 0) {
|
|
GstMXFDemuxIndex entry;
|
|
if (find_edit_entry (demux, etrack,
|
|
etrack->position, FALSE, &entry)) {
|
|
if (lowest_offset == G_MAXUINT64
|
|
|| entry.offset < lowest_offset)
|
|
lowest_offset = entry.offset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (partition_done || lowest_offset != G_MAXUINT64) {
|
|
GstMXFDemuxPartition *next_partition = NULL;
|
|
GList *cur_part = g_list_find (demux->partitions, partition);
|
|
if (cur_part && cur_part->next)
|
|
next_partition = (GstMXFDemuxPartition *) cur_part->next->data;
|
|
|
|
/* If we have completely processed this partition, skip to next partition */
|
|
if (partition_done
|
|
|| lowest_offset > next_partition->partition.this_partition) {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Partition entirely processed, skipping to next one");
|
|
demux->offset = next_partition->partition.this_partition;
|
|
} else {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Skipping to demuxer offset %" G_GUINT64_FORMAT " (from %"
|
|
G_GUINT64_FORMAT ")", lowest_offset, demux->offset);
|
|
demux->offset = lowest_offset;
|
|
if (partition->single_track
|
|
&& partition->single_track->wrapping !=
|
|
MXF_ESSENCE_WRAPPING_FRAME_WRAPPING) {
|
|
demux->state = GST_MXF_DEMUX_STATE_ESSENCE;
|
|
demux->current_partition->clip_klv.consumed =
|
|
demux->offset - demux->current_partition->clip_klv.offset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)) && (force_switch
|
|
|| demux->segment.position - earliest->position >
|
|
demux->max_drift)) {
|
|
guint64 offset;
|
|
gint64 position;
|
|
|
|
GST_DEBUG_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);
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Switching to offset %" G_GUINT64_FORMAT " for position %"
|
|
G_GINT64_FORMAT " on track %d (body_sid:%d index_sid:%d)",
|
|
demux->offset, position, earliest->current_essence_track->track_id,
|
|
earliest->current_essence_track->body_sid,
|
|
earliest->current_essence_track->index_sid);
|
|
if (demux->current_partition->single_track
|
|
&& demux->current_partition->single_track->wrapping !=
|
|
MXF_ESSENCE_WRAPPING_FRAME_WRAPPING) {
|
|
demux->state = GST_MXF_DEMUX_STATE_ESSENCE;
|
|
demux->current_partition->clip_klv.consumed =
|
|
offset - demux->current_partition->clip_klv.offset;
|
|
} else
|
|
demux->state = GST_MXF_DEMUX_STATE_KLV;
|
|
|
|
earliest->current_essence_track->position = position;
|
|
GST_DEBUG_OBJECT (earliest, "Switching to this pad");
|
|
break;
|
|
}
|
|
}
|
|
|
|
beach:
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
gst_mxf_demux_loop (GstPad * pad)
|
|
{
|
|
GstMXFDemux *demux = NULL;
|
|
GstFlowReturn flow = GST_FLOW_OK;
|
|
|
|
demux = GST_MXF_DEMUX (gst_pad_get_parent (pad));
|
|
|
|
if (demux->state == GST_MXF_DEMUX_STATE_UNKNOWN) {
|
|
GstMXFKLV klv;
|
|
|
|
/* Skip run-in, which is at most 64K and is finished
|
|
* by a header partition pack */
|
|
while (demux->offset < 64 * 1024) {
|
|
if ((flow =
|
|
gst_mxf_demux_peek_klv_packet (demux, demux->offset,
|
|
&klv)) != GST_FLOW_OK)
|
|
goto pause;
|
|
|
|
if (mxf_is_header_partition_pack (&klv.key)) {
|
|
GST_DEBUG_OBJECT (demux,
|
|
"Found header partition pack at offset %" G_GUINT64_FORMAT,
|
|
demux->offset);
|
|
demux->state = GST_MXF_DEMUX_STATE_KLV;
|
|
demux->run_in = demux->offset;
|
|
break;
|
|
}
|
|
demux->offset++;
|
|
}
|
|
|
|
if (G_UNLIKELY (demux->run_in == -1)) {
|
|
GST_ERROR_OBJECT (demux, "No valid header partition pack found");
|
|
flow = GST_FLOW_ERROR;
|
|
goto pause;
|
|
}
|
|
|
|
/* Grab the RIP at the end of the file (if present) */
|
|
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.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->src->len == 0) {
|
|
GST_ELEMENT_ERROR (demux, STREAM, WRONG_TYPE,
|
|
("This stream contains no data."),
|
|
("got eos and didn't find any streams"));
|
|
} else 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_FLOW_ERROR (demux, flow);
|
|
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;
|
|
const guint8 *data = NULL;
|
|
gboolean res;
|
|
GstMXFKLV klv;
|
|
#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;
|
|
demux->state = GST_MXF_DEMUX_STATE_UNKNOWN;
|
|
}
|
|
|
|
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->state == GST_MXF_DEMUX_STATE_UNKNOWN) {
|
|
/* 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;
|
|
demux->state = GST_MXF_DEMUX_STATE_KLV;
|
|
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 (demux->state == GST_MXF_DEMUX_STATE_UNKNOWN) {
|
|
/* Need more data */
|
|
if (demux->offset < 64 * 1024)
|
|
break;
|
|
|
|
GST_ERROR_OBJECT (demux, "No valid header partition pack found");
|
|
ret = GST_FLOW_ERROR;
|
|
break;
|
|
}
|
|
|
|
if (gst_adapter_available (demux->adapter) < 17)
|
|
break;
|
|
|
|
/* FIXME : Handle non-klv state */
|
|
g_assert (demux->state == GST_MXF_DEMUX_STATE_KLV);
|
|
|
|
/* Now actually do something */
|
|
memset (&klv, 0, sizeof (GstMXFKLV));
|
|
|
|
/* Pull 16 byte key and first byte of BER encoded length */
|
|
data = gst_adapter_map (demux->adapter, 17);
|
|
|
|
memcpy (&klv.key, data, 16);
|
|
|
|
GST_DEBUG_OBJECT (demux, "Got KLV packet with key %s",
|
|
mxf_ul_to_string (&klv.key, str));
|
|
|
|
/* Decode BER encoded packet length */
|
|
if ((data[16] & 0x80) == 0) {
|
|
klv.length = data[16];
|
|
klv.data_offset = 17;
|
|
} else {
|
|
guint slen = data[16] & 0x7f;
|
|
|
|
klv.data_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;
|
|
|
|
klv.length = 0;
|
|
while (slen) {
|
|
klv.length = (klv.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 (klv.length > G_MAXUINT) {
|
|
GST_ERROR_OBJECT (demux,
|
|
"Unsupported KLV packet length: %" G_GSIZE_FORMAT, klv.length);
|
|
ret = GST_FLOW_ERROR;
|
|
break;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (demux, "KLV packet with key %s has length "
|
|
"%" G_GSIZE_FORMAT, mxf_ul_to_string (&klv.key, str), klv.length);
|
|
|
|
if (gst_adapter_available (demux->adapter) < klv.data_offset + klv.length)
|
|
break;
|
|
|
|
gst_adapter_flush (demux->adapter, klv.data_offset);
|
|
|
|
if (klv.length > 0) {
|
|
klv.data = gst_adapter_take_buffer (demux->adapter, klv.length);
|
|
|
|
ret = gst_mxf_demux_handle_klv_packet (demux, &klv, FALSE);
|
|
}
|
|
gst_mxf_demux_consume_klv (demux, &klv);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Given a stream time for an output pad, figure out:
|
|
* * The Essence track for that stream time
|
|
* * The position on that track
|
|
*/
|
|
static gboolean
|
|
gst_mxf_demux_pad_to_track_and_position (GstMXFDemux * demux,
|
|
GstMXFDemuxPad * pad, GstClockTime streamtime,
|
|
GstMXFDemuxEssenceTrack ** etrack, gint64 * position)
|
|
{
|
|
gint64 material_position;
|
|
guint64 sum = 0;
|
|
guint i;
|
|
MXFMetadataSourceClip *clip = NULL;
|
|
gchar str[96];
|
|
|
|
/* Convert to material position */
|
|
material_position =
|
|
gst_util_uint64_scale (streamtime, pad->material_track->edit_rate.n,
|
|
pad->material_track->edit_rate.d * GST_SECOND);
|
|
|
|
GST_DEBUG_OBJECT (pad,
|
|
"streamtime %" GST_TIME_FORMAT " position %" G_GINT64_FORMAT,
|
|
GST_TIME_ARGS (streamtime), material_position);
|
|
|
|
|
|
/* Find sequence component covering that position */
|
|
for (i = 0; i < pad->material_track->parent.sequence->n_structural_components;
|
|
i++) {
|
|
clip =
|
|
MXF_METADATA_SOURCE_CLIP (pad->material_track->parent.sequence->
|
|
structural_components[i]);
|
|
GST_LOG_OBJECT (pad,
|
|
"clip %d start_position:%" G_GINT64_FORMAT " duration %"
|
|
G_GINT64_FORMAT, clip->source_track_id, clip->start_position,
|
|
clip->parent.duration);
|
|
if (clip->parent.duration <= 0)
|
|
break;
|
|
if ((sum + clip->parent.duration) > material_position)
|
|
break;
|
|
sum += clip->parent.duration;
|
|
}
|
|
|
|
if (i == pad->material_track->parent.sequence->n_structural_components) {
|
|
GST_WARNING_OBJECT (pad, "Requested position beyond the last clip");
|
|
/* Outside of current components. Setting to the end of the last clip */
|
|
material_position = sum;
|
|
sum -= clip->parent.duration;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (pad, "Looking for essence track for track_id:%d umid:%s",
|
|
clip->source_track_id, mxf_umid_to_string (&clip->source_package_id,
|
|
str));
|
|
|
|
/* Get the corresponding essence track for the given source package and stream id */
|
|
for (i = 0; i < demux->essence_tracks->len; i++) {
|
|
GstMXFDemuxEssenceTrack *track =
|
|
g_ptr_array_index (demux->essence_tracks, i);
|
|
GST_LOG_OBJECT (pad, "Looking at essence track body_sid:%d index_sid:%d",
|
|
track->body_sid, track->index_sid);
|
|
if (clip->source_track_id == 0 || (track->track_id == clip->source_track_id
|
|
&& mxf_umid_is_equal (&clip->source_package_id,
|
|
&track->source_package_uid))) {
|
|
GST_DEBUG_OBJECT (pad,
|
|
"Found matching essence track body_sid:%d index_sid:%d",
|
|
track->body_sid, track->index_sid);
|
|
*etrack = track;
|
|
*position = material_position - sum;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* Given a track+position for a given pad, figure out the resulting stream time */
|
|
static gboolean
|
|
gst_mxf_demux_pad_get_stream_time (GstMXFDemux * demux,
|
|
GstMXFDemuxPad * pad, GstMXFDemuxEssenceTrack * etrack,
|
|
gint64 position, GstClockTime * stream_time)
|
|
{
|
|
guint i;
|
|
guint64 sum = 0;
|
|
MXFMetadataSourceClip *clip = NULL;
|
|
|
|
/* Find the component for that */
|
|
/* Find sequence component covering that position */
|
|
for (i = 0; i < pad->material_track->parent.sequence->n_structural_components;
|
|
i++) {
|
|
clip =
|
|
MXF_METADATA_SOURCE_CLIP (pad->material_track->parent.sequence->
|
|
structural_components[i]);
|
|
GST_LOG_OBJECT (pad,
|
|
"clip %d start_position:%" G_GINT64_FORMAT " duration %"
|
|
G_GINT64_FORMAT, clip->source_track_id, clip->start_position,
|
|
clip->parent.duration);
|
|
if (etrack->track_id == clip->source_track_id
|
|
&& mxf_umid_is_equal (&clip->source_package_id,
|
|
&etrack->source_package_uid)) {
|
|
/* This is the clip */
|
|
break;
|
|
}
|
|
/* Fetch in the next one */
|
|
sum += clip->parent.duration;
|
|
}
|
|
|
|
/* Theoretically impossible */
|
|
if (i == pad->material_track->parent.sequence->n_structural_components) {
|
|
/* Outside of current components ?? */
|
|
return FALSE;
|
|
}
|
|
|
|
*stream_time =
|
|
gst_util_uint64_scale (position + sum,
|
|
pad->material_track->edit_rate.d * GST_SECOND,
|
|
pad->material_track->edit_rate.n);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_mxf_demux_pad_set_position (GstMXFDemux * demux, GstMXFDemuxPad * p,
|
|
GstClockTime start)
|
|
{
|
|
guint i;
|
|
guint64 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;
|
|
p->current_material_track_position = p->current_essence_track_position;
|
|
|
|
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 += clip->parent.duration;
|
|
|
|
if (gst_util_uint64_scale (sum, p->material_track->edit_rate.d * GST_SECOND,
|
|
p->material_track->edit_rate.n) > start)
|
|
break;
|
|
}
|
|
|
|
if (i == p->material_track->parent.sequence->n_structural_components) {
|
|
p->position =
|
|
gst_util_uint64_scale (sum, p->material_track->edit_rate.d * GST_SECOND,
|
|
p->material_track->edit_rate.n);
|
|
p->position_accumulated_error = 0.0;
|
|
p->current_material_track_position = sum;
|
|
|
|
gst_mxf_demux_pad_set_component (demux, p, i);
|
|
return;
|
|
}
|
|
|
|
if (clip->parent.duration > 0)
|
|
sum -= clip->parent.duration;
|
|
|
|
start -=
|
|
gst_util_uint64_scale (sum, p->material_track->edit_rate.d * GST_SECOND,
|
|
p->material_track->edit_rate.n);
|
|
|
|
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 = gst_util_uint64_scale (sum,
|
|
GST_SECOND * p->material_track->edit_rate.d,
|
|
p->material_track->edit_rate.n) + gst_util_uint64_scale (essence_offset,
|
|
GST_SECOND * p->current_essence_track->source_track->edit_rate.d,
|
|
p->current_essence_track->source_track->edit_rate.n);
|
|
p->position_accumulated_error = 0.0;
|
|
p->current_material_track_position = sum + essence_offset;
|
|
}
|
|
|
|
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 (sum + p->current_component->parent.duration,
|
|
p->material_track->edit_rate.d * GST_SECOND,
|
|
p->material_track->edit_rate.n);
|
|
p->position_accumulated_error = 0.0;
|
|
p->current_material_track_position =
|
|
sum + p->current_component->parent.duration;
|
|
}
|
|
}
|
|
|
|
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_ptr_array_index (demux->essence_tracks, 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;
|
|
|
|
/* This function can also be called when a RIP is not present. This can happen
|
|
* if index table segments were discovered while scanning the file */
|
|
if (demux->random_index_pack) {
|
|
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;
|
|
}
|
|
|
|
if (demux->pending_index_table_segments == NULL) {
|
|
GST_DEBUG_OBJECT (demux, "No pending index table segments to collect");
|
|
return;
|
|
}
|
|
|
|
GST_LOG_OBJECT (demux, "Collecting pending index table segments");
|
|
|
|
for (l = demux->pending_index_table_segments; l; l = l->next) {
|
|
MXFIndexTableSegment *segment = l->data;
|
|
GstMXFDemuxIndexTable *t = NULL;
|
|
GList *k;
|
|
guint didx;
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
gchar str[48];
|
|
#endif
|
|
|
|
GST_LOG_OBJECT (demux,
|
|
"Collecting from segment bodySID:%d indexSID:%d instance_id: %s",
|
|
segment->body_sid, segment->index_sid,
|
|
mxf_uuid_to_string (&segment->instance_id, str));
|
|
|
|
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->max_temporal_offset = 0;
|
|
t->segments = g_array_new (FALSE, TRUE, sizeof (MXFIndexTableSegment));
|
|
g_array_set_clear_func (t->segments,
|
|
(GDestroyNotify) mxf_index_table_segment_reset);
|
|
t->reordered_delta_entry = -1;
|
|
t->reverse_temporal_offsets = g_array_new (FALSE, TRUE, 1);
|
|
demux->index_tables = g_list_prepend (demux->index_tables, t);
|
|
}
|
|
|
|
/* Store index segment */
|
|
g_array_append_val (t->segments, *segment);
|
|
|
|
/* Check if temporal reordering tables should be pre-calculated */
|
|
for (didx = 0; didx < segment->n_delta_entries; didx++) {
|
|
MXFDeltaEntry *delta = &segment->delta_entries[didx];
|
|
if (delta->pos_table_index == -1) {
|
|
if (t->reordered_delta_entry != -1 && didx != t->reordered_delta_entry)
|
|
GST_WARNING_OBJECT (demux,
|
|
"Index Table specifies more than one stream using temporal reordering (%d and %d)",
|
|
didx, t->reordered_delta_entry);
|
|
else
|
|
t->reordered_delta_entry = didx;
|
|
} else if (delta->pos_table_index > 0)
|
|
GST_WARNING_OBJECT (delta,
|
|
"Index Table uses fractional offset, please file a bug");
|
|
}
|
|
|
|
}
|
|
|
|
/* Handle temporal offset if present and needed */
|
|
for (l = demux->index_tables; l; l = l->next) {
|
|
GstMXFDemuxIndexTable *table = l->data;
|
|
guint segidx;
|
|
|
|
/* No reordered entries, skip */
|
|
if (table->reordered_delta_entry == -1)
|
|
continue;
|
|
|
|
GST_DEBUG_OBJECT (demux,
|
|
"bodySID:%d indexSID:%d Calculating reverse temporal offset table",
|
|
table->body_sid, table->index_sid);
|
|
|
|
for (segidx = 0; segidx < table->segments->len; segidx++) {
|
|
MXFIndexTableSegment *s =
|
|
&g_array_index (table->segments, MXFIndexTableSegment, segidx);
|
|
guint start = s->index_start_position;
|
|
guint stop =
|
|
s->index_duration ? start + s->index_duration : start +
|
|
s->n_index_entries;
|
|
guint entidx = 0;
|
|
|
|
if (stop > table->reverse_temporal_offsets->len)
|
|
g_array_set_size (table->reverse_temporal_offsets, stop);
|
|
|
|
for (entidx = 0; entidx < s->n_index_entries; entidx++) {
|
|
MXFIndexEntry *entry = &s->index_entries[entidx];
|
|
gint8 offs = -entry->temporal_offset;
|
|
/* Check we don't exceed boundaries */
|
|
if ((start + entidx + entry->temporal_offset) < 0 ||
|
|
(start + entidx + entry->temporal_offset) >
|
|
table->reverse_temporal_offsets->len) {
|
|
GST_ERROR_OBJECT (demux,
|
|
"Temporal offset exceeds boundaries. entry:%d offset:%d max:%d",
|
|
start + entidx, entry->temporal_offset,
|
|
table->reverse_temporal_offsets->len);
|
|
} else {
|
|
/* Applying the temporal offset gives us the entry that should contain this PTS.
|
|
* We store the reverse temporal offset on that entry, i.e. the value it should apply
|
|
* to go from DTS to PTS. (i.e. entry.pts = entry.dts + rto[idx]) */
|
|
g_array_index (table->reverse_temporal_offsets, gint8,
|
|
start + entidx + entry->temporal_offset) = offs;
|
|
if (entry->temporal_offset > (gint) table->max_temporal_offset) {
|
|
GST_LOG_OBJECT (demux,
|
|
"Updating max temporal offset to %d (was %d)",
|
|
entry->temporal_offset, table->max_temporal_offset);
|
|
table->max_temporal_offset = entry->temporal_offset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
g_list_free_full (demux->pending_index_table_segments, g_free);
|
|
demux->pending_index_table_segments = NULL;
|
|
|
|
GST_DEBUG_OBJECT (demux, "Done collecting segments");
|
|
}
|
|
|
|
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 (seqnum == demux->seqnum) {
|
|
GST_DEBUG_OBJECT (demux, "Already handled requested seek");
|
|
return TRUE;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (demux, "Seek %" GST_PTR_FORMAT, 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);
|
|
|
|
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 initially configured to %" GST_SEGMENT_FORMAT, &seeksegment);
|
|
|
|
/* Initialize and reset ourselves if needed */
|
|
if (flush || seeksegment.position != demux->segment.position) {
|
|
GList *tmp;
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* Reset all single-track KLV tracking */
|
|
for (tmp = demux->partitions; tmp; tmp = tmp->next) {
|
|
GstMXFDemuxPartition *partition = (GstMXFDemuxPartition *) tmp->data;
|
|
if (partition->single_track) {
|
|
partition->clip_klv.consumed = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
keyunit_ts = seeksegment.position;
|
|
|
|
/* Do a first round without changing positions. This is needed to figure out
|
|
* the supporting keyframe position (if any) */
|
|
for (i = 0; i < demux->src->len; i++) {
|
|
GstMXFDemuxPad *p = g_ptr_array_index (demux->src, i);
|
|
GstMXFDemuxEssenceTrack *etrack;
|
|
gint64 track_pos, seeked_pos;
|
|
|
|
/* Get track and track position for requested time, handles out of bound internally */
|
|
if (!gst_mxf_demux_pad_to_track_and_position (demux, p,
|
|
seeksegment.position, &etrack, &track_pos))
|
|
goto invalid_position;
|
|
|
|
GST_LOG_OBJECT (p,
|
|
"track %d (body_sid:%d index_sid:%d), position %" G_GINT64_FORMAT,
|
|
etrack->track_id, etrack->body_sid, etrack->index_sid, track_pos);
|
|
|
|
/* Find supporting keyframe entry */
|
|
seeked_pos = track_pos;
|
|
if (gst_mxf_demux_find_essence_element (demux, etrack, &seeked_pos,
|
|
TRUE) == -1) {
|
|
/* Couldn't find entry, ignore */
|
|
break;
|
|
}
|
|
|
|
GST_LOG_OBJECT (p,
|
|
"track %d (body_sid:%d index_sid:%d), position %" G_GINT64_FORMAT
|
|
" entry position %" G_GINT64_FORMAT, etrack->track_id, etrack->body_sid,
|
|
etrack->index_sid, track_pos, seeked_pos);
|
|
|
|
if (seeked_pos != track_pos) {
|
|
GstClockTime stream_time;
|
|
if (!gst_mxf_demux_pad_get_stream_time (demux, p, etrack, seeked_pos,
|
|
&stream_time))
|
|
goto invalid_position;
|
|
GST_LOG_OBJECT (p, "Need to seek to stream time %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (stream_time));
|
|
keyunit_ts = MIN (seeksegment.position, stream_time);
|
|
}
|
|
}
|
|
|
|
if (keyframe && keyunit_ts != seeksegment.position) {
|
|
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);
|
|
}
|
|
|
|
/* Finally set the position to the calculated position */
|
|
if (flush || keyunit_ts != demux->segment.position) {
|
|
guint64 new_offset = -1;
|
|
|
|
/* 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, seeksegment.position);
|
|
|
|
/* 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->position_accumulated_error = 0.0;
|
|
p->current_material_track_position -=
|
|
gst_util_uint64_scale (p->current_essence_track_position -
|
|
position,
|
|
p->material_track->edit_rate.n *
|
|
p->current_essence_track->source_track->edit_rate.d,
|
|
p->material_track->edit_rate.d *
|
|
p->current_essence_track->source_track->edit_rate.n);
|
|
}
|
|
p->current_essence_track_position = position;
|
|
}
|
|
p->current_essence_track->position = p->current_essence_track_position;
|
|
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);
|
|
/* Reset the state accordingly */
|
|
if (demux->current_partition->single_track
|
|
&& demux->current_partition->single_track->wrapping !=
|
|
MXF_ESSENCE_WRAPPING_FRAME_WRAPPING)
|
|
demux->state = GST_MXF_DEMUX_STATE_ESSENCE;
|
|
else
|
|
demux->state = GST_MXF_DEMUX_STATE_KLV;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* 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_ptr_array_index (demux->essence_tracks, 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;
|
|
}
|
|
|
|
invalid_position:
|
|
{
|
|
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);
|
|
}
|
|
gst_pad_start_task (demux->sinkpad,
|
|
(GstTaskFunction) gst_mxf_demux_loop, demux->sinkpad, NULL);
|
|
GST_PAD_STREAM_UNLOCK (demux->sinkpad);
|
|
GST_WARNING_OBJECT (demux, "Requested seek position is not valid");
|
|
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 =
|
|
format ==
|
|
GST_FORMAT_DEFAULT ? mxfpad->current_material_track_position :
|
|
mxfpad->position;
|
|
|
|
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;
|
|
gint64 duration;
|
|
|
|
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 (!gst_pad_query_duration (pad, GST_FORMAT_TIME, &duration)) {
|
|
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, duration);
|
|
} 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, duration);
|
|
else
|
|
gst_query_set_seeking (query, GST_FORMAT_TIME, FALSE, -1, -1);
|
|
|
|
gst_query_unref (peerquery);
|
|
}
|
|
|
|
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;
|
|
|
|
if (demux->src->len == 0) {
|
|
GST_ELEMENT_ERROR (demux, STREAM, WRONG_TYPE,
|
|
("This stream contains no data."),
|
|
("got eos and didn't find any streams"));
|
|
}
|
|
|
|
for (i = 0; i < demux->essence_tracks->len; i++) {
|
|
GstMXFDemuxEssenceTrack *t =
|
|
g_ptr_array_index (demux->essence_tracks, 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_ptr_array_index (demux->essence_tracks, 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_ptr_array_index (demux->essence_tracks, 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 &&
|
|
MXF_METADATA_BASE (demux->preface)->resolved ==
|
|
MXF_METADATA_BASE_RESOLVE_STATE_SUCCESS)
|
|
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_ptr_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, DEFAULT_MAX_DRIFT,
|
|
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_static_pad_template (gstelement_class,
|
|
&mxf_sink_template);
|
|
gst_element_class_add_static_pad_template (gstelement_class,
|
|
&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 = DEFAULT_MAX_DRIFT;
|
|
|
|
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_ptr_array_new_with_free_func ((GDestroyNotify)
|
|
gst_mxf_demux_essence_track_free);
|
|
|
|
gst_segment_init (&demux->segment, GST_FORMAT_TIME);
|
|
|
|
gst_mxf_demux_reset (demux);
|
|
}
|