🔥
Vulkanを利用してAndroidカメラ映像を表示する
本稿は、Androidでのカメラ映像をVulkan
を用いてSurfaceView
に表示することを扱ったエントリーになります。前提としてVulkan Tutorialを終えておりAndroidのCamera2の映像を表示したい方を対象としてサンプルコードを掲載しています。
システム要件
- minSdk = 26 で Android8以降
- VulkanのAPIは1.1以降
- ドライバーの実装で実際に動作できるのは、Android10以降
- Pixel3a + Android11で確認しています
- Vulkan-Hppを利用
Surface(ANativeWindow)との接続
vk::Swapchain
の作成方法です。ANativeWindow
からvk::Surface
を作成します。
ANativeWindow *newNativeWindow = nullptr;
// jobjectからANativeWindowの変換
newNativeWindow = ANativeWindow_fromSurface(env, surface);
// vk::Surfaceの作成
auto surface = instance->createAndroidSurfaceKHRUnique(
vk::AndroidSurfaceCreateInfoKHR()
.setFlags(vk::AndroidSurfaceCreateFlagsKHR())
.setPNext(nullptr)
.setWindow(newNativeWindow));
// vk::SurfaceからSwapChainを作成
auto swapchain = device->createSwapchainKHRUnique(
vk::SwapchainCreateInfoKHR
.setSurface(surface.get())
.setMinImageCount(capabilities.minImageCount)
.setImageFormat(format.format)
(...省略...)
カメラとの接続
カメラ映像(Camera2)の取り込みは、Surface経由でAImageReader
を用いて取得します。sdkのバージョンは26
。Android8以降で利用可能です。また、JavaのImageReader
で、UsageをセットできるようになるのがTiramisu以降のようです。C++を選択しましょう。
AImageReader *reader;
ANativeWindow *window;
AImageReader_newWithUsage(
width,
height,
format,
AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
AHARDWAREBUFFER_USAGE_CPU_READ_RARELY,
maxImages,
&reader);
AImageReader_getWindow(reader, &window);
// この window をJNIのSurface経由で、カメラと接続する。
ANativeWindow_toSurface(env, window);
カメラ映像の取得
AImageReader
からAImage
を取得し。AHardwareBuffer
を取得します。
AImage *image = nullptr;
AHardwareBuffer *buffer;
if (AImageReader_acquireLatestImage(reader, &image) == AMEDIA_OK) {
AImage_getHardwareBuffer(image, &buffer) != AMEDIA_OK);
}
Samplerの作成
AHardwareBuffer
からvk::Sampler
を作成します。Ycbcr
のRGB
変換をGPUに、やってもらえます。
vk::AndroidHardwareBufferFormatPropertiesANDROID format;
vk::AndroidHardwareBufferPropertiesANDROID properties;
properties.pNext = &format;
device->getAndroidHardwareBufferPropertiesANDROID(buffer, &properties);
auto externalFormat = vk::ExternalFormatANDROID().setExternalFormat(format.externalFormat);
auto conversion = device->createSamplerYcbcrConversionUnique(
vk::SamplerYcbcrConversionCreateInfo()
.setPNext(&externalFormat)
.setFormat(vk::Format::eUndefined)
.setYcbcrModel(format.suggestedYcbcrModel)
.setYcbcrRange(format.suggestedYcbcrRange)
.setComponents(format.samplerYcbcrConversionComponents)
.setXChromaOffset(format.suggestedXChromaOffset)
.setYChromaOffset(format.suggestedYChromaOffset)
.setChromaFilter(vk::Filter::eNearest)
.setForceExplicitReconstruction(false));
auto samplerCreate = vk::SamplerCreateInfo()
.setMagFilter(vk::Filter::eNearest)
.setMinFilter(vk::Filter::eNearest)
.setAddressModeU(vk::SamplerAddressMode::eClampToEdge)
.setAddressModeV(vk::SamplerAddressMode::eClampToEdge)
.setAddressModeW(vk::SamplerAddressMode::eClampToEdge)
.setMipLodBias(0.0f)
.setMaxAnisotropy(1)
.setCompareOp(vk::CompareOp::eNever)
.setMinLod(0.0f)
.setMaxLod(0.0f)
.setBorderColor(vk::BorderColor::eFloatOpaqueWhite)
.setUnnormalizedCoordinates(false);
auto ycbcrConversionInfo = vk::SamplerYcbcrConversionInfo()
.setConversion(conversion.get());
samplerCreate.setPNext(&ycbcrConversionInfo);
auto sampler = device->createSamplerUnique(samplerCreate);
バッファーの利用
vk::Image
を作成。vk::Memory
を利用して、AHardwareBuffer
とvk::Image
を紐づけます。vk::Image
ができたらvk::ImageView
を作成してDescriptor
にセットします。
vk::UniqueImage image = device->createImageUnique(
vk::ImageCreateInfo()
.setInitialLayout(vk::ImageLayout::eUndefined)
.setPNext(&vk::ExternalMemoryImageCreateInfo()
.setHandleTypes(vk::ExternalMemoryHandleTypeFlagBits::eAndroidHardwareBufferANDROID)
.setPNext(&externalFormat)));
const auto hardwareBufferProperties = device->getAndroidHardwareBufferPropertiesANDROID(
*buffer);
vk::UniqueMemory memory = device->allocateMemoryUnique(
vk::MemoryAllocateInfo()
.setAllocationSize(hardwareBufferProperties.allocationSize)
.setMemoryTypeIndex(FindMemoryType(
hardwareBufferProperties.memoryTypeBits,
vk::MemoryPropertyFlagBits::eHostVisible
))
.setPNext(&vk::MemoryDedicatedAllocateInfo()
.setImage(image.get())
.setPNext(&vk::ImportAndroidHardwareBufferInfoANDROID().setBuffer(buffer))));
終わりに
以上です。AndroidでVulkanコードを書く際に基本となるコードを記載しました。実際に動くコードは、こちらに置いてあります。
Discussion