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");
--
⑨