[cvs-annodex] commit (/annodex): libannodex/trunk/src/importers/anx_import_ogg.c

conrad nobody at lists.annodex.net
Fri Feb 11 04:01:20 EST 2005


Update of /annodex (new revision 874)

Modified files:
   libannodex/trunk/src/importers/anx_import_ogg.c

Log Message:
add annodex v3 and theora keyframe seeking:
	+ scan imported ogg files for granuleshift info
	+ build a table of desired keyframes for all tracks
	+ deliver the correct recomposition, dropping uneeded intermediate
	packets from other tracks
Tested with single and multi-file ogg, anx and cmml sources.


Modified: libannodex/trunk/src/importers/anx_import_ogg.c
===================================================================
--- libannodex/trunk/src/importers/anx_import_ogg.c	2005-02-10 16:50:03 UTC (rev 873)
+++ libannodex/trunk/src/importers/anx_import_ogg.c	2005-02-10 17:01:10 UTC (rev 874)
@@ -39,6 +39,7 @@
 #include <string.h>
 
 /*#define DEBUG*/
+/*#define DEBUG_VERBOSE*/
 
 #define MIN(a,b) ((a)<(b)?(a):(b))
 
@@ -67,39 +68,59 @@
 #define SUBSECONDS 1000.0
 
 typedef enum {
+  STATE_HEADERS,
+  STATE_GRANULEINFO,
+  STATE_FILTER,
+  STATE_DATA
+} state_t;
+
+typedef enum {
   NEED_SEEK_PENDING = 0,
   NEED_SEEK = 1,
   NEED_SEEK_DONE = 2
 } need_seek_t;
 
-typedef struct _AnxOggMedia AnxOggMedia;
+typedef struct _AnxOggPacket AnxOggPacket;
+typedef struct _AnxOggTrack AnxOggTrack;
 typedef struct _AnxOggData AnxOggData;
 
-struct _AnxOggMedia {
+struct _AnxOggPacket {
   long length;
   unsigned char * data;
   long granulepos;
-  AnxSourceTrack * media_track;
+  AnxSourceTrack * source_track;
   double current_time;
   int eos;
 };
 
+struct _AnxOggTrack {
+  AnxSourceTrack source_track;
+  int need_keygranule;
+  ogg_int64_t keygranule;
+  double keygranule_time;
+};
+
 struct _AnxOggData {
   OGGZ * oggz;
 
-  AnxSource * anx_media;
+  AnxSource * anx_source;
 
+  state_t state;
+
   char * id;
   int ignore_media;
-
   int got_non_bos;
 
   long nr_headers_remaining;
   long headers_unread;
 
+  int use_granule_seek;
+  double min_granule_seek;
+
   need_seek_t need_seek;
   int got_end;
 
+  /* Maintain a list of AnxOggTracks */
   OggzTable * logicals;
 
   AnxList * media_packets;
@@ -168,13 +189,38 @@
 }
 
 static double
-anxogg_seek_update (AnxSource * media)
+gp_to_time (OGGZ * oggz, long serialno, ogg_int64_t granulepos)
 {
-  AnxOggData * aod = (AnxOggData *)media->custom_data;
+  int granuleshift;
+  ogg_int64_t iframe, pframe;
+  ogg_int64_t gr_n, gr_d;
+
+  granuleshift = oggz_get_granuleshift (oggz, serialno);
+
+  if (oggz_get_granulerate (oggz, serialno, &gr_n, &gr_d) != 0) return -1.0;
+
+  iframe = granulepos >> granuleshift;
+  pframe = granulepos - (iframe << granuleshift);
+  return (double)((iframe + pframe) * gr_d) / ((double)gr_n * SUBSECONDS);
+}
+
+static double
+anxogg_seek_update (AnxSource * source)
+{
+  AnxOggData * aod = (AnxOggData *)source->custom_data;
+  double seek_offset;
   ogg_int64_t units, units_at;
   double offset;
 
-  units = (ogg_int64_t)(SUBSECONDS * media->start_time);
+  if (aod->use_granule_seek) {
+    seek_offset = aod->min_granule_seek;
+  } else {
+    seek_offset = source->start_time;
+  }
+  seek_offset -= 1.0;
+  if (seek_offset < 0.0) seek_offset = 0.0;
+
+  units = (ogg_int64_t)(SUBSECONDS * seek_offset);
   units_at = oggz_seek_units (aod->oggz, units, SEEK_SET);
 
   if (units_at == -1) {
@@ -190,10 +236,10 @@
 
 #ifdef DEBUG
   printf ("anxogg: seek_update to %lld (wanted %lld (%f))\n", units_at,
-	  units, media->start_time);
+	  units, seek_offset);
 #endif
 
-  if (media->end_time != -1.0 && offset >= media->end_time) {
+  if (source->end_time != -1.0 && offset >= source->end_time) {
     aod->got_end = 1;
   }
 
@@ -201,15 +247,18 @@
 }
 
 static int
-read_packet (OGGZ * oggz, ogg_packet * op, long serialno, void * user_data)
+read_packet_headers (OGGZ * oggz, ogg_packet * op, long serialno,
+		     void * user_data)
 {
   unsigned char * header = op->packet;
   AnxOggData * aod = (AnxOggData *)user_data;
-  AnxSource * m = aod->anx_media;
+  AnxOggTrack * aot = NULL;
+  AnxOggPacket * aop;
+  AnxSource * m = aod->anx_source;
   AnxSourceTrack * track = NULL;
-  AnxOggMedia * aam;
 
   if (op->b_o_s) {
+
     if (!strncmp ((char *)header, "CMML", 5)) {
       ogg_int64_t gr_n, gr_d;
 
@@ -219,8 +268,10 @@
       gr_d = INT64_LE_AT(&header[20]);
       aod->cmml_granulerate = gr_n / gr_d;
     } else {
-      track = (AnxSourceTrack *) anx_malloc (sizeof (AnxSourceTrack));
-      memset (track, 0, sizeof(AnxSourceTrack));
+      aot = (AnxOggTrack *) anx_malloc (sizeof (AnxOggTrack));
+      memset (aot, 0, sizeof(AnxOggTrack));
+
+      track = &(aot->source_track);
       track->eos = 0;
 
       if (!strncmp ((char *)&header[1], "vorbis", 6)) {
@@ -279,10 +330,15 @@
       aod->nr_headers_remaining += track->nr_header_packets;
       aod->headers_unread += track->nr_header_packets;
 
+      if (track->granuleshift > 0) {
+	aod->use_granule_seek = 1;
+	aot->need_keygranule = 1;
+      }
+
       oggz_table_insert (aod->logicals, serialno, track);
 
 #ifdef DEBUG
-      printf ("anxogg::read_packet: Added track for (%ld): %ld/%ld, +%d headers\n", serialno,
+      printf ("anxogg::read_packet: Added track for (%010ld): %ld/%ld, +%d headers\n", serialno,
 	      (long)track->granule_rate_n, (long)track->granule_rate_d,
 	      (int)track->nr_header_packets);
 #endif
@@ -310,14 +366,84 @@
       m->tracks = anx_list_append (m->tracks, track);
     }
   } else {
+    aod->got_non_bos = 1;
+  }
+
+  if (aod->nr_headers_remaining > 0) {
+    aod->nr_headers_remaining--;
+  }
+
+  if (aod->got_non_bos && aod->nr_headers_remaining == 0) {
+    if (m->start_time != 0.0) {
+      if (oggz_seek_units (oggz, 0, SEEK_CUR) >= 0) {
+	oggz_set_data_start (oggz, oggz_tell (oggz));
+	if (aod->use_granule_seek) aod->state = STATE_GRANULEINFO;
+      }
+    } else {
+      aod->state = STATE_DATA;
+    }
+  }
+
+  return OGGZ_STOP_OK;
+}
+
+static int
+filter (AnxOggData * aod, AnxOggTrack * aot, long serialno,
+	ogg_int64_t granulepos)
+{
+  AnxSourceTrack * track = &(aot->source_track);
+  double timestamp;
+
+  /* TRUE if we're not in state FILTER */
+  if (aod->state != STATE_FILTER) return 1;
+
+  /* TRUE if we haven't done the seek yet */
+  if (aod->need_seek == NEED_SEEK_PENDING) return 1;
+  
+  timestamp =
+    gp_to_time (aod->oggz, serialno, granulepos);
+
+  /* TRUE if we're beyond the start time */
+  if (timestamp >= aod->anx_source->start_time) {
+    aod->state = STATE_DATA;
+    return 1;
+  }
+
+  /* from here, we know we're filtering */
+
+  /* FALSE if we're not using granule_seek at all */
+  if (!aod->use_granule_seek) return 0;
+
+  /* FALSE if this track doesn't use granuleshift */
+  if (track->granuleshift == 0) return 0;
+
+  /* FALSE if this track uses keygranules, and we're before its time */
+  if (granulepos != -1 && timestamp < aot->keygranule_time) return 0;
+
+  /* TRUE otherwise */
+  return 1;
+}
+
+static int
+read_packet_data (OGGZ * oggz, ogg_packet * op, long serialno,
+		  void * user_data)
+{
+  unsigned char * header = op->packet;
+  AnxOggData * aod = (AnxOggData *)user_data;
+  AnxOggTrack * aot = NULL;
+  AnxOggPacket * aop;
+  AnxSource * m = aod->anx_source;
+  AnxSourceTrack * track = NULL;
+
+  if (!op->b_o_s) {
     if (aod->cmml_serialno != -1 && serialno == aod->cmml_serialno) {
       double at_time;
-
+      
       if (op->granulepos == -1) {
 	return OGGZ_STOP_ERR;
       }
       at_time = op->granulepos / aod->cmml_granulerate;
-
+      
 #ifdef DEBUG
       printf ("anxogg::read_packet: got CMML <%c%c%c%c> at %f\n",
 	      header[1], header[2], header[3], header[4], at_time);
@@ -328,10 +454,12 @@
       return OGGZ_CONTINUE;
     } else {
       aod->got_non_bos = 1;
-      track = (AnxSourceTrack *) oggz_table_lookup (aod->logicals, serialno);
     }
   }
 
+  aot = (AnxOggTrack *) oggz_table_lookup (aod->logicals, serialno);
+  track = &(aot->source_track);
+
   if (!track) {
     return OGGZ_STOP_ERR;
   }
@@ -342,24 +470,26 @@
     aod->got_end = 1;
   }
 
-  if (!aod->ignore_media && !aod->got_end) {
-    aam = anx_malloc (sizeof (AnxOggMedia));
-    aam->length = op->bytes;
-    aam->data = anx_malloc (op->bytes);
-    aam->granulepos = op->granulepos;
-    aam->current_time = ((double)oggz_tell_units (oggz)) / SUBSECONDS;
-    aam->media_track = track;
-    aam->eos = op->e_o_s;
+  if (!aod->ignore_media && !aod->got_end &&
+      (aod->state != STATE_FILTER ||
+       filter (aod, aot, serialno, op->granulepos))) {
+    aop = anx_malloc (sizeof (AnxOggPacket));
+    aop->length = op->bytes;
+    aop->data = anx_malloc (op->bytes);
+    aop->granulepos = op->granulepos;
+    aop->current_time = ((double)oggz_tell_units (oggz)) / SUBSECONDS;
+    aop->source_track = track;
+    aop->eos = op->e_o_s;
 
 #if 0
-    if (aod->nr_headers_remaining == 0 && aam->granulepos != -1) {
-      aam->granulepos -= track->start_granule;
+    if (aod->nr_headers_remaining == 0 && aop->granulepos != -1) {
+      aop->granulepos -= track->start_granule;
     }
 #endif
 
-    memcpy (aam->data, op->packet, op->bytes);
+    memcpy (aop->data, op->packet, op->bytes);
     
-    aod->media_packets = anx_list_append (aod->media_packets, aam);
+    aod->media_packets = anx_list_append (aod->media_packets, aop);
 
     if (aod->nr_headers_remaining > 0) {
       aod->nr_headers_remaining--;
@@ -377,7 +507,6 @@
 #ifdef DEBUG
 	printf ("anxogg::read_packet NEED seek\n");
 #endif
-	oggz_set_data_start (oggz, oggz_tell (oggz));
 	aod->need_seek = NEED_SEEK;
 	return OGGZ_CONTINUE;
       }
@@ -387,6 +516,123 @@
   return OGGZ_STOP_OK;
 }
 
+static int
+granuleinfo_update_state (AnxOggData * aod)
+{
+  AnxOggTrack * aot;
+  int i, n;
+
+  n = oggz_table_size (aod->logicals);
+
+  for (i = 0; i < n; i++) {
+    aot = (AnxOggTrack *)oggz_table_nth (aod->logicals, i, NULL);
+    if (aot->need_keygranule) return 0;
+  }
+
+  aod->state = STATE_FILTER;
+
+  return 0;
+}
+
+static int
+read_page_granuleinfo (OGGZ * oggz, const ogg_page * og, long serialno,
+		       void * user_data)
+{
+  AnxOggData * aod = (AnxOggData *)user_data;
+  AnxOggTrack * aot = NULL;
+  AnxSourceTrack * track = NULL;
+  ogg_int64_t granulepos, iframe, pframe;
+  double offset;
+
+#ifdef DEBUG
+  printf ("read_page_granuleinfo: track %010ld\n", serialno);
+#endif
+
+  aot = (AnxOggTrack *) oggz_table_lookup (aod->logicals, serialno);
+  if (aot == NULL) {
+    /* If this track is not in the table, ignore it. */
+    return OGGZ_STOP_OK;
+  }
+
+  track = &(aot->source_track);
+
+  granulepos = ogg_page_granulepos ((ogg_page *)og);
+
+  /* Slurp in granuleinfo */
+  if (aot->need_keygranule && granulepos != -1) {
+    iframe = granulepos >> track->granuleshift;
+    pframe = granulepos - (iframe << track->granuleshift);
+    granulepos = (iframe + pframe);
+    aot->keygranule = iframe << track->granuleshift;
+    aot->need_keygranule = 0;
+
+    offset = gp_to_time (aod->oggz, serialno, aot->keygranule);
+    aot->keygranule_time = offset;
+    if (aod->min_granule_seek == 0.0 || offset < aod->min_granule_seek)
+      aod->min_granule_seek = offset;
+
+#ifdef DEBUG
+    printf ("read_page_granuleinfo: ^^^ has keygranule %llx (%f seconds) (granuleshift %d)\n",
+	    aot->keygranule, offset, track->granuleshift);
+#endif
+  }
+
+  /* Update state */
+  granuleinfo_update_state (aod);
+
+  return OGGZ_STOP_OK;
+}
+
+static int
+anxogg_setup (AnxOggData * aod)
+{
+  long n;
+  double start_time = 0.0;
+  ogg_int64_t units, units_at;
+  double offset;
+
+  /* Slurp headers */
+  oggz_set_read_callback (aod->oggz, -1, read_packet_headers, aod);
+  while (aod->state == STATE_HEADERS &&
+	 (n = oggz_read (aod->oggz, 1024)) != 0);
+
+#ifdef DEBUG
+  printf ("anxogg: slurped headers:\n"
+	  "\toggz_read returned %ld\n"
+	  "\taod->got_non_bos == %d\n"
+	  "\taod->nr_headers_remaining = %d\n",
+	  n, aod->got_non_bos, aod->nr_headers_remaining);
+#endif
+
+  /* Find granule_seek info */
+  if (aod->anx_source) start_time = aod->anx_source->start_time;
+
+  if (aod->use_granule_seek && start_time > 0.0) {
+    oggz_set_read_callback (aod->oggz, -1, NULL, NULL);
+
+    units = (ogg_int64_t)(SUBSECONDS * aod->anx_source->start_time);
+    units_at = oggz_seek_units (aod->oggz, units, SEEK_SET);
+
+    if (units_at == -1) {
+#ifdef DEBUG
+      printf ("anxogg_seek_update: oggz_seek_units FAIL\n");
+#endif
+      return -1;
+    }
+    
+    oggz_set_read_page (aod->oggz, -1, read_page_granuleinfo, aod);
+    while (aod->state == STATE_GRANULEINFO &&
+	   (n = oggz_read (aod->oggz, 1024)) != 0);
+    oggz_set_read_page (aod->oggz, -1, NULL, NULL);
+  }
+
+  /* Reset to beginning */
+  oggz_seek (aod->oggz, 0, SEEK_SET);
+  aod->nr_headers_remaining = aod->headers_unread;
+
+  return 0;
+}
+
 static AnxSource *
 anxogg_open (const char * path, const char * id, int ignore_media,
 	     double start_time, double end_time,
@@ -420,15 +666,17 @@
     return NULL;
 
   aod->oggz = oggz;
-  aod->anx_media = m;
-
+  aod->anx_source = m;
+  aod->state = STATE_HEADERS;
   aod->id = (char *)id;
-
   aod->ignore_media = ignore_media;
 
   aod->media_packets = NULL;
   aod->current_offset = 0;
 
+  aod->use_granule_seek = 0;
+  aod->min_granule_seek = 0;
+
   aod->got_non_bos = 0;
   aod->need_seek = NEED_SEEK_PENDING;
   aod->got_end = 0;
@@ -440,22 +688,15 @@
   aod->import_user_data = import_callbacks->import_user_data;
   aod->cmml_serialno = -1;
 
+  anxogg_setup (aod);
+
+  /* Set normal data reading callback */
+  oggz_set_read_callback (oggz, -1, read_packet_data, aod);
+  
   if (ignore_media) {
-    oggz_set_read_callback (oggz, -1, read_packet, aod);
+    /* Slurp CMML */
     while ((n = oggz_read (oggz, 1024)) != 0) {
     }
-  } else {
-    oggz_set_read_callback (oggz, -1, read_packet, aod);
-    while ((!(aod->got_non_bos && aod->nr_headers_remaining == 0)) &&
-	   (n = oggz_read (oggz, 1024)) != 0) {
-    }
-#ifdef DEBUG
-    printf ("anxogg: slurped headers:\n"
-	    "\toggz_read returned %ld\n"
-	    "\taod->got_non_bos == %d\n"
-	    "\taod->nr_headers_remaining = %d\n",
-	    n, aod->got_non_bos, aod->nr_headers_remaining);
-#endif
   }
 
   m->custom_data = aod;
@@ -484,7 +725,7 @@
 anxogg_read (AnxSource * media, char * buf, long n, long bound)
 {
   AnxOggData * aod = (AnxOggData *)media->custom_data;
-  AnxOggMedia * aam;
+  AnxOggPacket * aop;
   long bytes_to_read;
 
   if (aod->ignore_media) return -1;
@@ -496,47 +737,47 @@
     return 0;
   }
 
-  aam = (AnxOggMedia *)aod->media_packets->data;
-  bytes_to_read = MIN (n, aam->length - aod->current_offset);
+  aop = (AnxOggPacket *)aod->media_packets->data;
+  bytes_to_read = MIN (n, aop->length - aod->current_offset);
 
-  memcpy (buf, &aam->data[aod->current_offset], bytes_to_read);
+  memcpy (buf, &aop->data[aod->current_offset], bytes_to_read);
 
   aod->current_offset += bytes_to_read;
 
   if (aod->headers_unread > 0) aod->headers_unread--;
   if (aod->headers_unread == 0) media->written_secondaries = 1;
 
-  media->current_track = aam->media_track;
+  media->current_track = aop->source_track;
 
-#ifdef DEBUG
+#ifdef DEBUG_VERBOSE
   printf ("anxogg: reading from stream %s\n",
 	  media->current_track->content_type);
 #endif
 
-  media->current_track->current_granule = aam->granulepos;
-  media->current_track->eos = aam->eos;
+  media->current_track->current_granule = aop->granulepos;
+  media->current_track->eos = aop->eos;
 
   /* If that's finished this media packet, advance to the next one */
-  if (aod->current_offset >= aam->length) {
+  if (aod->current_offset >= aop->length) {
     aod->media_packets =
       anx_list_remove (aod->media_packets, aod->media_packets);
 
-    anx_free (aam->data);
-    anx_free (aam);
-    aam = NULL;
+    anx_free (aop->data);
+    anx_free (aop);
+    aop = NULL;
 
     aod->current_offset = 0;
 
     anxogg_read_update (media);
 
     if (aod->media_packets != NULL)
-      aam = (AnxOggMedia *)aod->media_packets->data;
+      aop = (AnxOggPacket *)aod->media_packets->data;
   }
 
   /* Set current time to the current time of the next packet that would
    * be served */
-  if (aam && aam->current_time != -1)
-    media->current_time = aam->current_time;   
+  if (aop && aop->current_time != -1)
+    media->current_time = aop->current_time;   
 
   return bytes_to_read;
 }
@@ -545,7 +786,7 @@
 anxogg_sizeof_next_read (AnxSource * media, long bound)
 {
   AnxOggData * aod = (AnxOggData *)media->custom_data;
-  AnxOggMedia * aam;
+  AnxOggPacket * aop;
   long bytes_to_read;
 
   if (aod->ignore_media) return -1;
@@ -557,21 +798,29 @@
     return 0;
   }
 
-  aam = (AnxOggMedia *)aod->media_packets->data;
-  bytes_to_read = aam->length - aod->current_offset;
+  aop = (AnxOggPacket *)aod->media_packets->data;
+  bytes_to_read = aop->length - aod->current_offset;
 
   return bytes_to_read;
 }
 
 static int
-anxogg_close (AnxSource * media)
+anxogg_close (AnxSource * source)
 {
-  AnxOggData * aod = (AnxOggData *)media->custom_data;
+  AnxOggData * aod = (AnxOggData *)source->custom_data;
+  AnxOggTrack * aot;
+  int i, n;
 
   oggz_close (aod->oggz);
 
-  anx_free (media);
+  n = oggz_table_size (aod->logicals);
+  for (i = 0; i < n; i++) {
+    aot = (AnxOggTrack *)oggz_table_nth (aod->logicals, i, NULL);
+    if (aot) free (aot);
+  }
 
+  anx_free (source);
+
   return 0;
 }
 


-- 
conrad



More information about the cvs-annodex mailing list