blob: 80175b1c2992b1db091c9c2742af18cd4eaef043 [file] [log] [blame]
// Copyright (c) 2013 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.
// Unit test for VideoCaptureBufferPool.
#include "media/capture/video/video_capture_buffer_pool.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/test/scoped_task_environment.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/media/video_capture_controller.h"
#include "media/base/video_frame.h"
#include "media/capture/video/video_capture_buffer_pool_impl.h"
#include "media/capture/video/video_capture_buffer_tracker_factory_impl.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
static const media::VideoPixelFormat kCapturePixelFormats[] = {
media::PIXEL_FORMAT_I420, media::PIXEL_FORMAT_ARGB, media::PIXEL_FORMAT_Y16,
};
static const int kTestBufferPoolSize = 3;
// Note that this test does not exercise the class VideoCaptureBufferPool
// in isolation. The "unit under test" is an instance of VideoCaptureBufferPool
// with some context that is specific to renderer_host/media, and therefore
// this test must live here and not in media/capture/video.
class VideoCaptureBufferPoolTest
: public testing::TestWithParam<media::VideoPixelFormat> {
protected:
// This is a generic Buffer tracker
class Buffer {
public:
Buffer(const scoped_refptr<media::VideoCaptureBufferPool> pool,
std::unique_ptr<media::VideoCaptureBufferHandle> buffer_handle,
int id)
: id_(id), pool_(pool), buffer_handle_(std::move(buffer_handle)) {}
~Buffer() { pool_->RelinquishProducerReservation(id()); }
int id() const { return id_; }
size_t mapped_size() { return buffer_handle_->mapped_size(); }
void* data() { return buffer_handle_->data(); }
private:
const int id_;
const scoped_refptr<media::VideoCaptureBufferPool> pool_;
const std::unique_ptr<media::VideoCaptureBufferHandle> buffer_handle_;
};
VideoCaptureBufferPoolTest()
: expected_dropped_id_(0),
pool_(new media::VideoCaptureBufferPoolImpl(
std::make_unique<media::VideoCaptureBufferTrackerFactoryImpl>(),
kTestBufferPoolSize)) {}
void ExpectDroppedId(int expected_dropped_id) {
expected_dropped_id_ = expected_dropped_id;
}
std::unique_ptr<Buffer> ReserveBuffer(const gfx::Size& dimensions,
media::VideoPixelFormat pixel_format) {
// To verify that ReserveBuffer always sets |buffer_id_to_drop|,
// initialize it to something different than the expected value.
int buffer_id_to_drop = ~expected_dropped_id_;
DVLOG(1) << media::VideoPixelFormatToString(pixel_format) << " "
<< dimensions.ToString();
const int arbitrary_frame_feedback_id = 0;
int buffer_id = media::VideoCaptureBufferPool::kInvalidId;
const auto reserve_result = pool_->ReserveForProducer(
dimensions, pixel_format, nullptr, arbitrary_frame_feedback_id,
&buffer_id, &buffer_id_to_drop);
if (reserve_result !=
media::VideoCaptureDevice::Client::ReserveResult::kSucceeded) {
return std::unique_ptr<Buffer>();
}
EXPECT_EQ(expected_dropped_id_, buffer_id_to_drop);
std::unique_ptr<media::VideoCaptureBufferHandle> buffer_handle =
pool_->GetHandleForInProcessAccess(buffer_id);
return std::unique_ptr<Buffer>(
new Buffer(pool_, std::move(buffer_handle), buffer_id));
}
base::test::ScopedTaskEnvironment task_environment_;
int expected_dropped_id_;
scoped_refptr<media::VideoCaptureBufferPool> pool_;
private:
DISALLOW_COPY_AND_ASSIGN(VideoCaptureBufferPoolTest);
};
TEST_P(VideoCaptureBufferPoolTest, BufferPool) {
const gfx::Size size_lo = gfx::Size(10, 10);
const gfx::Size size_hi = gfx::Size(21, 33);
const media::VideoCaptureFormat format_lo(size_lo, 0.0, GetParam());
const media::VideoCaptureFormat format_hi(size_hi, 0.0, GetParam());
// Reallocation won't happen for the first part of the test.
ExpectDroppedId(media::VideoCaptureBufferPool::kInvalidId);
// The buffer pool should have zero utilization before any buffers have been
// reserved.
ASSERT_EQ(0.0, pool_->GetBufferPoolUtilization());
std::unique_ptr<Buffer> buffer1 = ReserveBuffer(size_lo, GetParam());
ASSERT_NE(nullptr, buffer1.get());
ASSERT_EQ(1.0 / kTestBufferPoolSize, pool_->GetBufferPoolUtilization());
std::unique_ptr<Buffer> buffer2 = ReserveBuffer(size_lo, GetParam());
ASSERT_NE(nullptr, buffer2.get());
ASSERT_EQ(2.0 / kTestBufferPoolSize, pool_->GetBufferPoolUtilization());
std::unique_ptr<Buffer> buffer3 = ReserveBuffer(size_lo, GetParam());
ASSERT_NE(nullptr, buffer3.get());
ASSERT_EQ(3.0 / kTestBufferPoolSize, pool_->GetBufferPoolUtilization());
ASSERT_LE(format_lo.ImageAllocationSize(), buffer1->mapped_size());
ASSERT_LE(format_lo.ImageAllocationSize(), buffer2->mapped_size());
ASSERT_LE(format_lo.ImageAllocationSize(), buffer3->mapped_size());
ASSERT_NE(nullptr, buffer1->data());
ASSERT_NE(nullptr, buffer2->data());
ASSERT_NE(nullptr, buffer3->data());
// Touch the memory.
if (buffer1->data() != nullptr)
memset(buffer1->data(), 0x11, buffer1->mapped_size());
if (buffer2->data() != nullptr)
memset(buffer2->data(), 0x44, buffer2->mapped_size());
if (buffer3->data() != nullptr)
memset(buffer3->data(), 0x77, buffer3->mapped_size());
// Fourth buffer should fail. Buffer pool utilization should be at 100%.
ASSERT_FALSE(ReserveBuffer(size_lo, GetParam())) << "Pool should be empty";
ASSERT_EQ(1.0, pool_->GetBufferPoolUtilization());
// Release 1st buffer and retry; this should succeed.
buffer1.reset();
ASSERT_EQ(2.0 / kTestBufferPoolSize, pool_->GetBufferPoolUtilization());
std::unique_ptr<Buffer> buffer4 = ReserveBuffer(size_lo, GetParam());
ASSERT_NE(nullptr, buffer4.get());
ASSERT_EQ(3.0 / kTestBufferPoolSize, pool_->GetBufferPoolUtilization());
ASSERT_FALSE(ReserveBuffer(size_lo, GetParam())) << "Pool should be empty";
ASSERT_EQ(1.0, pool_->GetBufferPoolUtilization());
ASSERT_FALSE(ReserveBuffer(size_hi, GetParam())) << "Pool should be empty";
ASSERT_EQ(1.0, pool_->GetBufferPoolUtilization());
// Validate the IDs
int buffer_id2 = buffer2->id();
ASSERT_EQ(1, buffer_id2);
const int buffer_id3 = buffer3->id();
ASSERT_EQ(2, buffer_id3);
const int buffer_id4 = buffer4->id();
ASSERT_EQ(0, buffer_id4);
void* const memory_pointer3 = buffer3->data();
// Deliver a buffer.
pool_->HoldForConsumers(buffer_id3, 2);
ASSERT_FALSE(ReserveBuffer(size_lo, GetParam())) << "Pool should be empty";
ASSERT_EQ(1.0, pool_->GetBufferPoolUtilization());
buffer3.reset(); // Old producer releases buffer. Should be a noop.
ASSERT_FALSE(ReserveBuffer(size_lo, GetParam())) << "Pool should be empty";
ASSERT_EQ(1.0, pool_->GetBufferPoolUtilization());
ASSERT_FALSE(ReserveBuffer(size_hi, GetParam())) << "Pool should be empty";
ASSERT_EQ(1.0, pool_->GetBufferPoolUtilization());
buffer2.reset(); // Active producer releases buffer. Should free a buffer.
buffer1 = ReserveBuffer(size_lo, GetParam());
ASSERT_NE(nullptr, buffer1.get());
ASSERT_EQ(3.0 / kTestBufferPoolSize, pool_->GetBufferPoolUtilization());
ASSERT_FALSE(ReserveBuffer(size_lo, GetParam())) << "Pool should be empty";
ASSERT_EQ(1.0, pool_->GetBufferPoolUtilization());
// First consumer finishes.
pool_->RelinquishConsumerHold(buffer_id3, 1);
ASSERT_FALSE(ReserveBuffer(size_lo, GetParam())) << "Pool should be empty";
ASSERT_EQ(1.0, pool_->GetBufferPoolUtilization());
// Second consumer finishes. This should free that buffer.
pool_->RelinquishConsumerHold(buffer_id3, 1);
buffer3 = ReserveBuffer(size_lo, GetParam());
ASSERT_NE(nullptr, buffer3.get());
ASSERT_EQ(buffer_id3, buffer3->id()) << "Buffer ID should be reused.";
ASSERT_EQ(memory_pointer3, buffer3->data());
ASSERT_EQ(3.0 / kTestBufferPoolSize, pool_->GetBufferPoolUtilization());
ASSERT_FALSE(ReserveBuffer(size_lo, GetParam())) << "Pool should be empty";
ASSERT_EQ(1.0, pool_->GetBufferPoolUtilization());
// Now deliver & consume buffer1, but don't release the buffer.
int buffer_id1 = buffer1->id();
ASSERT_EQ(1, buffer_id1);
pool_->HoldForConsumers(buffer_id1, 5);
pool_->RelinquishConsumerHold(buffer_id1, 5);
// Even though the consumer is done with the buffer at |buffer_id1|, it cannot
// be re-allocated to the producer, because |buffer1| still references it. But
// when |buffer1| goes away, we should be able to re-reserve the buffer (and
// the ID ought to be the same).
ASSERT_FALSE(ReserveBuffer(size_lo, GetParam())) << "Pool should be empty";
ASSERT_EQ(1.0, pool_->GetBufferPoolUtilization());
buffer1.reset(); // Should free the buffer.
ASSERT_EQ(2.0 / kTestBufferPoolSize, pool_->GetBufferPoolUtilization());
buffer2 = ReserveBuffer(size_lo, GetParam());
ASSERT_NE(nullptr, buffer2.get());
ASSERT_EQ(buffer_id1, buffer2->id());
buffer_id2 = buffer_id1;
ASSERT_EQ(3.0 / kTestBufferPoolSize, pool_->GetBufferPoolUtilization());
ASSERT_FALSE(ReserveBuffer(size_lo, GetParam())) << "Pool should be empty";
ASSERT_EQ(1.0, pool_->GetBufferPoolUtilization());
// Now try reallocation with different resolutions. We expect reallocation
// to occur only when the old buffer is too small.
buffer2.reset();
ExpectDroppedId(buffer_id2);
ASSERT_EQ(2.0 / kTestBufferPoolSize, pool_->GetBufferPoolUtilization());
buffer2 = ReserveBuffer(size_hi, GetParam());
ASSERT_NE(nullptr, buffer2.get());
ASSERT_LE(format_hi.ImageAllocationSize(), buffer2->mapped_size());
ASSERT_EQ(3, buffer2->id());
ASSERT_EQ(3.0 / kTestBufferPoolSize, pool_->GetBufferPoolUtilization());
void* const memory_pointer_hi = buffer2->data();
buffer2.reset(); // Frees it.
ExpectDroppedId(media::VideoCaptureBufferPool::kInvalidId);
ASSERT_EQ(2.0 / kTestBufferPoolSize, pool_->GetBufferPoolUtilization());
buffer2 = ReserveBuffer(size_lo, GetParam());
void* const memory_pointer_lo = buffer2->data();
ASSERT_EQ(memory_pointer_hi, memory_pointer_lo)
<< "Decrease in resolution should not reallocate buffer";
ASSERT_NE(nullptr, buffer2.get());
ASSERT_EQ(3, buffer2->id());
ASSERT_LE(format_lo.ImageAllocationSize(), buffer2->mapped_size());
ASSERT_EQ(3.0 / kTestBufferPoolSize, pool_->GetBufferPoolUtilization());
ASSERT_FALSE(ReserveBuffer(size_lo, GetParam())) << "Pool should be empty";
ASSERT_EQ(1.0, pool_->GetBufferPoolUtilization());
// Tear down the pool_, writing into the buffers. The buffer should preserve
// the lifetime of the underlying memory.
buffer3.reset();
ASSERT_EQ(2.0 / kTestBufferPoolSize, pool_->GetBufferPoolUtilization());
pool_ = nullptr;
// Touch the memory.
if (buffer2->data() != nullptr)
memset(buffer2->data(), 0x22, buffer2->mapped_size());
if (buffer4->data() != nullptr)
memset(buffer4->data(), 0x55, buffer4->mapped_size());
buffer2.reset();
if (buffer4->data() != nullptr)
memset(buffer4->data(), 0x77, buffer4->mapped_size());
buffer4.reset();
}
INSTANTIATE_TEST_CASE_P(,
VideoCaptureBufferPoolTest,
testing::ValuesIn(kCapturePixelFormats));
} // namespace content