diff options
Diffstat (limited to 'media/base')
-rw-r--r-- | media/base/seekable_buffer.cc | 192 | ||||
-rw-r--r-- | media/base/seekable_buffer.h | 132 | ||||
-rw-r--r-- | media/base/seekable_buffer_unittest.cc | 283 |
3 files changed, 607 insertions, 0 deletions
diff --git a/media/base/seekable_buffer.cc b/media/base/seekable_buffer.cc new file mode 100644 index 0000000..a3f31bc --- /dev/null +++ b/media/base/seekable_buffer.cc @@ -0,0 +1,192 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/base/seekable_buffer.h" + +#include <algorithm> + +#include "base/logging.h" +#include "base/stl_util-inl.h" + +namespace media { + +SeekableBuffer::SeekableBuffer(size_t backward_capacity, + size_t forward_capacity) + : current_buffer_offset_(0), + backward_capacity_(backward_capacity), + backward_bytes_(0), + forward_capacity_(forward_capacity), + forward_bytes_(0) { + current_buffer_ = buffers_.begin(); +} + +SeekableBuffer::~SeekableBuffer() { + STLDeleteElements(&buffers_); +} + +size_t SeekableBuffer::Read(size_t size, uint8* data) { + DCHECK(data); + return InternalRead(size, data); +} + +bool SeekableBuffer::Append(size_t size, const uint8* data) { + // Since the forward capacity is only used to check the criteria for buffer + // full, we will always append data to the buffer. + Buffer* buffer = new Buffer(size); + memcpy(buffer->data.get(), data, size); + buffers_.push_back(buffer); + + // After we have written the first buffer, update the |current_buffer_| to + // point to it. + if (current_buffer_ == buffers_.end()) { + DCHECK_EQ(0u, forward_bytes_); + current_buffer_ = buffers_.begin(); + } + + // Update the |forward_bytes_| counter since we have more bytes. + forward_bytes_ += size; + + // Advise the user to stop append if the amount of forward bytes exceeds + // the forward capacity. A false return value means the user should stop + // appending more data to this buffer. + if (forward_bytes_ >= forward_capacity_) + return false; + return true; +} + +bool SeekableBuffer::Seek(int32 offset) { + if (offset > 0) + return SeekForward(offset); + else if (offset < 0) + return SeekBackward(-offset); + return true; +} + +bool SeekableBuffer::SeekForward(size_t size) { + // Perform seeking forward only if we have enough bytes in the queue. + if (size > forward_bytes_) + return false; + + // Do a read of |size| bytes. + size_t taken = InternalRead(size, NULL); + DCHECK_EQ(taken, size); + return true; +} + +bool SeekableBuffer::SeekBackward(size_t size) { + if (size > backward_bytes_) + return false; + // Record the number of bytes taken. + size_t taken = 0; + // Loop until we taken enough bytes and rewind by the desired |size|. + while (taken < size) { + // |current_buffer_| can never be invalid when we are in this loop. It can + // only be invalid before any data is appended, this case should be handled + // by checks before we enter this loop. + DCHECK(current_buffer_ != buffers_.end()); + + // We try to at most |size| bytes in the backward direction, we also have + // to account for the offset we are in the current buffer, take the minimum + // between the two to determine the amount of bytes to take from the + // current buffer. + size_t consumed = std::min(size - taken, current_buffer_offset_); + + // Decreases the offset in the current buffer since we are rewinding. + current_buffer_offset_ -= consumed; + + // Increase the amount of bytes taken in the backward direction, this + // determines when to stop the loop. + taken += consumed; + + // Forward bytes increases, and backward bytes decreases by the amount + // consumed in the current buffer. + forward_bytes_ += consumed; + backward_bytes_ -= consumed; + DCHECK_GE(backward_bytes_, 0u); + + // The current buffer pointed by current iterator has been consumed, + // move the iterator backward so it points to the previous buffer. + if (current_buffer_offset_ == 0) { + if (current_buffer_ == buffers_.begin()) + break; + // Move the iterator backward. + --current_buffer_; + // Set the offset into the current buffer to be the buffer size as we + // are preparing for rewind for next iteration. + current_buffer_offset_ = (*current_buffer_)->size; + } + } + DCHECK_EQ(taken, size); + return true; +} + +void SeekableBuffer::EvictBackwardBuffers() { + // Advances the iterator until we hit the current pointer. + while (backward_bytes_ > backward_capacity_) { + BufferQueue::iterator i = buffers_.begin(); + if (i == current_buffer_) + break; + Buffer* buffer = *i; + backward_bytes_ -= buffer->size; + DCHECK_GE(backward_bytes_, 0u); + + delete *i; + buffers_.erase(i); + } +} + +size_t SeekableBuffer::InternalRead(size_t size, uint8* data) { + // Counts how many bytes are actually read from the buffer queue. + size_t taken = 0; + + while (taken < size) { + // |current_buffer_| is valid since the first time this buffer is appended + // with data. + if (current_buffer_ == buffers_.end()) { + DCHECK_EQ(0u, forward_bytes_); + break; + } + Buffer* buffer = *current_buffer_; + + // Find the right amount to copy from the current buffer referenced by + // |buffer|. We shall copy no more than |size| bytes in total and each + // single step copied no more than the current buffer size. + size_t copied = std::min(size - taken, + buffer->size - current_buffer_offset_); + + // |data| is NULL if we are seeking forward, thus there's no need to copy. + if (data) + memcpy(data + taken, buffer->data.get() + current_buffer_offset_, copied); + + // Increase total number of bytes copied, which regulates when to end this + // loop. + taken += copied; + + // We have read |copied| bytes from the current buffer, advances the offset. + current_buffer_offset_ += copied; + + // We have less forward bytes and more backward bytes, update these counters + // by |copied|. + forward_bytes_ -= copied; + backward_bytes_ += copied; + DCHECK_GE(forward_bytes_, 0u); + + // The buffer has been consumed. + if (current_buffer_offset_ == buffer->size) { + BufferQueue::iterator next = current_buffer_; + ++next; + // If we are at the last buffer, don't advance. + if (next == buffers_.end()) + break; + + // Advances the iterator. + current_buffer_ = next; + current_buffer_offset_ = 0; + } + } + EvictBackwardBuffers(); + return taken; +} + +} // namespace media diff --git a/media/base/seekable_buffer.h b/media/base/seekable_buffer.h new file mode 100644 index 0000000..7360bfd --- /dev/null +++ b/media/base/seekable_buffer.h @@ -0,0 +1,132 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// SeekableBuffer to support backward and forward seeking in a buffer for +// reading a media data source. +// +// In order to support backward and forward seeking, this class buffers data in +// both backward and forward directions, the current read position can be reset +// to anywhere in the buffered data. +// +// The amount of data buffered is regulated by two variables at construction, +// |backward_capacity| and |forward_capacity|. +// +// In the case of reading and seeking forward, the current read position +// advances and there will be more data in the backward direction. If backward +// bytes exceeds |backward_capacity|, the exceeding bytes are evicted and thus +// backward_bytes() will always be less than or equal to |backward_capacity|. +// The eviction will be caused by Read() and Seek() in the forward direction and +// is done internally when the mentioned criteria is fulfilled. +// +// In the case of appending data to the buffer, there is an advisory limit of +// how many bytes can be kept in the forward direction, regulated by +// |forward_capacity|. The append operation (by calling Append()) that caused +// forward bytes to exceed |forward_capacity| will have a return value that +// advises a halt of append operation, further append operations are allowed but +// are not advised. Since this class is used as a backend buffer for caching +// media files downloaded from network we cannot afford losing data, we can +// only advise a halt of further writing to this buffer. +// This class is not inherently thread-safe. Concurrent access must be +// externally serialized. + +#ifndef MEDIA_BASE_SEEKABLE_BUFFER_H_ +#define MEDIA_BASE_SEEKABLE_BUFFER_H_ + +#include <list> + +#include "base/basictypes.h" +#include "base/lock.h" +#include "base/scoped_ptr.h" + +namespace media { + +class SeekableBuffer { + public: + // Construct an instance with forward capacity of |forward_capacity| + // and backward capacity of |backward_capacity|. The values are in bytes. + SeekableBuffer(size_t backward_capacity, size_t forward_capacity); + + ~SeekableBuffer(); + + // Read a maximum of |size| bytes into |buffer| from the current read + // position. Return the number of bytes read. + // The current read position will advances by the amount of bytes read. If + // reading caused backward bytes to exceed backward_capacity(), an eviction + // of backward buffer will be done internally. + size_t Read(size_t size, uint8* buffer); + + // Append |data| with |size| bytes to this buffer. If this buffer becomes full + // or is already full then return false, otherwise true. + // Append operations are always successful, a return value of false only means + // that there's more forward bytes than the capacity, data is still in this + // buffer, but user is advised not to write any more. + bool Append(size_t size, const uint8* data); + + // Move the read position by an offset of |offset| bytes. If |offset| is + // positive, the current read position is moved forward. If negative, the + // current read position is moved backward. A zero |offset| value will keep + // the current read position stationary. + // If |offset| exceeds bytes buffered in either direction, reported by + // forward_bytes() when seeking forward and backward_bytes() when seeking + // backward, the seek operation will fail and return value will be false. + // If the seek operation fails, the current read position will not be updated. + // If a forward seeking caused backward bytes to exceed backward_capacity(), + // this method call will cause an eviction of backward buffer. + bool Seek(int32 offset); + + // Returns the number of bytes buffered beyond the current read position. + size_t forward_bytes() const { return forward_bytes_; } + + // Returns the number of bytes buffered that precedes the current read + // position. + size_t backward_bytes() const { return backward_bytes_; } + + // Returns the maximum number of bytes that should be kept in the forward + // direction. + size_t forward_capacity() const { return forward_capacity_; } + + // Returns the maximum number of bytes that should be kept in the backward + // direction. + size_t backward_capacity() const { return backward_capacity_; } + + private: + // A helper method to evict buffers in the backward direction until backward + // bytes is within the backward capacity. + void EvictBackwardBuffers(); + + // An internal method shared by Read() and SeekForward() that actually does + // reading. It reads a maximum of |size| bytes into |data|. Returns the number + // of bytes read. The current read position will be moved forward by the + // number of bytes read. If |data| is NULL, only the current read position + // will advance but no data will be copied. + size_t InternalRead(size_t size, uint8* data); + + bool SeekForward(size_t size); + + bool SeekBackward(size_t size); + + // A structure that contains a block of data. + struct Buffer { + explicit Buffer(size_t len) : data(new uint8[len]), size(len) {} + // Pointer to data. + scoped_array<uint8> data; + // Size of this block. + size_t size; + }; + + typedef std::list<Buffer*> BufferQueue; + BufferQueue::iterator current_buffer_; + BufferQueue buffers_; + size_t current_buffer_offset_; + + size_t backward_capacity_; + size_t backward_bytes_; + + size_t forward_capacity_; + size_t forward_bytes_; +}; + +} // namespace media + +#endif // MEDIA_BASE_SEEKABLE_BUFFER_H_ diff --git a/media/base/seekable_buffer_unittest.cc b/media/base/seekable_buffer_unittest.cc new file mode 100644 index 0000000..65df7e1 --- /dev/null +++ b/media/base/seekable_buffer_unittest.cc @@ -0,0 +1,283 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/time.h" +#include "media/base/seekable_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class SeekableBufferTest : public testing::Test { + protected: + virtual void SetUp() { + // Setup seed. + size_t seed = static_cast<int32>(base::Time::Now().ToInternalValue()); + srand(seed); + LOG(INFO) << "Random seed: " << seed; + + // Creates a test data. + data_.reset(new uint8[kDataSize]); + for (size_t i = 0; i < kDataSize; i++) + data_.get()[i] = static_cast<char>(rand()); + + // Creates a temp buffer. + write_buffer_.reset(new uint8[kDataSize]); + + // Setup |buffer_|. + buffer_.reset(new media::SeekableBuffer(kBufferSize, kBufferSize)); + } + + size_t GetRandomInt(size_t maximum) { + return rand() % maximum + 1; + } + + static const size_t kDataSize = 409600; + static const size_t kBufferSize = 4096; + scoped_ptr<media::SeekableBuffer> buffer_; + scoped_array<uint8> data_; + scoped_array<uint8> write_buffer_; +}; + +TEST_F(SeekableBufferTest, RandomReadWrite) { + size_t write_position = 0; + size_t read_position = 0; + while (read_position < kDataSize) { + // Write a random amount of data. + size_t write_size = GetRandomInt(kBufferSize); + write_size = std::min(write_size, kDataSize - write_position); + bool should_append = + buffer_->Append(write_size, data_.get() + write_position); + write_position += write_size; + EXPECT_GE(write_position, read_position); + EXPECT_EQ(write_position - read_position, buffer_->forward_bytes()); + EXPECT_EQ(should_append, buffer_->forward_bytes() < kBufferSize) + << "Incorrect buffer full reported"; + + // Read a random amount of data. + size_t read_size = GetRandomInt(kBufferSize); + size_t bytes_read = buffer_->Read(read_size, write_buffer_.get()); + EXPECT_GE(read_size, bytes_read); + EXPECT_EQ(0, memcmp(write_buffer_.get(), + data_.get() + read_position, + bytes_read)); + read_position += bytes_read; + EXPECT_GE(write_position, read_position); + EXPECT_EQ(write_position - read_position, buffer_->forward_bytes()); + } +} + +TEST_F(SeekableBufferTest, ReadWriteSeek) { + const size_t kWriteSize = 512; + const size_t kReadSize = kWriteSize / 4; + + size_t write_position = 0; + size_t read_position = 0; + size_t forward_bytes = 0; + for (int i = 0; i < 10; ++i) { + // Write until buffer is full. + for (int j = kBufferSize / kWriteSize; j > 0; --j) { + bool should_append = + buffer_->Append(kWriteSize, data_.get() + write_position); + EXPECT_EQ(j > 1, should_append) << "Incorrect buffer full reported"; + write_position += kWriteSize; + forward_bytes += kWriteSize; + EXPECT_EQ(forward_bytes, buffer_->forward_bytes()); + } + + // Simulate a read and seek pattern. Each loop reads 4 times, each time + // reading a quarter of |kWriteSize|. + for (size_t j = 0; j < kBufferSize / kWriteSize; ++j) { + // Read. + EXPECT_EQ(kReadSize, buffer_->Read(kReadSize, write_buffer_.get())); + forward_bytes -= kReadSize; + EXPECT_EQ(forward_bytes, buffer_->forward_bytes()); + EXPECT_EQ(0, memcmp(write_buffer_.get(), + data_.get() + read_position, + kReadSize)); + read_position += kReadSize; + + // Seek forward. + EXPECT_TRUE(buffer_->Seek(2 * kReadSize)); + forward_bytes -= 2 * kReadSize; + read_position += 2 * kReadSize; + EXPECT_EQ(forward_bytes, buffer_->forward_bytes()); + + // Read. + EXPECT_EQ(kReadSize, buffer_->Read(kReadSize, write_buffer_.get())); + forward_bytes -= kReadSize; + EXPECT_EQ(forward_bytes, buffer_->forward_bytes()); + EXPECT_EQ(0, memcmp(write_buffer_.get(), + data_.get() + read_position, + kReadSize)); + read_position += kReadSize; + + // Seek backward. + EXPECT_TRUE(buffer_->Seek(-3 * static_cast<int32>(kReadSize))); + forward_bytes += 3 * kReadSize; + read_position -= 3 * kReadSize; + EXPECT_EQ(forward_bytes, buffer_->forward_bytes()); + + // Read. + EXPECT_EQ(kReadSize, buffer_->Read(kReadSize, write_buffer_.get())); + forward_bytes -= kReadSize; + EXPECT_EQ(forward_bytes, buffer_->forward_bytes()); + EXPECT_EQ(0, memcmp(write_buffer_.get(), + data_.get() + read_position, + kReadSize)); + read_position += kReadSize; + + // Read. + EXPECT_EQ(kReadSize, buffer_->Read(kReadSize, write_buffer_.get())); + forward_bytes -= kReadSize; + EXPECT_EQ(forward_bytes, buffer_->forward_bytes()); + EXPECT_EQ(0, memcmp(write_buffer_.get(), + data_.get() + read_position, + kReadSize)); + read_position += kReadSize; + + // Seek forward. + EXPECT_TRUE(buffer_->Seek(kReadSize)); + forward_bytes -= kReadSize; + read_position += kReadSize; + EXPECT_EQ(forward_bytes, buffer_->forward_bytes()); + } + } +} + +TEST_F(SeekableBufferTest, BufferFull) { + const size_t kWriteSize = 512; + + // Write and expect the buffer to be not full. + size_t write_position = 0; + for (size_t i = 0; i < kBufferSize / kWriteSize - 1; ++i) { + EXPECT_TRUE(buffer_->Append(kWriteSize, data_.get() + write_position)); + write_position += kWriteSize; + EXPECT_EQ(write_position, buffer_->forward_bytes()); + } + + // Write 10 more times, the buffer is full. + for (int i = 0; i < 10; ++i) { + EXPECT_FALSE(buffer_->Append(kWriteSize, data_.get() + write_position)); + write_position += kWriteSize; + EXPECT_EQ(write_position, buffer_->forward_bytes()); + } + + // Read until the buffer is empty. + size_t read_position = 0; + while (buffer_->forward_bytes()) { + // Read a random amount of data. + size_t read_size = GetRandomInt(kBufferSize); + size_t forward_bytes = buffer_->forward_bytes(); + size_t bytes_read = buffer_->Read(read_size, write_buffer_.get()); + EXPECT_EQ(0, memcmp(write_buffer_.get(), + data_.get() + read_position, + bytes_read)); + if (read_size > forward_bytes) + EXPECT_EQ(forward_bytes, bytes_read); + else + EXPECT_EQ(read_size, bytes_read); + read_position += bytes_read; + EXPECT_GE(write_position, read_position); + EXPECT_EQ(write_position - read_position, buffer_->forward_bytes()); + } + + // Expect we have no bytes left. + EXPECT_EQ(0u, buffer_->forward_bytes()); + EXPECT_EQ(0u, buffer_->Read(1, write_buffer_.get())); +} + +TEST_F(SeekableBufferTest, SeekBackward) { + EXPECT_EQ(0u, buffer_->forward_bytes()); + EXPECT_EQ(0u, buffer_->backward_bytes()); + EXPECT_FALSE(buffer_->Seek(1)); + EXPECT_FALSE(buffer_->Seek(-1)); + + const size_t kWriteSize = 512; + const size_t kReadSize = 256; + + // Write into buffer until it's full. + size_t write_position = 0; + for (size_t i = 0; i < kBufferSize / kWriteSize; ++i) { + // Write a random amount of data. + buffer_->Append(kWriteSize, data_.get() + write_position); + write_position += kWriteSize; + } + + // Read until buffer is empty. + size_t read_position = 0; + for (size_t i = 0; i < kBufferSize / kReadSize; ++i) { + EXPECT_EQ(kReadSize, buffer_->Read(kReadSize, write_buffer_.get())); + EXPECT_EQ(0, memcmp(write_buffer_.get(), + data_.get() + read_position, + kReadSize)); + read_position += kReadSize; + } + + // Seek backward. + EXPECT_TRUE(buffer_->Seek(-static_cast<int32>(kBufferSize))); + EXPECT_FALSE(buffer_->Seek(-1)); + + // Read again. + read_position = 0; + for (size_t i = 0; i < kBufferSize / kReadSize; ++i) { + EXPECT_EQ(kReadSize, buffer_->Read(kReadSize, write_buffer_.get())); + EXPECT_EQ(0, memcmp(write_buffer_.get(), + data_.get() + read_position, + kReadSize)); + read_position += kReadSize; + } +} + +TEST_F(SeekableBufferTest, SeekForward) { + size_t write_position = 0; + size_t read_position = 0; + while (read_position < kDataSize) { + for (int i = 0; i < 10; ++i) { + // Write a random amount of data. + size_t write_size = GetRandomInt(kBufferSize); + write_size = std::min(write_size, kDataSize - write_position); + if (!write_size) + break; + bool should_append = + buffer_->Append(write_size, data_.get() + write_position); + write_position += write_size; + EXPECT_GE(write_position, read_position); + EXPECT_EQ(write_position - read_position, buffer_->forward_bytes()); + EXPECT_EQ(should_append, buffer_->forward_bytes() < kBufferSize) + << "Incorrect buffer full status reported"; + } + + // Read a random amount of data. + size_t seek_size = GetRandomInt(kBufferSize); + if (buffer_->Seek(seek_size)) + read_position += seek_size; + EXPECT_GE(write_position, read_position); + EXPECT_EQ(write_position - read_position, buffer_->forward_bytes()); + + // Read a random amount of data. + size_t read_size = GetRandomInt(kBufferSize); + size_t bytes_read = buffer_->Read(read_size, write_buffer_.get()); + EXPECT_GE(read_size, bytes_read); + EXPECT_EQ(0, memcmp(write_buffer_.get(), + data_.get() + read_position, + bytes_read)); + read_position += bytes_read; + EXPECT_GE(write_position, read_position); + EXPECT_EQ(write_position - read_position, buffer_->forward_bytes()); + } +} + +TEST_F(SeekableBufferTest, AllMethods) { + EXPECT_EQ(0u, buffer_->Read(0, write_buffer_.get())); + EXPECT_EQ(0u, buffer_->Read(1, write_buffer_.get())); + EXPECT_TRUE(buffer_->Seek(0)); + EXPECT_FALSE(buffer_->Seek(-1)); + EXPECT_FALSE(buffer_->Seek(1)); + EXPECT_EQ(0u, buffer_->forward_bytes()); + EXPECT_EQ(0u, buffer_->backward_bytes()); +} + +} |