Skip to content

Commit

Permalink
Creates a util for creating little-endian 32-bit ints from uint8s
Browse files Browse the repository at this point in the history
To be used in LpcmDecoder.

PiperOrigin-RevId: 631819934
  • Loading branch information
trevorknight authored and jwcullen committed May 9, 2024
1 parent 1e8f6e9 commit ff96df6
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 0 deletions.
1 change: 1 addition & 0 deletions iamf/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ cc_library(
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:span",
],
)

Expand Down
24 changes: 24 additions & 0 deletions iamf/common/obu_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/types/span.h"
#include "iamf/obu/leb128.h"

namespace iamf_tools {
Expand Down Expand Up @@ -112,6 +113,29 @@ absl::Status Int32ToInt16(int32_t input, int16_t& output) {
return absl::OkStatus();
}

absl::Status LittleEndianBytesToInt32(absl::Span<const uint8_t> bytes,
int32_t& output) {
// If we have bytes A, B, C, D, then we need to read them as:
// (D << 24) | (C << 16) | (B << 8) | A
// If we have less than four bytes, e.g. two bytes, we would read them as:
// (B << 8) | A
// with the upper bits filled with the sign bit.
const size_t num_bytes = bytes.size();
if (num_bytes > 4 || num_bytes < 1) {
return absl::InvalidArgumentError("Need [1, 4] bytes to make an int32_t");
}
int32_t result = 0;
for (int i = 0; i < bytes.size(); ++i) {
result |= static_cast<int32_t>(bytes[i]) << (i * 8);
}
// If we had less than 4 input bytes, we need to fill the upper bits with the
// sign bit to get the correct result.
const int shift = 8 * (4 - num_bytes);
result = (result << shift) >> shift;
output = result;
return absl::OkStatus();
}

absl::Status ClipDoubleToInt32(double input, int32_t& output) {
if (std::isnan(input)) {
return absl::InvalidArgumentError("");
Expand Down
11 changes: 11 additions & 0 deletions iamf/common/obu_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/types/span.h"
#include "iamf/common/macros.h"
#include "iamf/obu/leb128.h"

Expand Down Expand Up @@ -129,6 +130,16 @@ absl::Status Uint32ToUint16(uint32_t input, uint16_t& output);
*/
absl::Status Int32ToInt16(int32_t input, int16_t& output);

/*!\brief Creates a 32-bit signed integer from the [1, 4] bytes.
*
* \param bytes Bytes to convert.
* \param output Converted value if successful.
* \return `absl::OkStatus()` if successful. `absl::InvalidArgumentError()` if
* the number of bytes is not in the range of [1, 4].
*/
absl::Status LittleEndianBytesToInt32(absl::Span<const uint8_t> bytes,
int32_t& output);

/*!\brief Clips and typecasts the input value and writes to the output argument.
*
* \param input Value to convert.
Expand Down
1 change: 1 addition & 0 deletions iamf/common/tests/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ cc_test(
deps = [
"//iamf/common:obu_util",
"@com_google_absl//absl/status",
"@com_google_absl//absl/types:span",
"@com_google_googletest//:gtest_main",
],
)
Expand Down
69 changes: 69 additions & 0 deletions iamf/common/tests/obu_util_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <vector>

#include "absl/status/status.h"
#include "absl/types/span.h"
#include "gtest/gtest.h"

namespace iamf_tools {
Expand Down Expand Up @@ -516,6 +517,74 @@ INSTANTIATE_TEST_SUITE_P(Invalid, Int32ToInt16Format,
{INT32_MAX, 0, absl::StatusCode::kInvalidArgument},
}));

struct LittleEndianBytesToInt32TestCase {
std::vector<uint8_t> test_val;
int32_t expected_val;
};

using LittleEndianBytesToInt32Format =
::testing::TestWithParam<LittleEndianBytesToInt32TestCase>;

TEST_P(LittleEndianBytesToInt32Format, TestLittleEndianBytesToInt32) {
const LittleEndianBytesToInt32TestCase& test_case = GetParam();

int32_t result;
EXPECT_TRUE(LittleEndianBytesToInt32(test_case.test_val, result).ok());
EXPECT_EQ(result, test_case.expected_val);
}

TEST(LittleEndianBytesToInt32Test, InvalidTooManyBytes) {
int32_t unused_result = 0;
absl::Status status =
LittleEndianBytesToInt32({1, 2, 3, 4, 5}, unused_result);

EXPECT_FALSE(status.ok());
EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
}

TEST(LittleEndianBytesToInt32Test, InvalidTooFewBytes) {
int32_t result = 0;
absl::Status status = LittleEndianBytesToInt32({}, result);

EXPECT_FALSE(status.ok());
EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
}

INSTANTIATE_TEST_SUITE_P(OneByte, LittleEndianBytesToInt32Format,
::testing::ValuesIn<LittleEndianBytesToInt32TestCase>({
{{0b00000000}, 0},
{{0b01111111}, 127},
{{0b11111111}, -1},
{{0b10000000}, -128},
}));

INSTANTIATE_TEST_SUITE_P(TwoBytes, LittleEndianBytesToInt32Format,
::testing::ValuesIn<LittleEndianBytesToInt32TestCase>({
{{0x00, 0x00}, 0},
{{0x01, 0x00}, 1},
{{0xff, 0x7f}, 32767},
{{0xff, 0xff}, -1},
{{0x00, 0x80}, -32768},
}));

INSTANTIATE_TEST_SUITE_P(ThreeBytes, LittleEndianBytesToInt32Format,
::testing::ValuesIn<LittleEndianBytesToInt32TestCase>({
{{0x00, 0x00, 0x00}, 0},
{{0x01, 0x00, 0x00}, 1},
{{0xff, 0xff, 0x7f}, 8388607},
{{0xff, 0xff, 0xff}, -1},
{{0x00, 0x00, 0x80}, -8388608},
}));

INSTANTIATE_TEST_SUITE_P(FourBytes, LittleEndianBytesToInt32Format,
::testing::ValuesIn<LittleEndianBytesToInt32TestCase>({
{{0x00, 0x00, 0x00, 0x00}, 0},
{{0x01, 0x00, 0x00, 0x00}, 1},
{{0xff, 0xff, 0xff, 0x7f}, 2147483647},
{{0xff, 0xff, 0xff, 0xff}, -1},
{{0x00, 0x00, 0x00, 0x80}, -2147483648},
}));

struct ClipDoubleToInt32TestCase {
double test_val;
int32_t expected_val;
Expand Down

0 comments on commit ff96df6

Please sign in to comment.