shithub: opus

Download patch

ref: 10ceaedb30197b9fe366b0a52838f3ec73ed493e
parent: 58bf8b413c064c41913ac1a5ad734971c811892c
author: Timothy B. Terriberry <tterribe@xiph.org>
date: Thu Jul 25 16:48:36 EDT 2024

Encoder implementation of Repeat These Extensions.

Also adds tests which exercise generating repeated extensions as
 well as the count_ext() and parse_ext() API for parsing extensions
 in frame order.

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

--- a/src/extensions.c
+++ b/src/extensions.c
@@ -400,33 +400,197 @@
    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)
+static int write_extension_payload(unsigned char *data, opus_int32 len,
+ opus_int32 pos, const opus_extension_data *ext, int last) {
+   celt_assert(ext->id >= 3 && ext->id <= 127);
+   if (ext->id < 32)
+   {
+      if (ext->len < 0 || ext->len > 1)
+         return OPUS_BAD_ARG;
+      if (ext->len > 0) {
+         if (len-pos < ext->len)
+            return OPUS_BUFFER_TOO_SMALL;
+         if (data) data[pos] = ext->data[0];
+         pos++;
+      }
+   } else {
+      opus_int32 length_bytes;
+      if (ext->len < 0)
+         return OPUS_BAD_ARG;
+      length_bytes = 1 + ext->len/255;
+      if (last)
+         length_bytes = 0;
+      if (len-pos < length_bytes + ext->len)
+         return OPUS_BUFFER_TOO_SMALL;
+      if (!last)
+      {
+         opus_int32 j;
+         for (j=0;j<ext->len/255;j++) {
+            if (data) data[pos] = 255;
+            pos++;
+         }
+         if (data) data[pos] = ext->len % 255;
+         pos++;
+      }
+      if (data) OPUS_COPY(&data[pos], ext->data, ext->len);
+      pos += ext->len;
+   }
+   return pos;
+}
+
+static int write_extension(unsigned char *data, opus_int32 len, opus_int32 pos,
+ const opus_extension_data *ext, int last) {
+   if (len-pos < 1)
+      return OPUS_BUFFER_TOO_SMALL;
+   celt_assert(ext->id >= 3 && ext->id <= 127);
+   if (data) data[pos] = (ext->id<<1) + (ext->id < 32 ? ext->len : !last);
+   pos++;
+   return write_extension_payload(data, len, pos, ext, last);
+}
+
+opus_int32 opus_packet_extensions_generate(unsigned char *data, opus_int32 len,
+ const opus_extension_data  *extensions, opus_int32 nb_extensions,
+ int nb_frames, int pad)
 {
-   int max_frame=0;
+   opus_int32 frame_min_idx[48];
+   opus_int32 frame_max_idx[48];
+   opus_int32 frame_repeat_idx[48];
    opus_int32 i;
-   int frame;
+   int f;
    int curr_frame = 0;
    opus_int32 pos = 0;
    opus_int32 written = 0;
 
    celt_assert(len >= 0);
+   if (nb_frames > 48) return OPUS_BAD_ARG;
 
+   /* Do a little work up-front to make this O(nb_extensions) instead of
+       O(nb_extensions*nb_frames) so long as the extensions are in frame
+       order (without requiring that they be in frame order). */
+   for (f=0;f<nb_frames;f++) frame_min_idx[f] = nb_extensions;
+   OPUS_CLEAR(frame_max_idx, nb_frames);
    for (i=0;i<nb_extensions;i++)
    {
-      max_frame = IMAX(max_frame, extensions[i].frame);
-      if (extensions[i].id < 3 || extensions[i].id > 127)
-         return OPUS_BAD_ARG;
+      f = extensions[i].frame;
+      if (f < 0 || f >= nb_frames) return OPUS_BAD_ARG;
+      if (extensions[i].id < 3 || extensions[i].id > 127) return OPUS_BAD_ARG;
+      frame_min_idx[f] = IMIN(frame_min_idx[f], i);
+      frame_max_idx[f] = IMAX(frame_max_idx[f], i+1);
    }
-   if (max_frame >= 48) return OPUS_BAD_ARG;
-   for (frame=0;frame<=max_frame;frame++)
+   for (f=0;f<nb_frames;f++) frame_repeat_idx[f] = frame_min_idx[f];
+   for (f=0;f<nb_frames;f++)
    {
-      for (i=0;i<nb_extensions;i++)
+      opus_int32 repeat_data_end_idx;
+      int repeat_count;
+      repeat_count = 0;
+      repeat_data_end_idx = -1;
+      if (f + 1 < nb_frames)
       {
-         if (extensions[i].frame == frame)
+         for (i=frame_min_idx[f];i<frame_max_idx[f];i++)
          {
+            if (extensions[i].frame == f)
+            {
+               int g;
+               /* Test if we can repeat this extension in future frames. */
+               for (g=f+1;g<nb_frames;g++)
+               {
+                  if (frame_repeat_idx[g] >= frame_max_idx[g]) break;
+                  celt_assert(extensions[frame_repeat_idx[g]].frame == g);
+                  if (extensions[frame_repeat_idx[g]].id != extensions[i].id)
+                  {
+                     break;
+                  }
+                  if (extensions[frame_repeat_idx[g]].id < 32
+                    && extensions[frame_repeat_idx[g]].len
+                    != extensions[i].len)
+                  {
+                     break;
+                  }
+               }
+               if (g < nb_frames) break;
+               /* We can! */
+               /* If this extension can have a non-empty payload, save the
+                   index of the last instance, so we can modify its L flag. */
+               if (extensions[i].id >= 32 || extensions[i].len) {
+                  repeat_data_end_idx = frame_repeat_idx[nb_frames-1];
+               }
+               /* Using the repeat mechanism almost always makes the
+                   encoding smaller (or at least no larger).
+                  However, there's one case where that might not be true: if
+                   the last repeated extension in the last frame was previously
+                   the last extension, but using the repeat mechanism makes
+                   that no longer true (because there are other non-repeated
+                   extensions in earlier frames that must now be coded after
+                   it), and coding its length requires more bytes than the
+                   repeat mechanism saves.
+                  This can only be true if it is a long extension with at least
+                   255 bytes of payload data (although sometimes it requires
+                   even more).
+                  Currently we do not check for that, and just always use the
+                   repeat mechanism if we can. */
+#if 0
+               if (frame_repeat_idx[nb_frames-1]+1 >=
+                frame_max_idx[nb_frames-1]
+                && extensions[frame_repeat_idx[nb_frames-1]].len >= 255) {
+                  opus_int32 savings;
+                  opus_int32 last_idx;
+                  int last_f;
+                  /* Start by assuming we will save one extension ID for each
+                      repeated extension in each future frame, as well as one
+                      frame separator. */
+                  savings = (nb_frames - 1 - f)*((repeat_count + 1) + 1);
+                  /* Count up the bytes we still need to spend on frame
+                      separators to code any non-repeated extensions.
+                     Also find the frame with the new last extension.
+                     Fortunately, we know that no future frames will try to use
+                      a repeat, because we know the last frame will not have
+                      that extension. */
+                  last_f = f;
+                  last_idx = i;
+                  for (g=f+1; g<nb_frames; g++) {
+                     if (frame_repeat_idx[g]+1 < frame_max_idx[g]) {
+                        savings--;
+                        if (last_f != g-1) savings--;
+                        last_f = g;
+                        last_idx = frame_repeat_idx[g];
+                     }
+                  }
+                  if (last_idx+1 < frame_max_idx[last_f]) {
+                     last_idx = frame_max_idx[last_f] - 1;
+                     /* If the new last extension is a long extension, we no
+                         longer need to code its length. */
+                     if (extensions[last_idx].id >= 32) {
+                        savings += extensions[last_idx].len/255 + 1;
+                     }
+                     savings -=
+                      (extensions[frame_repeat_idx[nb_frames-1]].len/255 + 1);
+                     if (savings < 0) break;
+                  }
+                  celt_assert(savings >= 0);
+               }
+#endif
+               /* Advance the repeat pointers. */
+               for (g=f+1; g<nb_frames; g++)
+               {
+                  int j;
+                  for (j=frame_repeat_idx[g]+1; j<frame_max_idx[g]
+                   && extensions[j].frame != g; j++);
+                  frame_repeat_idx[g] = j;
+               }
+               repeat_count++;
+               /* Point the repeat pointer for this frame to the current
+                   extension, so we know when to trigger the repeats. */
+               frame_repeat_idx[f] = i;
+            }
+         }
+      }
+      for (i=frame_min_idx[f];i<frame_max_idx[f];i++)
+      {
+         if (extensions[i].frame == f)
+         {
             /* Insert separator when needed. */
-            if (frame != curr_frame) {
-               int diff = frame - curr_frame;
+            if (f != curr_frame) {
+               int diff = f - curr_frame;
                if (len-pos < 2)
                   return OPUS_BUFFER_TOO_SMALL;
                if (diff == 1) {
@@ -438,50 +602,45 @@
                   if (data) data[pos] = diff;
                   pos++;
                }
-               curr_frame = frame;
+               curr_frame = f;
             }
-            if (extensions[i].id < 32)
-            {
-               if (extensions[i].len < 0 || extensions[i].len > 1)
-                  return OPUS_BAD_ARG;
-               if (len-pos < extensions[i].len+1)
-                  return OPUS_BUFFER_TOO_SMALL;
-               if (data) data[pos] = (extensions[i].id<<1) + extensions[i].len;
-               pos++;
-               if (extensions[i].len > 0) {
-                  if (data) data[pos] = extensions[i].data[0];
-                  pos++;
-               }
-            } else {
+
+            pos = write_extension(data, len, pos, extensions + i,
+             written == nb_extensions - 1);
+            if (pos < 0) return pos;
+            written++;
+
+            if (repeat_count > 0 && frame_repeat_idx[f] == i) {
+               int nb_repeated;
                int last;
-               opus_int32 length_bytes;
-               if (extensions[i].len < 0)
-                  return OPUS_BAD_ARG;
-               last = (written == nb_extensions - 1);
-               length_bytes = 1 + extensions[i].len/255;
-               if (last)
-                  length_bytes = 0;
-               if (len-pos < 1 + length_bytes + extensions[i].len)
+               int g;
+               /* Add the repeat indicator. */
+               nb_repeated = repeat_count*(nb_frames - (f + 1));
+               last = written + nb_repeated == nb_extensions;
+               if (len-pos < 1)
                   return OPUS_BUFFER_TOO_SMALL;
-               if (data) data[pos] = (extensions[i].id<<1) + !last;
+               if (data) data[pos] = 0x04 + !last;
                pos++;
-               if (!last)
+               for (g=f+1;g<nb_frames;g++)
                {
-                  opus_int32 j;
-                  for (j=0;j<extensions[i].len/255;j++) {
-                     if (data) data[pos] = 255;
-                     pos++;
+                  int j;
+                  for (j=frame_min_idx[g];j<frame_repeat_idx[g];j++)
+                  {
+                     if (extensions[j].frame == g)
+                     {
+                        pos = write_extension_payload(data, len, pos,
+                         extensions + j, last && j == repeat_data_end_idx);
+                        if (pos < 0) return pos;
+                        written++;
+                     }
                   }
-                  if (data) data[pos] = extensions[i].len % 255;
-                  pos++;
+                  frame_min_idx[g] = j;
                }
-               if (data) OPUS_COPY(&data[pos], extensions[i].data, extensions[i].len);
-               pos += extensions[i].len;
             }
-            written++;
          }
       }
    }
+   celt_assert(written == nb_extensions);
    /* If we need to pad, just prepend 0x01 bytes. Even better would be to fill the
       end with zeros, but that requires checking that turning the last extesion into
       an L=1 case still fits. */
--- a/src/opus_private.h
+++ b/src/opus_private.h
@@ -246,7 +246,9 @@
  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_generate(unsigned char *data, opus_int32 len,
+ const opus_extension_data *extensions, opus_int32 nb_extensions,
+ int nb_frames, int pad);
 
 opus_int32 opus_packet_extensions_count(const unsigned char *data,
  opus_int32 len, int nb_frames);
--- a/src/repacketizer.c
+++ b/src/repacketizer.c
@@ -260,7 +260,8 @@
       if (ext_count>0)
       {
          /* figure out how much space we need for the extensions */
-         ext_len = opus_packet_extensions_generate(NULL, maxlen-tot_size, all_extensions, ext_count, 0);
+         ext_len = opus_packet_extensions_generate(NULL, maxlen-tot_size,
+          all_extensions, ext_count, count, 0);
          if (ext_len < 0) return ext_len;
          if (!pad)
             pad_amount = ext_len + ext_len/254 + 1;
@@ -305,7 +306,8 @@
       ptr += len[i];
    }
    if (ext_len > 0) {
-      int ret = opus_packet_extensions_generate(&data[ext_begin], ext_len, all_extensions, ext_count, 0);
+      int ret = opus_packet_extensions_generate(&data[ext_begin], ext_len,
+       all_extensions, ext_count, count, 0);
       celt_assert(ret == ext_len);
    }
    for (i=ones_begin;i<ones_end;i++)
--- a/tests/test_opus_extensions.c
+++ b/tests/test_opus_extensions.c
@@ -55,7 +55,7 @@
    int result;
    unsigned char packet[32];
    const unsigned char *p = packet;
-   result = opus_packet_extensions_generate(packet, 23+4, ext, 4, 1);
+   result = opus_packet_extensions_generate(packet, 23+4, ext, 4, 11, 1);
    expect_true(result == 23+4, "expected length 23+4");
 
    /* expect padding */
@@ -117,7 +117,7 @@
    unsigned char packet[32];
 
    /* zero length packet, zero extensions */
-   result = opus_packet_extensions_generate(packet, 0, NULL, 0, 1);
+   result = opus_packet_extensions_generate(packet, 0, NULL, 0, 0, 1);
    expect_true(result == 0, "expected length 0");
 }
 
@@ -132,7 +132,8 @@
 
    int result;
    unsigned char packet[32];
-   result = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
+   result =
+    opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 11, 0);
    expect_true(result == 23, "expected length 23");
 }
 
@@ -157,7 +158,7 @@
       {
          size_t i;
          for (i=len;i<sizeof(packet);i++) packet[i] = 0xFE;
-         result = opus_packet_extensions_generate(packet, len, ext, 4, 1);
+         result = opus_packet_extensions_generate(packet, len, ext, 4, 11, 1);
          expect_true(result == OPUS_BUFFER_TOO_SMALL,
           "expected OPUS_BUFFER_TOO_SMALL");
          for (i=len;i<sizeof(packet);i++)
@@ -173,7 +174,8 @@
       static const opus_extension_data id_too_big[] = {
          {256, 0, (const unsigned char *)"a", 1},
       };
-      result = opus_packet_extensions_generate(packet, sizeof(packet), id_too_big, 1, 1);
+      result = opus_packet_extensions_generate(packet, sizeof(packet),
+       id_too_big, 1, 11, 1);
       expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
    }
 
@@ -182,16 +184,38 @@
       static const opus_extension_data id_too_small[] = {
          {2, 0, (const unsigned char *)"a", 1},
       };
-      result = opus_packet_extensions_generate(packet, sizeof(packet), id_too_small, 1, 1);
+      result = opus_packet_extensions_generate(packet, sizeof(packet),
+       id_too_small, 1, 11, 1);
       expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
    }
 
+   /* frame count too big */
+   {
+      static const opus_extension_data frame_too_big[] = {
+         {33, 11, (const unsigned char *)"a", 1},
+      };
+      result = opus_packet_extensions_generate(packet, sizeof(packet),
+       frame_too_big, 1, 49, 1);
+      expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
+   }
+
+   /* frame index too small */
+   {
+      static const opus_extension_data frame_too_big[] = {
+         {33, -1, (const unsigned char *)"a", 1},
+      };
+      result = opus_packet_extensions_generate(packet, sizeof(packet),
+       frame_too_big, 1, 11, 1);
+      expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
+   }
+
    /* frame index too big */
    {
       static const opus_extension_data frame_too_big[] = {
-         {33, 48, (const unsigned char *)"a", 1},
+         {33, 11, (const unsigned char *)"a", 1},
       };
-      result = opus_packet_extensions_generate(packet, sizeof(packet), frame_too_big, 1, 1);
+      result = opus_packet_extensions_generate(packet, sizeof(packet),
+       frame_too_big, 1, 11, 1);
       expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
    }
 
@@ -200,7 +224,8 @@
       static const opus_extension_data size_too_big[] = {
          {3, 0, (const unsigned char *)"abcd", 4},
       };
-      result = opus_packet_extensions_generate(packet, sizeof(packet), size_too_big, 1, 1);
+      result = opus_packet_extensions_generate(packet, sizeof(packet),
+       size_too_big, 1, 1, 1);
       expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
    }
 
@@ -209,7 +234,8 @@
       static const opus_extension_data neg_size[] = {
          {3, 0, NULL, -4},
       };
-      result = opus_packet_extensions_generate(packet, sizeof(packet), neg_size, 1, 1);
+      result = opus_packet_extensions_generate(packet, sizeof(packet),
+       neg_size, 1, 1, 1);
       expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
    }
 
@@ -218,7 +244,8 @@
       static const opus_extension_data neg_size_33[] = {
          {33, 0, NULL, -4},
       };
-      result = opus_packet_extensions_generate(packet, sizeof(packet), neg_size_33, 1, 1);
+      result = opus_packet_extensions_generate(packet, sizeof(packet),
+       neg_size_33, 1, 1, 1);
       expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
    }
 }
@@ -239,7 +266,7 @@
 
    nb_ext = 10;
    nb_frames = 11;
-   len = opus_packet_extensions_generate(packet, 32, ext, 4, 1);
+   len = opus_packet_extensions_generate(packet, 32, ext, 4, 11, 1);
    expect_true(len == 32, "expected length 32");
    result = opus_packet_extensions_count(packet, len, nb_frames);
    expect_true(result == 4, "expected opus_packet_extensions_count 4");
@@ -277,7 +304,7 @@
    int len, result;
    unsigned char packet[32];
 
-   len = opus_packet_extensions_generate(packet, 32, ext, 1, 1);
+   len = opus_packet_extensions_generate(packet, 32, ext, 1, 2, 1);
    expect_true(len == 32, "expected length 32");
 
    nb_ext = 0;
@@ -301,7 +328,7 @@
    unsigned char packet[32];
 
    /* create invalid length */
-   len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
+   len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 11, 0);
    packet[4] = 255;
    nb_ext = 10;
    nb_frames = 11;
@@ -315,7 +342,7 @@
 
    /* create invalid frame increment */
    nb_ext = 10;
-   len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
+   len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 11, 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");
@@ -338,7 +365,7 @@
 
    /* not enough space */
    nb_ext = 1;
-   len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
+   len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 11, 0);
    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");
@@ -358,6 +385,159 @@
    }
 }
 
+/* Check that the same extensions with the same data appear in both ext_in and
+    ext_out, with extensions in ext_out appearing in frame order, and the order
+    within a frame matching the order within a frame in ext_in. */
+static void check_ext_data(const opus_extension_data *ext_in,
+ const opus_extension_data *ext_out, int nb_ext)
+{
+   opus_int32 i;
+   opus_int32 j;
+   int prev_frame;
+   prev_frame = -1;
+   j = 0;
+   for (i=0;i<nb_ext;i++)
+   {
+      expect_true(ext_out[i].frame >= prev_frame,
+       "expected parsed extensions to be returned in frame order");
+      if (ext_out[i].frame > prev_frame) {
+         j = 0;
+      }
+      while (j < nb_ext && ext_in[j].frame != ext_out[i].frame) j++;
+      expect_true(j < nb_ext,
+       "expected enough extensions matching this frame");
+      expect_true(ext_in[j].id == ext_out[i].id,
+       "expected extension IDs to match");
+      expect_true(ext_in[j].len == ext_out[i].len,
+       "expected extension lengths to match");
+      /* The len check allows ext_in[j].data to be NULL without triggering
+          undefined behavior. */
+      expect_true(ext_in[j].len == 0 ||
+       memcmp(ext_in[j].data, ext_out[i].data, ext_out[i].len) == 0,
+       "expected extension data to match");
+      prev_frame = ext_out[i].frame;
+      j++;
+   }
+}
+
+#define NB_EXT (12)
+
+void test_extensions_repeating(void)
+{
+   static const opus_extension_data ext[] = {
+      {3, 0, (const unsigned char *)"a", 1},
+      {3, 1, (const unsigned char *)"b", 1},
+      {3, 2, (const unsigned char *)"c", 1},
+      {4, 0, (const unsigned char *)"d", 1},
+      {4, 1, (const unsigned char *)NULL, 0},
+      {4, 2, (const unsigned char *)NULL, 0},
+      {32, 1, (const unsigned char *)"DRED", 4},
+      {32, 2, (const unsigned char *)"DRED2", 5},
+      {5, 1, (const unsigned char *)NULL, 0},
+      {5, 2, (const unsigned char *)NULL, 0},
+      {6, 1, (const unsigned char *)"e", 1},
+      {6, 2, (const unsigned char *)"f", 1}
+   };
+   static const opus_int32 encoded_len[] = {
+      0,
+      2,
+      /* nb_ext = 2: don't try to repeat if the same extension is not used in
+          every frame */
+      5,
+      /* nb_ext = 3: do repeat if the same extension is used in every frame */
+      5,
+      7,
+      9,
+      /* nb_ext = 6: do not repeat short extensions if the lengths do not match
+          ... but do repeat after the first frame when the lengths do match. */
+      10,
+      /* nb_ext = 7: code a long extension with L=0 despite having more
+          extensions in future frames, because we already coded them via
+          repeats. */
+      15,
+      /* nb_ext = 8: repeat multiple extensions in the same frame.
+         code the last repeated extension with L=0 if it is a long
+          extension. */
+      21,
+      23,
+      /* nb_ext = 10: code the last repeated long extension with L=0 even if it
+          is followed by repeated short extensions, as long as they have L=0. */
+      22,
+      25,
+      /* nb_ext = 12: don't code the last repeated long extension with L=0 if
+          it is followed by repeated short extensions with L=1. */
+      26
+   };
+   opus_int32 nb_ext;
+   for (nb_ext = 0; nb_ext <= NB_EXT; nb_ext++) {
+      opus_extension_data ext_out[NB_EXT];
+      opus_int32 nb_frame_exts[48];
+      opus_int32 nb_ext_out;
+      int len, result;
+      unsigned char packet[32];
+      len = opus_packet_extensions_generate(packet, sizeof(packet), ext,
+       nb_ext, 3, 0);
+      expect_true(len == encoded_len[nb_ext],
+       "expected extension encoding length to match");
+      result = opus_packet_extensions_count_ext(packet, len, nb_frame_exts, 3);
+      expect_true(result == nb_ext, "expected extension count to match");
+      nb_ext_out = NB_EXT;
+      result = opus_packet_extensions_parse_ext(packet, len, ext_out,
+       &nb_ext_out, nb_frame_exts, 3);
+      expect_true(result == 0, "expected extension parsing to succeed");
+      expect_true(nb_ext_out == nb_ext, "expected extension count to match");
+      check_ext_data(ext, ext_out, nb_ext);
+      /* Special case some modifications to test things our generator will
+          never produce. */
+      if (nb_ext == 6) {
+         /* allow a repeat in the last frame, as well as trailing junk after an
+             L=0 repeat that MUST be ignored. */
+         packet[len++] = 2<<1|0;
+         packet[len++] = 3<<1|0;
+      }
+      else if (nb_ext == 8) {
+         /* insert padding before the repeat indicator */
+         memmove(packet+16, packet+15, len-15);
+         packet[15] = 0x1;
+         len++;
+         /* don't repeat the padding and continue decoding last extension with
+             L=0 */
+      }
+      else if (nb_ext == 10) {
+         /* use a frame separator with an increment of 0 as padding.
+            This should _not_ change which extensions get repeated. */
+         memmove(packet+17, packet+15, len-15);
+         packet[15] = 0x3;
+         packet[16] = 0;
+         len += 2;
+      }
+      else continue;
+      result = opus_packet_extensions_count_ext(packet, len, nb_frame_exts, 3);
+      expect_true(result == nb_ext, "expected extension count to match");
+      nb_ext_out = NB_EXT;
+      result = opus_packet_extensions_parse_ext(packet, len, ext_out,
+       &nb_ext_out, nb_frame_exts, 3);
+      expect_true(result == 0, "expected extension parsing to succeed");
+      expect_true(nb_ext_out == nb_ext, "expected extension count to match");
+      check_ext_data(ext, ext_out, nb_ext);
+      if (nb_ext == 8) {
+         /* allow multiple repeat indicators in the same frame */
+         memmove(packet+6, packet+5, len-5);
+         packet[5] = 2<<1|1;
+         len++;
+      }
+      else continue;
+      result = opus_packet_extensions_count_ext(packet, len, nb_frame_exts, 3);
+      expect_true(result == nb_ext, "expected extension count to match");
+      nb_ext_out = NB_EXT;
+      result = opus_packet_extensions_parse_ext(packet, len, ext_out,
+       &nb_ext_out, nb_frame_exts, 3);
+      expect_true(result == 0, "expected extension parsing to succeed");
+      expect_true(nb_ext_out == nb_ext, "expected extension count to match");
+      check_ext_data(ext, ext_out, nb_ext);
+   }
+}
+
 #define NB_RANDOM_EXTENSIONS 100000000
 #define MAX_EXTENSION_SIZE 200
 /* The worst case is a 48-frame packet filled entirely with 0-byte short
@@ -420,7 +600,8 @@
    packet[3] = 0;
 
    /* generate 2 extensions, id 33 and 100 */
-   len = opus_packet_extensions_generate(&packet[4], sizeof(packet)-4, ext, 2, 0);
+   len = opus_packet_extensions_generate(&packet[4], sizeof(packet)-4, ext, 2,
+    1, 0);
    /* update the padding length */
    packet[2] = len;
 
@@ -499,6 +680,7 @@
    test_extensions_parse_success();
    test_extensions_parse_zero();
    test_extensions_parse_fail();
+   test_extensions_repeating();
    test_random_extensions_parse();
    test_opus_repacketizer_out_range_impl();
    fprintf(stderr,"Tests completed successfully.\n");
--