Commit 5320dd38 authored by Henrik Boström's avatar Henrik Boström Committed by Commit Bot

[macOS Capture] Add PixelBufferPool and tests.

The pixel buffer pool is a light-weight wrapper for CVPixelBufferPool.
It takes care of configuring the pool, which avoids working with some
cumbersome Core Foundation objects.

This CL also adds tests to make sure the pool is behaving as expected,
since the Apple documentation is rather poor in details.

The intent is to use the PixelBufferPool in future CLs in order to
use the VTPixelTransferSession for more efficient (hopefully) scaling
and pixel format conversions.

// Guido is OOO today so TBR-landing for BUILD.gn changes.
TBR=guidou@chromium.org

Bug: chromium:1132299
Change-Id: I28ffa7d75dbffd4fcf44c7fb2b22e89e19d6a0ed
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2431427Reviewed-by: default avatarHenrik Boström <hbos@chromium.org>
Reviewed-by: default avatarMarkus Handell <handellm@google.com>
Reviewed-by: default avatarccameron <ccameron@chromium.org>
Commit-Queue: Henrik Boström <hbos@chromium.org>
Cr-Commit-Position: refs/heads/master@{#811650}
parent 60b43aef
......@@ -169,6 +169,8 @@ component("capture_lib") {
if (is_mac) {
sources += [
"video/mac/pixel_buffer_pool_mac.cc",
"video/mac/pixel_buffer_pool_mac.h",
"video/mac/video_capture_device_avfoundation_legacy_mac.h",
"video/mac/video_capture_device_avfoundation_legacy_mac.mm",
"video/mac/video_capture_device_avfoundation_mac.h",
......@@ -195,6 +197,7 @@ component("capture_lib") {
"CoreVideo.framework",
"Foundation.framework",
"IOSurface.framework",
"VideoToolbox.framework",
]
}
......@@ -407,6 +410,7 @@ test("capture_unittests") {
"video/linux/camera_config_chromeos_unittest.cc",
"video/linux/v4l2_capture_delegate_unittest.cc",
"video/linux/video_capture_device_factory_linux_unittest.cc",
"video/mac/pixel_buffer_pool_mac_unittest.mm",
"video/mac/test/mock_video_capture_device_avfoundation_frame_receiver_mac.h",
"video/mac/test/mock_video_capture_device_avfoundation_frame_receiver_mac.mm",
"video/mac/test/video_capture_test_utils_mac.h",
......@@ -459,6 +463,7 @@ test("capture_unittests") {
frameworks = [
"AVFoundation.framework",
"CoreMedia.framework",
"CoreVideo.framework",
]
}
......
// Copyright 2020 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/capture/video/mac/pixel_buffer_pool_mac.h"
#include "base/check.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
namespace media {
namespace {
template <typename T>
class CFNumber {
public:
CFNumber(CFNumberType type, T value)
: value_(value), number_(CFNumberCreate(nil, type, &value_)) {}
T* get() { return &value_; }
const T* get() const { return &value_; }
CFNumberRef cf_number_ref() { return number_.get(); }
private:
T value_;
base::ScopedCFTypeRef<CFNumberRef> number_;
};
base::ScopedCFTypeRef<CFDictionaryRef> CreateEmptyCFDictionary() {
return base::ScopedCFTypeRef<CFDictionaryRef>(
CFDictionaryCreate(nil, nil, nil, 0, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
}
} // namespace
// static
std::unique_ptr<PixelBufferPool> PixelBufferPool::Create(
OSType format,
int width,
int height,
base::Optional<size_t> max_buffers) {
// Pixel buffer attributes: The attributes of buffers created by the pool.
CFStringRef pixel_buffer_attribute_keys[] = {
kCVPixelBufferIOSurfacePropertiesKey, // We want IOSurfaces.
kCVPixelBufferPixelFormatTypeKey, kCVPixelBufferWidthKey,
kCVPixelBufferHeightKey};
constexpr size_t pixel_buffer_attribute_count =
sizeof(pixel_buffer_attribute_keys) /
sizeof(pixel_buffer_attribute_keys[0]);
// Rely on default IOSurface properties.
base::ScopedCFTypeRef<CFDictionaryRef> io_surface_options =
CreateEmptyCFDictionary();
CFNumber<int> pixel_buffer_format(kCFNumberSInt32Type, format);
CFNumber<int> pixel_buffer_width(kCFNumberSInt32Type, width);
CFNumber<int> pixel_buffer_height(kCFNumberSInt32Type, height);
CFTypeRef pixel_buffer_attribute_values[] = {
io_surface_options.get(), pixel_buffer_format.cf_number_ref(),
pixel_buffer_width.cf_number_ref(), pixel_buffer_height.cf_number_ref()};
static_assert(pixel_buffer_attribute_count ==
sizeof(pixel_buffer_attribute_values) /
sizeof(pixel_buffer_attribute_values[0]),
"Key count and value count must match");
base::ScopedCFTypeRef<CFDictionaryRef> pixel_buffer_attributes(
CFDictionaryCreate(nil, (const void**)pixel_buffer_attribute_keys,
(const void**)pixel_buffer_attribute_values,
pixel_buffer_attribute_count,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
// Create the pool.
// We don't specify any pool attributes. It is unclear from the documentation
// what pool attributes are available; they might be
// kCVPixelBufferPoolMinimumBufferCountKey and
// kCVPixelBufferPoolMaximumBufferAgeKey unless these are more auxiliary
// attributes for CVPixelBufferPoolCreatePixelBufferWithAuxAttributes().
base::ScopedCFTypeRef<CVPixelBufferPoolRef> buffer_pool;
CVReturn pool_creation_error = CVPixelBufferPoolCreate(
nil, nil, pixel_buffer_attributes.get(), buffer_pool.InitializeInto());
if (pool_creation_error != noErr) {
DLOG(ERROR) << "Failed to create CVPixelBufferPool with CVReturn error: "
<< pool_creation_error;
return nullptr;
}
return std::make_unique<PixelBufferPool>(std::move(buffer_pool),
std::move(max_buffers));
}
PixelBufferPool::PixelBufferPool(
base::ScopedCFTypeRef<CVPixelBufferPoolRef> buffer_pool,
base::Optional<size_t> max_buffers)
: buffer_pool_(std::move(buffer_pool)),
max_buffers_(std::move(max_buffers)) {
DCHECK(buffer_pool_);
}
PixelBufferPool::~PixelBufferPool() {
// Flushing before freeing probably isn't needed, but it can't hurt.
Flush();
}
base::ScopedCFTypeRef<CVPixelBufferRef> PixelBufferPool::CreateBuffer() {
DCHECK(buffer_pool_);
base::ScopedCFTypeRef<CVPixelBufferRef> buffer;
CVReturn buffer_creation_error;
if (!max_buffers_.has_value()) {
buffer_creation_error = CVPixelBufferPoolCreatePixelBuffer(
nil, buffer_pool_, buffer.InitializeInto());
} else {
// Specify the allocation threshold using auxiliary attributes.
CFStringRef attribute_keys[] = {kCVPixelBufferPoolAllocationThresholdKey};
constexpr size_t attribute_count =
sizeof(attribute_keys) / sizeof(attribute_keys[0]);
CFNumber<int> poolAllocationThreshold(
kCFNumberSInt32Type, base::checked_cast<int>(max_buffers_.value()));
CFTypeRef attribute_values[] = {poolAllocationThreshold.cf_number_ref()};
static_assert(attribute_count ==
sizeof(attribute_values) / sizeof(attribute_values[0]),
"Key count and value count must match");
base::ScopedCFTypeRef<CFDictionaryRef> attributes(CFDictionaryCreate(
nil, (const void**)attribute_keys, (const void**)attribute_values,
attribute_count, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
buffer_creation_error = CVPixelBufferPoolCreatePixelBufferWithAuxAttributes(
nil, buffer_pool_, attributes.get(), buffer.InitializeInto());
}
if (buffer_creation_error == kCVReturnWouldExceedAllocationThreshold) {
// PixelBufferPool cannot create more buffers.
return base::ScopedCFTypeRef<CVPixelBufferRef>(nil);
}
// If |max_buffers_| wasn't reached, this operation must succeed.
CHECK(buffer_creation_error == noErr)
<< "Failed to create destination CVPixelBuffer with CVReturn error: "
<< buffer_creation_error;
return buffer;
}
void PixelBufferPool::Flush() {
DCHECK(buffer_pool_);
CVPixelBufferPoolFlush(buffer_pool_, kCVPixelBufferPoolFlushExcessBuffers);
}
} // namespace media
// Copyright 2020 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.
#ifndef MEDIA_CAPTURE_VIDEO_MAC_PIXEL_BUFFER_POOL_MAC_H_
#define MEDIA_CAPTURE_VIDEO_MAC_PIXEL_BUFFER_POOL_MAC_H_
#import <VideoToolbox/VideoToolbox.h>
#include <memory>
#include "base/mac/scoped_cftyperef.h"
#include "base/optional.h"
#include "media/capture/capture_export.h"
namespace media {
class CAPTURE_EXPORT PixelBufferPool {
public:
// All buffers created by the pool will have the specified properties.
// If specified, the pool allows at most |max_buffers| to exist at a time by
// having CreateBuffer() returns null if this threshold would be exceeded.
//
// If an unsupportd |format| is specified, or the pool cannot be created for
// unknown reasons, null is returned.
static std::unique_ptr<PixelBufferPool> Create(
OSType format,
int width,
int height,
base::Optional<size_t> max_buffers);
~PixelBufferPool();
// Creates a new buffer from the pool, or returns null if |max_buffers_| would
// be exceeded. The underlying buffers may be recycled.
//
// The caller owns the returned buffer and is responsible for calling
// CFRelease() after they are done using it. This returns the underlying
// buffer to the pool. In order to free memory, you must both release all
// buffers and call Flush() or delete the pool. It is safe for a buffer to
// outlive its pool.
base::ScopedCFTypeRef<CVPixelBufferRef> CreateBuffer();
// Frees the memory of any released buffers returned to the pool.
void Flush();
private:
friend std::unique_ptr<PixelBufferPool> std::make_unique<PixelBufferPool>(
base::ScopedCFTypeRef<CVPixelBufferPoolRef>&& buffer_pool,
base::Optional<size_t>&& max_buffers);
PixelBufferPool(base::ScopedCFTypeRef<CVPixelBufferPoolRef> buffer_pool,
base::Optional<size_t> max_buffers);
base::ScopedCFTypeRef<CVPixelBufferPoolRef> buffer_pool_;
const base::Optional<size_t> max_buffers_;
};
} // namespace media
#endif // MEDIA_CAPTURE_VIDEO_MAC_PIXEL_BUFFER_POOL_MAC_H_
// Copyright 2020 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/capture/video/mac/pixel_buffer_pool_mac.h"
#include "base/bind.h"
#include "base/mac/scoped_nsobject.h"
#import "media/capture/video/mac/test/video_capture_test_utils_mac.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace {
// NV12, also known as 420v, also known as media::PIXEL_FORMAT_NV12.
constexpr OSType kPixelFormatNv12 =
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
// A common 4:3 resolution.
constexpr int kVgaWidth = 640;
constexpr int kVgaHeight = 480;
} // namespace
TEST(PixelBufferPoolTest, CannotCreatePoolWithNonsenseArguments) {
EXPECT_FALSE(PixelBufferPool::Create(0, -1, -1, 1));
}
TEST(PixelBufferPoolTest, CreatedBufferHasSpecifiedAttributes) {
std::unique_ptr<PixelBufferPool> pool =
PixelBufferPool::Create(kPixelFormatNv12, kVgaWidth, kVgaHeight, 1);
base::ScopedCFTypeRef<CVPixelBufferRef> buffer = pool->CreateBuffer();
EXPECT_TRUE(CVPixelBufferGetPixelFormatType(buffer) == kPixelFormatNv12);
EXPECT_EQ(CVPixelBufferGetWidth(buffer), static_cast<size_t>(kVgaWidth));
EXPECT_EQ(CVPixelBufferGetHeight(buffer), static_cast<size_t>(kVgaHeight));
}
TEST(PixelBufferPoolTest, CreatedBufferHasIOSurface) {
std::unique_ptr<PixelBufferPool> pool =
PixelBufferPool::Create(kPixelFormatNv12, kVgaWidth, kVgaHeight, 1);
base::ScopedCFTypeRef<CVPixelBufferRef> buffer = pool->CreateBuffer();
EXPECT_TRUE(CVPixelBufferGetIOSurface(buffer));
}
TEST(PixelBufferPoolTest, CannotExceedMaxBuffers) {
std::unique_ptr<PixelBufferPool> pool =
PixelBufferPool::Create(kPixelFormatNv12, kVgaWidth, kVgaHeight, 2);
base::ScopedCFTypeRef<CVPixelBufferRef> first_buffer = pool->CreateBuffer();
EXPECT_TRUE(first_buffer);
base::ScopedCFTypeRef<CVPixelBufferRef> second_buffer = pool->CreateBuffer();
EXPECT_TRUE(second_buffer);
base::ScopedCFTypeRef<CVPixelBufferRef> third_buffer = pool->CreateBuffer();
EXPECT_FALSE(third_buffer);
}
TEST(PixelBufferPoolTest, CanCreateBuffersIfMaxIsNull) {
std::unique_ptr<PixelBufferPool> pool = PixelBufferPool::Create(
kPixelFormatNv12, kVgaWidth, kVgaHeight, base::nullopt);
base::ScopedCFTypeRef<CVPixelBufferRef> first_buffer = pool->CreateBuffer();
EXPECT_TRUE(first_buffer);
base::ScopedCFTypeRef<CVPixelBufferRef> second_buffer = pool->CreateBuffer();
EXPECT_TRUE(second_buffer);
base::ScopedCFTypeRef<CVPixelBufferRef> third_buffer = pool->CreateBuffer();
EXPECT_TRUE(third_buffer);
}
TEST(PixelBufferPoolTest, CanCreateBufferAfterPreviousBufferIsReleased) {
std::unique_ptr<PixelBufferPool> pool =
PixelBufferPool::Create(kPixelFormatNv12, kVgaWidth, kVgaHeight, 1);
base::ScopedCFTypeRef<CVPixelBufferRef> buffer = pool->CreateBuffer();
buffer.reset();
buffer = pool->CreateBuffer();
EXPECT_TRUE(buffer);
}
TEST(PixelBufferPoolTest, BuffersCanOutliveThePool) {
std::unique_ptr<PixelBufferPool> pool =
PixelBufferPool::Create(kPixelFormatNv12, kVgaWidth, kVgaHeight, 1);
base::ScopedCFTypeRef<CVPixelBufferRef> buffer = pool->CreateBuffer();
pool.reset();
EXPECT_TRUE(CVPixelBufferGetPixelFormatType(buffer) == kPixelFormatNv12);
EXPECT_EQ(CVPixelBufferGetWidth(buffer), static_cast<size_t>(kVgaWidth));
EXPECT_EQ(CVPixelBufferGetHeight(buffer), static_cast<size_t>(kVgaHeight));
EXPECT_TRUE(CVPixelBufferGetIOSurface(buffer));
}
TEST(PixelBufferPoolTest, CanFlushWhileBufferIsInUse) {
std::unique_ptr<PixelBufferPool> pool = PixelBufferPool::Create(
kPixelFormatNv12, kVgaWidth, kVgaHeight, base::nullopt);
base::ScopedCFTypeRef<CVPixelBufferRef> retained_buffer =
pool->CreateBuffer();
base::ScopedCFTypeRef<CVPixelBufferRef> released_buffer =
pool->CreateBuffer();
released_buffer.reset();
// We expect the memory of |released_buffer| to be freed now, but there is no
// way to assert this in a unittest.
pool->Flush();
// We expect |retained_buffer| is still usable. Inspecting its properties.
EXPECT_TRUE(CVPixelBufferGetPixelFormatType(retained_buffer) ==
kPixelFormatNv12);
EXPECT_EQ(CVPixelBufferGetWidth(retained_buffer),
static_cast<size_t>(kVgaWidth));
EXPECT_EQ(CVPixelBufferGetHeight(retained_buffer),
static_cast<size_t>(kVgaHeight));
EXPECT_TRUE(CVPixelBufferGetIOSurface(retained_buffer));
}
} // namespace media
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment