1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Unit test for VideoCaptureBufferPool.
6
7#include "content/browser/renderer_host/media/video_capture_buffer_pool.h"
8
9#include "base/bind.h"
10#include "base/memory/ref_counted.h"
11#include "base/memory/scoped_ptr.h"
12#include "content/browser/renderer_host/media/video_capture_controller.h"
13#include "media/base/video_frame.h"
14#include "media/base/video_util.h"
15#include "testing/gtest/include/gtest/gtest.h"
16
17namespace content {
18
19class VideoCaptureBufferPoolTest : public testing::Test {
20 protected:
21  class Buffer {
22   public:
23    Buffer(const scoped_refptr<VideoCaptureBufferPool> pool,
24           int id,
25           void* data,
26           size_t size)
27        : pool_(pool), id_(id), data_(data), size_(size) {}
28    ~Buffer() { pool_->RelinquishProducerReservation(id()); }
29    int id() const { return id_; }
30    void* data() const { return data_; }
31    size_t size() const { return size_; }
32
33   private:
34    const scoped_refptr<VideoCaptureBufferPool> pool_;
35    const int id_;
36    void* const data_;
37    const size_t size_;
38  };
39  VideoCaptureBufferPoolTest()
40      : expected_dropped_id_(0),
41        pool_(new VideoCaptureBufferPool(3)) {}
42
43  void ExpectDroppedId(int expected_dropped_id) {
44    expected_dropped_id_ = expected_dropped_id;
45  }
46
47  scoped_ptr<Buffer> ReserveI420Buffer(const gfx::Size& dimensions) {
48    const size_t frame_bytes =
49        media::VideoFrame::AllocationSize(media::VideoFrame::I420, dimensions);
50    // To verify that ReserveI420Buffer always sets |buffer_id_to_drop|,
51    // initialize it to something different than the expected value.
52    int buffer_id_to_drop = ~expected_dropped_id_;
53    int buffer_id = pool_->ReserveForProducer(frame_bytes, &buffer_id_to_drop);
54    if (buffer_id == VideoCaptureBufferPool::kInvalidId)
55      return scoped_ptr<Buffer>();
56
57    void* memory;
58    size_t size;
59    pool_->GetBufferInfo(buffer_id, &memory, &size);
60    EXPECT_EQ(expected_dropped_id_, buffer_id_to_drop);
61    return scoped_ptr<Buffer>(new Buffer(pool_, buffer_id, memory, size));
62  }
63
64  int expected_dropped_id_;
65  scoped_refptr<VideoCaptureBufferPool> pool_;
66
67 private:
68  DISALLOW_COPY_AND_ASSIGN(VideoCaptureBufferPoolTest);
69};
70
71TEST_F(VideoCaptureBufferPoolTest, BufferPool) {
72  const gfx::Size size_lo = gfx::Size(640, 480);
73  const gfx::Size size_hi = gfx::Size(1024, 768);
74  scoped_refptr<media::VideoFrame> non_pool_frame =
75      media::VideoFrame::CreateFrame(media::VideoFrame::YV12, size_lo,
76                                     gfx::Rect(size_lo), size_lo,
77                                     base::TimeDelta());
78
79  // Reallocation won't happen for the first part of the test.
80  ExpectDroppedId(VideoCaptureBufferPool::kInvalidId);
81
82  scoped_ptr<Buffer> buffer1 = ReserveI420Buffer(size_lo);
83  ASSERT_TRUE(NULL != buffer1.get());
84  ASSERT_LE(media::VideoFrame::AllocationSize(media::VideoFrame::I420, size_lo),
85            buffer1->size());
86  scoped_ptr<Buffer> buffer2 = ReserveI420Buffer(size_lo);
87  ASSERT_TRUE(NULL != buffer2.get());
88  ASSERT_LE(media::VideoFrame::AllocationSize(media::VideoFrame::I420, size_lo),
89            buffer2->size());
90  scoped_ptr<Buffer> buffer3 = ReserveI420Buffer(size_lo);
91  ASSERT_TRUE(NULL != buffer3.get());
92  ASSERT_LE(media::VideoFrame::AllocationSize(media::VideoFrame::I420, size_lo),
93            buffer3->size());
94
95  // Touch the memory.
96  memset(buffer1->data(), 0x11, buffer1->size());
97  memset(buffer2->data(), 0x44, buffer2->size());
98  memset(buffer3->data(), 0x77, buffer3->size());
99
100  // Fourth buffer should fail.
101  ASSERT_FALSE(ReserveI420Buffer(size_lo)) << "Pool should be empty";
102
103  // Release 1st buffer and retry; this should succeed.
104  buffer1.reset();
105  scoped_ptr<Buffer> buffer4 = ReserveI420Buffer(size_lo);
106  ASSERT_TRUE(NULL != buffer4.get());
107
108  ASSERT_FALSE(ReserveI420Buffer(size_lo)) << "Pool should be empty";
109  ASSERT_FALSE(ReserveI420Buffer(size_hi)) << "Pool should be empty";
110
111  // Validate the IDs
112  int buffer_id2 = buffer2->id();
113  ASSERT_EQ(1, buffer_id2);
114  int buffer_id3 = buffer3->id();
115  ASSERT_EQ(2, buffer_id3);
116  void* const memory_pointer3 = buffer3->data();
117  int buffer_id4 = buffer4->id();
118  ASSERT_EQ(0, buffer_id4);
119
120  // Deliver a buffer.
121  pool_->HoldForConsumers(buffer_id3, 2);
122
123  ASSERT_FALSE(ReserveI420Buffer(size_lo)) << "Pool should be empty";
124
125  buffer3.reset();  // Old producer releases buffer. Should be a noop.
126  ASSERT_FALSE(ReserveI420Buffer(size_lo)) << "Pool should be empty";
127  ASSERT_FALSE(ReserveI420Buffer(size_hi)) << "Pool should be empty";
128
129  buffer2.reset();  // Active producer releases buffer. Should free a buffer.
130
131  buffer1 = ReserveI420Buffer(size_lo);
132  ASSERT_TRUE(NULL != buffer1.get());
133  ASSERT_FALSE(ReserveI420Buffer(size_lo)) << "Pool should be empty";
134
135  // First consumer finishes.
136  pool_->RelinquishConsumerHold(buffer_id3, 1);
137  ASSERT_FALSE(ReserveI420Buffer(size_lo)) << "Pool should be empty";
138
139  // Second consumer finishes. This should free that buffer.
140  pool_->RelinquishConsumerHold(buffer_id3, 1);
141  buffer3 = ReserveI420Buffer(size_lo);
142  ASSERT_TRUE(NULL != buffer3.get());
143  ASSERT_EQ(buffer_id3, buffer3->id()) << "Buffer ID should be reused.";
144  ASSERT_EQ(memory_pointer3, buffer3->data());
145  ASSERT_FALSE(ReserveI420Buffer(size_lo)) << "Pool should be empty";
146
147  // Now deliver & consume buffer1, but don't release the buffer.
148  int buffer_id1 = buffer1->id();
149  ASSERT_EQ(1, buffer_id1);
150  pool_->HoldForConsumers(buffer_id1, 5);
151  pool_->RelinquishConsumerHold(buffer_id1, 5);
152
153  // Even though the consumer is done with the buffer at |buffer_id1|, it cannot
154  // be re-allocated to the producer, because |buffer1| still references it. But
155  // when |buffer1| goes away, we should be able to re-reserve the buffer (and
156  // the ID ought to be the same).
157  ASSERT_FALSE(ReserveI420Buffer(size_lo)) << "Pool should be empty";
158  buffer1.reset();  // Should free the buffer.
159  buffer2 = ReserveI420Buffer(size_lo);
160  ASSERT_TRUE(NULL != buffer2.get());
161  ASSERT_EQ(buffer_id1, buffer2->id());
162  buffer_id2 = buffer_id1;
163  ASSERT_FALSE(ReserveI420Buffer(size_lo)) << "Pool should be empty";
164
165  // Now try reallocation with different resolutions. We expect reallocation
166  // to occur only when the old buffer is too small.
167  buffer2.reset();
168  ExpectDroppedId(buffer_id2);
169  buffer2 = ReserveI420Buffer(size_hi);
170  ASSERT_TRUE(NULL != buffer2.get());
171  ASSERT_LE(media::VideoFrame::AllocationSize(media::VideoFrame::I420, size_hi),
172            buffer2->size());
173  ASSERT_EQ(3, buffer2->id());
174  void* const memory_pointer_hi = buffer2->data();
175  buffer2.reset();  // Frees it.
176  ExpectDroppedId(VideoCaptureBufferPool::kInvalidId);
177  buffer2 = ReserveI420Buffer(size_lo);
178  void* const memory_pointer_lo = buffer2->data();
179  ASSERT_EQ(memory_pointer_hi, memory_pointer_lo)
180      << "Decrease in resolution should not reallocate buffer";
181  ASSERT_TRUE(NULL != buffer2.get());
182  ASSERT_EQ(3, buffer2->id());
183  ASSERT_LE(media::VideoFrame::AllocationSize(media::VideoFrame::I420, size_lo),
184            buffer2->size());
185  ASSERT_FALSE(ReserveI420Buffer(size_lo)) << "Pool should be empty";
186
187  // Tear down the pool_, writing into the buffers. The buffer should preserve
188  // the lifetime of the underlying memory.
189  buffer3.reset();
190  pool_ = NULL;
191
192  // Touch the memory.
193  memset(buffer2->data(), 0x22, buffer2->size());
194  memset(buffer4->data(), 0x55, buffer4->size());
195
196  buffer2.reset();
197
198  memset(buffer4->data(), 0x77, buffer4->size());
199  buffer4.reset();
200}
201
202} // namespace content
203