VK_KHR_video_decode_h265
- 1. 问题陈述
- 2. 解决方案空间
- 3. 提案
- 4. 示例
- 5. 问题
- 5.1. 已解决:编解码器特定参数应以何种形式提供?
- 5.2. 已解决:为什么
vulkan_video_codec_h265std
视频标准头文件没有版本号? - 5.3. 已解决:对编解码器特定的输入参数和比特流数据有什么要求?
- 5.4. 已解决:应如何识别 PPS 条目?
- 5.5. 已解决:为什么需要应用程序指定解码图片的各个切片片段的偏移量?
- 5.6. 已解决:是否支持 H.265 多视图内容?
- 5.7. 已解决:H.265 VPS 和 SPS 条目的所有输入结构的最坏情况大小对于嵌入式设备来说是否过大?
- 5.8. 已解决:为什么 H.265 级别指示器值的指定方式与编解码器规范中的定义方式不同?
- 5.9. 已解决:如何为 H.265 解码操作请求参考图片设置?
- 6. 进一步的功能
本文档概述了一项提案,以支持在 Vulkan 中执行 H.265/HEVC 视频解码操作。
1. 问题陈述
VK_KHR_video_queue
扩展引入了对视频编码操作的支持,而 VK_KHR_video_decode_queue
扩展通过特定于视频解码的 API 进一步扩展了这一点。
此提案的目标是基于此基础架构引入对解码符合 H.265/HEVC 视频压缩标准的原始视频流序列的支持。
2. 解决方案空间
由于 VK_KHR_video_queue
和 VK_KHR_video_decode_queue
扩展已经为如何设计编解码器特定的视频解码扩展奠定了基础,因此此扩展仅需要定义 API,以便在编解码器无关 API 的使用过程中在各个点提供必要的编解码器特定参数。特别是
-
允许指定 H.265 视频、序列和图像参数集(VPS、SPS、PPS)存储在视频会话参数对象中的 API
-
允许指定 H.265 特定于解码图像的信息的 API,包括对先前存储的 VPS、SPS 和 PPS 条目的引用
-
允许指定 H.265 参考图片信息的 API,这些信息特定于活动参考图片和视频解码操作中使用的可选重建图片
为了选择这些定义的结构,已经考虑了以下选项
-
允许以比特流中出现的打包编解码器特定数据的形式将数据指定给 API
-
通过自定义类型定义来指定编解码器特定的参数,应用程序可以在解析码流中相应的数据元素后填充这些参数。
选项(1)可以简化 API,但它要求实现包含对这些数据元素的适当解析器。由于解码应用程序通常出于其他原因也会解析这些数据元素,因此本提案选择选项(2),以允许应用程序通过专门用于 H.265 视频解码的视频标准头文件中提供的自定义定义来提供所需的参数。
已考虑以下附加选项来选择定义此视频标准头文件的方式
-
将所有定义包含在此 H.265 视频解码标准头文件中
-
添加一个单独的视频标准头文件,其中包含 H.265 参数定义,这些定义可以在视频解码和视频编码用例之间共享,H.265 视频解码标准头文件依赖于此头文件,并且仅将特定于解码的定义包含在 H.265 视频解码标准头文件中
这两个选项都是合理的,但是,由于 H.265 视频解码和 H.265 视频编码功能是并行设计的,因此此扩展使用选项 (2) 并引入以下新的视频标准头文件
-
vulkan_video_codec_h265std
- 包含所有 H.265 视频编码操作的通用定义 -
vulkan_video_codec_h265std_decode
- 包含特定于 H.265 视频解码操作的定义
这些头文件可以按如下方式包含
#include <vk_video/vulkan_video_codec_h265std.h>
#include <vk_video/vulkan_video_codec_h265std_decode.h>
3. 提案
3.2. H.265 解码配置文件
此扩展引入了新的视频编解码器操作 VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_KHR
。此标志可用于检查特定的队列族是否支持解码 H.265/HEVC 内容,如 VkQueueFamilyVideoPropertiesKHR
中返回的那样。
可以通过使用此新的视频编解码器操作,并在 pNext
链中包含以下新的编解码器特定配置文件信息结构,通过 VkVideoProfileInfoKHR
结构定义 H.265 解码配置文件
typedef struct VkVideoDecodeH265ProfileInfoKHR {
VkStructureType sType;
const void* pNext;
StdVideoH265ProfileIdc stdProfileIdc;
} VkVideoDecodeH265ProfileInfoKHR;
stdProfileIdc
指定 H.265 配置文件指示符。
3.3. H.265 解码能力
应用程序在调用 vkGetPhysicalDeviceVideoCapabilitiesKHR
命令以检索特定于 H.265 视频解码的能力时,需要在 VkVideoCapabilitiesKHR
的 pNext
链中包含以下新结构
typedef struct VkVideoDecodeH265CapabilitiesKHR {
VkStructureType sType;
void* pNext;
StdVideoH265LevelIdc maxLevelIdc;
} VkVideoDecodeH265CapabilitiesKHR;
maxLevelIdc
指示支持的最大 H.265 级别指示符。
3.4. H.265 解码参数集
解码 H.265 视频流时,必须使用视频会话参数对象。应用程序在创建用于 H.265 解码的视频会话参数对象时,需要在 VkVideoSessionParametersCreateInfoKHR
的 pNext
链中包含以下新结构,以指定所创建对象的参数集容量
typedef struct VkVideoDecodeH265SessionParametersCreateInfoKHR {
VkStructureType sType;
const void* pNext;
uint32_t maxStdVPSCount;
uint32_t maxStdSPSCount;
uint32_t maxStdPPSCount;
const VkVideoDecodeH265SessionParametersAddInfoKHR* pParametersAddInfo;
} VkVideoDecodeH265SessionParametersCreateInfoKHR;
可选的 pParametersAddInfo
成员还允许指定要添加到所创建对象的初始参数集
typedef struct VkVideoDecodeH265SessionParametersAddInfoKHR {
VkStructureType sType;
const void* pNext;
uint32_t stdVPSCount;
const StdVideoH265VideoParameterSet* pStdVPSs;
uint32_t stdSPSCount;
const StdVideoH265SequenceParameterSet* pStdSPSs;
uint32_t stdPPSCount;
const StdVideoH265PictureParameterSet* pStdPPSs;
} VkVideoDecodeH265SessionParametersAddInfoKHR;
此结构也可以包含在 VkVideoSessionParametersUpdateInfoKHR
的 pNext
链中,该结构用于视频会话参数更新操作,以便在创建对象后向其添加更多参数集。
各个参数集使用参数集 ID 作为键存储,具体来说
-
H.265 VPS 条目使用
vps_video_parameter_set_id
值标识 -
H.265 SPS 条目使用
sps_video_parameter_set_id
和sps_seq_parameter_set_id
值对标识 -
H.265 PPS 条目使用
sps_video_parameter_set_id
、pps_seq_parameter_set_id
和pps_pic_parameter_set_id
值的三元组标识
请注意在 PPS 键中包含 VPS ID。这是必要的,因为 PPS 不能由其 ID 和父 SPS 的 ID 唯一标识,因为可能存在多个具有相同 ID 但具有不同父 VPS ID 的 SPS 条目。为了确保键的唯一性,本提案中引用 PPS 的所有 API 还采用 PPS 所属的 SPS 的父 VPS ID,以指定完整的 ID 层次结构。
H.265/HEVC 视频压缩标准始终需要 VPS、SPS 和 PPS,因此应用程序必须在使用参数对象之前向其添加每个参数集的实例,才能录制视频解码操作。
此外,H.265/HEVC 视频压缩标准还允许修改现有参数集,但是由于已经存储在视频会话参数对象中的参数无法在 Vulkan 中更改,因此在这种情况下,应用程序必须创建新的参数对象,如 VK_KHR_video_queue
的提案中所述。
3.5. H.265 解码参数
特定于 H.265 的解码参数需要由应用程序通过 VkVideoDecodeInfoKHR
的 pNext
链提供,使用以下新结构
typedef struct VkVideoDecodeH265PictureInfoKHR {
VkStructureType sType;
const void* pNext;
const StdVideoDecodeH265PictureInfo* pStdPictureInfo;
uint32_t sliceSegmentCount;
const uint32_t* pSliceSegmentOffsets;
} VkVideoDecodeH265PictureInfoKHR;
pStdPictureInfo
指向 vulkan_video_codec_h265std_decode
视频标准头文件中定义的编解码器特定解码参数,而 pSliceSegmentOffsets
数组包含图片中各个切片片段相对于视频解码操作使用的视频码流范围的偏移量。
活动的 VPS、SPS 和 PPS(源自绑定的视频会话参数对象)由 sps_video_parameter_set_id
、pps_seq_parameter_set_id
和 pps_pic_parameter_set_id
参数标识。
活动参考图片和可选的重建图片特有的 H.265 图片信息需要由应用程序通过 VkVideoDecodeInfoKHR::pReferenceSlots
相应元素的 pNext
链和 VkVideoDecodeInfoKHR::pSetupReferenceSlot
的 pNext
链提供,使用以下新结构
typedef struct VkVideoDecodeH265DpbSlotInfoKHR {
VkStructureType sType;
const void* pNext;
const StdVideoDecodeH265ReferenceInfo* pStdReferenceInfo;
} VkVideoDecodeH265DpbSlotInfoKHR;
pStdReferenceInfo
指向 vulkan_video_codec_h265std_decode
视频标准头文件中定义的编解码器特定参考图片参数。
应用程序有责任指定符合 H.265/HEVC 视频压缩标准定义的规则的视频码流缓冲区数据和编解码器特定参数。从 API 使用的角度来看,指定不符合规定的输入并非非法,但它们可能导致视频解码操作无法成功完成,并且会导致操作执行后输出图片(解码输出和重建图片)的内容未定义。
有关如何解析各个 H.265 码流语法元素、计算派生值以及一般而言如何解释这些参数的更多信息,请参阅ITU-T H.265 规范的相应部分。
4. 示例
4.1. 选择支持 H.265 解码的队列族
uint32_t queueFamilyIndex;
uint32_t queueFamilyCount;
vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueFamilyCount, NULL);
VkQueueFamilyProperties2* props = calloc(queueFamilyCount,
sizeof(VkQueueFamilyProperties2));
VkQueueFamilyVideoPropertiesKHR* videoProps = calloc(queueFamilyCount,
sizeof(VkQueueFamilyVideoPropertiesKHR));
for (queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; ++queueFamilyIndex) {
props[queueFamilyIndex].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
props[queueFamilyIndex].pNext = &videoProps[queueFamilyIndex];
videoProps[queueFamilyIndex].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_VIDEO_PROPERTIES_KHR;
}
vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueFamilyCount, props);
for (queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; ++queueFamilyIndex) {
if ((props[queueFamilyIndex].queueFamilyProperties.queueFlags & VK_QUEUE_VIDEO_DECODE_BIT_KHR) != 0 &&
(videoProps[queueFamilyIndex].videoCodecOperations & VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_KHR) != 0) {
break;
}
}
if (queueFamilyIndex < queueFamilyCount) {
// Found appropriate queue family
...
} else {
// Did not find a queue family with the needed capabilities
...
}
4.2. 检查对 H.265 解码配置文件的支持并查询其能力
VkResult result;
VkVideoDecodeH265ProfileInfoKHR decodeH265ProfileInfo = {
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_PROFILE_INFO_KHR,
.pNext = NULL,
.stdProfileIdc = STD_VIDEO_H265_PROFILE_IDC_MAIN
};
VkVideoProfileInfoKHR profileInfo = {
.sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_INFO_KHR,
.pNext = &decodeH265ProfileInfo,
.videoCodecOperation = VK_VIDEO_CODEC_OPERATION_DECODE_H265_BIT_KHR,
.chromaSubsampling = VK_VIDEO_CHROMA_SUBSAMPLING_420_BIT_KHR,
.lumaBitDepth = VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR,
.chromaBitDepth = VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR
};
VkVideoDecodeH265CapabilitiesKHR decodeH265Capabilities = {
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_CAPABILITIES_KHR,
.pNext = NULL,
};
VkVideoDecodeCapabilitiesKHR decodeCapabilities = {
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_CAPABILITIES_KHR,
.pNext = &decodeH265Capabilities
}
VkVideoCapabilitiesKHR capabilities = {
.sType = VK_STRUCTURE_TYPE_VIDEO_CAPABILITIES_KHR,
.pNext = &decodeCapabilities
};
result = vkGetPhysicalDeviceVideoCapabilitiesKHR(physicalDevice, &profileInfo, &capabilities);
if (result == VK_SUCCESS) {
// Profile is supported, check additional capabilities
...
} else {
// Profile is not supported, result provides additional information about why
...
}
4.3. 创建和更新 H.265 视频会话参数对象
VkVideoSessionParametersKHR videoSessionParams = VK_NULL_HANDLE;
VkVideoDecodeH265SessionParametersCreateInfoKHR decodeH265CreateInfo = {
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_SESSION_PARAMETERS_CREATE_INFO_KHR,
.pNext = NULL,
.maxStdVPSCount = ... // VPS capacity
.maxStdSPSCount = ... // SPS capacity
.maxStdPPSCount = ... // PPS capacity
.pParametersAddInfo = ... // parameters to add at creation time or NULL
};
VkVideoSessionParametersCreateInfoKHR createInfo = {
.sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_PARAMETERS_CREATE_INFO_KHR,
.pNext = &decodeH265CreateInfo,
.flags = 0,
.videoSessionParametersTemplate = ... // template to use or VK_NULL_HANDLE
.videoSession = videoSession
};
vkCreateVideoSessionParametersKHR(device, &createInfo, NULL, &videoSessionParams);
...
StdVideoH265VideoParameterSet vps = {};
// parse and populate VPS parameters
...
StdVideoH265SequenceParameterSet sps = {};
// parse and populate SPS parameters
...
StdVideoH265PictureParameterSet pps = {};
// parse and populate PPS parameters
...
VkVideoDecodeH265SessionParametersAddInfoKHR decodeH265AddInfo = {
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_SESSION_PARAMETERS_ADD_INFO_KHR,
.pNext = NULL,
.stdVPSCount = 1,
.pStdVPSs = &vps,
.stdSPSCount = 1,
.pStdSPSs = &sps,
.stdPPSCount = 1,
.pStdPPSs = &pps
};
VkVideoSessionParametersUpdateInfoKHR updateInfo = {
.sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_PARAMETERS_UPDATE_INFO_KHR,
.pNext = &decodeH265AddInfo,
.updateSequenceCount = 1 // incremented for each subsequent update
};
vkUpdateVideoSessionParametersKHR(device, &videoSessionParams, &updateInfo);
4.4. 记录 H.265 解码操作(无 DPB 插槽的视频会话)
vkCmdBeginVideoCodingKHR(commandBuffer, ...);
StdVideoDecodeH265PictureInfo stdPictureInfo = {};
// parse and populate picture info from slice segment header data
...
VkVideoDecodeH265PictureInfoKHR decodeH265PictureInfo = {
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_PICTURE_INFO_KHR,
.pNext = NULL,
.pStdPictureInfo = &stdPictureInfo,
.sliceSegmentCount = ... // number of slice segments
.pSliceSegmentOffsets = ... // array of slice segment offsets relative to the bitstream buffer range
};
VkVideoDecodeInfoKHR decodeInfo = {
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_INFO_KHR,
.pNext = &decodeH265PictureInfo,
...
// reconstructed picture is not needed if video session was created without DPB slots
.pSetupReferenceSlot = NULL,
.referenceSlotCount = 0,
.pReferenceSlots = NULL
};
vkCmdDecodeVideoKHR(commandBuffer, &decodeInfo);
vkCmdEndVideoCodingKHR(commandBuffer, ...);
4.5. 记录 H.265 解码操作,带可选参考图像设置
vkCmdBeginVideoCodingKHR(commandBuffer, ...);
StdVideoDecodeH265ReferenceInfo stdReferenceInfo = {};
// parse and populate reconstructed reference picture info from slice segment header data
...
VkVideoDecodeH265DpbSlotInfoKHR decodeH265DpbSlotInfo = {
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_DPB_SLOT_INFO_KHR,
.pNext = NULL,
.pStdReferenceInfo = &stdReferenceInfo
};
VkVideoReferenceSlotInfoKHR setupSlotInfo = {
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
.pNext = &decodeH265DpbSlotInfo
...
};
StdVideoDecodeH265PictureInfo stdPictureInfo = {};
// parse and populate picture info from frame header data
...
if (stdPictureInfo.flags.IsReference) {
// reconstructed picture will be used for reference picture setup and DPB slot activation
} else {
// reconstructed picture and slot may only be used by implementations as transient resource
}
VkVideoDecodeH265PictureInfoKHR decodeH265PictureInfo = {
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_PICTURE_INFO_KHR,
.pNext = NULL,
.pStdPictureInfo = &stdPictureInfo,
.sliceSegmentCount = ... // number of slice segments
.pSliceSegmentOffsets = ... // array of slice segment offsets relative to the bitstream buffer range
};
VkVideoDecodeInfoKHR decodeInfo = {
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_INFO_KHR,
.pNext = &decodeH265PictureInfo,
...
.pSetupReferenceSlot = &setupSlotInfo,
...
};
vkCmdDecodeVideoKHR(commandBuffer, &decodeInfo);
vkCmdEndVideoCodingKHR(commandBuffer, ...);
4.6. 记录 H.265 解码操作,带参考图像列表
vkCmdBeginVideoCodingKHR(commandBuffer, ...);
StdVideoDecodeH265ReferenceInfo stdReferenceInfo[] = {};
// populate reference picture info for each active reference picture
...
VkVideoDecodeH265DpbSlotInfoKHR decodeH265DpbSlotInfo[] = {
{
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_DPB_SLOT_INFO_KHR,
.pNext = NULL,
.pStdReferenceInfo = &stdReferenceInfo[0]
},
{
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_DPB_SLOT_INFO_KHR,
.pNext = NULL,
.pStdReferenceInfo = &stdReferenceInfo[1]
},
...
};
VkVideoReferenceSlotInfoKHR referenceSlotInfo[] = {
{
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
.pNext = &decodeH265DpbSlotInfo[0],
...
},
{
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
.pNext = &decodeH265DpbSlotInfo[1],
...
},
...
};
StdVideoDecodeH265PictureInfo stdPictureInfo = {};
// parse and populate picture info from frame header data
...
if (stdPictureInfo.flags.IsReference) {
// reconstructed picture will be used for reference picture setup and DPB slot activation
} else {
// reconstructed picture and slot may only be used by implementations as transient resource
}
VkVideoDecodeH265PictureInfoKHR decodeH265PictureInfo = {
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_H265_PICTURE_INFO_KHR,
.pNext = NULL,
.pStdPictureInfo = &stdPictureInfo,
.sliceSegmentCount = ... // number of slice segments
.pSliceSegmentOffsets = ... // array of slice segment offsets relative to the bitstream buffer range
};
VkVideoDecodeInfoKHR decodeInfo = {
.sType = VK_STRUCTURE_TYPE_VIDEO_DECODE_INFO_KHR,
.pNext = &decodeH265PictureInfo,
...
.referenceSlotCount = sizeof(referenceSlotInfo) / sizeof(referenceSlotInfo[0]),
.pReferenceSlots = &referenceSlotInfo[0]
};
vkCmdDecodeVideoKHR(commandBuffer, &decodeInfo);
vkCmdEndVideoCodingKHR(commandBuffer, ...);
5. 问题
5.1. 已解决:编解码器特定参数应以何种形式提供?
以 vulkan_video_codec_h265std_decode
和 vulkan_video_codec_h265std
视频标准头文件中定义的结构体形式提供。应用程序负责解析参数集和切片段头数据,并使用解析后的数据来填充视频标准头文件中定义的结构体。应用程序还负责维护和管理这些数据结构,以便在需要时将它们作为视频解码操作的输入提供。
5.2. 已解决:为什么 vulkan_video_codec_h265std
视频标准头文件没有版本号?
引入 vulkan_video_codec_h265std
视频标准头文件的目的是为了共享 H.265/HEVC 视频解码和视频编码中使用的通用定义,因为这两个功能是并行设计的。然而,由于没有视频编码扩展直接使用这个视频标准头文件,而是作为特定视频编码操作的视频标准头文件的依赖项,因此没有必要使用单独的版本控制方案。
5.3. 已解决:编解码器特定输入参数和比特流数据的要求是什么?
从 API 使用的角度来看,应用程序为编解码器特定输入参数(参数集、图像信息等)或视频比特流数据提供任何值都是合法的。但是,如果输入数据不符合 H.265/HEVC 视频压缩标准的要求,那么视频解码操作可能会失败,并且通常情况下,视频解码操作产生的输出将具有未定义的内容。
5.4. 已解决:如何标识 PPS 条目?
H.265 图像参数集语法仅包括 PPS ID(pps_pic_parameter_set_id
)和父 SPS ID(pps_seq_parameter_set_id
)。然而,SPS ID 不是全局唯一的,因为只要多个序列参数集具有不同的父 VPS ID,它们就可以具有相同的 ID。
为了能够唯一地标识(并因此键入)参数集,视频标准头结构提供存储在视频会话参数对象中的 PPS 内容,以及指示视频解码操作中要使用的活动 PPS 的参数,两者都包含一个额外的 sps_video_parameter_set_id
成员,该成员不是 PPS 语法或切片段头语法的一部分,但使实现能够唯一地标识存储在视频会话参数对象中并引用的 PPS 条目。
5.7. 已解决:H.265 VPS 和 SPS 条目的所有输入结构的最坏情况大小对于嵌入式设备来说是否过大?
虽然 H.265 VPS 和 SPS 条目的所有输入结构的最大可能大小可能很大,但在实践中,预计不会全部指定,因为大多数内容不需要它们。嵌套数组通常通过指向视频标准头文件中数组的指针来指定,这使应用程序能够仅指定当前内容所需的元素。
因此,不建议应用程序静态分配 H.265 VPS 和 SPS 条目的最坏情况大小。由于这些是带外数据条目,如果非典型内容可能需要更大的输入数据条目,应用程序应首选动态分配足够的空间。