279 lines
11 KiB
C++
279 lines
11 KiB
C++
// Copyright 2020 The libgav1 Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include "examples/gav1_decode_cv_pixel_buffer_pool.h"
|
|
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <memory>
|
|
#include <new>
|
|
#include <type_traits>
|
|
|
|
namespace {
|
|
|
|
struct CFTypeDeleter {
|
|
void operator()(CFTypeRef cf) const { CFRelease(cf); }
|
|
};
|
|
|
|
using UniqueCFNumberRef =
|
|
std::unique_ptr<std::remove_pointer<CFNumberRef>::type, CFTypeDeleter>;
|
|
|
|
using UniqueCFDictionaryRef =
|
|
std::unique_ptr<std::remove_pointer<CFDictionaryRef>::type, CFTypeDeleter>;
|
|
|
|
} // namespace
|
|
|
|
extern "C" {
|
|
|
|
libgav1::StatusCode Gav1DecodeOnCVPixelBufferSizeChanged(
|
|
void* callback_private_data, int bitdepth,
|
|
libgav1::ImageFormat image_format, int width, int height, int left_border,
|
|
int right_border, int top_border, int bottom_border, int stride_alignment) {
|
|
auto* buffer_pool =
|
|
static_cast<Gav1DecodeCVPixelBufferPool*>(callback_private_data);
|
|
return buffer_pool->OnCVPixelBufferSizeChanged(
|
|
bitdepth, image_format, width, height, left_border, right_border,
|
|
top_border, bottom_border, stride_alignment);
|
|
}
|
|
|
|
libgav1::StatusCode Gav1DecodeGetCVPixelBuffer(
|
|
void* callback_private_data, int bitdepth,
|
|
libgav1::ImageFormat image_format, int width, int height, int left_border,
|
|
int right_border, int top_border, int bottom_border, int stride_alignment,
|
|
libgav1::FrameBuffer* frame_buffer) {
|
|
auto* buffer_pool =
|
|
static_cast<Gav1DecodeCVPixelBufferPool*>(callback_private_data);
|
|
return buffer_pool->GetCVPixelBuffer(
|
|
bitdepth, image_format, width, height, left_border, right_border,
|
|
top_border, bottom_border, stride_alignment, frame_buffer);
|
|
}
|
|
|
|
void Gav1DecodeReleaseCVPixelBuffer(void* callback_private_data,
|
|
void* buffer_private_data) {
|
|
auto* buffer_pool =
|
|
static_cast<Gav1DecodeCVPixelBufferPool*>(callback_private_data);
|
|
buffer_pool->ReleaseCVPixelBuffer(buffer_private_data);
|
|
}
|
|
|
|
} // extern "C"
|
|
|
|
// static
|
|
std::unique_ptr<Gav1DecodeCVPixelBufferPool>
|
|
Gav1DecodeCVPixelBufferPool::Create(size_t num_buffers) {
|
|
std::unique_ptr<Gav1DecodeCVPixelBufferPool> buffer_pool(
|
|
new (std::nothrow) Gav1DecodeCVPixelBufferPool(num_buffers));
|
|
return buffer_pool;
|
|
}
|
|
|
|
Gav1DecodeCVPixelBufferPool::Gav1DecodeCVPixelBufferPool(size_t num_buffers)
|
|
: num_buffers_(static_cast<int>(num_buffers)) {}
|
|
|
|
Gav1DecodeCVPixelBufferPool::~Gav1DecodeCVPixelBufferPool() {
|
|
CVPixelBufferPoolRelease(pool_);
|
|
}
|
|
|
|
libgav1::StatusCode Gav1DecodeCVPixelBufferPool::OnCVPixelBufferSizeChanged(
|
|
int bitdepth, libgav1::ImageFormat image_format, int width, int height,
|
|
int left_border, int right_border, int top_border, int bottom_border,
|
|
int stride_alignment) {
|
|
if (bitdepth != 8 || (image_format != libgav1::kImageFormatYuv420 &&
|
|
image_format != libgav1::kImageFormatMonochrome400)) {
|
|
fprintf(stderr,
|
|
"Only bitdepth 8, 4:2:0 videos are supported: bitdepth %d, "
|
|
"image_format: %d.\n",
|
|
bitdepth, image_format);
|
|
return libgav1::kStatusUnimplemented;
|
|
}
|
|
|
|
// stride_alignment must be a power of 2.
|
|
assert((stride_alignment & (stride_alignment - 1)) == 0);
|
|
|
|
// The possible keys for CVPixelBufferPool are:
|
|
// kCVPixelBufferPoolMinimumBufferCountKey
|
|
// kCVPixelBufferPoolMaximumBufferAgeKey
|
|
// kCVPixelBufferPoolAllocationThresholdKey
|
|
const void* pool_keys[] = {kCVPixelBufferPoolMinimumBufferCountKey};
|
|
const int min_buffer_count = 10;
|
|
UniqueCFNumberRef cf_min_buffer_count(
|
|
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &min_buffer_count));
|
|
if (cf_min_buffer_count == nullptr) {
|
|
fprintf(stderr, "CFNumberCreate failed.\n");
|
|
return libgav1::kStatusUnknownError;
|
|
}
|
|
const void* pool_values[] = {cf_min_buffer_count.get()};
|
|
UniqueCFDictionaryRef pool_attributes(CFDictionaryCreate(
|
|
nullptr, pool_keys, pool_values, 1, &kCFTypeDictionaryKeyCallBacks,
|
|
&kCFTypeDictionaryValueCallBacks));
|
|
if (pool_attributes == nullptr) {
|
|
fprintf(stderr, "CFDictionaryCreate failed.\n");
|
|
return libgav1::kStatusUnknownError;
|
|
}
|
|
|
|
// The pixelBufferAttributes argument to CVPixelBufferPoolCreate() cannot be
|
|
// null and must contain the pixel format, width, and height, otherwise
|
|
// CVPixelBufferPoolCreate() fails with kCVReturnInvalidPixelBufferAttributes
|
|
// (-6682).
|
|
|
|
// I420: kCVPixelFormatType_420YpCbCr8Planar (video range).
|
|
const int pixel_format = (image_format == libgav1::kImageFormatYuv420)
|
|
? kCVPixelFormatType_420YpCbCr8PlanarFullRange
|
|
: kCVPixelFormatType_OneComponent8;
|
|
UniqueCFNumberRef cf_pixel_format(
|
|
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &pixel_format));
|
|
UniqueCFNumberRef cf_width(
|
|
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &width));
|
|
UniqueCFNumberRef cf_height(
|
|
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &height));
|
|
UniqueCFNumberRef cf_left_border(
|
|
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &left_border));
|
|
UniqueCFNumberRef cf_right_border(
|
|
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &right_border));
|
|
UniqueCFNumberRef cf_top_border(
|
|
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &top_border));
|
|
UniqueCFNumberRef cf_bottom_border(
|
|
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &bottom_border));
|
|
UniqueCFNumberRef cf_stride_alignment(
|
|
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &stride_alignment));
|
|
|
|
const void* buffer_keys[] = {
|
|
kCVPixelBufferPixelFormatTypeKey,
|
|
kCVPixelBufferWidthKey,
|
|
kCVPixelBufferHeightKey,
|
|
kCVPixelBufferExtendedPixelsLeftKey,
|
|
kCVPixelBufferExtendedPixelsRightKey,
|
|
kCVPixelBufferExtendedPixelsTopKey,
|
|
kCVPixelBufferExtendedPixelsBottomKey,
|
|
kCVPixelBufferBytesPerRowAlignmentKey,
|
|
};
|
|
const void* buffer_values[] = {
|
|
cf_pixel_format.get(), cf_width.get(),
|
|
cf_height.get(), cf_left_border.get(),
|
|
cf_right_border.get(), cf_top_border.get(),
|
|
cf_bottom_border.get(), cf_stride_alignment.get(),
|
|
};
|
|
UniqueCFDictionaryRef buffer_attributes(CFDictionaryCreate(
|
|
kCFAllocatorDefault, buffer_keys, buffer_values, 8,
|
|
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
|
|
if (buffer_attributes == nullptr) {
|
|
fprintf(stderr, "CFDictionaryCreate of buffer_attributes failed.\n");
|
|
return libgav1::kStatusUnknownError;
|
|
}
|
|
CVPixelBufferPoolRef cv_pool;
|
|
CVReturn ret = CVPixelBufferPoolCreate(
|
|
/*allocator=*/nullptr, pool_attributes.get(), buffer_attributes.get(),
|
|
&cv_pool);
|
|
if (ret != kCVReturnSuccess) {
|
|
fprintf(stderr, "CVPixelBufferPoolCreate failed: %d.\n",
|
|
static_cast<int>(ret));
|
|
return libgav1::kStatusOutOfMemory;
|
|
}
|
|
CVPixelBufferPoolRelease(pool_);
|
|
pool_ = cv_pool;
|
|
return libgav1::kStatusOk;
|
|
}
|
|
|
|
libgav1::StatusCode Gav1DecodeCVPixelBufferPool::GetCVPixelBuffer(
|
|
int bitdepth, libgav1::ImageFormat image_format, int /*width*/,
|
|
int /*height*/, int /*left_border*/, int /*right_border*/,
|
|
int /*top_border*/, int /*bottom_border*/, int /*stride_alignment*/,
|
|
libgav1::FrameBuffer* frame_buffer) {
|
|
static_cast<void>(bitdepth);
|
|
assert(bitdepth == 8 && (image_format == libgav1::kImageFormatYuv420 ||
|
|
image_format == libgav1::kImageFormatMonochrome400));
|
|
const bool is_monochrome =
|
|
(image_format == libgav1::kImageFormatMonochrome400);
|
|
|
|
// The dictionary must have kCVPixelBufferPoolAllocationThresholdKey,
|
|
// otherwise CVPixelBufferPoolCreatePixelBufferWithAuxAttributes() fails with
|
|
// kCVReturnWouldExceedAllocationThreshold (-6689).
|
|
UniqueCFNumberRef cf_num_buffers(
|
|
CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &num_buffers_));
|
|
|
|
const void* buffer_keys[] = {
|
|
kCVPixelBufferPoolAllocationThresholdKey,
|
|
};
|
|
const void* buffer_values[] = {
|
|
cf_num_buffers.get(),
|
|
};
|
|
UniqueCFDictionaryRef aux_attributes(CFDictionaryCreate(
|
|
kCFAllocatorDefault, buffer_keys, buffer_values, 1,
|
|
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
|
|
if (aux_attributes == nullptr) {
|
|
fprintf(stderr, "CFDictionaryCreate of aux_attributes failed.\n");
|
|
return libgav1::kStatusUnknownError;
|
|
}
|
|
|
|
CVPixelBufferRef pixel_buffer;
|
|
CVReturn ret = CVPixelBufferPoolCreatePixelBufferWithAuxAttributes(
|
|
/*allocator=*/nullptr, pool_, aux_attributes.get(), &pixel_buffer);
|
|
if (ret != kCVReturnSuccess) {
|
|
fprintf(stderr,
|
|
"CVPixelBufferPoolCreatePixelBufferWithAuxAttributes failed: %d.\n",
|
|
static_cast<int>(ret));
|
|
return libgav1::kStatusOutOfMemory;
|
|
}
|
|
|
|
ret = CVPixelBufferLockBaseAddress(pixel_buffer, /*lockFlags=*/0);
|
|
if (ret != kCVReturnSuccess) {
|
|
fprintf(stderr, "CVPixelBufferLockBaseAddress failed: %d.\n",
|
|
static_cast<int>(ret));
|
|
CFRelease(pixel_buffer);
|
|
return libgav1::kStatusUnknownError;
|
|
}
|
|
|
|
// If the pixel format type is kCVPixelFormatType_OneComponent8, the pixel
|
|
// buffer is nonplanar (CVPixelBufferIsPlanar returns false and
|
|
// CVPixelBufferGetPlaneCount returns 0), but
|
|
// CVPixelBufferGetBytesPerRowOfPlane and CVPixelBufferGetBaseAddressOfPlane
|
|
// still work for plane index 0, even though the documentation says they
|
|
// return NULL for nonplanar pixel buffers.
|
|
frame_buffer->stride[0] =
|
|
static_cast<int>(CVPixelBufferGetBytesPerRowOfPlane(pixel_buffer, 0));
|
|
frame_buffer->plane[0] = static_cast<uint8_t*>(
|
|
CVPixelBufferGetBaseAddressOfPlane(pixel_buffer, 0));
|
|
if (is_monochrome) {
|
|
frame_buffer->stride[1] = 0;
|
|
frame_buffer->stride[2] = 0;
|
|
frame_buffer->plane[1] = nullptr;
|
|
frame_buffer->plane[2] = nullptr;
|
|
} else {
|
|
frame_buffer->stride[1] =
|
|
static_cast<int>(CVPixelBufferGetBytesPerRowOfPlane(pixel_buffer, 1));
|
|
frame_buffer->stride[2] =
|
|
static_cast<int>(CVPixelBufferGetBytesPerRowOfPlane(pixel_buffer, 2));
|
|
frame_buffer->plane[1] = static_cast<uint8_t*>(
|
|
CVPixelBufferGetBaseAddressOfPlane(pixel_buffer, 1));
|
|
frame_buffer->plane[2] = static_cast<uint8_t*>(
|
|
CVPixelBufferGetBaseAddressOfPlane(pixel_buffer, 2));
|
|
}
|
|
frame_buffer->private_data = pixel_buffer;
|
|
|
|
return libgav1::kStatusOk;
|
|
}
|
|
|
|
void Gav1DecodeCVPixelBufferPool::ReleaseCVPixelBuffer(
|
|
void* buffer_private_data) {
|
|
auto const pixel_buffer = static_cast<CVPixelBufferRef>(buffer_private_data);
|
|
CVReturn ret =
|
|
CVPixelBufferUnlockBaseAddress(pixel_buffer, /*unlockFlags=*/0);
|
|
if (ret != kCVReturnSuccess) {
|
|
fprintf(stderr, "%s:%d: CVPixelBufferUnlockBaseAddress failed: %d.\n",
|
|
__FILE__, __LINE__, static_cast<int>(ret));
|
|
abort();
|
|
}
|
|
CFRelease(pixel_buffer);
|
|
}
|