shithub: opus

Download patch

ref: 58bf8b413c064c41913ac1a5ad734971c811892c
parent: 5c13840b53e6124fa52ba3e83a42c4f528626211
author: Timothy B. Terriberry <tterribe@xiph.org>
date: Wed Jul 24 10:28:45 EDT 2024

Decoder implementation of Repeat These Extensions.

Right now, opus_packet_extensions_parse() returns the extensions in
 the order they appear in the packet, which is no longer necessarily
 in frame order.
This adds a new (still private) API that returns parsed extensions
 in frame order, even when repeated extensions are used.
Nothing has been converted to use this new API yet.

Signed-off-by: Jean-Marc Valin <jeanmarcv@google.com>

--- a/src/extensions.c
+++ b/src/extensions.c
@@ -36,80 +36,121 @@
 #include "opus_private.h"
 
 
-/* Given an extension payload, advance data to the next extension and return the
-   length of the remaining extensions. */
-static opus_int32 skip_extension(const unsigned char **data, opus_int32 len,
- opus_int32 *header_size)
+/* Given an extension payload (i.e., excluding the initial ID byte), advance
+    data to the next extension and return the length of the remaining
+    extensions.
+   N.B., a "Repeat These Extensions" extension (ID==2) does not advance past
+    the repeated extension payloads.
+   That requires higher-level logic. */
+static opus_int32 skip_extension_payload(const unsigned char **pdata,
+ opus_int32 len, opus_int32 *pheader_size, int id_byte)
 {
+   const unsigned char *data;
+   opus_int32 header_size;
    int id, L;
-   if (len==0)
-      return 0;
-   id = **data>>1;
-   L = **data&1;
-   if (id == 0 && L == 1)
+   data = *pdata;
+   header_size = 0;
+   id = id_byte>>1;
+   L = id_byte&1;
+   if ((id == 0 && L == 1) || id == 2)
    {
-      *header_size = 1;
-      if (len < 1)
-         return -1;
-      (*data)++;
-      len--;
-      return len;
+      /* Nothing to do. */
    } else if (id > 0 && id < 32)
    {
-      if (len < 1+L)
+      if (len < L)
          return -1;
-      *data += 1+L;
-      len -= 1+L;
-      *header_size = 1;
-      return len;
+      data += L;
+      len -= L;
    } else {
       if (L==0)
       {
-         *data += len;
-         *header_size = 1;
-         return 0;
+         data += len;
+         len = 0;
       } else {
          opus_int32 bytes=0;
          opus_int32 lacing;
-         *header_size = 1;
          do {
-            (*data)++;
-            len--;
             if (len < 1)
                return -1;
-            lacing = **data;
+            lacing = *data++;
             bytes += lacing;
-            (*header_size)++;
-            len -= lacing;
+            header_size++;
+            len -= lacing + 1;
          } while (lacing == 255);
-         if (len < 1)
+         if (len < 0)
             return -1;
-         (*data)++;
-         len--;
-         *data += bytes;
-         return len;
+         data += bytes;
       }
    }
+   *pdata = data;
+   *pheader_size = header_size;
+   return len;
 }
 
+/* Given an extension, advance data to the next extension and return the
+   length of the remaining extensions.
+   N.B., a "Repeat These Extensions" extension (ID==2) only advances past the
+    extension ID byte.
+   Higher-level logic is required to skip the extension payloads that come
+    after it.*/
+static opus_int32 skip_extension(const unsigned char **pdata, opus_int32 len,
+ opus_int32 *pheader_size)
+{
+   const unsigned char *data;
+   int id_byte;
+   if (len == 0) {
+      *pheader_size = 0;
+      return 0;
+   }
+   if (len < 1)
+      return -1;
+   data = *pdata;
+   id_byte = *data++;
+   len--;
+   len = skip_extension_payload(&data, len, pheader_size, id_byte);
+   if (len >= 0) {
+      *pdata = data;
+      (*pheader_size)++;
+   }
+   return len;
+}
+
 void opus_extension_iterator_init(OpusExtensionIterator *iter,
- const unsigned char *data, opus_int32 len) {
+ const unsigned char *data, opus_int32 len, opus_int32 nb_frames) {
    celt_assert(len >= 0);
    celt_assert(data != NULL || len == 0);
-   iter->curr_data = iter->data = data;
+   celt_assert(nb_frames >= 0 && nb_frames <= 48);
+   iter->repeat_data_end = iter->repeat_data = iter->curr_data = iter->data =
+    data;
+   iter->src_data = NULL;
    iter->curr_len = iter->len = len;
-   iter->curr_frame = 0;
+   iter->repeat_len = iter->src_len = 0;
+   iter->frame_max = iter->nb_frames = nb_frames;
+   iter->repeat_frame = iter->curr_frame = 0;
+   iter->repeat_l = 0;
 }
 
 /* Reset the iterator so it can start iterating again from the first
     extension. */
 void opus_extension_iterator_reset(OpusExtensionIterator *iter) {
-   iter->curr_data = iter->data;
+   iter->repeat_data_end = iter->repeat_data = iter->curr_data = iter->data;
    iter->curr_len = iter->len;
-   iter->curr_frame = 0;
+   iter->repeat_frame = iter->curr_frame = 0;
 }
 
-/* Return the next extension (excluding real padding and separators). */
+/* Tell the iterator not to return any extensions for frames of index
+    frame_max or larger.
+   This can allow it to stop iterating early if these extensions are not
+    needed. */
+void opus_extension_iterator_set_frame_max(OpusExtensionIterator *iter,
+ int frame_max) {
+   iter->frame_max = frame_max;
+}
+
+/* Return the next extension (excluding real padding, separators, and repeat
+    indicators, but including the repeated extensions) in bitstream order.
+   Due to the extension repetition mechanism, extensions are not necessarily
+    returned in frame order. */
 int opus_extension_iterator_next(OpusExtensionIterator *iter,
  opus_extension_data *ext) {
    opus_int32 header_size;
@@ -116,6 +157,75 @@
    if (iter->curr_len < 0) {
       return OPUS_INVALID_PACKET;
    }
+   /* Checking this here allows opus_extension_iterator_set_frame_max() to be
+       called at any point. */
+   if (iter->curr_frame >= iter->frame_max) {
+      return 0;
+   }
+   if (iter->repeat_frame > 0) {
+      /* We are in the process of repeating some extensions. */
+      for (;iter->repeat_frame < iter->nb_frames; iter->repeat_frame++) {
+         while (iter->src_len > 0) {
+            const unsigned char *curr_data0;
+            int repeat_id_byte;
+            repeat_id_byte = *iter->src_data;
+            iter->src_len = skip_extension(&iter->src_data, iter->src_len,
+             &header_size);
+            /* We skipped this extension earlier, so it should not fail now. */
+            celt_assert(iter->src_len >= 0);
+            /* Don't repeat padding or frame separators with a 0 increment. */
+            if (repeat_id_byte <= 3) continue;
+            /* If the "Repeat These Extensions" extension had L == 0 and this
+                is the last repeated extension, and it is a long extension,
+                then force decoding the payload with L = 0. */
+            if (iter->repeat_l == 0
+             && iter->repeat_frame + 1 >= iter->nb_frames
+             && iter->src_data == iter->repeat_data_end
+             && repeat_id_byte >= 64) {
+               repeat_id_byte &= ~1;
+            }
+            curr_data0 = iter->curr_data;
+            iter->curr_len = skip_extension_payload(&iter->curr_data,
+             iter->curr_len, &header_size, repeat_id_byte);
+            if (iter->curr_len < 0) {
+               return OPUS_INVALID_PACKET;
+            }
+            celt_assert(iter->curr_data - iter->data
+             == iter->len - iter->curr_len);
+            /* If we were asked to stop at frame_max, skip extensions for later
+                frames. */
+            if (iter->repeat_frame >= iter->frame_max) {
+               if (iter->repeat_l == 0) {
+                  /* If L == 0, there will be no more extensions after these
+                      repeats, so we can just stop. */
+                  iter->repeat_frame = 0;
+                  iter->curr_len = 0;
+                  return 0;
+               }
+               continue;
+            }
+            if (ext != NULL) {
+               ext->id = repeat_id_byte >> 1;
+               ext->frame = iter->repeat_frame;
+               ext->data = curr_data0 + header_size;
+               ext->len = iter->curr_data - curr_data0 - header_size;
+            }
+            return 1;
+         }
+         /* We finished repeating the extensions for this frame. */
+         iter->src_data = iter->repeat_data;
+         iter->src_len = iter->repeat_len;
+      }
+      /* We finished repeating extensions. */
+      iter->repeat_data_end = iter->repeat_data = iter->curr_data;
+      /* Even if there is more data (because there was nothing to repeat or
+          because the last extension was a short extension and we did not use
+          all the data), when L == 0 we are done decoding extensions. */
+      if (iter->repeat_l == 0) {
+         iter->curr_len = 0;
+      }
+      iter->repeat_frame = 0;
+   }
    while (iter->curr_len > 0) {
       const unsigned char *curr_data0;
       int id;
@@ -134,14 +244,38 @@
             iter->curr_frame++;
          }
          else {
+            /* A frame increment of 0 is a no-op. */
+            if (!curr_data0[1]) continue;
             iter->curr_frame += curr_data0[1];
          }
-         if (iter->curr_frame >= 48) {
+         if (iter->curr_frame >= iter->nb_frames) {
             iter->curr_len = -1;
             return OPUS_INVALID_PACKET;
          }
+         /* If we were asked to stop at frame_max, skip extensions for later
+             frames. */
+         if (iter->curr_frame >= iter->frame_max) {
+            iter->curr_len = 0;
+         }
+         iter->repeat_data_end = iter->repeat_data = iter->curr_data;
       }
-      else if (id > 1) {
+      else if (id == 2) {
+         iter->repeat_l = L;
+         iter->repeat_frame = iter->curr_frame + 1;
+         iter->repeat_len = curr_data0 - iter->repeat_data;
+         iter->src_data = iter->repeat_data;
+         iter->src_len = iter->repeat_len;
+         return opus_extension_iterator_next(iter, ext);
+      }
+      else if (id > 2) {
+         /* Update the stopping point for repeating extension data.
+            This lets us detect when we have hit the last repeated extension,
+             for purposes of modifying the L flag if it is a long extension.
+            Only extensions which can have a non-empty payload count, as we can
+             still use L=0 to code a final long extension if there cannot be
+             any more payload data, even if there are more short L=0
+             extensions (or padding). */
+         if (L || id >= 32) iter->repeat_data_end = iter->curr_data;
          if (ext != NULL) {
             ext->id = id;
             ext->frame = iter->curr_frame;
@@ -170,25 +304,47 @@
    }
 }
 
-/* Count the number of extensions, excluding real padding and separators. */
-opus_int32 opus_packet_extensions_count(const unsigned char *data, opus_int32 len)
+/* Count the number of extensions, excluding real padding, separators, and
+    repeat indicators, but including the repeated extensions. */
+opus_int32 opus_packet_extensions_count(const unsigned char *data,
+ opus_int32 len, int nb_frames)
 {
    OpusExtensionIterator iter;
    int count;
-   opus_extension_iterator_init(&iter, data, len);
+   opus_extension_iterator_init(&iter, data, len, nb_frames);
    for (count=0; opus_extension_iterator_next(&iter, NULL) > 0; count++);
    return count;
 }
 
-/* Extract extensions from Opus padding (excluding real padding and separators) */
-opus_int32 opus_packet_extensions_parse(const unsigned char *data, opus_int32 len, opus_extension_data *extensions, opus_int32 *nb_extensions)
-{
+/* Count the number of extensions for each frame, excluding real padding and
+    separators and repeat indicators, but including the repeated extensions. */
+opus_int32 opus_packet_extensions_count_ext(const unsigned char *data,
+ opus_int32 len, opus_int32 *nb_frame_exts, int nb_frames) {
    OpusExtensionIterator iter;
+   opus_extension_data ext;
    int count;
+   opus_extension_iterator_init(&iter, data, len, nb_frames);
+   OPUS_CLEAR(nb_frame_exts, nb_frames);
+   for (count=0; opus_extension_iterator_next(&iter, &ext) > 0; count++) {
+      nb_frame_exts[ext.frame]++;
+   }
+   return count;
+}
+
+/* Extract extensions from Opus padding (excluding real padding, separators,
+    and repeat indicators, but including the repeated extensions) in bitstream
+    order.
+   Due to the extension repetition mechanism, extensions are not necessarily
+    returned in frame order. */
+opus_int32 opus_packet_extensions_parse(const unsigned char *data,
+ opus_int32 len, opus_extension_data *extensions, opus_int32 *nb_extensions,
+ int nb_frames) {
+   OpusExtensionIterator iter;
+   int count;
    int ret;
    celt_assert(nb_extensions != NULL);
    celt_assert(extensions != NULL || *nb_extensions == 0);
-   opus_extension_iterator_init(&iter, data, len);
+   opus_extension_iterator_init(&iter, data, len, nb_frames);
    for (count=0;; count++) {
       opus_extension_data ext;
       ret = opus_extension_iterator_next(&iter, &ext);
@@ -202,6 +358,48 @@
    return ret;
 }
 
+/* Extract extensions from Opus padding (excluding real padding, separators,
+    and repeat indicators, but including the repeated extensions) in frame
+    order.
+   nb_frame_exts must be filled with the output of
+    opus_packet_extensions_count_ext(). */
+opus_int32 opus_packet_extensions_parse_ext(const unsigned char *data,
+ opus_int32 len, opus_extension_data *extensions, opus_int32 *nb_extensions,
+ const opus_int32 *nb_frame_exts, int nb_frames) {
+   OpusExtensionIterator iter;
+   opus_extension_data ext;
+   opus_int32 nb_frames_cum[49];
+   int count;
+   int prev_total;
+   int ret;
+   celt_assert(nb_extensions != NULL);
+   celt_assert(extensions != NULL || *nb_extensions == 0);
+   celt_assert(nb_frames <= 48);
+   /* Convert the frame extension count array to a cumulative sum. */
+   prev_total = 0;
+   for (count=0; count<nb_frames; count++) {
+      int total;
+      total = nb_frame_exts[count] + prev_total;
+      nb_frames_cum[count] = prev_total;
+      prev_total = total;
+   }
+   nb_frames_cum[count] = prev_total;
+   opus_extension_iterator_init(&iter, data, len, nb_frames);
+   for (count=0;; count++) {
+      opus_int32 idx;
+      ret = opus_extension_iterator_next(&iter, &ext);
+      if (ret <= 0) break;
+      idx = nb_frames_cum[ext.frame]++;
+      if (idx >= *nb_extensions) {
+         return OPUS_BUFFER_TOO_SMALL;
+      }
+      celt_assert(idx < nb_frames_cum[ext.frame + 1]);
+      extensions[idx] = ext;
+   }
+   *nb_extensions = count;
+   return ret;
+}
+
 opus_int32 opus_packet_extensions_generate(unsigned char *data, opus_int32 len, const opus_extension_data  *extensions, opus_int32 nb_extensions, int pad)
 {
    int max_frame=0;
@@ -216,7 +414,7 @@
    for (i=0;i<nb_extensions;i++)
    {
       max_frame = IMAX(max_frame, extensions[i].frame);
-      if (extensions[i].id < 2 || extensions[i].id > 127)
+      if (extensions[i].id < 3 || extensions[i].id > 127)
          return OPUS_BAD_ARG;
    }
    if (max_frame >= 48) return OPUS_BAD_ARG;
--- a/src/opus_decoder.c
+++ b/src/opus_decoder.c
@@ -1314,13 +1314,11 @@
       return ret;
    nb_frames = ret;
    frame_size = opus_packet_get_samples_per_frame(data, 48000);
-   opus_extension_iterator_init(&iter, padding, padding_len);
+   opus_extension_iterator_init(&iter, padding, padding_len, nb_frames);
    for (;;) {
       ret = opus_extension_iterator_find(&iter, &ext, DRED_EXTENSION_ID);
       if (ret <= 0)
          return ret;
-      if (ext.frame >= nb_frames)
-         break;
       /* DRED position in the packet, in units of 2.5 ms like for the signaled DRED offset. */
       *dred_frame_offset = ext.frame*frame_size/120;
 #ifdef DRED_EXPERIMENTAL_VERSION
--- a/src/opus_private.h
+++ b/src/opus_private.h
@@ -44,14 +44,24 @@
    int framesize;
    const unsigned char *paddings[48];
    opus_int32 padding_len[48];
+   unsigned char padding_nb_frames[48];
 };
 
 typedef struct OpusExtensionIterator {
    const unsigned char *data;
    const unsigned char *curr_data;
+   const unsigned char *repeat_data;
+   const unsigned char *repeat_data_end;
+   const unsigned char *src_data;
    opus_int32 len;
    opus_int32 curr_len;
+   opus_int32 repeat_len;
+   opus_int32 src_len;
+   int nb_frames;
+   int frame_max;
    int curr_frame;
+   int repeat_frame;
+   unsigned char repeat_l;
 } OpusExtensionIterator;
 
 typedef struct {
@@ -62,9 +72,11 @@
 } opus_extension_data;
 
 void opus_extension_iterator_init(OpusExtensionIterator *iter,
- const unsigned char *data, opus_int32 len);
+ const unsigned char *data, opus_int32 len, opus_int32 nb_frames);
 
 void opus_extension_iterator_reset(OpusExtensionIterator *iter);
+void opus_extension_iterator_set_frame_max(OpusExtensionIterator *iter,
+ int frame_max);
 int opus_extension_iterator_next(OpusExtensionIterator *iter,
  opus_extension_data *ext);
 int opus_extension_iterator_find(OpusExtensionIterator *iter,
@@ -226,11 +238,21 @@
   void *user_data
 );
 
-opus_int32 opus_packet_extensions_parse(const unsigned char *data, opus_int32 len, opus_extension_data *extensions, opus_int32 *nb_extensions);
+opus_int32 opus_packet_extensions_parse(const unsigned char *data,
+ opus_int32 len, opus_extension_data *extensions, opus_int32 *nb_extensions,
+ int nb_frames);
 
+opus_int32 opus_packet_extensions_parse_ext(const unsigned char *data,
+ opus_int32 len, opus_extension_data *extensions, opus_int32 *nb_extensions,
+ const opus_int32 *nb_frame_exts, int nb_frames);
+
 opus_int32 opus_packet_extensions_generate(unsigned char *data, opus_int32 len, const opus_extension_data  *extensions, opus_int32 nb_extensions, int pad);
 
-opus_int32 opus_packet_extensions_count(const unsigned char *data, opus_int32 len);
+opus_int32 opus_packet_extensions_count(const unsigned char *data,
+ opus_int32 len, int nb_frames);
+
+opus_int32 opus_packet_extensions_count_ext(const unsigned char *data,
+ opus_int32 len, opus_int32 *nb_frame_exts, int nb_frames);
 
 opus_int32 opus_packet_pad_impl(unsigned char *data, opus_int32 len, opus_int32 new_len, int pad, const opus_extension_data  *extensions, int nb_extensions);
 
--- a/src/repacketizer.c
+++ b/src/repacketizer.c
@@ -86,6 +86,7 @@
    ret=opus_packet_parse_impl(data, len, self_delimited, &tmp_toc, &rp->frames[rp->nb_frames], &rp->len[rp->nb_frames],
        NULL, NULL, &rp->paddings[rp->nb_frames], &rp->padding_len[rp->nb_frames]);
    if(ret<1)return ret;
+   rp->padding_nb_frames[rp->nb_frames]=ret;
 
    /* set padding length to zero for all but the first frame */
    while (curr_nb_frames > 1)
@@ -92,6 +93,7 @@
    {
       rp->nb_frames++;
       rp->padding_len[rp->nb_frames] = 0;
+      rp->padding_nb_frames[rp->nb_frames] = 0;
       rp->paddings[rp->nb_frames] = NULL;
       curr_nb_frames--;
    }
@@ -142,7 +144,8 @@
    total_ext_count = nb_extensions;
    for (i=begin;i<end;i++)
    {
-      int n = opus_packet_extensions_count(rp->paddings[i], rp->padding_len[i]);
+      int n = opus_packet_extensions_count(rp->paddings[i], rp->padding_len[i],
+       rp->padding_nb_frames[i]);
       if (n > 0) total_ext_count += n;
    }
    ALLOC(all_extensions, total_ext_count ? total_ext_count : ALLOC_NONE, opus_extension_data);
@@ -159,7 +162,7 @@
       opus_int32 frame_ext_count;
       frame_ext_count = total_ext_count - ext_count;
       int ret = opus_packet_extensions_parse(rp->paddings[i], rp->padding_len[i],
-         &all_extensions[ext_count], &frame_ext_count);
+         &all_extensions[ext_count], &frame_ext_count, rp->padding_nb_frames[i]);
       if (ret<0)
       {
          RESTORE_STACK;
--- a/tests/test_opus_extensions.c
+++ b/tests/test_opus_extensions.c
@@ -46,10 +46,10 @@
 void test_extensions_generate_success(void)
 {
    static const opus_extension_data ext[] = {
-      {2, 0, (const unsigned char *)"a", 1},
+      {3, 0, (const unsigned char *)"a", 1},
       {32, 10, (const unsigned char *)"DRED", 4},
       {33, 1, (const unsigned char *)"NOT DRED", 8},
-      {3, 4, (const unsigned char *)NULL, 0}
+      {4, 4, (const unsigned char *)NULL, 0}
    };
 
    int result;
@@ -62,8 +62,8 @@
    expect_true(p[0] == 1 && p[1] == 1 && p[2] == 1 && p[3] == 1, "expected padding");
    p += 4;
 
-   /* extension ID=2 */
-   expect_true((p[0] >> 1) == 2, "expected extension id 2");
+   /* extension ID=3 */
+   expect_true((p[0] >> 1) == 3, "expected extension id 3");
    /* For extension IDs 1 through 31, L=0 means that no data follows the
       extension, whereas L=1 means that exactly one byte of extension data follows. */
    expect_true((p[0] & 0x01) == 1, "expected L-bit set");
@@ -90,8 +90,8 @@
    expect_true(p[0] == 0x03, "bad frame separator");
    expect_true(p[1] == 0x03, "bad frame increment");
    p += 2;
-   /* extension ID=3 */
-   expect_true((p[0] >> 1) == 3, "expected extension id 3");
+   /* extension ID=4 */
+   expect_true((p[0] >> 1) == 4, "expected extension id 4");
    /* For extension IDs 1 through 31, L=0 means that no data follows the
       extension, whereas L=1 means that exactly one byte of extension data follows. */
    expect_true((p[0] & 0x01) == 0, "expected L-bit unset");
@@ -124,10 +124,10 @@
 void test_extensions_generate_no_padding(void)
 {
    static const opus_extension_data ext[] = {
-      {2, 0, (const unsigned char *)"a", 1},
+      {3, 0, (const unsigned char *)"a", 1},
       {32, 10, (const unsigned char *)"DRED", 4},
       {33, 1, (const unsigned char *)"NOT DRED", 8},
-      {3, 4, (const unsigned char *)NULL, 0}
+      {4, 4, (const unsigned char *)NULL, 0}
    };
 
    int result;
@@ -139,10 +139,10 @@
 void test_extensions_generate_fail(void)
 {
    static const opus_extension_data ext[] = {
-      {2, 0, (const unsigned char *)"a", 1},
+      {3, 0, (const unsigned char *)"a", 1},
       {32, 10, (const unsigned char *)"DRED", 4},
       {33, 1, (const unsigned char *)"NOT DRED", 8},
-      {3, 4, (const unsigned char *)NULL, 0}
+      {4, 4, (const unsigned char *)NULL, 0}
    };
 
    int result;
@@ -180,7 +180,7 @@
    /* invalid id */
    {
       static const opus_extension_data id_too_small[] = {
-         {1, 0, (const unsigned char *)"a", 1},
+         {2, 0, (const unsigned char *)"a", 1},
       };
       result = opus_packet_extensions_generate(packet, sizeof(packet), id_too_small, 1, 1);
       expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
@@ -198,7 +198,7 @@
    /* size too big for extension IDs 1 through 31 */
    {
       static const opus_extension_data size_too_big[] = {
-         {2, 0, (const unsigned char *)"abcd", 4},
+         {3, 0, (const unsigned char *)"abcd", 4},
       };
       result = opus_packet_extensions_generate(packet, sizeof(packet), size_too_big, 1, 1);
       expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
@@ -207,7 +207,7 @@
    /* negative size for extension IDs 1 through 31 */
    {
       static const opus_extension_data neg_size[] = {
-         {2, 0, NULL, -4},
+         {3, 0, NULL, -4},
       };
       result = opus_packet_extensions_generate(packet, sizeof(packet), neg_size, 1, 1);
       expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
@@ -226,25 +226,28 @@
 void test_extensions_parse_success(void)
 {
    static const opus_extension_data ext[] = {
-      {2, 0, (const unsigned char *)"a", 1},
+      {3, 0, (const unsigned char *)"a", 1},
       {32, 10, (const unsigned char *)"DRED", 4},
       {33, 1, (const unsigned char *)"NOT DRED", 8},
-      {3, 4, (const unsigned char *)NULL, 0}
+      {4, 4, (const unsigned char *)NULL, 0}
    };
    opus_extension_data ext_out[10];
    int nb_ext;
+   int nb_frames;
    int len, result;
    unsigned char packet[32];
 
    nb_ext = 10;
+   nb_frames = 11;
    len = opus_packet_extensions_generate(packet, 32, ext, 4, 1);
    expect_true(len == 32, "expected length 32");
-   result = opus_packet_extensions_count(packet, len);
+   result = opus_packet_extensions_count(packet, len, nb_frames);
    expect_true(result == 4, "expected opus_packet_extensions_count 4");
-   result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext);
+   result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext,
+    nb_frames);
    expect_true(nb_ext == 4, "expected 4 extensions");
 
-   expect_true(ext_out[0].id == 2, "expected id 2");
+   expect_true(ext_out[0].id == 3, "expected id 3");
    expect_true(ext_out[0].frame == 0, "expected frame 0");
    expect_true(ext_out[0].len == 1, "expected len 1");
    expect_true(0 == memcmp(ext_out[0].data, ext[0].data, 1), "expected data");
@@ -254,7 +257,7 @@
    expect_true(ext_out[1].len == 8, "expected len 8");
    expect_true(0 == memcmp(ext_out[1].data, ext[2].data, 8), "expected data");
 
-   expect_true(ext_out[2].id == 3, "expected id 3");
+   expect_true(ext_out[2].id == 4, "expected id 4");
    expect_true(ext_out[2].frame == 4, "expected frame 4");
    expect_true(ext_out[2].len == 0, "expected len 0");
 
@@ -270,6 +273,7 @@
       {32, 1, (const unsigned char *)"DRED", 4},
    };
    int nb_ext;
+   int nb_frames;
    int len, result;
    unsigned char packet[32];
 
@@ -277,7 +281,8 @@
    expect_true(len == 32, "expected length 32");
 
    nb_ext = 0;
-   result = opus_packet_extensions_parse(packet, len, NULL, &nb_ext);
+   nb_frames = 2;
+   result = opus_packet_extensions_parse(packet, len, NULL, &nb_ext, nb_frames);
    expect_true(result == OPUS_BUFFER_TOO_SMALL, "expected OPUS_BUFFER_TOO_SMALL");
 }
 
@@ -284,13 +289,14 @@
 void test_extensions_parse_fail(void)
 {
    static const opus_extension_data ext[] = {
-      {2, 0, (const unsigned char *)"a", 1},
+      {3, 0, (const unsigned char *)"a", 1},
       {33, 1, (const unsigned char *)"NOT DRED", 8},
-      {3, 4, (const unsigned char *)NULL, 0},
+      {4, 4, (const unsigned char *)NULL, 0},
       {32, 10, (const unsigned char *)"DRED", 4}
    };
    opus_extension_data ext_out[10];
    int nb_ext;
+   int nb_frames;
    int len, result;
    unsigned char packet[32];
 
@@ -298,28 +304,43 @@
    len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
    packet[4] = 255;
    nb_ext = 10;
-   result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext);
+   nb_frames = 11;
+   result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext,
+    nb_frames);
    expect_true(result == OPUS_INVALID_PACKET, "expected OPUS_INVALID_PACKET");
    /* note, opus_packet_extensions_count stops at the invalid frame increment
       and tells us that we have 1 extension */
-   result = opus_packet_extensions_count(packet, len);
+   result = opus_packet_extensions_count(packet, len, nb_frames);
    expect_true(result == 1, "expected opus_packet_extensions_count to return 1");
 
    /* create invalid frame increment */
    nb_ext = 10;
    len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
+   /* first by reducing the number of frames */
+   result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext, 5);
+   expect_true(result == OPUS_INVALID_PACKET, "expected OPUS_INVALID_PACKET");
+
+   /* note, opus_packet_extensions_count stops at the invalid frame increment
+      and tells us that we have 3 extensions */
+   result = opus_packet_extensions_count(packet, len, 5);
+   expect_true(result == 3, "expected opus_packet_extensions_count to return 3");
+
+   /* then by increasing the increment */
    packet[14] = 255;
-   result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext);
+   result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext,
+    nb_frames);
    expect_true(result == OPUS_INVALID_PACKET, "expected OPUS_INVALID_PACKET");
+
    /* note, opus_packet_extensions_count stops at the invalid frame increment
       and tells us that we have 2 extensions */
-   result = opus_packet_extensions_count(packet, len);
+   result = opus_packet_extensions_count(packet, len, nb_frames);
    expect_true(result == 2, "expected opus_packet_extensions_count to return 2");
 
    /* not enough space */
    nb_ext = 1;
    len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
-   result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext);
+   result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext,
+    nb_frames);
    expect_true(result == OPUS_BUFFER_TOO_SMALL, "expected OPUS_BUFFER_TOO_SMALL");
 
    /* overflow for long extension length */
@@ -331,7 +352,7 @@
       buf[0] = 33<<1 | 1;
       memset(buf + 1, 0xFF, LENSIZE - 1);
       buf[LENSIZE] = 0xFE;
-      result = opus_packet_extensions_parse(buf, len, ext_out, &nb_ext);
+      result = opus_packet_extensions_parse(buf, len, ext_out, &nb_ext, 1);
       expect_true(result == OPUS_INVALID_PACKET, "expected OPUS_INVALID_PACKET");
       free(buf);
    }
@@ -339,7 +360,9 @@
 
 #define NB_RANDOM_EXTENSIONS 100000000
 #define MAX_EXTENSION_SIZE 200
-#define MAX_NB_EXTENSIONS 100
+/* The worst case is a 48-frame packet filled entirely with 0-byte short
+    extensions followed by an RTE. */
+#define MAX_NB_EXTENSIONS ((MAX_EXTENSION_SIZE - 1)*48)
 
 void test_random_extensions_parse(void)
 {
@@ -356,7 +379,7 @@
       for (j=0;j<len;j++)
          payload[j] = fast_rand()&0xFF;
       nb_ext = fast_rand()%(MAX_NB_EXTENSIONS+1);
-      result = opus_packet_extensions_parse(payload, len, ext_out, &nb_ext);
+      result = opus_packet_extensions_parse(payload, len, ext_out, &nb_ext, 48);
       expect_true(result == OPUS_OK || result == OPUS_BUFFER_TOO_SMALL || result == OPUS_INVALID_PACKET, "expected OPUS_OK, OPUS_BUFFER_TOO_SMALL or OPUS_INVALID_PACKET");
       /* Even if parsing fails, check that the extensions that got extracted make sense. */
       for (j=0;j<nb_ext;j++)
@@ -425,7 +448,8 @@
    res = opus_packet_parse_impl(packet_out, res, 0, NULL, NULL, size,
       NULL, NULL, &padding, &padding_len);
    nb_ext = 10;
-   res = opus_packet_extensions_parse(padding, padding_len, ext_out, &nb_ext);
+   res = opus_packet_extensions_parse(padding, padding_len, ext_out, &nb_ext,
+    3);
    expect_true(nb_ext == 4, "Expected 4 extensions");
    for (i = 0 ; i < nb_ext; i++)
    {
--