[xiph-commits] r16673 - in trunk/ffmpeg2theora: . src

j at svn.xiph.org j at svn.xiph.org
Fri Oct 30 10:35:26 PDT 2009


Author: j
Date: 2009-10-30 10:35:25 -0700 (Fri, 30 Oct 2009)
New Revision: 16673

Modified:
   trunk/ffmpeg2theora/ffmpeg2theora.1
   trunk/ffmpeg2theora/src/ffmpeg2theora.c
   trunk/ffmpeg2theora/src/theorautils.c
   trunk/ffmpeg2theora/src/theorautils.h
Log:
Add indexing to skeleton track encoding as per the spec at http://github.com/cpearce/OggIndex/blob/skeleton-index-per-stream/
patch (patch by Chris Pearce)



Modified: trunk/ffmpeg2theora/ffmpeg2theora.1
===================================================================
--- trunk/ffmpeg2theora/ffmpeg2theora.1	2009-10-30 04:52:14 UTC (rev 16672)
+++ trunk/ffmpeg2theora/ffmpeg2theora.1	2009-10-30 17:35:25 UTC (rev 16673)
@@ -39,6 +39,12 @@
 .B  \-\-no-skeleton
 Disables Ogg Skeleton metadata output.
 .TP
+.B  \-\-seek-index
+Enables keyframe index in skeleton track.
+.TP
+.B  \-\-index-interval
+Set minimum distance between indexed keyframes (in ms, default: 2000)
+.TP
 .B \-s, \-\-starttime
 Start encoding at this time (in seconds).
 .TP

Modified: trunk/ffmpeg2theora/src/ffmpeg2theora.c
===================================================================
--- trunk/ffmpeg2theora/src/ffmpeg2theora.c	2009-10-30 04:52:14 UTC (rev 16672)
+++ trunk/ffmpeg2theora/src/ffmpeg2theora.c	2009-10-30 17:35:25 UTC (rev 16673)
@@ -86,6 +86,8 @@
     SPEEDLEVEL_FLAG,
     PP_FLAG,
     NOSKELETON,
+    SEEK_INDEX,
+    INDEX_INTERVAL,
     INFO_FLAG
 } F2T_FLAGS;
 
@@ -1204,6 +1206,7 @@
           }
         }
 #endif
+
         oggmux_init(&info);
         /*seek to start time*/
         if (this->start_time) {
@@ -1588,6 +1591,12 @@
                 audio_resample_close(this->audio_resample_ctx);
             avcodec_close(aenc);
         }
+
+        /* Write the index out to disk. */
+        if (info.passno != 1) {
+            write_seek_index (&info);
+        }
+
         oggmux_close(&info);
         if (ppContext)
             pp_free_context(ppContext);
@@ -1802,6 +1811,8 @@
         "General output options:\n"
         "  -o, --output           alternative output filename\n"
         "      --no-skeleton      disables ogg skeleton metadata output\n"
+        "      --seek-index       enables keyframe index in skeleton track\n"
+        "      --index-interval   set minimum distance between indexed keyframes (in ms, default: 2000)\n"
         "  -s, --starttime        start encoding at this time (in sec.)\n"
         "  -e, --endtime          end encoding at this time (in sec.)\n"
         "  -p, --preset           encode file with preset.\n"
@@ -1978,6 +1989,8 @@
         {"output",required_argument,NULL,'o'},
         {"skeleton",no_argument,NULL,'k'},
         {"no-skeleton",no_argument,&flag,NOSKELETON},
+        {"seek-index",no_argument,&flag,SEEK_INDEX},
+        {"index-interval",required_argument,&flag,INDEX_INTERVAL},
         {"format",required_argument,NULL,'f'},
         {"width",required_argument,NULL,'x'},
         {"height",required_argument,NULL,'y'},
@@ -2207,6 +2220,13 @@
                         case NOSKELETON:
                             info.with_skeleton=0;
                             break;
+                        case SEEK_INDEX:
+                            info.with_seek_index = 1;
+                            break;
+                        case INDEX_INTERVAL:
+                            info.index_interval = atoi(optarg);
+                            flag = -1;
+                            break;
                         case INFO_FLAG:
                             output_json = 1;
                             break;
@@ -2431,6 +2451,11 @@
         }
     }
 
+    if (info.with_seek_index && !info.with_skeleton) {
+        fprintf(stderr, "ERROR: Cannot use --no-skeleton and --seek-index options together!\n");
+        exit(1);
+    }
+
     if (output_json && !outputfile_set) {
         snprintf(outputfile_name, sizeof(outputfile_name), "-");
         outputfile_set = 1;

Modified: trunk/ffmpeg2theora/src/theorautils.c
===================================================================
--- trunk/ffmpeg2theora/src/theorautils.c	2009-10-30 04:52:14 UTC (rev 16672)
+++ trunk/ffmpeg2theora/src/theorautils.c	2009-10-30 17:35:25 UTC (rev 16673)
@@ -48,6 +48,7 @@
 #include <time.h>
 #include <sys/stat.h>
 #include <assert.h>
+#include <math.h>
 
 #include "theora/theoraenc.h"
 #include "vorbis/codec.h"
@@ -59,17 +60,12 @@
 #include "theorautils.h"
 
 
-static double rint(double x)
-{
-    if (x < 0.0)
-        return (double)(int)(x - 0.5);
-    else
-        return (double)(int)(x + 0.5);
-}
-
 void init_info(oggmux_info *info) {
     info->output_seekable = MAYBE_SEEKABLE;
     info->with_skeleton = 1; /* skeleton is enabled by default    */
+    info->with_seek_index = 0; /* keyframe index disabled by default. */
+    info->index_interval = 2000;
+    info->indexing_complete = 0;
     info->frontend = NULL; /*frontend mode*/
     info->videotime =  0;
     info->audiotime = 0;
@@ -122,6 +118,14 @@
     }
 }
 
+static ogg_uint32_t read_uint32(unsigned const char* p) {
+  ogg_uint32_t i = p[0] +
+                  (p[1] << 8) + 
+                  (p[2] << 16) +
+                  (p[3] << 24);
+  return i;  
+}
+
 static void write16le(unsigned char *ptr,ogg_uint16_t v)
 {
     ptr[0]=v&0xff;
@@ -188,18 +192,77 @@
     assert(info->output_seekable != MAYBE_SEEKABLE);
 }
 
-void add_fishead_packet (oggmux_info *info) {
+#define MAX(a,b) (((a) > (b)) ? (a) : (b))
+#define MIN(a,b) (((a) < (b)) ? (a) : (b))
+
+static ogg_int64_t index_start_time(oggmux_info* info)
+{
+    if (!info->with_seek_index || !info->indexing_complete) {
+        return -1;
+    }
+    return MIN(info->theora_index.start_time, info->vorbis_index.start_time);
+}
+
+static ogg_int64_t index_end_time(oggmux_info* info)
+{
+    if (!info->with_seek_index || !info->indexing_complete) {
+        return -1;
+    }
+    return MAX(info->theora_index.end_time, info->vorbis_index.end_time);
+}
+
+static ogg_int64_t output_file_length(oggmux_info* info)
+{
+    ogg_int64_t offset, length;
+    if (!info->with_seek_index || !info->indexing_complete) {
+        return -1;
+    }
+    offset = ftello(info->outfile);
+    if (fseeko(info->outfile, 0, SEEK_END) < 0) {
+        fprintf(stderr, "ERROR: Can't seek output file to write index!\n");
+        return -1;
+    }
+    length = ftello(info->outfile);
+    if (fseeko(info->outfile, offset, SEEK_SET) < 0) {
+        fprintf(stderr, "ERROR: Can't seek output file to write index!\n");
+        return -1;
+    }
+
+    return length;
+}
+
+
+void add_fishead_packet (oggmux_info *info,
+                         ogg_uint16_t ver_maj,
+                         ogg_uint16_t ver_min) {
     ogg_packet op;
+    size_t packet_size = 0;
+    ogg_uint32_t version = SKELETON_VERSION(ver_maj, ver_min);
 
+    assert(version >= SKELETON_VERSION(3,0) &&
+           version <= SKELETON_VERSION(3,1));
+
+    switch (version) {
+        case SKELETON_VERSION(3,0):
+            packet_size = 64;
+            break;
+        case SKELETON_VERSION(3,1):
+            packet_size = 88;
+            break;
+        default:
+            fprintf (stderr, "ERROR: Unknown skeleton version");
+            exit (1);
+    };
+
     memset (&op, 0, sizeof (op));
 
-    op.packet = _ogg_calloc (64, sizeof(unsigned char));
+    op.packet = _ogg_calloc (packet_size, sizeof(unsigned char));
     if (op.packet == NULL) return;
 
-    memset (op.packet, 0, 64);
+    memset (op.packet, 0, packet_size);
     memcpy (op.packet, FISHEAD_IDENTIFIER, 8); /* identifier */
-    write16le(op.packet+8, SKELETON_VERSION_MAJOR); /* version major */
-    write16le(op.packet+10, SKELETON_VERSION_MINOR); /* version minor */
+    write16le(op.packet+8, ver_maj); /* version major */
+    write16le(op.packet+10, ver_min); /* version minor */
     write64le(op.packet+12, (ogg_int64_t)0); /* presentationtime numerator */
     write64le(op.packet+20, (ogg_int64_t)1000); /* presentationtime denominator */
     write64le(op.packet+28, (ogg_int64_t)0); /* basetime numerator */
@@ -207,9 +270,15 @@
     /* both the numerator are zero hence handled by the memset */
     write32le(op.packet+44, 0); /* UTC time, set to zero for now */
 
+    /* Index start/end time, if unknown or non-indexed, will be -1. */
+    if (version >= SKELETON_VERSION(3,1)) {
+        write64le(op.packet+64, index_start_time(info));
+        write64le(op.packet+72, index_end_time(info));
+        write64le(op.packet+80, output_file_length(info));
+    }
     op.b_o_s = 1; /* its the first packet of the stream */
     op.e_o_s = 0; /* its not the last packet of the stream */
-    op.bytes = 64; /* length of the packet in bytes */
+    op.bytes = packet_size; /* length of the packet in bytes */
 
     ogg_stream_packetin (&info->so, &op); /* adding the packet to the skeleton stream */
     _ogg_free (op.packet);
@@ -309,6 +378,277 @@
 #endif
 }
 
+static int keypoints_per_index(seek_index* index, double duration)
+{
+    double keypoints_per_second = (double)index->packet_interval / 1000.0;
+    return !keypoints_per_second ? 0 : ((int)ceil(duration / keypoints_per_second) + 2);
+}
+
+static int create_index_packet(int num_allocated_keypoints,
+                               ogg_packet* op,
+                               ogg_uint32_t serialno,
+                               int num_used_keypoints)
+{
+    size_t size = 14 + num_allocated_keypoints * KEYPOINT_SIZE;
+    memset (op, 0, sizeof(op));
+    op->packet = malloc(size);
+    if (op->packet == NULL)
+        return -1;
+    memset(op->packet, -1, size);
+    op->b_o_s = 0;
+    op->e_o_s = 0;
+    op->bytes = size;
+
+    /* Write identifier bytes into packet. */
+    memcpy(op->packet, "index", 6);
+
+    /* Write the serialno into packet. */
+    write32le(op->packet+6, serialno);
+
+    /* Write number of valid keypoints in index into packet. */
+    write32le(op->packet+10, num_used_keypoints);
+
+    return 0;
+}
+
+/*
+ * Writes a placeholder index page for a particular stream.
+ */
+static int write_index_placeholder_for_stream (oggmux_info *info,
+                                               seek_index* index,
+                                               ogg_uint32_t serialno)
+{
+    ogg_packet op;
+    ogg_page og;
+    int num_keypoints = keypoints_per_index(&info->theora_index,
+                                            info->duration);
+    if (create_index_packet(num_keypoints, &op, serialno, 0) == -1) {
+        return -1;
+    }
+
+    seek_index_set_max_keypoints(index, num_keypoints);
+
+    /* Remember where we wrote the index pages, so that we can overwrite them
+       once we've encoded the entire file. */
+    index->page_location = ftello(info->outfile);
+
+    /* There should be no packets in the stream. */
+    assert(ogg_stream_flush(&info->so, &og) == 0);
+
+    ogg_stream_packetin(&info->so, &op);
+    free(op.packet);
+
+    while (ogg_stream_flush(&info->so, &og)) {
+        assert(index->page_location != 0);
+        write_page (info, &og);
+    }
+
+    return 0;
+}
+
+typedef struct {
+    ogg_int64_t offset;
+    ogg_uint32_t checksum;
+    ogg_int64_t time;
+} keypoint;
+
+/* Overwrites pages on disk for a stream's index with actual index data. */
+static int
+write_index_pages (seek_index* index, oggmux_info *info, ogg_uint32_t serialno)
+{
+    ogg_packet op;
+    ogg_page og;
+    int i;
+    int result;
+    int num_keypoints;
+    int packetno;
+    int packet_start_count = 0;
+    keypoint* keypoints = 0;
+    int pageno = 0;
+
+    /* Must have indexed keypoints. */
+    assert(index->max_keypoints > 0 && index->packet_num > 0);
+    /* Must have placeholder packet to rewrite. */
+    assert(index->page_location > 0);
+
+
+    if (index->max_keypoints <= index->packet_num) {
+        fprintf(stderr, "WARNING: Recorded more keyframes than there's room "
+                "for in the index. We must have miscalculated index size. "
+                "Not writing index.\n");
+        return -1;
+    }
+    num_keypoints = index->packet_num;
+
+    /* Calculate and store the keypoints. */
+    keypoints = (keypoint*)malloc(sizeof(keypoint) * num_keypoints);
+    if (!keypoints) {
+        fprintf(stderr, "ERROR: malloc failure in rewrite_index_page\n");
+        return -1;
+    }
+    memset(keypoints, -1, sizeof(keypoint) * num_keypoints);
+    pageno = 0;
+    packet_start_count = 3; /* Take into account header packets... */
+    for (i=0; i<num_keypoints; i++) {
+        packetno = index->packets[i].packetno;
+        /* Inrement pageno until we find the page which contains the start of
+         * the keyframe's packet. */
+        while (pageno < index->pages_num &&
+               packet_start_count + index->pages[pageno].packet_start_num < packetno)
+        {
+            packet_start_count += index->pages[pageno].packet_start_num;
+            pageno++;
+        }
+        assert(pageno < index->pages_num);
+        keypoints[i].offset = index->pages[pageno].offset;
+        keypoints[i].checksum = index->pages[pageno].checksum;
+        keypoints[i].time = index->packets[i].start_time;
+    }
+
+    if (create_index_packet(index->max_keypoints, &op, serialno, num_keypoints) == -1) {
+        free(keypoints);
+        return -1;
+    }
+   
+    /* Write keypoint data into packet. */
+    for (i=0; i<num_keypoints; i++) {
+        unsigned char* p = op.packet + 14 + i * KEYPOINT_SIZE;
+        keypoint* k = &keypoints[i];
+        write64le(p, k->offset);
+        write32le(p+8, k->checksum);
+        write64le(p+12, k->time);
+    }
+    free(keypoints);
+
+    /* Skeleton stream must be empty. */
+    assert(ogg_stream_flush(&info->so, &og) == 0);
+    ogg_stream_packetin(&info->so, &op);
+    free(op.packet);
+
+    /* Seek to location of existing index pages. */
+    if (fseeko(info->outfile, index->page_location, SEEK_SET) < 0) {
+        fprintf(stderr, "ERROR: Can't seek output file to write index.!\n");
+        return -1;
+    }
+
+    /* Overwrite pages to disk. */
+    while (ogg_stream_pageout(&info->so, &og)) {
+        /* Index pages should not be BOS or EOS. */
+        assert(!(og.header[5] & 0x6));
+        write_page (info, &og);
+    }
+
+    /* Ensure we're flushed to disk. */
+    result = ogg_stream_flush(&info->so, &og);
+    if (result != 0) {
+        write_page (info, &og);
+    }
+
+    return 0;
+}
+
+/* Overwrites existing skeleton index placeholder packets with valid keyframe
+   index data. Must only be called once we've constructed the index data after
+   encoding the entire file. */
+int write_seek_index (oggmux_info* info)
+{
+    ogg_uint32_t serialno;
+    ogg_page og;
+
+    /* If the index is disabled, we shouldn't be doing this! */
+    assert(info->with_seek_index);
+
+    /* Mark that we're done indexing. This causes the header packets' fields
+       to be filled with valid, non-unknown values. */
+    info->indexing_complete = 1;
+
+    /* Re-encode the entire skeleton track, to ensure the packet and page
+       counts don't change. */
+    serialno = info->so.serialno;
+    ogg_stream_clear(&info->so);
+    ogg_stream_init(&info->so, serialno);
+
+    add_fishead_packet (info, 3, 1);
+    if (ogg_stream_flush(&info->so, &og) != 1) {
+        fprintf (stderr, "Internal Ogg library error.\n");
+        exit (1);
+    }
+
+    /* Rewrite the skeleton BOS page. It will have changed to account for
+       learning the start time, end time, and length. */
+    if (fseeko(info->outfile, 0, SEEK_SET) < 0) {
+        fprintf(stderr, "ERROR: Can't seek output file to rewrite skeleton BOS!\n");
+        return -1;
+    }
+    write_page (info, &og);
+
+    /* Encode and discard fisbone packets, their contents' doesn't need
+       to be updated. */
+    add_fisbone_packet(info);
+    while (1) {
+        int result = ogg_stream_flush(&info->so, &og);
+        if (result < 0) {
+            fprintf(stderr, "Internal Ogg library error.\n");
+            exit (1);
+        }
+        if (result == 0)
+            break;
+    }
+
+    if (!info->audio_only &&
+        write_index_pages(&info->theora_index,
+                          info,
+                          info->to.serialno) == -1)
+    {
+        return -1;
+    }
+    if (!info->video_only && 
+        write_index_pages(&info->vorbis_index,
+                          info,
+                          info->vo.serialno) == -1)
+    {
+        return -1;
+    }
+
+#ifdef HAVE_KATE
+    if (info->with_kate) {
+        /* TODO: Add indexing for Kate. */
+    }
+#endif
+
+    return 0;
+}
+
+/* Adds skeleton index packets to the output file at the current write cursor
+   for every stream. We'll fill them with valid data later, we just fill them
+   with placeholder data so that we don't need to rewrite the entire file
+   after encode when we add the index. */
+static int write_placeholder_index_pages (oggmux_info *info)
+{
+    if (!info->audio_only &&
+        write_index_placeholder_for_stream(info,
+                                           &info->theora_index,
+                                           info->to.serialno) == -1)
+    {
+        return -1;
+    }
+    if (!info->video_only &&
+        write_index_placeholder_for_stream(info,
+                                           &info->vorbis_index,
+                                           info->vo.serialno) == -1)
+    {
+        return -1;
+    }
+
+#ifdef HAVE_KATE
+    if (info->with_kate) {
+        /* TODO: Add indexing for Kate. */
+    }
+#endif
+
+    return 0;
+}
+
 void oggmux_init (oggmux_info *info) {
     ogg_page og;
     ogg_packet op;
@@ -327,6 +667,7 @@
 
     if (!info->audio_only) {
         ogg_stream_init (&info->to, rand ());    /* oops, add one ot the above */
+        seek_index_init(&info->theora_index, info->index_interval);
     }
     /* init theora done */
     /* initialize Vorbis too, if we have audio. */
@@ -349,7 +690,8 @@
         /* set up the analysis state and auxiliary encoding storage */
         vorbis_analysis_init (&info->vd, &info->vi);
         vorbis_block_init (&info->vd, &info->vb);
-
+        
+        seek_index_init(&info->vorbis_index, info->index_interval);
     }
     /* audio init done */
 
@@ -376,16 +718,59 @@
     }
     /* kate init done */
 
+    if (info->with_seek_index &&
+        info->duration == -1)
+    {
+        /* We've not got a duration, we can't index the keyframes. */
+        fprintf(stderr, "WARNING: Can't get duration of media, not indexing.\n");
+        info->with_seek_index = 0;
+    }
+    /* We must only index keyframes when the skeleton track is also present. */
+    assert (!info->with_seek_index || info->with_skeleton);
+
     /* first packet should be skeleton fishead packet, if skeleton is used */
 
     if (info->with_skeleton && info->passno!=1) {
+        /* Sometimes the output file is not seekable. We can't write the seek
+           index if the output is not seekable. So write a Skeleton3.0 header
+           packet, which will in turn determines if the file is seekable. If it
+           is, we can safely construct an index, so then overwrite the header
+           page with a Skeleton3.1 header page. */
+        int with_seek_index = info->with_seek_index;
+        info->with_seek_index = 0;
         ogg_stream_init (&info->so, rand());
-        add_fishead_packet (info);
+        add_fishead_packet (info, 3, 0);
         if (ogg_stream_pageout (&info->so, &og) != 1) {
             fprintf (stderr, "Internal Ogg library error.\n");
             exit (1);
         }
         write_page (info, &og);
+        assert(info->output_seekable != MAYBE_SEEKABLE);
+
+        if (info->output_seekable == NOT_SEEKABLE && with_seek_index) {
+            fprintf(stderr, "WARNING: Can't write keyframe-seek-index into "
+                            "non-seekable output stream!\n");
+        }
+
+        info->with_seek_index = with_seek_index &&
+                                info->output_seekable == SEEKABLE;
+
+        if (info->with_seek_index) {
+            /* Output is seekable and we're indexing. Overwrite the
+               Skeleton3.0 BOS page with a Skeleton3.1 BOS page. */
+            if (fseeko (info->outfile, 0, SEEK_SET) < 0) {
+                fprintf (stderr, "ERROR: failed to seek in seekable output file!?!\n");
+                exit (1);
+            }
+            ogg_stream_clear (&info->so);
+            ogg_stream_init (&info->so, rand());
+            add_fishead_packet (info, 3, 1);
+            if (ogg_stream_pageout (&info->so, &og) != 1) {
+                fprintf (stderr, "Internal Ogg library error.\n");
+                exit (1);
+            }
+            write_page (info, &og);
+        }
     }
 
     /* write the bitstream header packets with proper page interleave */
@@ -530,6 +915,12 @@
     if (info->with_skeleton && info->passno!=1) {
         int result;
 
+        if (info->with_seek_index) {
+            /* Add placeholder packets to reserve space for the index
+             * at the start of file. */
+            write_placeholder_index_pages (info);
+        }
+
         /* build and add the e_o_s packet */
         memset (&op, 0, sizeof (op));
             op.b_o_s = 0;
@@ -546,11 +937,13 @@
         }
         write_page (info, &og);
     }
-    if (!info->audio_only) {
-        th_info_clear(&info->ti);
-    }
 }
 
+static ogg_int64_t
+s_to_ms(double seconds) {
+    return (ogg_int64_t)(seconds * 1000.0);
+}
+
 /**
  * adds a video frame to the encoding sink
  * if e_o_s is 1 the end of the logical bitstream will be marked.
@@ -613,6 +1006,18 @@
     }
 
     while (th_encode_packetout (info->td, e_o_s, &op) > 0) {
+        if (info->with_seek_index &&
+            info->passno != 1)
+        {
+            ogg_int64_t duration = (info->ti.fps_denominator * 1000) / info->ti.fps_numerator;
+            ogg_int64_t end_time = s_to_ms(th_granule_time(info->td, op.granulepos));
+            ogg_int64_t start_time = end_time - duration;
+            seek_index_record_sample(&info->theora_index,
+                                     op.packetno,
+                                     start_time,
+                                     end_time,
+                                     th_packet_iskeyframe(&op));
+        }
         ogg_stream_packetin (&info->to, &op);
         info->v_pkg++;
     }
@@ -645,9 +1050,11 @@
  */
 void oggmux_add_audio (oggmux_info *info, int16_t * buffer, int bytes, int samples, int e_o_s) {
     ogg_packet op;
+    ogg_int64_t prev_granule = 0;
 
     int i,j, count = 0;
     float **vorbis_buffer;
+
     if (bytes <= 0 && samples <= 0) {
         /* end of audio stream */
         if (e_o_s)
@@ -666,17 +1073,38 @@
         if (e_o_s)
             vorbis_analysis_wrote (&info->vd, 0);
     }
+
+    /* The presentation time of a vorbis packet is the end-time of the previous
+       packet, i.e. of the previously encoded block. */
+    prev_granule = info->vb.granulepos;
     while (vorbis_analysis_blockout (&info->vd, &info->vb) == 1) {
+        ogg_int64_t start_time = s_to_ms(vorbis_granule_time(&info->vd,
+                                                             prev_granule));
+
         /* analysis, assume we want to use bitrate management */
         vorbis_analysis (&info->vb, NULL);
         vorbis_bitrate_addblock (&info->vb);
 
         /* weld packets into the bitstream */
         while (vorbis_bitrate_flushpacket (&info->vd, &op)) {
+            if (op.granulepos != -1 &&
+                info->with_seek_index &&
+                info->passno != 1)
+            {
+                ogg_int64_t end_time = s_to_ms(vorbis_granule_time(&info->vd,
+                                                                   op.granulepos));
+                seek_index_record_sample(&info->vorbis_index,
+                                         op.packetno,
+                                         start_time,
+                                         end_time,
+                                         1);
+            }
             ogg_stream_packetin (&info->vo, &op);
             info->a_pkg++;
         }
+        prev_granule = info->vb.granulepos;
     }
+
 }
 
 /**
@@ -699,6 +1127,10 @@
         ogg_stream_packetin (&ks->ko, &op);
         ogg_packet_clear (&op);
         info->k_pkg++;
+
+#ifdef HAVE_KATE
+        /* TODO: Add indexing for Kate. */
+#endif
     }
     else {
         fprintf(stderr, "Failed to encode kate data packet (%f --> %f, [%s]): %d\n",
@@ -819,9 +1251,30 @@
     }
 }
 
+/* Returns the number of packets that start on a page. */
+static int
+ogg_page_start_packets(unsigned char* page)
+{
+  int i;
+  /* If we're not continuing a packet, we're at a packet start. */
+  int packets_start = (page[5]&0x01) ? 0 : 1; 
+  int num_lacing_vals = page[26];
+  unsigned char* lacing_vals = &page[27];
+  for (i=1; i<num_lacing_vals; i++) {
+    if (lacing_vals[i-1] < 0xff)
+      packets_start++;
+  }
+  return packets_start;
+}
+
+
 static void write_audio_page(oggmux_info *info)
 {
     int ret;
+    ogg_int64_t page_offset = ftello(info->outfile);
+    int packets = ogg_page_packets((ogg_page *)&info->audiopage);
+    int packet_start_num = ogg_page_start_packets(info->audiopage);
+    ogg_uint32_t checksum = read_uint32(info->audiopage + 22);
 
     ret = fwrite(info->audiopage, 1, info->audiopage_len, info->outfile);
     if (ret < info->audiopage_len) {
@@ -831,7 +1284,13 @@
         info->audio_bytesout += ret;
     }
     info->audiopage_valid = 0;
-    info->a_pkg -=ogg_page_packets((ogg_page *)&info->audiopage);
+    info->a_pkg -= packets;
+
+    ret = seek_index_record_page(&info->vorbis_index,
+                                 page_offset,
+                                 checksum,
+                                 packet_start_num);
+    assert(ret == 0);
 #ifdef OGGMUX_DEBUG
     info->a_page++;
     info->v_page=0;
@@ -847,6 +1306,10 @@
 static void write_video_page(oggmux_info *info)
 {
     int ret;
+    ogg_int64_t page_offset = ftello(info->outfile);
+    int packets = ogg_page_packets((ogg_page *)&info->videopage);
+    int packet_start_num = ogg_page_start_packets(info->videopage);
+    ogg_uint32_t checksum = read_uint32(info->videopage + 22);
 
     ret = fwrite(info->videopage, 1, info->videopage_len, info->outfile);
     if (ret < info->videopage_len) {
@@ -856,7 +1319,13 @@
         info->video_bytesout += ret;
     }
     info->videopage_valid = 0;
-    info->v_pkg -= ogg_page_packets((ogg_page *)&info->videopage);
+    info->v_pkg -= packets;
+
+    ret = seek_index_record_page(&info->theora_index,
+                                 page_offset,
+                                 checksum,
+                                 packet_start_num);
+    assert(ret == 0);
 #ifdef OGGMUX_DEBUG
     info->v_page++;
     info->a_page=0;
@@ -1062,6 +1531,10 @@
 void oggmux_close (oggmux_info *info) {
     int n;
 
+    if (!info->audio_only) {
+        th_info_clear(&info->ti);
+    }
+
     print_stats(info, info->duration);
 
     ogg_stream_clear (&info->vo);

Modified: trunk/ffmpeg2theora/src/theorautils.h
===================================================================
--- trunk/ffmpeg2theora/src/theorautils.h	2009-10-30 04:52:14 UTC (rev 16672)
+++ trunk/ffmpeg2theora/src/theorautils.h	2009-10-30 17:35:25 UTC (rev 16673)
@@ -28,15 +28,17 @@
 #include "kate/kate.h"
 #endif
 #include "ogg/ogg.h"
+#include "index.h"
 
 //#define OGGMUX_DEBUG
 
-#define SKELETON_VERSION_MAJOR 3
-#define SKELETON_VERSION_MINOR 0
 #define FISHEAD_IDENTIFIER "fishead\0"
 #define FISBONE_IDENTIFIER "fisbone\0"
+#define INDEX_IDENTIFIER "index\0"
 #define FISBONE_SIZE 52
 #define FISBONE_MESSAGE_HEADER_OFFSET 44
+#define KEYPOINT_SIZE 20
+#define SKELETON_VERSION(major, minor) (((major)<<16)|(minor))
 
 typedef struct
 {
@@ -73,6 +75,9 @@
     int audio_only;
     int video_only;
     int with_skeleton;
+    int with_seek_index;
+    int index_interval;
+    int indexing_complete;
     FILE *frontend;
     /* vorbis settings */
     int sample_rate;
@@ -140,6 +145,9 @@
 
     int n_kate_streams;
     oggmux_kate_stream *kate_streams;
+
+    seek_index theora_index;
+    seek_index vorbis_index;
 }
 oggmux_info;
 
@@ -153,5 +161,7 @@
 extern void oggmux_flush (oggmux_info *info, int e_o_s);
 extern void oggmux_close (oggmux_info *info);
 
+extern int write_seek_index (oggmux_info* info);
 
+
 #endif



More information about the commits mailing list