From 86153c26a00b30579a5b6c226ad5fd2b46b9926b Mon Sep 17 00:00:00 2001 From: Noah Richards Date: Tue, 28 Apr 2015 15:13:44 -0700 Subject: [PATCH] Added a BitBufferWriter subclass that contains methods for writing bit and byte-sized data, along with exponential golomb encoded data. This pattern (read-only base, writable subclass) was picked to maintain a *Buffer option that doesn't copy the source bits when parsing. ByteBuffer and Buffer both copy. I'm open to discussion on what the type relationship would be, though :) Tests have been added to ensure the symmetric nature of read/write operations. BUG= R=bcornell@google.com, pthatcher@webrtc.org Review URL: https://webrtc-codereview.appspot.com/45259005 Cr-Commit-Position: refs/heads/master@{#9107} --- webrtc/base/bitbuffer.cc | 135 +++++++++++++++++++++++++++- webrtc/base/bitbuffer.h | 52 +++++++++-- webrtc/base/bitbuffer_unittest.cc | 140 +++++++++++++++++++++++++++--- 3 files changed, 307 insertions(+), 20 deletions(-) diff --git a/webrtc/base/bitbuffer.cc b/webrtc/base/bitbuffer.cc index f41962acb..753083816 100644 --- a/webrtc/base/bitbuffer.cc +++ b/webrtc/base/bitbuffer.cc @@ -10,6 +10,7 @@ #include "webrtc/base/bitbuffer.h" +#include #include #include "webrtc/base/checks.h" @@ -19,8 +20,7 @@ namespace { // Returns the lowest (right-most) |bit_count| bits in |byte|. uint8 LowestBits(uint8 byte, size_t bit_count) { DCHECK_LE(bit_count, 8u); - uint8 mask_shift = 8 - static_cast(bit_count); - return byte & (0xFF >> mask_shift); + return byte & ((1 << bit_count) - 1); } // Returns the highest (left-most) |bit_count| bits in |byte|, shifted to the @@ -32,6 +32,41 @@ uint8 HighestBits(uint8 byte, size_t bit_count) { return (byte & mask) >> shift; } +// Returns the highest byte of |val| in a uint8. +uint8 HighestByte(uint64 val) { + return static_cast(val >> 56); +} + +// Returns the result of writing partial data from |source|, of +// |source_bit_count| size in the highest bits, to |target| at +// |target_bit_offset| from the highest bit. +uint8 WritePartialByte(uint8 source, size_t source_bit_count, + uint8 target, size_t target_bit_offset) { + DCHECK(target_bit_offset < 8); + DCHECK(source_bit_count < 9); + DCHECK(source_bit_count <= (8 - target_bit_offset)); + // Generate a mask for just the bits we're going to overwrite, so: + uint8 mask = + // The number of bits we want, in the most significant bits... + static_cast(0xFF << (8 - source_bit_count)) + // ...shifted over to the target offset from the most signficant bit. + >> target_bit_offset; + + // We want the target, with the bits we'll overwrite masked off, or'ed with + // the bits from the source we want. + return (target & ~mask) | (source >> target_bit_offset); +} + +// Counts the number of bits used in the binary representation of val. +size_t CountBits(uint64 val) { + size_t bit_count = 0; + while (val != 0) { + bit_count++; + val >>= 1; + } + return bit_count; +} + } // namespace namespace rtc { @@ -143,12 +178,104 @@ bool BitBuffer::ReadExponentialGolomb(uint32* val) { // read the value. size_t value_bit_count = zero_bit_count + 1; if (value_bit_count > 32 || !ReadBits(val, value_bit_count)) { - byte_offset_ = original_byte_offset; - bit_offset_ = original_bit_offset; + CHECK(Seek(original_byte_offset, original_bit_offset)); return false; } *val -= 1; return true; } +void BitBuffer::GetCurrentOffset( + size_t* out_byte_offset, size_t* out_bit_offset) { + CHECK(out_byte_offset != NULL); + CHECK(out_bit_offset != NULL); + *out_byte_offset = byte_offset_; + *out_bit_offset = bit_offset_; +} + +bool BitBuffer::Seek(size_t byte_offset, size_t bit_offset) { + if (byte_offset > byte_count_ || bit_offset > 7 || + (byte_offset == byte_count_ && bit_offset > 0)) { + return false; + } + byte_offset_ = byte_offset; + bit_offset_ = bit_offset; + return true; +} + +BitBufferWriter::BitBufferWriter(uint8* bytes, size_t byte_count) + : BitBuffer(bytes, byte_count), writable_bytes_(bytes) { +} + +bool BitBufferWriter::WriteUInt8(uint8 val) { + return WriteBits(val, sizeof(uint8) * 8); +} + +bool BitBufferWriter::WriteUInt16(uint16 val) { + return WriteBits(val, sizeof(uint16) * 8); +} + +bool BitBufferWriter::WriteUInt32(uint32 val) { + return WriteBits(val, sizeof(uint32) * 8); +} + +bool BitBufferWriter::WriteBits(uint64 val, size_t bit_count) { + if (bit_count > RemainingBitCount()) { + return false; + } + size_t total_bits = bit_count; + + // For simplicity, push the bits we want to read from val to the highest bits. + val <<= (sizeof(uint64) * 8 - bit_count); + + uint8* bytes = writable_bytes_ + byte_offset_; + + // The first byte is relatively special; the bit offset to write to may put us + // in the middle of the byte, and the total bit count to write may require we + // save the bits at the end of the byte. + size_t remaining_bits_in_current_byte = 8 - bit_offset_; + size_t bits_in_first_byte = + std::min(bit_count, remaining_bits_in_current_byte); + *bytes = WritePartialByte( + HighestByte(val), bits_in_first_byte, *bytes, bit_offset_); + if (bit_count <= remaining_bits_in_current_byte) { + // Nothing left to write, so quit early. + return ConsumeBits(total_bits); + } + + // Subtract what we've written from the bit count, shift it off the value, and + // write the remaining full bytes. + val <<= bits_in_first_byte; + bytes++; + bit_count -= bits_in_first_byte; + while (bit_count >= 8) { + *bytes++ = HighestByte(val); + val <<= 8; + bit_count -= 8; + } + + // Last byte may also be partial, so write the remaining bits from the top of + // val. + if (bit_count > 0) { + *bytes = WritePartialByte(HighestByte(val), bit_count, *bytes, 0); + } + + // All done! Consume the bits we've written. + return ConsumeBits(total_bits); +} + +bool BitBufferWriter::WriteExponentialGolomb(uint32 val) { + // We don't support reading UINT32_MAX, because it doesn't fit in a uint32 + // when encoded, so don't support writing it either. + if (val == std::numeric_limits::max()) { + return false; + } + uint64 val_to_encode = static_cast(val) + 1; + + // We need to write CountBits(val+1) 0s and then val+1. Since val (as a + // uint64) has leading zeros, we can just write the total golomb encoded size + // worth of bits, knowing the value will appear last. + return WriteBits(val_to_encode, CountBits(val_to_encode) * 2 - 1); +} + } // namespace rtc diff --git a/webrtc/base/bitbuffer.h b/webrtc/base/bitbuffer.h index 356a817a6..5aa1f1323 100644 --- a/webrtc/base/bitbuffer.h +++ b/webrtc/base/bitbuffer.h @@ -16,14 +16,20 @@ namespace rtc { // A class, similar to ByteBuffer, that can parse bit-sized data out of a set of -// bytes. Has a similar API to the read-only parts of ByteBuffer, plus methods -// for reading bit-sized data and processing exponential golomb encoded data. +// bytes. Has a similar API to ByteBuffer, plus methods for reading bit-sized +// and exponential golomb encoded data. For a writable version, use +// BitBufferWriter. Unlike ByteBuffer, this class doesn't make a copy of the +// source bytes, so it can be used on read-only data. // Sizes/counts specify bits/bytes, for clarity. // Byte order is assumed big-endian/network. class BitBuffer { public: BitBuffer(const uint8* bytes, size_t byte_count); + // Gets the current offset, in bytes/bits, from the start of the buffer. The + // bit offset is the offset into the current byte, in the range [0,7]. + void GetCurrentOffset(size_t* out_byte_offset, size_t* out_bit_offset); + // The remaining bits in the byte buffer. uint64 RemainingBitCount() const; @@ -34,14 +40,15 @@ class BitBuffer { bool ReadUInt32(uint32* val); // Reads bit-sized values from the buffer. Returns false if there isn't enough - // data left for the specified type. + // data left for the specified bit count.. bool ReadBits(uint32* val, size_t bit_count); // Peeks bit-sized values from the buffer. Returns false if there isn't enough - // data left for the specified type. Doesn't move the current read offset. + // data left for the specified number of bits. Doesn't move the current + // offset. bool PeekBits(uint32* val, size_t bit_count); - // Reads the exponential golomb encoded value at the current bit offset. + // Reads the exponential golomb encoded value at the current offset. // Exponential golomb values are encoded as: // 1) x = source val + 1 // 2) In binary, write [countbits(x) - 1] 0s, then x @@ -58,7 +65,11 @@ class BitBuffer { // there aren't enough bits left in the buffer. bool ConsumeBits(size_t bit_count); - private: + // Sets the current offset to the provied byte/bit offsets. The bit + // offset is from the given byte, in the range [0,7]. + bool Seek(size_t byte_offset, size_t bit_offset); + + protected: const uint8* const bytes_; // The total size of |bytes_|. size_t byte_count_; @@ -70,6 +81,35 @@ class BitBuffer { DISALLOW_COPY_AND_ASSIGN(BitBuffer); }; +// A BitBuffer API for write operations. Supports symmetric write APIs to the +// reading APIs of BitBuffer. Note that the read/write offset is shared with the +// BitBuffer API, so both reading and writing will consume bytes/bits. +class BitBufferWriter : public BitBuffer { + public: + // Constructs a bit buffer for the writable buffer of |bytes|. + BitBufferWriter(uint8* bytes, size_t byte_count); + + // Writes byte-sized values from the buffer. Returns false if there isn't + // enough data left for the specified type. + bool WriteUInt8(uint8 val); + bool WriteUInt16(uint16 val); + bool WriteUInt32(uint32 val); + + // Writes bit-sized values to the buffer. Returns false if there isn't enough + // room left for the specified number of bits. + bool WriteBits(uint64 val, size_t bit_count); + + // Writes the exponential golomb encoded version of the supplied value. + // Returns false if there isn't enough room left for the value. + bool WriteExponentialGolomb(uint32 val); + + private: + // The buffer, as a writable array. + uint8* const writable_bytes_; + + DISALLOW_COPY_AND_ASSIGN(BitBufferWriter); +}; + } // namespace rtc #endif // WEBRTC_BASE_BITBUFFER_H_ diff --git a/webrtc/base/bitbuffer_unittest.cc b/webrtc/base/bitbuffer_unittest.cc index 143ea6fab..21cffd67a 100644 --- a/webrtc/base/bitbuffer_unittest.cc +++ b/webrtc/base/bitbuffer_unittest.cc @@ -135,6 +135,39 @@ TEST(BitBufferTest, ReadBits) { EXPECT_FALSE(buffer.ReadBits(&val, 1)); } +TEST(BitBufferTest, SetOffsetValues) { + uint8 bytes[4] = {0}; + BitBufferWriter buffer(bytes, 4); + + size_t byte_offset, bit_offset; + // Bit offsets are [0,7]. + EXPECT_TRUE(buffer.Seek(0, 0)); + EXPECT_TRUE(buffer.Seek(0, 7)); + buffer.GetCurrentOffset(&byte_offset, &bit_offset); + EXPECT_EQ(0u, byte_offset); + EXPECT_EQ(7u, bit_offset); + EXPECT_FALSE(buffer.Seek(0, 8)); + buffer.GetCurrentOffset(&byte_offset, &bit_offset); + EXPECT_EQ(0u, byte_offset); + EXPECT_EQ(7u, bit_offset); + // Byte offsets are [0,length]. At byte offset length, the bit offset must be + // 0. + EXPECT_TRUE(buffer.Seek(0, 0)); + EXPECT_TRUE(buffer.Seek(2, 4)); + buffer.GetCurrentOffset(&byte_offset, &bit_offset); + EXPECT_EQ(2u, byte_offset); + EXPECT_EQ(4u, bit_offset); + EXPECT_TRUE(buffer.Seek(4, 0)); + EXPECT_FALSE(buffer.Seek(5, 0)); + buffer.GetCurrentOffset(&byte_offset, &bit_offset); + EXPECT_EQ(4u, byte_offset); + EXPECT_EQ(0u, bit_offset); + EXPECT_FALSE(buffer.Seek(4, 1)); + + // Passing a NULL out parameter is death. + EXPECT_DEATH(buffer.GetCurrentOffset(&byte_offset, NULL), ""); +} + uint64 GolombEncoded(uint32 val) { val++; uint32 bit_counter = val; @@ -146,19 +179,23 @@ uint64 GolombEncoded(uint32 val) { return static_cast(val) << (64 - (bit_count * 2 - 1)); } -TEST(BitBufferTest, GolombString) { - char test_string[] = "my precious"; - for (size_t i = 0; i < ARRAY_SIZE(test_string); ++i) { - uint64 encoded_val = GolombEncoded(test_string[i]); - // Use ByteBuffer to convert to bytes, to account for endianness (BitBuffer - // requires network order). - ByteBuffer byteBuffer; +TEST(BitBufferTest, GolombUint32Values) { + ByteBuffer byteBuffer; + byteBuffer.Resize(16); + BitBuffer buffer(reinterpret_cast(byteBuffer.Data()), + byteBuffer.Capacity()); + // Test over the uint32 range with a large enough step that the test doesn't + // take forever. Around 20,000 iterations should do. + const int kStep = std::numeric_limits::max() / 20000; + for (uint32 i = 0; i < std::numeric_limits::max() - kStep; + i += kStep) { + uint64 encoded_val = GolombEncoded(i); + byteBuffer.Clear(); byteBuffer.WriteUInt64(encoded_val); - BitBuffer buffer(reinterpret_cast(byteBuffer.Data()), - byteBuffer.Length()); uint32 decoded_val; + EXPECT_TRUE(buffer.Seek(0, 0)); EXPECT_TRUE(buffer.ReadExponentialGolomb(&decoded_val)); - EXPECT_EQ(test_string[i], static_cast(decoded_val)); + EXPECT_EQ(i, decoded_val); } } @@ -180,4 +217,87 @@ TEST(BitBufferTest, NoGolombOverread) { EXPECT_EQ(0x01FEu, decoded_val); } +TEST(BitBufferWriterTest, SymmetricReadWrite) { + uint8 bytes[16] = {0}; + BitBufferWriter buffer(bytes, 4); + + // Write some bit data at various sizes. + EXPECT_TRUE(buffer.WriteBits(0x2u, 3)); + EXPECT_TRUE(buffer.WriteBits(0x1u, 2)); + EXPECT_TRUE(buffer.WriteBits(0x53u, 7)); + EXPECT_TRUE(buffer.WriteBits(0x0u, 2)); + EXPECT_TRUE(buffer.WriteBits(0x1u, 1)); + EXPECT_TRUE(buffer.WriteBits(0x1ABCDu, 17)); + // That should be all that fits in the buffer. + EXPECT_FALSE(buffer.WriteBits(1, 1)); + + EXPECT_TRUE(buffer.Seek(0, 0)); + uint32 val; + EXPECT_TRUE(buffer.ReadBits(&val, 3)); + EXPECT_EQ(0x2u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 2)); + EXPECT_EQ(0x1u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 7)); + EXPECT_EQ(0x53u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 2)); + EXPECT_EQ(0x0u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 1)); + EXPECT_EQ(0x1u, val); + EXPECT_TRUE(buffer.ReadBits(&val, 17)); + EXPECT_EQ(0x1ABCDu, val); + // And there should be nothing left. + EXPECT_FALSE(buffer.ReadBits(&val, 1)); +} + +TEST(BitBufferWriterTest, SymmetricBytesMisaligned) { + uint8 bytes[16] = {0}; + BitBufferWriter buffer(bytes, 16); + + // Offset 3, to get things misaligned. + EXPECT_TRUE(buffer.ConsumeBits(3)); + EXPECT_TRUE(buffer.WriteUInt8(0x12u)); + EXPECT_TRUE(buffer.WriteUInt16(0x3456u)); + EXPECT_TRUE(buffer.WriteUInt32(0x789ABCDEu)); + + buffer.Seek(0, 3); + uint8 val8; + uint16 val16; + uint32 val32; + EXPECT_TRUE(buffer.ReadUInt8(&val8)); + EXPECT_EQ(0x12u, val8); + EXPECT_TRUE(buffer.ReadUInt16(&val16)); + EXPECT_EQ(0x3456u, val16); + EXPECT_TRUE(buffer.ReadUInt32(&val32)); + EXPECT_EQ(0x789ABCDEu, val32); +} + +TEST(BitBufferWriterTest, SymmetricGolomb) { + char test_string[] = "my precious"; + uint8 bytes[64] = {0}; + BitBufferWriter buffer(bytes, 64); + for (size_t i = 0; i < ARRAY_SIZE(test_string); ++i) { + EXPECT_TRUE(buffer.WriteExponentialGolomb(test_string[i])); + } + buffer.Seek(0, 0); + for (size_t i = 0; i < ARRAY_SIZE(test_string); ++i) { + uint32 val; + EXPECT_TRUE(buffer.ReadExponentialGolomb(&val)); + EXPECT_LE(val, std::numeric_limits::max()); + EXPECT_EQ(test_string[i], static_cast(val)); + } +} + +TEST(BitBufferWriterTest, WriteClearsBits) { + uint8 bytes[] = {0xFF, 0xFF}; + BitBufferWriter buffer(bytes, 2); + EXPECT_TRUE(buffer.ConsumeBits(3)); + EXPECT_TRUE(buffer.WriteBits(0, 1)); + EXPECT_EQ(0xEFu, bytes[0]); + EXPECT_TRUE(buffer.WriteBits(0, 3)); + EXPECT_EQ(0xE1u, bytes[0]); + EXPECT_TRUE(buffer.WriteBits(0, 2)); + EXPECT_EQ(0xE0u, bytes[0]); + EXPECT_EQ(0x7F, bytes[1]); +} + } // namespace rtc