|
|
|
/*
|
|
|
|
* This file is part of FFmpeg.
|
|
|
|
*
|
|
|
|
* FFmpeg is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* FFmpeg is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with FFmpeg; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "libavutil/mem.h"
|
|
|
|
#include "libavutil/avassert.h"
|
|
|
|
#include "vulkan_encode.h"
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include "libavutil/vulkan_loader.h"
|
|
|
|
|
|
|
|
const AVCodecHWConfigInternal *const ff_vulkan_encode_hw_configs[] = {
|
|
|
|
HW_CONFIG_ENCODER_FRAMES(VULKAN, VULKAN),
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
av_cold void ff_vulkan_encode_uninit(FFVulkanEncodeContext *ctx)
|
|
|
|
{
|
|
|
|
FFVulkanContext *s = &ctx->s;
|
|
|
|
FFVulkanFunctions *vk = &s->vkfn;
|
|
|
|
|
|
|
|
/* Wait on and free execution pool */
|
|
|
|
ff_vk_exec_pool_free(s, &ctx->enc_pool);
|
|
|
|
|
|
|
|
/* Destroy the session params */
|
|
|
|
if (ctx->session_params)
|
|
|
|
vk->DestroyVideoSessionParametersKHR(s->hwctx->act_dev,
|
|
|
|
ctx->session_params,
|
|
|
|
s->hwctx->alloc);
|
|
|
|
|
|
|
|
ff_hw_base_encode_close(&ctx->base);
|
|
|
|
|
|
|
|
av_buffer_pool_uninit(&ctx->buf_pool);
|
|
|
|
|
|
|
|
ff_vk_video_common_uninit(s, &ctx->common);
|
|
|
|
|
|
|
|
ff_vk_uninit(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vulkan_encode_init(AVCodecContext *avctx, FFHWBaseEncodePicture *pic)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
FFVulkanEncodeContext *ctx = avctx->priv_data;
|
|
|
|
FFVulkanEncodePicture *vp = pic->priv;
|
|
|
|
|
|
|
|
AVFrame *f = pic->input_image;
|
|
|
|
AVHWFramesContext *hwfc = (AVHWFramesContext *)f->hw_frames_ctx->data;
|
|
|
|
AVVulkanFramesContext *vkfc = hwfc->hwctx;
|
|
|
|
AVVkFrame *vkf = (AVVkFrame *)f->data[0];
|
|
|
|
|
|
|
|
if (ctx->codec->picture_priv_data_size > 0) {
|
|
|
|
pic->codec_priv = av_mallocz(ctx->codec->picture_priv_data_size);
|
|
|
|
if (!pic->codec_priv)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Input image view */
|
|
|
|
err = ff_vk_create_view(&ctx->s, &ctx->common,
|
|
|
|
&vp->in.view, &vp->in.aspect,
|
|
|
|
vkf, vkfc->format[0], 0);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/* Reference view */
|
|
|
|
if (!ctx->common.layered_dpb) {
|
|
|
|
AVFrame *rf = pic->recon_image;
|
|
|
|
AVVkFrame *rvkf = (AVVkFrame *)rf->data[0];
|
|
|
|
err = ff_vk_create_view(&ctx->s, &ctx->common,
|
|
|
|
&vp->dpb.view, &vp->dpb.aspect,
|
|
|
|
rvkf, ctx->pic_format, 1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
} else {
|
|
|
|
vp->dpb.view = ctx->common.layered_view;
|
|
|
|
vp->dpb.aspect = ctx->common.layered_aspect;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vulkan_encode_free(AVCodecContext *avctx, FFHWBaseEncodePicture *pic)
|
|
|
|
{
|
|
|
|
FFVulkanEncodeContext *ctx = avctx->priv_data;
|
|
|
|
FFVulkanFunctions *vk = &ctx->s.vkfn;
|
|
|
|
|
|
|
|
FFVulkanEncodePicture *vp = pic->priv;
|
|
|
|
|
|
|
|
if (vp->in.view)
|
|
|
|
vk->DestroyImageView(ctx->s.hwctx->act_dev, vp->in.view,
|
|
|
|
ctx->s.hwctx->alloc);
|
|
|
|
|
|
|
|
if (!ctx->common.layered_dpb && vp->dpb.view)
|
|
|
|
vk->DestroyImageView(ctx->s.hwctx->act_dev, vp->dpb.view,
|
|
|
|
ctx->s.hwctx->alloc);
|
|
|
|
|
|
|
|
ctx->slots[vp->dpb_slot.slotIndex] = NULL;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int init_pic_rc(AVCodecContext *avctx, FFHWBaseEncodePicture *pic,
|
|
|
|
VkVideoEncodeRateControlInfoKHR *rc_info,
|
|
|
|
VkVideoEncodeRateControlLayerInfoKHR *rc_layer /* Goes in ^ */)
|
|
|
|
{
|
|
|
|
FFVulkanEncodeContext *ctx = avctx->priv_data;
|
|
|
|
|
|
|
|
*rc_info = (VkVideoEncodeRateControlInfoKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_RATE_CONTROL_INFO_KHR,
|
|
|
|
.rateControlMode = ctx->opts.rc_mode,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (ctx->opts.rc_mode > VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DISABLED_BIT_KHR) {
|
|
|
|
*rc_layer = (VkVideoEncodeRateControlLayerInfoKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_RATE_CONTROL_LAYER_INFO_KHR,
|
|
|
|
.averageBitrate = avctx->bit_rate,
|
|
|
|
.maxBitrate = avctx->rc_max_rate ? avctx->rc_max_rate : avctx->bit_rate,
|
|
|
|
.frameRateNumerator = avctx->framerate.num,
|
|
|
|
.frameRateDenominator = avctx->framerate.den,
|
|
|
|
};
|
|
|
|
rc_info->layerCount++;
|
|
|
|
rc_info->pLayers = rc_layer;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ctx->codec->init_pic_rc(avctx, pic, rc_info, rc_layer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vulkan_encode_issue(AVCodecContext *avctx,
|
|
|
|
FFHWBaseEncodePicture *base_pic)
|
|
|
|
{
|
|
|
|
FFVulkanEncodeContext *ctx = avctx->priv_data;
|
|
|
|
FFVulkanFunctions *vk = &ctx->s.vkfn;
|
|
|
|
|
|
|
|
const size_t size_align = ctx->caps.minBitstreamBufferSizeAlignment;
|
|
|
|
|
|
|
|
FFVulkanEncodePicture *vp = base_pic->priv;
|
|
|
|
AVFrame *src = (AVFrame *)base_pic->input_image;
|
|
|
|
AVVkFrame *vkf = (AVVkFrame *)src->data[0];
|
|
|
|
|
|
|
|
int err, max_pkt_size;
|
|
|
|
|
|
|
|
FFVkBuffer *sd_buf;
|
|
|
|
|
|
|
|
int slot_index = -1;
|
|
|
|
FFVkExecContext *exec;
|
|
|
|
VkCommandBuffer cmd_buf;
|
|
|
|
VkImageMemoryBarrier2 img_bar[37];
|
|
|
|
int nb_img_bar = 0;
|
|
|
|
|
|
|
|
/* Coding start/end */
|
|
|
|
VkVideoBeginCodingInfoKHR encode_start;
|
|
|
|
VkVideoEndCodingInfoKHR encode_end = {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_END_CODING_INFO_KHR,
|
|
|
|
};
|
|
|
|
|
|
|
|
VkVideoEncodeRateControlLayerInfoKHR rc_layer;
|
|
|
|
VkVideoEncodeRateControlInfoKHR rc_info;
|
|
|
|
VkVideoEncodeQualityLevelInfoKHR q_info;
|
|
|
|
VkVideoCodingControlInfoKHR encode_ctrl;
|
|
|
|
|
|
|
|
VkVideoReferenceSlotInfoKHR ref_slot[37];
|
|
|
|
VkVideoEncodeInfoKHR encode_info;
|
|
|
|
|
|
|
|
/* Create packet data buffer */
|
|
|
|
max_pkt_size = FFALIGN(3 * ctx->base.surface_width * ctx->base.surface_height + (1 << 16),
|
|
|
|
ctx->caps.minBitstreamBufferSizeAlignment);
|
|
|
|
|
|
|
|
err = ff_vk_get_pooled_buffer(&ctx->s, &ctx->buf_pool, &vp->pkt_buf,
|
|
|
|
VK_BUFFER_USAGE_VIDEO_ENCODE_DST_BIT_KHR,
|
|
|
|
&ctx->profile_list, max_pkt_size,
|
|
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
|
|
VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
sd_buf = (FFVkBuffer *)vp->pkt_buf->data;
|
|
|
|
|
|
|
|
/* Setup rate control */
|
|
|
|
err = init_pic_rc(avctx, base_pic, &rc_info, &rc_layer);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
q_info = (VkVideoEncodeQualityLevelInfoKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_QUALITY_LEVEL_INFO_KHR,
|
|
|
|
.pNext = &rc_info,
|
|
|
|
.qualityLevel = ctx->opts.quality,
|
|
|
|
};
|
|
|
|
encode_ctrl = (VkVideoCodingControlInfoKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_CODING_CONTROL_INFO_KHR,
|
|
|
|
.pNext = &q_info,
|
|
|
|
.flags = VK_VIDEO_CODING_CONTROL_ENCODE_QUALITY_LEVEL_BIT_KHR |
|
|
|
|
VK_VIDEO_CODING_CONTROL_ENCODE_RATE_CONTROL_BIT_KHR |
|
|
|
|
VK_VIDEO_CODING_CONTROL_RESET_BIT_KHR,
|
|
|
|
};
|
|
|
|
|
|
|
|
for (int i = 0; i < ctx->caps.maxDpbSlots; i++) {
|
|
|
|
if (ctx->slots[i] == NULL) {
|
|
|
|
slot_index = i;
|
|
|
|
ctx->slots[i] = base_pic;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
av_assert0(slot_index >= 0);
|
|
|
|
|
|
|
|
/* Current picture's ref slot */
|
|
|
|
vp->dpb_res = (VkVideoPictureResourceInfoKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR,
|
|
|
|
.pNext = NULL,
|
|
|
|
.codedOffset = { 0 },
|
|
|
|
.codedExtent = (VkExtent2D){ avctx->width, avctx->height },
|
|
|
|
.baseArrayLayer = ctx->common.layered_dpb ? slot_index : 0,
|
|
|
|
.imageViewBinding = vp->dpb.view,
|
|
|
|
};
|
|
|
|
|
|
|
|
vp->dpb_slot = (VkVideoReferenceSlotInfoKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
|
|
|
|
.pNext = NULL, // Set later
|
|
|
|
.slotIndex = slot_index,
|
|
|
|
.pPictureResource = &vp->dpb_res,
|
|
|
|
};
|
|
|
|
|
|
|
|
encode_info = (VkVideoEncodeInfoKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_INFO_KHR,
|
|
|
|
.pNext = NULL, // Set later
|
|
|
|
.flags = 0x0,
|
|
|
|
.srcPictureResource = (VkVideoPictureResourceInfoKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR,
|
|
|
|
.pNext = NULL,
|
|
|
|
.codedOffset = { 0, 0 },
|
|
|
|
.codedExtent = (VkExtent2D){ base_pic->input_image->width,
|
|
|
|
base_pic->input_image->height },
|
|
|
|
.baseArrayLayer = 0,
|
|
|
|
.imageViewBinding = vp->in.view,
|
|
|
|
},
|
|
|
|
.pSetupReferenceSlot = &vp->dpb_slot,
|
|
|
|
.referenceSlotCount = 0,
|
|
|
|
.pReferenceSlots = ref_slot,
|
|
|
|
.dstBuffer = sd_buf->buf,
|
|
|
|
.dstBufferOffset = 0,
|
|
|
|
.dstBufferRange = sd_buf->size,
|
|
|
|
.precedingExternallyEncodedBytes = 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
for (int i = 0; i < MAX_REFERENCE_LIST_NUM; i++) {
|
|
|
|
for (int j = 0; j < base_pic->nb_refs[i]; j++) {
|
|
|
|
FFHWBaseEncodePicture *ref = base_pic->refs[i][j];
|
|
|
|
FFVulkanEncodePicture *rvp = ref->priv;
|
|
|
|
ref_slot[encode_info.referenceSlotCount++] = rvp->dpb_slot;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Calling vkCmdBeginVideoCodingKHR requires to declare all references
|
|
|
|
* being enabled upfront, including the current frame's output ref. */
|
|
|
|
ref_slot[encode_info.referenceSlotCount] = vp->dpb_slot;
|
|
|
|
ref_slot[encode_info.referenceSlotCount].slotIndex = -1;
|
|
|
|
|
|
|
|
/* Setup picture parameters */
|
|
|
|
err = ctx->codec->init_pic_params(avctx, base_pic,
|
|
|
|
&encode_info);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
encode_start = (VkVideoBeginCodingInfoKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_BEGIN_CODING_INFO_KHR,
|
|
|
|
.pNext = !base_pic->force_idr ? &rc_info : NULL,
|
|
|
|
.videoSession = ctx->common.session,
|
|
|
|
.videoSessionParameters = ctx->session_params,
|
|
|
|
.referenceSlotCount = encode_info.referenceSlotCount + 1,
|
|
|
|
.pReferenceSlots = ref_slot,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Write header */
|
|
|
|
if (base_pic->type == FF_HW_PICTURE_TYPE_IDR) {
|
|
|
|
uint8_t *hdr_dst = sd_buf->mapped_mem + encode_info.dstBufferOffset;
|
|
|
|
size_t data_size = encode_info.dstBufferRange;
|
|
|
|
err = ctx->codec->write_sequence_headers(avctx, base_pic, hdr_dst, &data_size);
|
|
|
|
if (err < 0)
|
|
|
|
goto fail;
|
|
|
|
encode_info.dstBufferOffset += data_size;
|
|
|
|
encode_info.dstBufferRange -= data_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write extra units */
|
|
|
|
if (ctx->codec->write_extra_headers) {
|
|
|
|
uint8_t *hdr_dst = sd_buf->mapped_mem + encode_info.dstBufferOffset;
|
|
|
|
size_t data_size = encode_info.dstBufferRange;
|
|
|
|
err = ctx->codec->write_extra_headers(avctx, base_pic, hdr_dst, &data_size);
|
|
|
|
if (err < 0)
|
|
|
|
goto fail;
|
|
|
|
encode_info.dstBufferOffset += data_size;
|
|
|
|
encode_info.dstBufferRange -= data_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Align buffer offset to the required value with filler units */
|
|
|
|
if (ctx->codec->write_filler) {
|
|
|
|
uint8_t *hdr_dst = sd_buf->mapped_mem + encode_info.dstBufferOffset;
|
|
|
|
size_t data_size = encode_info.dstBufferRange;
|
|
|
|
|
|
|
|
uint32_t offset = encode_info.dstBufferOffset;
|
|
|
|
size_t offset_align = ctx->caps.minBitstreamBufferOffsetAlignment;
|
|
|
|
|
|
|
|
uint32_t filler_data = FFALIGN(offset, offset_align) - offset;
|
|
|
|
|
|
|
|
if (filler_data) {
|
|
|
|
while (filler_data < ctx->codec->filler_header_size)
|
|
|
|
filler_data += offset_align;
|
|
|
|
|
|
|
|
filler_data -= ctx->codec->filler_header_size;
|
|
|
|
|
|
|
|
err = ctx->codec->write_filler(avctx, filler_data,
|
|
|
|
hdr_dst, &data_size);
|
|
|
|
if (err < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
encode_info.dstBufferOffset += data_size;
|
|
|
|
encode_info.dstBufferRange -= data_size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
vp->slices_offset = encode_info.dstBufferOffset;
|
|
|
|
|
|
|
|
/* Align buffer size to the nearest lower alignment requirement. */
|
|
|
|
encode_info.dstBufferRange -= size_align;
|
|
|
|
encode_info.dstBufferRange = FFALIGN(encode_info.dstBufferRange,
|
|
|
|
size_align);
|
|
|
|
|
|
|
|
/* Start command buffer recording */
|
|
|
|
exec = vp->exec = ff_vk_exec_get(&ctx->s, &ctx->enc_pool);
|
|
|
|
ff_vk_exec_start(&ctx->s, exec);
|
|
|
|
cmd_buf = exec->buf;
|
|
|
|
|
|
|
|
/* Output packet buffer */
|
|
|
|
err = ff_vk_exec_add_dep_buf(&ctx->s, exec, &vp->pkt_buf, 1, 1);
|
|
|
|
if (err < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/* Source image */
|
|
|
|
err = ff_vk_exec_add_dep_frame(&ctx->s, exec, src,
|
|
|
|
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
|
|
|
VK_PIPELINE_STAGE_2_VIDEO_ENCODE_BIT_KHR);
|
|
|
|
if (err < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/* Source image layout conversion */
|
|
|
|
img_bar[nb_img_bar] = (VkImageMemoryBarrier2) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
|
|
|
|
.pNext = NULL,
|
|
|
|
.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
|
|
|
.srcAccessMask = vkf->access[0],
|
|
|
|
.dstStageMask = VK_PIPELINE_STAGE_2_VIDEO_ENCODE_BIT_KHR,
|
|
|
|
.dstAccessMask = VK_ACCESS_2_VIDEO_ENCODE_READ_BIT_KHR,
|
|
|
|
.oldLayout = vkf->layout[0],
|
|
|
|
.newLayout = VK_IMAGE_LAYOUT_VIDEO_ENCODE_SRC_KHR,
|
|
|
|
.srcQueueFamilyIndex = vkf->queue_family[0],
|
|
|
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
|
|
|
.image = vkf->img[0],
|
|
|
|
.subresourceRange = (VkImageSubresourceRange) {
|
|
|
|
.aspectMask = vp->in.aspect,
|
|
|
|
.layerCount = 1,
|
|
|
|
.levelCount = 1,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
ff_vk_exec_update_frame(&ctx->s, exec, src,
|
|
|
|
&img_bar[nb_img_bar], &nb_img_bar);
|
|
|
|
|
|
|
|
if (!ctx->common.layered_dpb) {
|
|
|
|
/* Source image's ref slot.
|
|
|
|
* No need to do a layout conversion, since the frames which are allocated
|
|
|
|
* with a DPB usage are automatically converted. */
|
|
|
|
err = ff_vk_exec_add_dep_frame(&ctx->s, exec, base_pic->recon_image,
|
|
|
|
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
|
|
|
VK_PIPELINE_STAGE_2_VIDEO_ENCODE_BIT_KHR);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/* All references */
|
|
|
|
for (int i = 0; i < MAX_REFERENCE_LIST_NUM; i++) {
|
|
|
|
for (int j = 0; j < base_pic->nb_refs[i]; j++) {
|
|
|
|
FFHWBaseEncodePicture *ref = base_pic->refs[i][j];
|
|
|
|
err = ff_vk_exec_add_dep_frame(&ctx->s, exec, ref->recon_image,
|
|
|
|
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
|
|
|
VK_PIPELINE_STAGE_2_VIDEO_ENCODE_BIT_KHR);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
err = ff_vk_exec_add_dep_frame(&ctx->s, exec, ctx->common.layered_frame,
|
|
|
|
VK_PIPELINE_STAGE_2_VIDEO_ENCODE_BIT_KHR,
|
|
|
|
VK_PIPELINE_STAGE_2_VIDEO_ENCODE_BIT_KHR);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Change image layout */
|
|
|
|
vk->CmdPipelineBarrier2(cmd_buf, &(VkDependencyInfo) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
|
|
|
.pImageMemoryBarriers = img_bar,
|
|
|
|
.imageMemoryBarrierCount = nb_img_bar,
|
|
|
|
});
|
|
|
|
|
|
|
|
/* Start, use parameters */
|
|
|
|
vk->CmdBeginVideoCodingKHR(cmd_buf, &encode_start);
|
|
|
|
|
|
|
|
/* Send control data */
|
|
|
|
if (!ctx->session_reset) {
|
|
|
|
vk->CmdControlVideoCodingKHR(cmd_buf, &encode_ctrl);
|
|
|
|
ctx->session_reset++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Encode */
|
|
|
|
vk->CmdBeginQuery(cmd_buf, ctx->enc_pool.query_pool, exec->query_idx + 0, 0);
|
|
|
|
vk->CmdEncodeVideoKHR(cmd_buf, &encode_info);
|
|
|
|
vk->CmdEndQuery(cmd_buf, ctx->enc_pool.query_pool, exec->query_idx + 0);
|
|
|
|
|
|
|
|
/* End encoding */
|
|
|
|
vk->CmdEndVideoCodingKHR(cmd_buf, &encode_end);
|
|
|
|
|
|
|
|
/* End recording and submit for execution */
|
|
|
|
err = ff_vk_exec_submit(&ctx->s, vp->exec);
|
|
|
|
if (err < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/* We don't need to keep the input image any longer, its already ref'd */
|
|
|
|
av_frame_free(&base_pic->input_image);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vulkan_encode_wait(AVCodecContext *avctx,
|
|
|
|
FFHWBaseEncodePicture *base_pic)
|
|
|
|
{
|
|
|
|
FFVulkanEncodeContext *ctx = avctx->priv_data;
|
|
|
|
FFVulkanEncodePicture *vp = base_pic->priv;
|
|
|
|
|
|
|
|
av_assert0(base_pic->encode_issued);
|
|
|
|
|
|
|
|
if (base_pic->encode_complete)
|
|
|
|
return;
|
|
|
|
|
|
|
|
ff_vk_exec_wait(&ctx->s, vp->exec);
|
|
|
|
base_pic->encode_complete = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vulkan_encode_output(AVCodecContext *avctx,
|
|
|
|
FFHWBaseEncodePicture *base_pic, AVPacket *pkt)
|
|
|
|
{
|
|
|
|
VkResult ret;
|
|
|
|
FFVulkanEncodePicture *vp = base_pic->priv;
|
|
|
|
FFVulkanEncodeContext *ctx = avctx->priv_data;
|
|
|
|
FFVkBuffer *sd_buf = (FFVkBuffer *)vp->pkt_buf->data;
|
|
|
|
uint32_t *query_data;
|
|
|
|
|
|
|
|
vulkan_encode_wait(avctx, base_pic);
|
|
|
|
|
|
|
|
ret = ff_vk_exec_get_query(&ctx->s, vp->exec, (void **)&query_data, 0);
|
|
|
|
if (ret == VK_NOT_READY) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to perform query: %s!\n",
|
|
|
|
ff_vk_ret2str(ret));
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret != VK_NOT_READY && ret != VK_SUCCESS) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to perform query: %s!\n",
|
|
|
|
ff_vk_ret2str(ret));
|
|
|
|
return AVERROR_EXTERNAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (query_data[2] != VK_QUERY_RESULT_STATUS_COMPLETE_KHR) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to encode: %u\n", query_data[2]);
|
|
|
|
return AVERROR_EXTERNAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Invalidate buffer if needed */
|
|
|
|
if (!(sd_buf->flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) {
|
|
|
|
FFVulkanFunctions *vk = &ctx->s.vkfn;
|
|
|
|
VkMappedMemoryRange invalidate_buf;
|
|
|
|
|
|
|
|
int offs = vp->slices_offset;
|
|
|
|
/* If the non-coherent alignment is greater than the bitstream buffer
|
|
|
|
* offset's alignment, and the offs value is not aligned already,
|
|
|
|
* align it to the previous alignment point. */
|
|
|
|
if (ctx->s.props.properties.limits.nonCoherentAtomSize >
|
|
|
|
ctx->caps.minBitstreamBufferOffsetAlignment && offs &&
|
|
|
|
(FFALIGN(offs, ctx->s.props.properties.limits.nonCoherentAtomSize) != offs)) {
|
|
|
|
offs -= ctx->s.props.properties.limits.nonCoherentAtomSize;
|
|
|
|
offs = FFALIGN(FFMAX(offs, 0), ctx->s.props.properties.limits.nonCoherentAtomSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
invalidate_buf = (VkMappedMemoryRange) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
|
|
|
|
.memory = sd_buf->mem,
|
|
|
|
.offset = offs,
|
|
|
|
.size = VK_WHOLE_SIZE,
|
|
|
|
};
|
|
|
|
|
|
|
|
vk->FlushMappedMemoryRanges(ctx->s.hwctx->act_dev, 1, &invalidate_buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
pkt->data = sd_buf->mapped_mem;
|
|
|
|
pkt->size = vp->slices_offset + /* base offset */
|
|
|
|
query_data[0] /* secondary offset */ +
|
|
|
|
query_data[1] /* size */;
|
|
|
|
|
|
|
|
/* Move reference */
|
|
|
|
pkt->buf = vp->pkt_buf;
|
|
|
|
vp->pkt_buf = NULL;
|
|
|
|
|
|
|
|
av_log(avctx, AV_LOG_DEBUG, "Frame %"PRId64"/%"PRId64 " encoded\n",
|
|
|
|
base_pic->display_order, base_pic->encode_order);
|
|
|
|
|
|
|
|
return ff_hw_base_encode_set_output_property(&ctx->base, avctx,
|
|
|
|
base_pic, pkt,
|
|
|
|
ctx->codec->flags & VK_ENC_FLAG_NO_DELAY);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const FFHWEncodePictureOperation vulkan_base_encode_ops = {
|
|
|
|
.priv_size = sizeof(FFVulkanEncodePicture),
|
|
|
|
.init = &vulkan_encode_init,
|
|
|
|
.issue = &vulkan_encode_issue,
|
|
|
|
.output = &vulkan_encode_output,
|
|
|
|
.free = &vulkan_encode_free,
|
|
|
|
};
|
|
|
|
|
|
|
|
int ff_vulkan_encode_receive_packet(AVCodecContext *avctx, AVPacket *pkt)
|
|
|
|
{
|
|
|
|
FFVulkanEncodeContext *ctx = avctx->priv_data;
|
|
|
|
return ff_hw_base_encode_receive_packet(&ctx->base, avctx, pkt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vulkan_encode_create_dpb(AVCodecContext *avctx, FFVulkanEncodeContext *ctx)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
FFHWBaseEncodeContext *base_ctx = &ctx->base;
|
|
|
|
AVVulkanFramesContext *hwfc;
|
|
|
|
|
|
|
|
enum AVPixelFormat dpb_format;
|
|
|
|
err = ff_hw_base_get_recon_format(base_ctx, NULL, &dpb_format);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
base_ctx->recon_frames_ref = av_hwframe_ctx_alloc(base_ctx->device_ref);
|
|
|
|
if (!base_ctx->recon_frames_ref)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
|
|
|
|
base_ctx->recon_frames = (AVHWFramesContext *)base_ctx->recon_frames_ref->data;
|
|
|
|
hwfc = (AVVulkanFramesContext *)base_ctx->recon_frames->hwctx;
|
|
|
|
|
|
|
|
base_ctx->recon_frames->format = AV_PIX_FMT_VULKAN;
|
|
|
|
base_ctx->recon_frames->sw_format = dpb_format;
|
|
|
|
base_ctx->recon_frames->width = avctx->width;
|
|
|
|
base_ctx->recon_frames->height = avctx->height;
|
|
|
|
|
|
|
|
hwfc->format[0] = ctx->pic_format;
|
|
|
|
hwfc->create_pnext = &ctx->profile_list;
|
|
|
|
hwfc->tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
|
|
hwfc->usage = VK_IMAGE_USAGE_SAMPLED_BIT |
|
|
|
|
VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR;
|
|
|
|
|
|
|
|
if (ctx->common.layered_dpb)
|
|
|
|
hwfc->nb_layers = ctx->caps.maxDpbSlots;
|
|
|
|
|
|
|
|
err = av_hwframe_ctx_init(base_ctx->recon_frames_ref);
|
|
|
|
if (err < 0) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to initialise DPB frame context: %s\n",
|
|
|
|
av_err2str(err));
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx->common.layered_dpb) {
|
|
|
|
ctx->common.layered_frame = av_frame_alloc();
|
|
|
|
if (!ctx->common.layered_frame)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
|
|
|
|
err = av_hwframe_get_buffer(base_ctx->recon_frames_ref,
|
|
|
|
ctx->common.layered_frame, 0);
|
|
|
|
if (err < 0)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
|
|
|
|
err = ff_vk_create_view(&ctx->s, &ctx->common,
|
|
|
|
&ctx->common.layered_view,
|
|
|
|
&ctx->common.layered_aspect,
|
|
|
|
(AVVkFrame *)ctx->common.layered_frame->data[0],
|
|
|
|
hwfc->format[0], 1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
av_buffer_unref(&base_ctx->recon_frames_ref);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static av_cold int init_rc(AVCodecContext *avctx, FFVulkanEncodeContext *ctx)
|
|
|
|
{
|
|
|
|
if (ctx->opts.qp) {
|
|
|
|
ctx->explicit_qp = ctx->opts.qp;
|
|
|
|
} else if (avctx->global_quality > 0) {
|
|
|
|
if (avctx->flags & AV_CODEC_FLAG_QSCALE)
|
|
|
|
ctx->explicit_qp = avctx->global_quality / FF_QP2LAMBDA;
|
|
|
|
else
|
|
|
|
ctx->explicit_qp = avctx->global_quality;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx->opts.rc_mode == FF_VK_RC_MODE_AUTO) {
|
|
|
|
if (ctx->explicit_qp >= 0) {
|
|
|
|
ctx->opts.rc_mode = VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DISABLED_BIT_KHR;
|
|
|
|
} else if (avctx->global_quality > 0) {
|
|
|
|
if (avctx->flags & AV_CODEC_FLAG_QSCALE)
|
|
|
|
ctx->explicit_qp = avctx->global_quality / FF_QP2LAMBDA;
|
|
|
|
else
|
|
|
|
ctx->explicit_qp = avctx->global_quality;
|
|
|
|
ctx->opts.rc_mode = VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DISABLED_BIT_KHR;
|
|
|
|
} else if (avctx->bit_rate) {
|
|
|
|
if (ctx->enc_caps.rateControlModes & VK_VIDEO_ENCODE_RATE_CONTROL_MODE_VBR_BIT_KHR)
|
|
|
|
ctx->opts.rc_mode = VK_VIDEO_ENCODE_RATE_CONTROL_MODE_VBR_BIT_KHR;
|
|
|
|
else if (ctx->enc_caps.rateControlModes & VK_VIDEO_ENCODE_RATE_CONTROL_MODE_CBR_BIT_KHR)
|
|
|
|
ctx->opts.rc_mode = VK_VIDEO_ENCODE_RATE_CONTROL_MODE_CBR_BIT_KHR;
|
|
|
|
else
|
|
|
|
ctx->opts.rc_mode = VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DEFAULT_KHR;
|
|
|
|
} else {
|
|
|
|
ctx->explicit_qp = 18;
|
|
|
|
ctx->opts.rc_mode = VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DISABLED_BIT_KHR;
|
|
|
|
av_log(avctx, AV_LOG_WARNING, "No rate control settings specified, using fixed QP = %i\n",
|
|
|
|
ctx->explicit_qp);
|
|
|
|
}
|
|
|
|
} else if (ctx->opts.rc_mode != VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DISABLED_BIT_KHR &&
|
|
|
|
!avctx->bit_rate) {
|
|
|
|
av_log(avctx, AV_LOG_WARNING, "No bitrate specified!\n");
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx->opts.rc_mode && !(ctx->enc_caps.rateControlModes & ctx->opts.rc_mode)) {
|
|
|
|
static const char *rc_modes[] = {
|
|
|
|
[VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DEFAULT_KHR] = "default",
|
|
|
|
[VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DISABLED_BIT_KHR] = "cqp",
|
|
|
|
[VK_VIDEO_ENCODE_RATE_CONTROL_MODE_CBR_BIT_KHR] = "cbr",
|
|
|
|
[VK_VIDEO_ENCODE_RATE_CONTROL_MODE_VBR_BIT_KHR] = "vbr",
|
|
|
|
};
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Unsupported rate control mode %s, supported are:\n",
|
|
|
|
rc_modes[FFMIN(FF_ARRAY_ELEMS(rc_modes), ctx->opts.rc_mode)]);
|
|
|
|
av_log(avctx, AV_LOG_ERROR, " %s\n", rc_modes[0]);
|
|
|
|
for (int i = VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DISABLED_BIT_KHR;
|
|
|
|
i <= VK_VIDEO_ENCODE_RATE_CONTROL_MODE_VBR_BIT_KHR; i <<= 1) {
|
|
|
|
if (!(ctx->enc_caps.rateControlModes & i))
|
|
|
|
continue;
|
|
|
|
av_log(avctx, AV_LOG_ERROR, " %s\n", rc_modes[i]);
|
|
|
|
}
|
|
|
|
return AVERROR(ENOTSUP);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
av_cold int ff_vulkan_write_global_header(AVCodecContext *avctx,
|
|
|
|
FFVulkanEncodeContext *ctx)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* Write extradata if needed */
|
|
|
|
if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) {
|
|
|
|
uint8_t data[4096];
|
|
|
|
size_t data_len = sizeof(data);
|
|
|
|
|
|
|
|
err = ctx->codec->write_sequence_headers(avctx, NULL, data, &data_len);
|
|
|
|
if (err < 0) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Failed to write sequence header "
|
|
|
|
"for extradata: %d.\n", err);
|
|
|
|
return err;
|
|
|
|
} else {
|
|
|
|
avctx->extradata_size = data_len;
|
|
|
|
avctx->extradata = av_mallocz(avctx->extradata_size +
|
|
|
|
AV_INPUT_BUFFER_PADDING_SIZE);
|
|
|
|
if (!avctx->extradata) {
|
|
|
|
err = AVERROR(ENOMEM);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
memcpy(avctx->extradata, data, avctx->extradata_size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
av_cold int ff_vulkan_encode_init(AVCodecContext *avctx, FFVulkanEncodeContext *ctx,
|
|
|
|
const FFVulkanEncodeDescriptor *vk_desc,
|
|
|
|
const FFVulkanCodec *codec,
|
|
|
|
void *codec_caps, void *quality_pnext)
|
|
|
|
{
|
|
|
|
int i, err;
|
|
|
|
VkResult ret;
|
|
|
|
FFVulkanFunctions *vk = &ctx->s.vkfn;
|
|
|
|
FFVulkanContext *s = &ctx->s;
|
|
|
|
FFHWBaseEncodeContext *base_ctx = &ctx->base;
|
|
|
|
|
|
|
|
const AVPixFmtDescriptor *desc;
|
|
|
|
|
|
|
|
VkVideoFormatPropertiesKHR *ret_info;
|
|
|
|
uint32_t nb_out_fmts = 0;
|
|
|
|
|
|
|
|
VkPhysicalDeviceVideoEncodeQualityLevelInfoKHR quality_info;
|
|
|
|
|
|
|
|
VkQueryPoolVideoEncodeFeedbackCreateInfoKHR query_create;
|
|
|
|
|
|
|
|
VkVideoSessionCreateInfoKHR session_create = {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_CREATE_INFO_KHR,
|
|
|
|
};
|
|
|
|
VkPhysicalDeviceVideoFormatInfoKHR fmt_info = {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VIDEO_FORMAT_INFO_KHR,
|
|
|
|
.pNext = &ctx->profile_list,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!avctx->hw_frames_ctx) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "A hardware frames reference is "
|
|
|
|
"required to associate the encoding device.\n");
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->base.op = &vulkan_base_encode_ops;
|
|
|
|
ctx->codec = codec;
|
|
|
|
|
|
|
|
s->frames_ref = av_buffer_ref(avctx->hw_frames_ctx);
|
|
|
|
s->frames = (AVHWFramesContext *)s->frames_ref->data;
|
|
|
|
s->hwfc = s->frames->hwctx;
|
|
|
|
|
|
|
|
s->device = (AVHWDeviceContext *)s->frames->device_ref->data;
|
|
|
|
s->hwctx = s->device->hwctx;
|
|
|
|
|
|
|
|
desc = av_pix_fmt_desc_get(avctx->sw_pix_fmt);
|
|
|
|
if (!desc)
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
|
|
|
|
s->extensions = ff_vk_extensions_to_mask(s->hwctx->enabled_dev_extensions,
|
|
|
|
s->hwctx->nb_enabled_dev_extensions);
|
|
|
|
|
|
|
|
if (!(s->extensions & FF_VK_EXT_VIDEO_ENCODE_QUEUE)) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Device does not support the %s extension!\n",
|
|
|
|
VK_KHR_VIDEO_ENCODE_QUEUE_EXTENSION_NAME);
|
|
|
|
return AVERROR(ENOSYS);
|
|
|
|
} else if (!(s->extensions & FF_VK_EXT_VIDEO_MAINTENANCE_1)) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Device does not support the %s extension!\n",
|
|
|
|
VK_KHR_VIDEO_MAINTENANCE_1_EXTENSION_NAME);
|
|
|
|
return AVERROR(ENOSYS);
|
|
|
|
} else if (!(s->extensions & vk_desc->encode_extension)) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Device does not support encoding %s!\n",
|
|
|
|
avcodec_get_name(avctx->codec_id));
|
|
|
|
return AVERROR(ENOSYS);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Load functions */
|
|
|
|
err = ff_vk_load_functions(s->device, vk, s->extensions, 1, 1);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/* Create queue context */
|
|
|
|
err = ff_vk_video_qf_init(s, &ctx->qf_enc,
|
|
|
|
VK_QUEUE_VIDEO_ENCODE_BIT_KHR,
|
|
|
|
vk_desc->encode_op);
|
|
|
|
if (err < 0) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Encoding of %s is not supported by this device\n",
|
|
|
|
avcodec_get_name(avctx->codec_id));
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Load all properties */
|
|
|
|
err = ff_vk_load_props(s);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/* Set tuning */
|
|
|
|
ctx->usage_info = (VkVideoEncodeUsageInfoKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_USAGE_INFO_KHR,
|
|
|
|
.videoUsageHints = ctx->opts.usage,
|
|
|
|
.videoContentHints = ctx->opts.content,
|
|
|
|
.tuningMode = ctx->opts.tune,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Load up the profile now, needed for caps and to create a query pool */
|
|
|
|
ctx->profile.sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_INFO_KHR;
|
|
|
|
ctx->profile.pNext = &ctx->usage_info;
|
|
|
|
ctx->profile.videoCodecOperation = vk_desc->encode_op;
|
|
|
|
ctx->profile.chromaSubsampling = ff_vk_subsampling_from_av_desc(desc);
|
|
|
|
ctx->profile.lumaBitDepth = ff_vk_depth_from_av_depth(desc->comp[0].depth);
|
|
|
|
ctx->profile.chromaBitDepth = ctx->profile.lumaBitDepth;
|
|
|
|
|
|
|
|
/* Setup a profile */
|
|
|
|
err = codec->init_profile(avctx, &ctx->profile, &ctx->usage_info);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
ctx->profile_list.sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_LIST_INFO_KHR;
|
|
|
|
ctx->profile_list.profileCount = 1;
|
|
|
|
ctx->profile_list.pProfiles = &ctx->profile;
|
|
|
|
|
|
|
|
/* Get the capabilities of the encoder for the given profile */
|
|
|
|
ctx->enc_caps.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_CAPABILITIES_KHR;
|
|
|
|
ctx->enc_caps.pNext = codec_caps;
|
|
|
|
ctx->caps.sType = VK_STRUCTURE_TYPE_VIDEO_CAPABILITIES_KHR;
|
|
|
|
ctx->caps.pNext = &ctx->enc_caps;
|
|
|
|
|
|
|
|
ret = vk->GetPhysicalDeviceVideoCapabilitiesKHR(s->hwctx->phys_dev,
|
|
|
|
&ctx->profile,
|
|
|
|
&ctx->caps);
|
|
|
|
if (ret == VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to initialize encoding: "
|
|
|
|
"%s profile \"%s\" not supported!\n",
|
|
|
|
avcodec_get_name(avctx->codec_id),
|
|
|
|
avcodec_profile_name(avctx->codec_id, avctx->profile));
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
} else if (ret == VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to initialize encoding: "
|
|
|
|
"format (%s) not supported!\n",
|
|
|
|
av_get_pix_fmt_name(avctx->sw_pix_fmt));
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
} else if (ret == VK_ERROR_FEATURE_NOT_PRESENT ||
|
|
|
|
ret == VK_ERROR_FORMAT_NOT_SUPPORTED) {
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
} else if (ret != VK_SUCCESS) {
|
|
|
|
return AVERROR_EXTERNAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = init_rc(avctx, ctx);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/* Create command and query pool */
|
|
|
|
query_create = (VkQueryPoolVideoEncodeFeedbackCreateInfoKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_VIDEO_ENCODE_FEEDBACK_CREATE_INFO_KHR,
|
|
|
|
.pNext = &ctx->profile,
|
|
|
|
.encodeFeedbackFlags = ctx->enc_caps.supportedEncodeFeedbackFlags &
|
|
|
|
(~VK_VIDEO_ENCODE_FEEDBACK_BITSTREAM_HAS_OVERRIDES_BIT_KHR),
|
|
|
|
};
|
|
|
|
err = ff_vk_exec_pool_init(s, &ctx->qf_enc, &ctx->enc_pool, base_ctx->async_depth,
|
|
|
|
1, VK_QUERY_TYPE_VIDEO_ENCODE_FEEDBACK_KHR, 0,
|
|
|
|
&query_create);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
if (ctx->opts.quality > ctx->enc_caps.maxQualityLevels) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Invalid quality level %i: allowed range is "
|
|
|
|
"0 to %i\n",
|
|
|
|
ctx->opts.quality, ctx->enc_caps.maxQualityLevels);
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get quality properties for the profile and quality level */
|
|
|
|
quality_info = (VkPhysicalDeviceVideoEncodeQualityLevelInfoKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VIDEO_ENCODE_QUALITY_LEVEL_INFO_KHR,
|
|
|
|
.pVideoProfile = &ctx->profile,
|
|
|
|
.qualityLevel = ctx->opts.quality,
|
|
|
|
};
|
|
|
|
ctx->quality_props = (VkVideoEncodeQualityLevelPropertiesKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_QUALITY_LEVEL_PROPERTIES_KHR,
|
|
|
|
.pNext = quality_pnext,
|
|
|
|
};
|
|
|
|
ret = vk->GetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR(s->hwctx->phys_dev,
|
|
|
|
&quality_info,
|
|
|
|
&ctx->quality_props);
|
|
|
|
if (ret != VK_SUCCESS)
|
|
|
|
return AVERROR_EXTERNAL;
|
|
|
|
|
|
|
|
/* Printout informative properties */
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, "Encoder capabilities for %s profile \"%s\":\n",
|
|
|
|
avcodec_get_name(avctx->codec_id),
|
|
|
|
avcodec_profile_name(avctx->codec_id, avctx->profile));
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, " Width: from %i to %i\n",
|
|
|
|
ctx->caps.minCodedExtent.width, ctx->caps.maxCodedExtent.width);
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, " Height: from %i to %i\n",
|
|
|
|
ctx->caps.minCodedExtent.height, ctx->caps.maxCodedExtent.height);
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, " Width alignment: %i\n",
|
|
|
|
ctx->caps.pictureAccessGranularity.width);
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, " Height alignment: %i\n",
|
|
|
|
ctx->caps.pictureAccessGranularity.height);
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, " Bitstream offset alignment: %"PRIu64"\n",
|
|
|
|
ctx->caps.minBitstreamBufferOffsetAlignment);
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, " Bitstream size alignment: %"PRIu64"\n",
|
|
|
|
ctx->caps.minBitstreamBufferSizeAlignment);
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, " Maximum references: %u\n",
|
|
|
|
ctx->caps.maxDpbSlots);
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, " Maximum active references: %u\n",
|
|
|
|
ctx->caps.maxActiveReferencePictures);
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, " Codec header version: %i.%i.%i (driver), %i.%i.%i (compiled)\n",
|
|
|
|
CODEC_VER(ctx->caps.stdHeaderVersion.specVersion),
|
|
|
|
CODEC_VER(vk_desc->ext_props.specVersion));
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, " Encoder max quality: %i\n",
|
|
|
|
ctx->enc_caps.maxQualityLevels);
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, " Encoder image width alignment: %i\n",
|
|
|
|
ctx->enc_caps.encodeInputPictureGranularity.width);
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, " Encoder image height alignment: %i\n",
|
|
|
|
ctx->enc_caps.encodeInputPictureGranularity.height);
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, " Capability flags:%s%s%s\n",
|
|
|
|
ctx->caps.flags ? "" :
|
|
|
|
" none",
|
|
|
|
ctx->caps.flags & VK_VIDEO_CAPABILITY_PROTECTED_CONTENT_BIT_KHR ?
|
|
|
|
" protected" : "",
|
|
|
|
ctx->caps.flags & VK_VIDEO_CAPABILITY_SEPARATE_REFERENCE_IMAGES_BIT_KHR ?
|
|
|
|
" separate_references" : "");
|
|
|
|
|
|
|
|
/* Setup width/height alignment */
|
|
|
|
base_ctx->surface_width = avctx->coded_width =
|
|
|
|
FFALIGN(avctx->width, ctx->enc_caps.encodeInputPictureGranularity.width);
|
|
|
|
base_ctx->surface_height = avctx->coded_height =
|
|
|
|
FFALIGN(avctx->height, ctx->enc_caps.encodeInputPictureGranularity.height);
|
|
|
|
|
|
|
|
/* Setup slice width/height */
|
|
|
|
base_ctx->slice_block_width = ctx->enc_caps.encodeInputPictureGranularity.width;
|
|
|
|
base_ctx->slice_block_height = ctx->enc_caps.encodeInputPictureGranularity.height;
|
|
|
|
|
|
|
|
/* Check if encoding is possible with the given parameters */
|
|
|
|
if (avctx->coded_width < ctx->caps.minCodedExtent.width ||
|
|
|
|
avctx->coded_height < ctx->caps.minCodedExtent.height ||
|
|
|
|
avctx->coded_width > ctx->caps.maxCodedExtent.width ||
|
|
|
|
avctx->coded_height > ctx->caps.maxCodedExtent.height) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Input of %ix%i too large for encoder limits: %ix%i max\n",
|
|
|
|
avctx->coded_width, avctx->coded_height,
|
|
|
|
ctx->caps.minCodedExtent.width, ctx->caps.minCodedExtent.height);
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt_info.imageUsage = VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR |
|
|
|
|
VK_IMAGE_USAGE_VIDEO_ENCODE_DST_BIT_KHR;
|
|
|
|
|
|
|
|
ctx->common.layered_dpb = !(ctx->caps.flags & VK_VIDEO_CAPABILITY_SEPARATE_REFERENCE_IMAGES_BIT_KHR);
|
|
|
|
|
|
|
|
/* Get the supported image formats */
|
|
|
|
ret = vk->GetPhysicalDeviceVideoFormatPropertiesKHR(s->hwctx->phys_dev,
|
|
|
|
&fmt_info,
|
|
|
|
&nb_out_fmts, NULL);
|
|
|
|
if (ret == VK_ERROR_FORMAT_NOT_SUPPORTED ||
|
|
|
|
(!nb_out_fmts && ret == VK_SUCCESS)) {
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
} else if (ret != VK_SUCCESS) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to get Vulkan format properties: %s!\n",
|
|
|
|
ff_vk_ret2str(ret));
|
|
|
|
return AVERROR_EXTERNAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret_info = av_mallocz(sizeof(*ret_info)*nb_out_fmts);
|
|
|
|
if (!ret_info)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
|
|
|
|
for (int i = 0; i < nb_out_fmts; i++)
|
|
|
|
ret_info[i].sType = VK_STRUCTURE_TYPE_VIDEO_FORMAT_PROPERTIES_KHR;
|
|
|
|
|
|
|
|
ret = vk->GetPhysicalDeviceVideoFormatPropertiesKHR(s->hwctx->phys_dev,
|
|
|
|
&fmt_info,
|
|
|
|
&nb_out_fmts, ret_info);
|
|
|
|
if (ret == VK_ERROR_FORMAT_NOT_SUPPORTED ||
|
|
|
|
(!nb_out_fmts && ret == VK_SUCCESS)) {
|
|
|
|
av_free(ret_info);
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
} else if (ret != VK_SUCCESS) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to get Vulkan format properties: %s!\n",
|
|
|
|
ff_vk_ret2str(ret));
|
|
|
|
av_free(ret_info);
|
|
|
|
return AVERROR_EXTERNAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, "Supported input formats:\n");
|
|
|
|
for (i = 0; i < nb_out_fmts; i++)
|
|
|
|
av_log(avctx, AV_LOG_VERBOSE, " %i: %i\n", i, ret_info[i].format);
|
|
|
|
|
|
|
|
for (i = 0; i < nb_out_fmts; i++) {
|
|
|
|
if (ff_vk_pix_fmt_from_vkfmt(ret_info[i].format) == s->frames->sw_format) {
|
|
|
|
ctx->pic_format = ret_info[i].format;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
av_free(ret_info);
|
|
|
|
|
|
|
|
if (i == nb_out_fmts) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Pixel format %s of input frames not supported!\n",
|
|
|
|
av_get_pix_fmt_name(s->frames->sw_format));
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create session */
|
|
|
|
session_create.pVideoProfile = &ctx->profile;
|
|
|
|
session_create.flags = 0x0;
|
|
|
|
session_create.queueFamilyIndex = ctx->qf_enc.queue_family;
|
|
|
|
session_create.maxCodedExtent = ctx->caps.maxCodedExtent;
|
|
|
|
session_create.maxDpbSlots = ctx->caps.maxDpbSlots;
|
|
|
|
session_create.maxActiveReferencePictures = ctx->caps.maxActiveReferencePictures;
|
|
|
|
session_create.pictureFormat = ctx->pic_format;
|
|
|
|
session_create.referencePictureFormat = session_create.pictureFormat;
|
|
|
|
session_create.pStdHeaderVersion = &vk_desc->ext_props;
|
|
|
|
|
|
|
|
err = ff_vk_video_common_init(avctx, s, &ctx->common, &session_create);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = ff_hw_base_encode_init(avctx, &ctx->base);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = vulkan_encode_create_dpb(avctx, ctx);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
base_ctx->async_encode = 1;
|
|
|
|
base_ctx->encode_fifo = av_fifo_alloc2(base_ctx->async_depth,
|
|
|
|
sizeof(FFVulkanEncodePicture *), 0);
|
|
|
|
if (!base_ctx->encode_fifo)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ff_vulkan_encode_create_session_params(AVCodecContext *avctx, FFVulkanEncodeContext *ctx,
|
|
|
|
void *codec_params_pnext)
|
|
|
|
{
|
|
|
|
VkResult ret;
|
|
|
|
FFVulkanFunctions *vk = &ctx->s.vkfn;
|
|
|
|
FFVulkanContext *s = &ctx->s;
|
|
|
|
|
|
|
|
VkVideoEncodeQualityLevelInfoKHR q_info;
|
|
|
|
VkVideoSessionParametersCreateInfoKHR session_params_create;
|
|
|
|
|
|
|
|
q_info = (VkVideoEncodeQualityLevelInfoKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_QUALITY_LEVEL_INFO_KHR,
|
|
|
|
.pNext = codec_params_pnext,
|
|
|
|
.qualityLevel = ctx->opts.quality,
|
|
|
|
};
|
|
|
|
session_params_create = (VkVideoSessionParametersCreateInfoKHR) {
|
|
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_PARAMETERS_CREATE_INFO_KHR,
|
|
|
|
.pNext = &q_info,
|
|
|
|
.videoSession = ctx->common.session,
|
|
|
|
.videoSessionParametersTemplate = VK_NULL_HANDLE,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Create session parameters */
|
|
|
|
ret = vk->CreateVideoSessionParametersKHR(s->hwctx->act_dev, &session_params_create,
|
|
|
|
s->hwctx->alloc, &ctx->session_params);
|
|
|
|
if (ret != VK_SUCCESS) {
|
|
|
|
av_log(avctx, AV_LOG_ERROR, "Unable to create Vulkan video session parameters: %s!\n",
|
|
|
|
ff_vk_ret2str(ret));
|
|
|
|
return AVERROR_EXTERNAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|