changeset 311:e07654b1049c libavformat

initial seek support
author bellard
date Mon, 10 Nov 2003 18:48:33 +0000
parents 944c8edaf609
children 8a04d2e1be2f
files avi.h avidec.c
diffstat 2 files changed, 293 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/avi.h	Mon Nov 10 18:47:52 2003 +0000
+++ b/avi.h	Mon Nov 10 18:48:33 2003 +0000
@@ -13,6 +13,9 @@
 #define AVI_MAX_RIFF_SIZE       0x40000000LL
 #define AVI_MASTER_INDEX_SIZE   256
 
+/* index flags */
+#define AVIIF_INDEX             0x10
+
 offset_t start_tag(ByteIOContext *pb, const char *tag);
 void end_tag(ByteIOContext *pb, offset_t start);
 
--- a/avidec.c	Mon Nov 10 18:47:52 2003 +0000
+++ b/avidec.c	Mon Nov 10 18:48:33 2003 +0000
@@ -21,18 +21,33 @@
 #include "dv.h"
 
 //#define DEBUG
+//#define DEBUG_SEEK
 
-typedef struct AVIIndex {
-    unsigned char tag[4];
-    unsigned int flags, pos, len;
-    struct AVIIndex *next;
-} AVIIndex;
+typedef struct AVIIndexEntry {
+    unsigned int flags;
+    unsigned int pos;
+    unsigned int cum_len; /* sum of all lengths before this packet */
+} AVIIndexEntry;
+
+typedef struct AVIStream {
+    AVIIndexEntry *index_entries;
+    int nb_index_entries;
+    int index_entries_allocated_size;
+    int frame_offset; /* current frame (video) or byte (audio) counter
+                         (used to compute the pts) */
+    int scale;
+    int rate;    
+    int sample_size; /* audio only data */
+    
+    int new_frame_offset; /* temporary storage (used during seek) */
+    int cum_len; /* temporary storage (used during seek) */
+} AVIStream;
 
 typedef struct {
     int64_t  riff_end;
     int64_t  movi_end;
     offset_t movi_list;
-    AVIIndex *first, *last;
+    int index_loaded;
     DVDemuxContext* dv_demux;
 } AVIContext;
 
@@ -74,8 +89,11 @@
     unsigned int size, nb_frames;
     int i, n;
     AVStream *st;
+    AVIStream *ast;
     int xan_video = 0;  /* hack to support Xan A/V */
 
+    av_set_pts_info(s, 64, 1, AV_TIME_BASE);
+
     if (get_riff(avi, pb) < 0)
         return -1;
 
@@ -100,7 +118,8 @@
             print_tag("list", tag1, 0);
 #endif
             if (tag1 == MKTAG('m', 'o', 'v', 'i')) {
-                avi->movi_end = url_ftell(pb) + size - 4;
+                avi->movi_list = url_ftell(pb) - 4;
+                avi->movi_end = avi->movi_list + size;
 #ifdef DEBUG
                 printf("movi end=%Lx\n", avi->movi_end);
 #endif
@@ -115,9 +134,14 @@
 	    url_fskip(pb, 4 * 4);
             n = get_le32(pb);
             for(i=0;i<n;i++) {
+                AVIStream *ast;
                 st = av_new_stream(s, i);
                 if (!st)
                     goto fail;
+                ast = av_mallocz(sizeof(AVIStream));
+                if (!ast)
+                    goto fail;
+                st->priv_data = ast;
 	    }
             url_fskip(pb, size - 7 * 4);
             break;
@@ -159,7 +183,8 @@
                 } 
 
                 st = s->streams[stream_index];
-
+                ast = st->priv_data;
+                
                 get_le32(pb); /* flags */
                 get_le16(pb); /* priority */
                 get_le16(pb); /* language */
@@ -168,27 +193,28 @@
                 rate = get_le32(pb); /* rate */
 
                 if(scale && rate){
-                    st->codec.frame_rate = rate;
-                    st->codec.frame_rate_base = scale;
                 }else if(frame_period){
-                    st->codec.frame_rate = 1000000;
-                    st->codec.frame_rate_base = frame_period;
+                    rate = 1000000;
+                    scale = frame_period;
                 }else{
-                    st->codec.frame_rate = 25;
-                    st->codec.frame_rate_base = 1;
+                    rate = 25;
+                    scale = 1;
                 }
+                ast->rate = rate;
+                ast->scale = scale;
+                st->codec.frame_rate = rate;
+                st->codec.frame_rate_base = scale;
                 get_le32(pb); /* start */
                 nb_frames = get_le32(pb);
                 st->start_time = 0;
                 st->duration = (double)nb_frames * 
                     st->codec.frame_rate_base * AV_TIME_BASE / 
                     st->codec.frame_rate;
-                
 		url_fskip(pb, size - 9 * 4);
                 break;
             case MKTAG('a', 'u', 'd', 's'):
                 {
-                    unsigned int length, rate;
+                    unsigned int length;
 
                     codec_type = CODEC_TYPE_AUDIO;
 
@@ -197,19 +223,23 @@
                         break;
                     } 
                     st = s->streams[stream_index];
-
+                    ast = st->priv_data;
+                    
                     get_le32(pb); /* flags */
                     get_le16(pb); /* priority */
                     get_le16(pb); /* language */
                     get_le32(pb); /* initial frame */
-                    get_le32(pb); /* scale */
-                    rate = get_le32(pb);
+                    ast->scale = get_le32(pb); /* scale */
+                    ast->rate = get_le32(pb);
                     get_le32(pb); /* start */
                     length = get_le32(pb); /* length, in samples or bytes */
+                    get_le32(pb); /* buffer size */
+                    get_le32(pb); /* quality */
+                    ast->sample_size = get_le32(pb); /* sample ssize */
                     st->start_time = 0;
-                    if (rate != 0)
-                        st->duration = (int64_t)length * AV_TIME_BASE / rate;
-                    url_fskip(pb, size - 9 * 4);
+                    if (ast->rate != 0)
+                        st->duration = (int64_t)length * AV_TIME_BASE / ast->rate;
+                    url_fskip(pb, size - 12 * 4);
                 }
                 break;
             default:
@@ -274,6 +304,9 @@
                         url_fskip(pb, 1);
                     /* special case time: To support Xan DPCM, hardcode
                      * the format if Xxan is the video codec */
+                    st->need_parsing = 1;
+                    /* force parsing as several audio frames can be in
+                       one packet */
                     if (xan_video)
                         st->codec.codec_id = CODEC_ID_XAN_DPCM;
                     break;
@@ -377,10 +410,31 @@
 	        size = dv_produce_packet(avi->dv_demux, pkt,
 		                         pkt->data, pkt->size);
 		pkt->destruct = dstr;
+                pkt->flags |= PKT_FLAG_KEY;
 	    } else {
+                AVStream *st;
+                AVIStream *ast;
+                st = s->streams[n];
+                ast = st->priv_data;
+
+                /* XXX: how to handle B frames in avi ? */
+                pkt->pts = ((int64_t)ast->frame_offset * ast->scale* AV_TIME_BASE) / ast->rate;
                 pkt->stream_index = n;
-                pkt->flags |= PKT_FLAG_KEY; // FIXME: We really should read 
-		                            //        index for that
+                /* FIXME: We really should read index for that */
+                if (st->codec.codec_type == CODEC_TYPE_VIDEO) {
+                    if (ast->frame_offset < ast->nb_index_entries) {
+                        if (ast->index_entries[ast->frame_offset].flags & AVIIF_INDEX)
+                            pkt->flags |= PKT_FLAG_KEY; 
+                    } else {
+                        /* if no index, better to say that all frames
+                           are key frames */
+                        pkt->flags |= PKT_FLAG_KEY;
+                    }
+                    ast->frame_offset++;
+                } else {
+                    ast->frame_offset += pkt->size;
+                    pkt->flags |= PKT_FLAG_KEY; 
+                }
 	    }
             return size;
         }
@@ -388,6 +442,214 @@
     return -1;
 }
 
+/* XXX: we make the implicit supposition that the position are sorted
+   for each stream */
+static int avi_read_idx1(AVFormatContext *s, int size)
+{
+    ByteIOContext *pb = &s->pb;
+    int nb_index_entries, i;
+    AVStream *st;
+    AVIStream *ast;
+    AVIIndexEntry *ie, *entries;
+    unsigned int index, tag, flags, pos, len;
+    
+    nb_index_entries = size / 16;
+    if (nb_index_entries <= 0)
+        return -1;
+
+    /* read the entries and sort them in each stream component */
+    for(i = 0; i < nb_index_entries; i++) {
+        tag = get_le32(pb);
+        flags = get_le32(pb);
+        pos = get_le32(pb);
+        len = get_le32(pb);
+#if defined(DEBUG_SEEK) && 0
+        printf("%d: tag=0x%x flags=0x%x pos=0x%x len=%d\n", 
+               i, tag, flags, pos, len);
+#endif
+        index = ((tag & 0xff) - '0') * 10;
+        index += ((tag >> 8) & 0xff) - '0';
+        if (index >= s->nb_streams)
+            continue;
+        st = s->streams[index];
+        ast = st->priv_data;
+        
+        entries = av_fast_realloc(ast->index_entries,
+                                  &ast->index_entries_allocated_size,
+                                  (ast->nb_index_entries + 1) * 
+                                  sizeof(AVIIndexEntry));
+        if (entries) {
+            ast->index_entries = entries;
+            ie = &entries[ast->nb_index_entries++];
+            ie->flags = flags;
+            ie->pos = pos;
+            ie->cum_len = ast->cum_len;
+            ast->cum_len += len;
+        }
+    }
+    return 0;
+}
+
+static int avi_load_index(AVFormatContext *s)
+{
+    AVIContext *avi = s->priv_data;
+    ByteIOContext *pb = &s->pb;
+    uint32_t tag, size;
+
+    url_fseek(pb, avi->movi_end, SEEK_SET);
+#ifdef DEBUG_SEEK
+    printf("movi_end=0x%llx\n", avi->movi_end);
+#endif
+    for(;;) {
+        if (url_feof(pb))
+            break;
+        tag = get_le32(pb);
+        size = get_le32(pb);
+#ifdef DEBUG_SEEK
+        printf("tag=%c%c%c%c size=0x%x\n",
+               tag & 0xff,
+               (tag >> 8) & 0xff,
+               (tag >> 16) & 0xff,
+               (tag >> 24) & 0xff,
+               size);
+#endif
+        switch(tag) {
+        case MKTAG('i', 'd', 'x', '1'):
+            if (avi_read_idx1(s, size) < 0)
+                goto skip;
+            else
+                goto the_end;
+            break;
+        default:
+        skip:
+            size += (size & 1);
+            url_fskip(pb, size);
+            break;
+        }
+    }
+ the_end:
+    return 0;
+}
+
+/* return the index entry whose position is immediately >= 'wanted_pos' */
+static int locate_frame_in_index(AVIIndexEntry *entries, 
+                                 int nb_entries, int wanted_pos)
+{
+    int a, b, m, pos;
+    
+    a = 0;
+    b = nb_entries - 1;
+    while (a <= b) {
+        m = (a + b) >> 1;
+        pos = entries[m].pos;
+        if (pos == wanted_pos)
+            goto found;
+        else if (pos > wanted_pos) {
+            b = m - 1;
+        } else {
+            a = m + 1;
+        }
+    }
+    m = a;
+    if (m > 0)
+        m--;
+ found:
+    return m;
+}
+
+static int avi_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp)
+{
+    AVIContext *avi = s->priv_data;
+    AVStream *st;
+    AVIStream *ast;
+    int frame_number, i;
+    int64_t pos;
+
+    if (!avi->index_loaded) {
+        /* we only load the index on demand */
+        avi_load_index(s);
+        avi->index_loaded = 1;
+    }
+    if (stream_index < 0) {
+        for(i = 0; i < s->nb_streams; i++) {
+            st = s->streams[i];
+            if (st->codec.codec_type == CODEC_TYPE_VIDEO)
+                goto found;
+        }
+        return -1;
+    found:
+        stream_index = i;
+    }
+
+    st = s->streams[stream_index];
+    if (st->codec.codec_type != CODEC_TYPE_VIDEO)
+        return -1;
+    ast = st->priv_data;
+    /* compute the frame number */
+    frame_number = (timestamp * ast->rate) /
+        (ast->scale * (int64_t)AV_TIME_BASE);
+#ifdef DEBUG_SEEK
+    printf("timestamp=%0.3f nb_indexes=%d frame_number=%d\n", 
+           (double)timestamp / AV_TIME_BASE,
+           ast->nb_index_entries, frame_number);
+#endif
+    /* find a closest key frame before */
+    if (frame_number >= ast->nb_index_entries)
+        return -1;
+    while (frame_number >= 0 &&
+           !(ast->index_entries[frame_number].flags & AVIIF_INDEX))
+        frame_number--;
+    if (frame_number < 0)
+        return -1;
+    ast->new_frame_offset = frame_number;
+
+    /* find the position */
+    pos = ast->index_entries[frame_number].pos;
+
+#ifdef DEBUG_SEEK
+    printf("key_frame_number=%d pos=0x%llx\n", 
+           frame_number, pos);
+#endif
+    
+    /* update the frame counters for all the other stream by looking
+       at the positions just after the one found */
+    for(i = 0; i < s->nb_streams; i++) {
+        int j;
+        if (i != stream_index) {
+            st = s->streams[i];
+            ast = st->priv_data;
+            if (ast->nb_index_entries <= 0)
+                return -1;
+            j = locate_frame_in_index(ast->index_entries,
+                                      ast->nb_index_entries,
+                                      pos);
+            /* get next frame */
+            if ((j  + 1) < ast->nb_index_entries)
+                j++;
+            /* extract the current frame number */
+            if (st->codec.codec_type == CODEC_TYPE_VIDEO)           
+                ast->new_frame_offset = j;
+            else
+                ast->new_frame_offset = ast->index_entries[j].cum_len;
+        }
+    }
+    
+    /* everything is OK now. We can update the frame offsets */
+    for(i = 0; i < s->nb_streams; i++) {
+        st = s->streams[i];
+        ast = st->priv_data;
+        ast->frame_offset = ast->new_frame_offset;
+#ifdef DEBUG_SEEK
+        printf("%d: frame_offset=%d\n", i, 
+               ast->frame_offset);
+#endif
+    }
+    /* do the seek */
+    pos += avi->movi_list;
+    url_fseek(&s->pb, pos, SEEK_SET);
+    return 0;
+}
+
 static int avi_read_close(AVFormatContext *s)
 {
     int i;
@@ -395,7 +657,9 @@
 
     for(i=0;i<s->nb_streams;i++) {
         AVStream *st = s->streams[i];
-//        av_free(st->priv_data);
+        AVIStream *ast = st->priv_data;
+        av_free(ast->index_entries);
+        av_free(ast);
         av_free(st->codec.extradata);
         av_free(st->codec.palctrl);
     }
@@ -428,6 +692,7 @@
     avi_read_header,
     avi_read_packet,
     avi_read_close,
+    avi_read_seek,
 };
 
 int avidec_init(void)