summaryrefslogtreecommitdiffstats
path: root/media/base
diff options
context:
space:
mode:
Diffstat (limited to 'media/base')
-rw-r--r--media/base/seekable_buffer.cc192
-rw-r--r--media/base/seekable_buffer.h132
-rw-r--r--media/base/seekable_buffer_unittest.cc283
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());
+}
+
+}