From 6dabc64aea921626a7f0a7f3b4dd4a00e7a32b7f Mon Sep 17 00:00:00 2001 From: "Timothy B. Terriberry" Date: Thu, 25 Jul 2024 11:39:29 -0700 Subject: [PATCH] Add extension iterator. Rather than repeating the code to iterate through extensions in three different places, each with slight differences, different edge cases, different error handling, etc., create an iterator that can be used everywhere. --- src/extensions.c | 166 ++++++++++++++++++++--------------- src/opus_decoder.c | 79 ++++++----------- src/opus_private.h | 20 ++++- tests/test_opus_extensions.c | 10 ++- 4 files changed, 145 insertions(+), 130 deletions(-) diff --git a/src/extensions.c b/src/extensions.c index d81dd1b52..543a6dbc7 100644 --- a/src/extensions.c +++ b/src/extensions.c @@ -38,7 +38,8 @@ /* Given an extension payload, advance data to the next extension and return the length of the remaining extensions. */ -opus_int32 skip_extension(const unsigned char **data, opus_int32 len, opus_int32 *header_size) +static opus_int32 skip_extension(const unsigned char **data, opus_int32 len, + opus_int32 *header_size) { int id, L; if (len==0) @@ -91,95 +92,114 @@ opus_int32 skip_extension(const unsigned char **data, opus_int32 len, opus_int32 } } -/* Count the number of extensions, excluding real padding and separators. */ -opus_int32 opus_packet_extensions_count(const unsigned char *data, opus_int32 len) -{ - opus_int32 curr_len; - opus_int32 count=0; - const unsigned char *curr_data = data; - +void opus_extension_iterator_init(OpusExtensionIterator *iter, + const unsigned char *data, opus_int32 len) { celt_assert(len >= 0); celt_assert(data != NULL || len == 0); + iter->curr_data = iter->data = data; + iter->curr_len = iter->len = len; + iter->curr_frame = 0; +} - curr_len = len; - while (curr_len > 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->curr_len = iter->len; + iter->curr_frame = 0; +} + +/* Return the next extension (excluding real padding and separators). */ +int opus_extension_iterator_next(OpusExtensionIterator *iter, + opus_extension_data *ext) { + opus_int32 header_size; + if (iter->curr_len < 0) { + return OPUS_INVALID_PACKET; + } + while (iter->curr_len > 0) { + const unsigned char *curr_data0; int id; - opus_int32 header_size; - id = *curr_data>>1; - curr_len = skip_extension(&curr_data, curr_len, &header_size); - if (curr_len < 0) + int L; + curr_data0 = iter->curr_data; + id = *curr_data0>>1; + L = *curr_data0&1; + iter->curr_len = skip_extension(&iter->curr_data, iter->curr_len, + &header_size); + if (iter->curr_len < 0) { return OPUS_INVALID_PACKET; - if (id > 1) - count++; + } + celt_assert(iter->curr_data - iter->data == iter->len - iter->curr_len); + if (id == 1) { + if (L == 0) { + iter->curr_frame++; + } + else { + iter->curr_frame += curr_data0[1]; + } + if (iter->curr_frame >= 48) { + iter->curr_len = -1; + return OPUS_INVALID_PACKET; + } + } + else if (id > 1) { + if (ext != NULL) { + ext->id = id; + ext->frame = iter->curr_frame; + ext->data = curr_data0 + header_size; + ext->len = iter->curr_data - curr_data0 - header_size; + } + return 1; + } } + return 0; +} + +int opus_extension_iterator_find(OpusExtensionIterator *iter, + opus_extension_data *ext, int id) { + opus_extension_data curr_ext; + int ret; + for(;;) { + ret = opus_extension_iterator_next(iter, &curr_ext); + if (ret <= 0) { + return ret; + } + if (curr_ext.id == id) { + *ext = curr_ext; + return ret; + } + } +} + +/* Count the number of extensions, excluding real padding and separators. */ +opus_int32 opus_packet_extensions_count(const unsigned char *data, opus_int32 len) +{ + OpusExtensionIterator iter; + int count; + opus_extension_iterator_init(&iter, data, len); + 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) { - const unsigned char *curr_data; - opus_int32 curr_len; - int curr_frame=0; - opus_int32 count=0; - - celt_assert(len >= 0); - celt_assert(data != NULL || len == 0); + OpusExtensionIterator iter; + int count; + int ret; celt_assert(nb_extensions != NULL); celt_assert(extensions != NULL || *nb_extensions == 0); - - curr_data = data; - curr_len = len; - while (curr_len > 0) - { - int id; - opus_int32 header_size; - opus_extension_data curr_ext; - id = *curr_data>>1; - if (id > 1) - { - curr_ext.id = id; - curr_ext.frame = curr_frame; - curr_ext.data = curr_data; - } else if (id == 1) - { - int L = *curr_data&1; - if (L==0) - curr_frame++; - else { - if (curr_len >= 2) - curr_frame += curr_data[1]; - /* Else we're at the end and it doesn't matter. */ - } - if (curr_frame >= 48) - { - *nb_extensions = count; - return OPUS_INVALID_PACKET; - } - } - curr_len = skip_extension(&curr_data, curr_len, &header_size); - /* printf("curr_len = %d, header_size = %d\n", curr_len, header_size); */ - if (curr_len < 0) - { - *nb_extensions = count; - return OPUS_INVALID_PACKET; - } - celt_assert(curr_data - data == len - curr_len); - if (id > 1) - { - if (count == *nb_extensions) - { - return OPUS_BUFFER_TOO_SMALL; - } - curr_ext.len = curr_data - curr_ext.data - header_size; - curr_ext.data += header_size; - extensions[count++] = curr_ext; + opus_extension_iterator_init(&iter, data, len); + for (count=0;; count++) { + opus_extension_data ext; + ret = opus_extension_iterator_next(&iter, &ext); + if (ret <= 0) break; + if (count == *nb_extensions) { + return OPUS_BUFFER_TOO_SMALL; } + extensions[count] = ext; } - celt_assert(curr_len == 0); *nb_extensions = count; - return OPUS_OK; + 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) diff --git a/src/opus_decoder.c b/src/opus_decoder.c index d15af58a8..7ed0979d4 100644 --- a/src/opus_decoder.c +++ b/src/opus_decoder.c @@ -1296,69 +1296,48 @@ int opus_dred_decoder_ctl(OpusDREDDecoder *dred_dec, int request, ...) #ifdef ENABLE_DRED static int dred_find_payload(const unsigned char *data, opus_int32 len, const unsigned char **payload, int *dred_frame_offset) { - const unsigned char *data0; - opus_int32 len0; - int frame = 0; + OpusExtensionIterator iter; + opus_extension_data ext; + const unsigned char *padding; + opus_int32 padding_len; int nb_frames; const unsigned char *frames[48]; opus_int16 size[48]; int frame_size; + int ret; *payload = NULL; /* Get the padding section of the packet. */ - nb_frames = opus_packet_parse_impl(data, len, 0, NULL, frames, size, NULL, NULL, &data0, &len0); - if (nb_frames < 0) - return nb_frames; + ret = opus_packet_parse_impl(data, len, 0, NULL, frames, size, NULL, NULL, + &padding, &padding_len); + if (ret < 0) + return ret; + nb_frames = ret; frame_size = opus_packet_get_samples_per_frame(data, 48000); - data = data0; - len = len0; - /* Scan extensions in order until we find the earliest frame with DRED data. */ - while (len > 0) - { - opus_int32 header_size; - int id, L; - len0 = len; - data0 = data; - id = *data0 >> 1; - L = *data0 & 0x1; - len = skip_extension(&data, len, &header_size); - if (len < 0) + opus_extension_iterator_init(&iter, padding, padding_len); + for (;;) { + ret = opus_extension_iterator_find(&iter, &ext, DRED_EXTENSION_ID); + if (ret <= 0) + return ret; + if (ext.frame >= nb_frames) break; - if (id == 1) - { - if (L==0) - { - frame++; - } else { - frame += data0[1]; - } - if (frame >= nb_frames) { - break; - } - } else if (id == DRED_EXTENSION_ID) - { - const unsigned char *curr_payload; - opus_int32 curr_payload_len; - curr_payload = data0+header_size; - curr_payload_len = (data-data0)-header_size; - /* DRED position in the packet, in units of 2.5 ms like for the signaled DRED offset. */ - *dred_frame_offset = frame*frame_size/120; + /* 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 - /* Check that temporary extension type and version match. - This check will be removed once extension is finalized. */ - if (curr_payload_len > DRED_EXPERIMENTAL_BYTES && curr_payload[0] == 'D' && curr_payload[1] == DRED_EXPERIMENTAL_VERSION) { - *payload = curr_payload+2; - return curr_payload_len-2; - } + /* Check that temporary extension type and version match. + This check will be removed once extension is finalized. */ + if (ext.len > DRED_EXPERIMENTAL_BYTES && ext.data[0] == 'D' + && ext.data[1] == DRED_EXPERIMENTAL_VERSION) { + *payload = ext.data+2; + return ext.len-2; + } #else - if (curr_payload_len > 0) { - *payload = curr_payload; - return curr_payload_len; - } -#endif + if (ext.len > 0) { + *payload = ext.data; + return ext.len; } +#endif } - return 0; } #endif diff --git a/src/opus_private.h b/src/opus_private.h index 279f5f95f..1f7158466 100644 --- a/src/opus_private.h +++ b/src/opus_private.h @@ -46,6 +46,14 @@ struct OpusRepacketizer { opus_int32 padding_len[48]; }; +typedef struct OpusExtensionIterator { + const unsigned char *data; + const unsigned char *curr_data; + opus_int32 len; + opus_int32 curr_len; + int curr_frame; +} OpusExtensionIterator; + typedef struct { int id; int frame; @@ -53,6 +61,15 @@ typedef struct { opus_int32 len; } opus_extension_data; +void opus_extension_iterator_init(OpusExtensionIterator *iter, + const unsigned char *data, opus_int32 len); + +void opus_extension_iterator_reset(OpusExtensionIterator *iter); +int opus_extension_iterator_next(OpusExtensionIterator *iter, + opus_extension_data *ext); +int opus_extension_iterator_find(OpusExtensionIterator *iter, + opus_extension_data *ext, int id); + typedef struct ChannelLayout { int nb_channels; int nb_streams; @@ -171,9 +188,6 @@ static OPUS_INLINE int align(int i) return ((i + alignment - 1) / alignment) * alignment; } -/* More than that is ridiculous for now (3 * max frames per packet)*/ -opus_int32 skip_extension(const unsigned char **data, opus_int32 len, opus_int32 *header_size); - int opus_packet_parse_impl(const unsigned char *data, opus_int32 len, int self_delimited, unsigned char *out_toc, const unsigned char *frames[48], opus_int16 size[48], diff --git a/tests/test_opus_extensions.c b/tests/test_opus_extensions.c index ce0e69140..2db72c43a 100644 --- a/tests/test_opus_extensions.c +++ b/tests/test_opus_extensions.c @@ -300,8 +300,10 @@ void test_extensions_parse_fail(void) nb_ext = 10; result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext); 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); - expect_true(result == OPUS_INVALID_PACKET, "expected OPUS_INVALID_PACKET"); + expect_true(result == 1, "expected opus_packet_extensions_count to return 1"); /* create invalid frame increment */ nb_ext = 10; @@ -309,10 +311,10 @@ void test_extensions_parse_fail(void) packet[14] = 255; result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext); expect_true(result == OPUS_INVALID_PACKET, "expected OPUS_INVALID_PACKET"); - /* note, opus_packet_extensions_count does not read the invalid frame increment - and tells us that we have 4 extensions */ + /* 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); - expect_true(result == 4, "expected opus_packet_extensions_count to return 4"); + expect_true(result == 2, "expected opus_packet_extensions_count to return 2"); /* not enough space */ nb_ext = 1;