[Ffmpeg-devel] Re: [PATCH] seeking in GXF

Reimar D?ffinger Reimar.Doeffinger
Mon Jul 31 17:44:47 CEST 2006


Hello,
On Mon, Jul 31, 2006 at 03:05:04PM +0200, Baptiste Coudurier wrote:
> I think st->time_base might need a check for 0. I get floating point
> exception on sample without fps track tag.

See (and test) attached version, it now leaves the timebase at the
default if it can't find anything better. This of course results in
horribly incorrect timestamps.
Thus, it now also reads the UMF packet, which must be always present in
conforming files. If you find the use of ff_log2_tab weird, well, that's
because each possible fps value corresponds to one bit. Which means you
could set multiple fps values, which would horribly break timestamps...
I decided to got the quick-and-dirty way, this is only a fallback
anyway.
I will apply if you don't come over any problems with your files on this
version.

> Btw, I think there is also a problem with last audio packet, which might
> not contain 32768 samples in uncompressed: last field in MEDIA_PKT will
> be less and that must be taken into account while reading. Atm I can
> hear glitches on last packet, I'll look at it

I think this is already marked in line 482 of the current code, the
"NOTE:...". But if you can _hear_ glitches this means that the last packet
has been filled up with some (almost) random crap, right? If the ffmpeg
muxer creates such files, it might be a good idea to "fix" there as well.

Greetings,
Reimar D?ffinger
-------------- next part --------------
Index: libavformat/gxf.c
===================================================================
--- libavformat/gxf.c	(revision 5870)
+++ libavformat/gxf.c	(working copy)
@@ -17,6 +17,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 #include "avformat.h"
+#include "common.h"
 
 typedef enum {
     PKT_MAP = 0xbc,
@@ -26,6 +27,32 @@
     PKT_UMF = 0xfd
 } pkt_type_t;
 
+typedef enum {
+    MAT_NAME = 0x40,
+    MAT_FIRST_FIELD = 0x41,
+    MAT_LAST_FIELD = 0x42,
+    MAT_MARK_IN = 0x43,
+    MAT_MARK_OUT = 0x44,
+    MAT_SIZE = 0x45
+} mat_tag_t;
+
+typedef enum {
+    TRACK_NAME = 0x4c,
+    TRACK_AUX = 0x4d,
+    TRACK_VER = 0x4e,
+    TRACK_MPG_AUX = 0x4f,
+    TRACK_FPS = 0x50,
+    TRACK_LINES = 0x51,
+    TRACK_FPF = 0x52
+} track_tag_t;
+
+typedef struct {
+    int32_t first_field;
+    int32_t last_field;
+    AVRational frames_per_second;
+    int32_t fields_per_frame;
+} st_info_t;
+
 /**
  * \brief parses a packet header, extracting type and length
  * \param pb ByteIOContext to read header from
@@ -147,11 +174,129 @@
     return s->nb_streams - 1;
 }
 
+/**
+ * \brief filters out interesting tags from material information.
+ * \param len lenght of tag section, will be adjusted to contain remaining bytes
+ * \param si struct to store collected information into
+ */
+static void gxf_material_tags(ByteIOContext *pb, int *len, st_info_t *si) {
+    while (*len >= 2) {
+        mat_tag_t tag = get_byte(pb);
+        int tlen = get_byte(pb);
+        *len -= 2;
+        if (tlen > *len)
+            return;
+        *len -= tlen;
+        switch (tag) {
+            case MAT_FIRST_FIELD:
+                if (tlen != 4)
+                    break;
+                si->first_field = get_be32(pb);
+                tlen = 0;
+                break;
+            case MAT_LAST_FIELD:
+                if (tlen != 4)
+                    break;
+                si->last_field = get_be32(pb);
+                tlen = 0;
+                break;
+        }
+        url_fskip(pb, tlen);
+    }
+}
+
+/**
+ * \brief convert fps tag value to AVRational fps
+ * \param fps fps value from tag
+ * \return fps as AVRational, or 0 / 0 if unknown
+ */
+static AVRational fps_tag2avr(int32_t fps) {
+    static const AVRational map[] = {{0, 0}, {60, 1}, {60000, 1001}, {50, 1},
+        {30, 1}, {30000, 1001}, {25, 1}, {24, 1}, {24000, 1001}};
+    if (fps < 0 || fps > 8) fps = 0;
+    return map[fps];
+}
+
+/**
+ * \brief convert UMF attributes flags to AVRational fps
+ * \param fps fps value from flags
+ * \return fps as AVRational, or 0 / 0 if unknown
+ */
+static AVRational fps_umf2avr(uint32_t flags) {
+    static const AVRational map[] = {{50, 1}, {60000, 1001}, {24, 1},
+        {25, 1}, {30000, 1001}};
+    int idx =  ff_log2_tab[(flags & 0x7c0) >> 6];
+    return map[idx];
+}
+
+/**
+ * \brief filters out interesting tags from track information.
+ * \param len lenght of tag section, will be adjusted to contain remaining bytes
+ * \param si struct to store collected information into
+ */
+static void gxf_track_tags(ByteIOContext *pb, int *len, st_info_t *si) {
+    while (*len >= 2) {
+        track_tag_t tag = get_byte(pb);
+        int tlen = get_byte(pb);
+        *len -= 2;
+        if (tlen > *len)
+            return;
+        *len -= tlen;
+        switch (tag) {
+            case TRACK_FPS:
+                if (tlen != 4)
+                    break;
+                si->frames_per_second = fps_tag2avr(get_be32(pb));
+                tlen = 0;
+                break;
+            case TRACK_FPF:
+                if (tlen != 4)
+                    break;
+                si->fields_per_frame = get_be32(pb);
+                if (si->fields_per_frame != 1 && si->fields_per_frame != 2)
+                    si->fields_per_frame = 1;
+                tlen = 0;
+                break;
+        }
+        url_fskip(pb, tlen);
+    }
+}
+
+/**
+ * \brief read index from FLT packet into stream 0 av_index
+ */
+static void gxf_read_index(AVFormatContext *s, int pkt_len) {
+    ByteIOContext *pb = &s->pb;
+    AVStream *st = s->streams[0];
+    uint32_t fields_per_map = get_le32(pb);
+    uint32_t map_cnt = get_le32(pb);
+    int i;
+    pkt_len -= 8;
+    if (map_cnt > 1000) {
+        av_log(s, AV_LOG_ERROR, "GXF: too many index entries %u (%x)\n", map_cnt, map_cnt);
+        map_cnt = 1000;
+    }
+    if (pkt_len < 4 * map_cnt) {
+        av_log(s, AV_LOG_ERROR, "GXF: invalid index length\n");
+        url_fskip(pb, pkt_len);
+        return;
+    }
+    pkt_len -= 4 * map_cnt;
+    av_add_index_entry(st, 0, 0, 0, 0, 0);
+    for (i = 0; i < map_cnt; i++)
+        av_add_index_entry(st, get_le32(pb) * 1024, i * fields_per_map + 1, 0, 0, 0);
+    url_fskip(pb, pkt_len);
+}
+
 static int gxf_header(AVFormatContext *s, AVFormatParameters *ap) {
+    AVRational main_timebase = {0, 0};
+    AVStream *st;
     ByteIOContext *pb = &s->pb;
     pkt_type_t pkt_type;
+    st_info_t si = {-1 , -1, {0, 0}, 1};
     int map_len;
     int len;
+    int i;
     if (!parse_packet_header(pb, &pkt_type, &map_len) || pkt_type != PKT_MAP) {
         av_log(s, AV_LOG_ERROR, "GXF: map packet not found\n");
         return 0;
@@ -168,6 +313,7 @@
         return 0;
     }
     map_len -= len;
+    gxf_material_tags(pb, &len, &si);
     url_fskip(pb, len);
     map_len -= 2;
     len = get_be16(pb); // length of track description
@@ -178,11 +324,13 @@
     map_len -= len;
     while (len > 0) {
         int track_type, track_id, track_len;
+        int idx;
         len -= 4;
         track_type = get_byte(pb);
         track_id = get_byte(pb);
         track_len = get_be16(pb);
         len -= track_len;
+        gxf_track_tags(pb, &track_len, &si);
         url_fskip(pb, track_len);
         if (!(track_type & 0x80)) {
            av_log(s, AV_LOG_ERROR, "GXF: invalid track type %x\n", track_type);
@@ -194,26 +342,127 @@
            continue;
         }
         track_id &= 0x3f;
-        get_sindex(s, track_id, track_type);
+        idx = get_sindex(s, track_id, track_type);
+        if (idx < 0) continue;
+        st = s->streams[idx];
+        if (!main_timebase.num || !main_timebase.den) {
+            main_timebase.num = si.frames_per_second.den;
+            main_timebase.den = si.frames_per_second.num * si.fields_per_frame;
+        }
+        if (si.first_field >= 0) {
+            st->start_time = si.first_field;
+            if (si.last_field >= 0)
+                st->duration = si.last_field - si.first_field;
+        }
     }
     if (len < 0)
         av_log(s, AV_LOG_ERROR, "GXF: invalid track description length specified\n");
     if (map_len)
         url_fskip(pb, map_len);
+    if (!parse_packet_header(pb, &pkt_type, &len)) {
+        av_log(s, AV_LOG_ERROR, "GXF: sync lost in header\n");
+        return -1;
+    }
+    if (pkt_type == PKT_FLT) {
+        gxf_read_index(s, len);
+        if (!parse_packet_header(pb, &pkt_type, &len)) {
+            av_log(s, AV_LOG_ERROR, "GXF: sync lost in header\n");
+            return -1;
+        }
+    }
+    if (pkt_type == PKT_UMF) {
+        if (len >= 9) {
+            AVRational fps;
+            len -= 9;
+            url_fskip(pb, 5);
+            fps = fps_umf2avr(get_le32(pb));
+            if (!main_timebase.num || !main_timebase.den) {
+                // this may not always be correct, but simply the best we can get
+                main_timebase.num = fps.den;
+                main_timebase.den = fps.num;
+            }
+        } else
+            av_log(s, AV_LOG_INFO, "GXF: UMF packet too short\n");
+    } else
+        av_log(s, AV_LOG_INFO, "GXF: UMF packet missing\n");
+    url_fskip(pb, len);
+    for (i = 0; i < s->nb_streams; i++) {
+        st = s->streams[i];
+        if (main_timebase.num && main_timebase.den)
+            st->time_base = main_timebase;
+        else {
+            st->start_time = st->duration = AV_NOPTS_VALUE;
+        }
+    }
     return 0;
 }
 
+#define READ_ONE() \
+    { \
+        if (!max_interval-- || url_feof(pb)) \
+            goto out; \
+        tmp = tmp << 8 | get_byte(pb); \
+    }
+
+/**
+ * \brief resync the stream on the next media packet with specified properties
+ * \param max_interval how many bytes to search for matching packet at most
+ * \param track track id the media packet must belong to, -1 for any
+ * \param timestamp minimum timestamp (== field number) the packet must have, -1 for any
+ * \return timestamp of packet found
+ */
+static int64_t gxf_resync_media(AVFormatContext *s, uint64_t max_interval, int track, int timestamp) {
+    uint32_t tmp;
+    uint64_t last_pos;
+    uint64_t last_found_pos = 0;
+    int cur_track;
+    int64_t cur_timestamp = AV_NOPTS_VALUE;
+    int len;
+    ByteIOContext *pb = &s->pb;
+    pkt_type_t type;
+    tmp = 0xff;
+start:
+    while (tmp)
+        READ_ONE();
+    READ_ONE();
+    if (tmp != 1)
+        goto start;
+    last_pos = url_ftell(pb);
+    url_fseek(pb, -5, SEEK_CUR);
+    if (!parse_packet_header(pb, &type, &len) || type != PKT_MEDIA) {
+        url_fseek(pb, last_pos, SEEK_SET);
+        goto start;
+    }
+    get_byte(pb);
+    cur_track = get_byte(pb);
+    cur_timestamp = get_be32(pb);
+    last_found_pos = url_ftell(pb) - 16 - 6;
+    if ((track >= 0 && track != cur_track) || (timestamp >= 0 && timestamp > cur_timestamp)) {
+        url_fseek(pb, last_pos, SEEK_SET);
+        goto start;
+    }
+out:
+    if (last_found_pos)
+        url_fseek(pb, last_found_pos, SEEK_SET);
+    return cur_timestamp;
+}
+
 static int gxf_packet(AVFormatContext *s, AVPacket *pkt) {
     ByteIOContext *pb = &s->pb;
     pkt_type_t pkt_type;
     int pkt_len;
     while (!url_feof(pb)) {
         int track_type, track_id, ret;
+        int field_nr;
         if (!parse_packet_header(pb, &pkt_type, &pkt_len)) {
             if (!url_feof(pb))
                 av_log(s, AV_LOG_ERROR, "GXF: sync lost\n");
             return -1;
         }
+        if (pkt_type == PKT_FLT) {
+            gxf_read_index(s, pkt_len);
+            continue;
+        }
         if (pkt_type != PKT_MEDIA) {
             url_fskip(pb, pkt_len);
             continue;
@@ -225,7 +474,7 @@
         pkt_len -= 16;
         track_type = get_byte(pb);
         track_id = get_byte(pb);
-        get_be32(pb); // field number
+        field_nr = get_be32(pb);
         get_be32(pb); // field information
         get_be32(pb); // "timeline" field number
         get_byte(pb); // flags
@@ -235,11 +484,42 @@
         // as well.
         ret = av_get_packet(pb, pkt, pkt_len);
         pkt->stream_index = get_sindex(s, track_id, track_type);
+        pkt->pts = field_nr;
         return ret;
     }
     return AVERROR_IO;
 }
 
+static int gxf_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) {
+    uint64_t pos;
+    uint64_t maxlen = 100 * 1024 * 1024;
+    AVStream *st = s->streams[0];
+    int64_t start_time = s->streams[stream_index]->start_time;
+    int idx;
+    if (timestamp < start_time) timestamp = start_time;
+    idx = av_index_search_timestamp(st, timestamp - start_time,
+                                    AVSEEK_FLAG_ANY | AVSEEK_FLAG_BACKWARD);
+    if (idx < 0)
+        return -1;
+    pos = st->index_entries[idx].pos;
+    if (idx < st->nb_index_entries - 2)
+        maxlen = st->index_entries[idx + 2].pos - pos;
+    if (maxlen < 200 * 1024) maxlen = 200 * 1024;
+    url_fseek(&s->pb, pos, SEEK_SET);
+    gxf_resync_media(s, maxlen, -1, timestamp);
+    return 0;
+}
+
+static int64_t gxf_read_timestamp(AVFormatContext *s, int stream_index,
+                                  int64_t *pos, int64_t pos_limit) {
+    ByteIOContext *pb = &s->pb;
+    int64_t res;
+    url_fseek(pb, *pos, SEEK_SET);
+    res = gxf_resync_media(s, pos_limit - *pos, -1, -1);
+    *pos = url_ftell(pb);
+    return res;
+}
+
 AVInputFormat gxf_demuxer = {
     "gxf",
     "GXF format",
@@ -248,5 +528,6 @@
     gxf_header,
     gxf_packet,
     NULL,
-    NULL,
+    gxf_seek,
+    gxf_read_timestamp,
 };



More information about the ffmpeg-devel mailing list