Bug 782457: Add max offset and cues preloading support to nestegg; r=kinetik
authorSteve Workman <sworkman@mozilla.com>
Mon, 17 Sep 2012 16:45:38 -0400
changeset 107285 c72351cdf9dc9bede102beada76d0aea64bc35ee
parent 107284 427ca4f66d8354c5d3d72c8cc24046a3bfd64e03
child 107286 cb424c9fb4a60ead0d43b13bed4f7afc261677b5
push id23480
push usergraememcc_firefox@graeme-online.co.uk
push dateTue, 18 Sep 2012 11:48:11 +0000
treeherdermozilla-central@e4757379b99a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs782457
milestone18.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 782457: Add max offset and cues preloading support to nestegg; r=kinetik
content/media/webm/nsWebMReader.cpp
layout/media/symbols.def.in
media/libnestegg/AUTHORS
media/libnestegg/include/nestegg.h
media/libnestegg/src/nestegg.c
media/libnestegg/update.sh
--- a/content/media/webm/nsWebMReader.cpp
+++ b/content/media/webm/nsWebMReader.cpp
@@ -183,17 +183,17 @@ nsresult nsWebMReader::ReadMetadata(nsVi
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   nestegg_io io;
   io.read = webm_read;
   io.seek = webm_seek;
   io.tell = webm_tell;
   io.userdata = static_cast<nsBuiltinDecoder*>(mDecoder);
-  int r = nestegg_init(&mContext, io, NULL);
+  int r = nestegg_init(&mContext, io, NULL, -1);
   if (r == -1) {
     return NS_ERROR_FAILURE;
   }
 
   uint64_t duration = 0;
   r = nestegg_duration(mContext, &duration);
   if (r == 0) {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
--- a/layout/media/symbols.def.in
+++ b/layout/media/symbols.def.in
@@ -14,16 +14,17 @@ nestegg_packet_data
 nestegg_packet_track
 nestegg_packet_tstamp
 nestegg_read_packet
 nestegg_track_audio_params
 nestegg_track_codec_data
 nestegg_track_codec_data_count
 nestegg_track_codec_id
 nestegg_track_count
+nestegg_get_cue_point
 nestegg_track_seek
 nestegg_track_type
 nestegg_track_video_params
 nestegg_tstamp_scale
 #endif
 #ifdef MOZ_VP8
 #ifndef MOZ_NATIVE_LIBVPX
 vpx_codec_control_
--- a/media/libnestegg/AUTHORS
+++ b/media/libnestegg/AUTHORS
@@ -1,1 +1,2 @@
 Matthew Gregan <kinetik@flim.org>
+Steve Workman <sjhworkman@gmail.com>
--- a/media/libnestegg/include/nestegg.h
+++ b/media/libnestegg/include/nestegg.h
@@ -12,20 +12,20 @@
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 /** @mainpage
 
     @section intro Introduction
 
-    This is the documentation fot the <tt>libnestegg</tt> C API.
+    This is the documentation for the <tt>libnestegg</tt> C API.
     <tt>libnestegg</tt> is a demultiplexing library for <a
-    href="http://www.matroska.org/">Matroska</a> and <a
-    href="http://www.webmproject.org/">WebMedia</a> media files.
+    href="http://www.webmproject.org/code/specs/container/">WebM</a>
+    media files.
 
     @section example Example code
 
     @code
     nestegg * demux_ctx;
     nestegg_init(&demux_ctx, io, NULL);
 
     nestegg_packet * pkt;
@@ -144,19 +144,20 @@ typedef struct {
 typedef void (* nestegg_log)(nestegg * context, unsigned int severity, char const * format, ...);
 
 /** Initialize a nestegg context.  During initialization the parser will
     read forward in the stream processing all elements until the first
     block of media is reached.  All track metadata has been processed at this point.
     @param context  Storage for the new nestegg context.  @see nestegg_destroy
     @param io       User supplied IO context.
     @param callback Optional logging callback function pointer.  May be NULL.
+    @param max_offset Optional maximum offset to be read. Set -1 to ignore.
     @retval  0 Success.
     @retval -1 Error. */
-int nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback);
+int nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback, int64_t max_offset);
 
 /** Destroy a nestegg context and free associated memory.
     @param context #nestegg context to be freed.  @see nestegg_init */
 void nestegg_destroy(nestegg * context);
 
 /** Query the duration of the media stream in nanoseconds.
     @param context  Stream context initialized by #nestegg_init.
     @param duration Storage for the queried duration.
@@ -175,16 +176,29 @@ int nestegg_tstamp_scale(nestegg * conte
 
 /** Query the number of tracks in the media stream.
     @param context Stream context initialized by #nestegg_init.
     @param tracks  Storage for the queried track count.
     @retval  0 Success.
     @retval -1 Error. */
 int nestegg_track_count(nestegg * context, unsigned int * tracks);
 
+/** Query the start and end offset for a particular cluster.
+    @param context     Stream context initialized by #nestegg_init.
+    @param cluster_num Zero-based cluster number; order they appear in cues.
+    @param max_offset  Optional maximum offset to be read. Set -1 to ignore.
+    @param start_pos   Starting offset of the cluster. -1 means non-existant.
+    @param end_pos     Starting offset of the cluster. -1 means non-existant or
+                       final cluster.
+    @retval  0 Success.
+    @retval -1 Error. */
+int nestegg_get_cue_point(nestegg * context, unsigned int cluster_num,
+                          int64_t max_offset, int64_t * start_pos,
+                          int64_t * end_pos);
+
 /** Seek @a track to @a tstamp.  Stream seek will terminate at the earliest
     key point in the stream at or before @a tstamp.  Other tracks in the
     stream will output packets with unspecified but nearby timestamps.
     @param context Stream context initialized by #nestegg_init.
     @param track   Zero based track number.
     @param tstamp  Absolute timestamp in nanoseconds.
     @retval  0 Success.
     @retval -1 Error. */
--- a/media/libnestegg/src/nestegg.c
+++ b/media/libnestegg/src/nestegg.c
@@ -20,17 +20,17 @@
 #define ID_DOCTYPE              0x4282
 #define ID_DOCTYPE_VERSION      0x4287
 #define ID_DOCTYPE_READ_VERSION 0x4285
 
 /* Global Elements */
 #define ID_VOID                 0xec
 #define ID_CRC32                0xbf
 
-/* WebMedia Elements */
+/* WebM Elements */
 #define ID_SEGMENT              0x18538067
 
 /* Seek Head Elements */
 #define ID_SEEK_HEAD            0x114d9b74
 #define ID_SEEK                 0x4dbb
 #define ID_SEEK_ID              0x53ab
 #define ID_SEEK_POSITION        0x53ac
 
@@ -337,17 +337,17 @@ static struct ebml_element_desc ne_ebml_
   E_FIELD(ID_EBML_MAX_ID_LENGTH, TYPE_UINT, struct ebml, ebml_max_id_length),
   E_FIELD(ID_EBML_MAX_SIZE_LENGTH, TYPE_UINT, struct ebml, ebml_max_size_length),
   E_FIELD(ID_DOCTYPE, TYPE_STRING, struct ebml, doctype),
   E_FIELD(ID_DOCTYPE_VERSION, TYPE_UINT, struct ebml, doctype_version),
   E_FIELD(ID_DOCTYPE_READ_VERSION, TYPE_UINT, struct ebml, doctype_read_version),
   E_LAST
 };
 
-/* WebMedia Element Lists */
+/* WebM Element Lists */
 static struct ebml_element_desc ne_seek_elements[] = {
   E_FIELD(ID_SEEK_ID, TYPE_BINARY, struct seek, id),
   E_FIELD(ID_SEEK_POSITION, TYPE_UINT, struct seek, position),
   E_LAST
 };
 
 static struct ebml_element_desc ne_seek_head_elements[] = {
   E_MASTER(ID_SEEK, TYPE_MASTER, struct seek_head, seek),
@@ -657,17 +657,17 @@ ne_read_float(nestegg_io * io, double * 
 {
   union {
     uint64_t u;
     float f;
     double d;
   } value;
   int r;
 
-  /* length == 10 not implemented */
+  /* Length == 10 not implemented. */
   if (length != 4 && length != 8)
     return -1;
   r = ne_read_uint(io, &value.u, length);
   if (r != 1)
     return r;
   if (length == 4)
     *val = value.f;
   else
@@ -927,18 +927,16 @@ ne_read_simple(nestegg * ctx, struct ebm
     return 0;
   }
 
   storage->type = desc->type;
 
   ctx->log(ctx, NESTEGG_LOG_DEBUG, "element %llx (%s) -> %p (%u)",
            desc->id, desc->name, storage, desc->offset);
 
-  r = -1;
-
   switch (desc->type) {
   case TYPE_UINT:
     r = ne_read_uint(ctx->io, &storage->v.u, length);
     break;
   case TYPE_FLOAT:
     r = ne_read_float(ctx->io, &storage->v.f, length);
     break;
   case TYPE_INT:
@@ -958,36 +956,32 @@ ne_read_simple(nestegg * ctx, struct ebm
 
   if (r == 1)
     storage->read = 1;
 
   return r;
 }
 
 static int
-ne_parse(nestegg * ctx, struct ebml_element_desc * top_level)
+ne_parse(nestegg * ctx, struct ebml_element_desc * top_level, int64_t max_offset)
 {
   int r;
   int64_t * data_offset;
   uint64_t id, size;
   struct ebml_element_desc * element;
 
-  /* loop until we need to return:
-     - hit suspend point
-     - parse complete
-     - error occurred */
-
-  /* loop over elements at current level reading them if sublevel found,
-     push ctx onto stack and continue if sublevel ended, pop ctx off stack
-     and continue */
-
   if (!ctx->ancestor)
     return -1;
 
   for (;;) {
+    if (max_offset > 0 && ne_io_tell(ctx->io) >= max_offset) {
+      /* Reached end of offset allowed for parsing - return gracefully */
+      r = 1;
+      break;
+    }
     r = ne_peek_element(ctx, &id, &size);
     if (r != 1)
       break;
 
     element = ne_find_element(id, ctx->ancestor->node);
     if (element) {
       if (element->flags & DESC_FLAG_SUSPEND) {
         assert(element->type == TYPE_BINARY);
@@ -1103,17 +1097,17 @@ ne_read_xiph_lacing(nestegg_io * io, siz
       return r;
     sum += sizes[i];
     i += 1;
   }
 
   if (*read + sum > block)
     return -1;
 
-  /* last frame is the remainder of the block */
+  /* Last frame is the remainder of the block. */
   sizes[i] = block - *read - sum;
   return 1;
 }
 
 static int
 ne_read_ebml_lacing(nestegg_io * io, size_t block, size_t * read, uint64_t n, uint64_t * sizes)
 {
   int r;
@@ -1140,17 +1134,17 @@ ne_read_ebml_lacing(nestegg_io * io, siz
     sizes[i] = sizes[i - 1] + slace;
     sum += sizes[i];
     i += 1;
   }
 
   if (*read + sum > block)
     return -1;
 
-  /* last frame is the remainder of the block */
+  /* Last frame is the remainder of the block. */
   sizes[i] = block - *read - sum;
   return 1;
 }
 
 static uint64_t
 ne_get_timecode_scale(nestegg * ctx)
 {
   uint64_t scale;
@@ -1216,18 +1210,18 @@ ne_read_block(nestegg * ctx, uint64_t bl
   r = ne_read_uint(ctx->io, &flags, 1);
   if (r != 1)
     return r;
 
   consumed += 1;
 
   frames = 0;
 
-  /* flags are different between block and simpleblock, but lacing is
-     encoded the same way */
+  /* Flags are different between Block and SimpleBlock, but lacing is
+     encoded the same way. */
   lacing = (flags & BLOCK_FLAGS_LACING) >> 1;
 
   switch (lacing) {
   case LACING_NONE:
     frames = 1;
     break;
   case LACING_XIPH:
   case LACING_FIXED:
@@ -1263,17 +1257,17 @@ ne_read_block(nestegg * ctx, uint64_t bl
     if (frames == 1)
       return -1;
     r = ne_read_ebml_lacing(ctx->io, block_size, &consumed, frames, frame_sizes);
     if (r != 1)
       return r;
     break;
   }
 
-  /* sanity check unlaced frame sizes against total block size. */
+  /* Sanity check unlaced frame sizes against total block size. */
   total = consumed;
   for (i = 0; i < frames; ++i)
     total += frame_sizes[i];
   if (total > block_size)
     return -1;
 
   entry = ne_find_track_entry(ctx, track - 1);
   if (!entry)
@@ -1392,31 +1386,93 @@ ne_find_cue_point_for_tstamp(struct ebml
   }
 
   return prev;
 }
 
 static int
 ne_is_suspend_element(uint64_t id)
 {
-  /* this could search the tree of elements for DESC_FLAG_SUSPEND */
   if (id == ID_SIMPLE_BLOCK || id == ID_BLOCK)
     return 1;
   return 0;
 }
 
 static void
 ne_null_log_callback(nestegg * ctx, unsigned int severity, char const * fmt, ...)
 {
   if (ctx && severity && fmt)
     return;
 }
 
+static int
+ne_init_cue_points(nestegg * ctx, int64_t max_offset)
+{
+  int r;
+  struct ebml_list_node * node = ctx->segment.cues.cue_point.head;
+  struct seek * found;
+  uint64_t seek_pos, id;
+  struct saved_state state;
+
+  /* If there are no cues loaded, check for cues element in the seek head
+     and load it. */
+  if (!node) {
+    found = ne_find_seek_for_id(ctx->segment.seek_head.head, ID_CUES);
+    if (!found)
+      return -1;
+
+    if (ne_get_uint(found->position, &seek_pos) != 0)
+      return -1;
+
+    /* Save old parser state. */
+    r = ne_ctx_save(ctx, &state);
+    if (r != 0)
+      return -1;
+
+    /* Seek and set up parser state for segment-level element (Cues). */
+    r = ne_io_seek(ctx->io, ctx->segment_offset + seek_pos, NESTEGG_SEEK_SET);
+    if (r != 0)
+      return -1;
+    ctx->last_id = 0;
+    ctx->last_size = 0;
+
+    r = ne_read_element(ctx, &id, NULL);
+    if (r != 1)
+      return -1;
+
+    if (id != ID_CUES)
+      return -1;
+
+    ctx->ancestor = NULL;
+    ne_ctx_push(ctx, ne_top_level_elements, ctx);
+    ne_ctx_push(ctx, ne_segment_elements, &ctx->segment);
+    ne_ctx_push(ctx, ne_cues_elements, &ctx->segment.cues);
+    /* parser will run until end of cues element. */
+    ctx->log(ctx, NESTEGG_LOG_DEBUG, "seek: parsing cue elements");
+    r = ne_parse(ctx, ne_cues_elements, max_offset);
+    while (ctx->ancestor)
+      ne_ctx_pop(ctx);
+
+    /* Reset parser state to original state and seek back to old position. */
+    if (ne_ctx_restore(ctx, &state) != 0)
+      return -1;
+
+    if (r < 0)
+      return -1;
+
+    node = ctx->segment.cues.cue_point.head;
+    if (!node)
+      return -1;
+  }
+
+  return 0;
+}
+
 int
-nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback)
+nestegg_init(nestegg ** context, nestegg_io io, nestegg_log callback, int64_t max_offset)
 {
   int r;
   uint64_t id, version, docversion;
   struct ebml_list_node * track;
   char * doctype;
   nestegg * ctx;
 
   if (!(io.read && io.seek && io.tell))
@@ -1442,17 +1498,17 @@ nestegg_init(nestegg ** context, nestegg
     nestegg_destroy(ctx);
     return -1;
   }
 
   ctx->log(ctx, NESTEGG_LOG_DEBUG, "ctx %p", ctx);
 
   ne_ctx_push(ctx, ne_top_level_elements, ctx);
 
-  r = ne_parse(ctx, NULL);
+  r = ne_parse(ctx, NULL, max_offset);
 
   if (r != 1) {
     nestegg_destroy(ctx);
     return -1;
   }
 
   if (ne_get_uint(ctx->ebml.ebml_read_version, &version) != 0)
     version = 1;
@@ -1528,70 +1584,93 @@ nestegg_tstamp_scale(nestegg * ctx, uint
 int
 nestegg_track_count(nestegg * ctx, unsigned int * tracks)
 {
   *tracks = ctx->track_count;
   return 0;
 }
 
 int
+nestegg_get_cue_point(nestegg * ctx, unsigned int cluster_num, int64_t max_offset,
+                      int64_t * start_pos, int64_t * end_pos)
+{
+  int range_obtained = 0;
+  unsigned int cluster_count = 0;
+  struct cue_point * cue_point;
+  struct cue_track_positions * pos;
+  uint64_t seek_pos, t;
+  struct ebml_list_node * cues_node = ctx->segment.cues.cue_point.head;
+  struct ebml_list_node * cue_pos_node = NULL;
+  unsigned int track = 0, track_count = 0;
+
+  if (!start_pos || !end_pos)
+    return -1;
+
+  /* Initialise return values */
+  *start_pos = -1;
+  *end_pos   = -1;
+
+  if (!cues_node) {
+    ne_init_cue_points(ctx, max_offset);
+    cues_node = ctx->segment.cues.cue_point.head;
+    /* Verify cues have been added to context. */
+    if (!cues_node)
+      return -1;
+  }
+
+  nestegg_track_count(ctx, &track_count);
+
+  while (cues_node && !range_obtained) {
+    assert(cues_node->id == ID_CUE_POINT);
+    cue_point = cues_node->data;
+    cue_pos_node = cue_point->cue_track_positions.head;
+    while (cue_pos_node) {
+      assert(cue_pos_node->id == ID_CUE_TRACK_POSITIONS);
+      pos = cue_pos_node->data;
+      for (track = 0; track < track_count; track++) {
+        if (ne_get_uint(pos->track, &t) == 0 && t - 1 == track) {
+          if (ne_get_uint(pos->cluster_position, &seek_pos) != 0)
+            return -1;
+          if (cluster_count == cluster_num) {
+            *start_pos = ctx->segment_offset+seek_pos;
+          } else if (cluster_count == cluster_num+1) {
+            *end_pos = (ctx->segment_offset+seek_pos)-1;
+            range_obtained = 1;
+            break;
+          }
+          cluster_count++;
+        }
+      }
+      cue_pos_node = cue_pos_node->next;
+    }
+    cues_node = cues_node->next;
+  }
+
+  return 0;
+}
+
+int
 nestegg_track_seek(nestegg * ctx, unsigned int track, uint64_t tstamp)
 {
   int r;
   struct cue_point * cue_point;
   struct cue_track_positions * pos;
-  struct saved_state state;
-  struct seek * found;
-  uint64_t seek_pos, tc_scale, t, id;
+  uint64_t seek_pos, tc_scale, t;
   struct ebml_list_node * node = ctx->segment.cues.cue_point.head;
 
   /* If there are no cues loaded, check for cues element in the seek head
      and load it. */
   if (!node) {
-    found = ne_find_seek_for_id(ctx->segment.seek_head.head, ID_CUES);
-    if (!found)
-      return -1;
-
-    if (ne_get_uint(found->position, &seek_pos) != 0)
-      return -1;
-
-    /* Save old parser state. */
-    r = ne_ctx_save(ctx, &state);
+    r = ne_init_cue_points(ctx, -1);
     if (r != 0)
       return -1;
 
-    /* Seek and set up parser state for segment-level element (Cues). */
-    r = ne_io_seek(ctx->io, ctx->segment_offset + seek_pos, NESTEGG_SEEK_SET);
-    if (r != 0)
-      return -1;
-    ctx->last_id = 0;
-    ctx->last_size = 0;
-
-    r = ne_read_element(ctx, &id, NULL);
-    if (r != 1)
-      return -1;
-
-    if (id != ID_CUES)
-      return -1;
-
-    ctx->ancestor = NULL;
-    ne_ctx_push(ctx, ne_top_level_elements, ctx);
-    ne_ctx_push(ctx, ne_segment_elements, &ctx->segment);
-    ne_ctx_push(ctx, ne_cues_elements, &ctx->segment.cues);
-    /* parser will run until end of cues element. */
-    ctx->log(ctx, NESTEGG_LOG_DEBUG, "seek: parsing cue elements");
-    r = ne_parse(ctx, ne_cues_elements);
-    while (ctx->ancestor)
-      ne_ctx_pop(ctx);
-
-    /* Reset parser state to original state and seek back to old position. */
-    if (ne_ctx_restore(ctx, &state) != 0)
-      return -1;
-
-    if (r < 0)
+    /* Check cues were loaded. */
+    node = ctx->segment.cues.cue_point.head;
+    if (!node)
       return -1;
   }
 
   tc_scale = ne_get_timecode_scale(ctx);
 
   cue_point = ne_find_cue_point_for_tstamp(ctx->segment.cues.cue_point.head, tc_scale, tstamp);
   if (!cue_point)
     return -1;
@@ -1619,17 +1698,17 @@ nestegg_track_seek(nestegg * ctx, unsign
   ctx->last_size = 0;
 
   while (ctx->ancestor)
     ne_ctx_pop(ctx);
 
   ne_ctx_push(ctx, ne_top_level_elements, ctx);
   ne_ctx_push(ctx, ne_segment_elements, &ctx->segment);
   ctx->log(ctx, NESTEGG_LOG_DEBUG, "seek: parsing cluster elements");
-  r = ne_parse(ctx, NULL);
+  r = ne_parse(ctx, NULL, -1);
   if (r != 1)
     return -1;
 
   if (!ne_is_suspend_element(ctx->last_id))
     return -1;
 
   return 0;
 }
@@ -1854,29 +1933,29 @@ nestegg_read_packet(nestegg * ctx, neste
 
   *pkt = NULL;
 
   for (;;) {
     r = ne_peek_element(ctx, &id, &size);
     if (r != 1)
       return r;
 
-    /* any suspend fields must be handled here */
+    /* Any DESC_FLAG_SUSPEND fields must be handled here. */
     if (ne_is_suspend_element(id)) {
       r = ne_read_element(ctx, &id, &size);
       if (r != 1)
         return r;
 
-      /* the only suspend fields are blocks and simple blocks, which we
+      /* The only DESC_FLAG_SUSPEND fields are Blocks and SimpleBlocks, which we
          handle directly. */
       r = ne_read_block(ctx, id, size, pkt);
       return r;
     }
 
-    r =  ne_parse(ctx, NULL);
+    r =  ne_parse(ctx, NULL, -1);
     if (r != 1)
       return r;
   }
 
   return 1;
 }
 
 void
old mode 100644
new mode 100755