(硬解框架的支持情况,表格内容来自 FFmpeg 官网)
可以看到硬解框架五花八门,不同的显卡厂商和设备有各自的专用解码框架,操作系统也有定义好的通用解码框架,由于显卡厂商众多,因此大部分播放器一般均基于通用框架实现硬解,少部分播放器在人力充裕的情况可能会为了更好的性能(显卡厂商自己的框架一般比通用框架性能更好,但也不绝对)额外对专用框架二次实现。
其中 Windows 平台通用的解码框架有 Media Foundation, Direct3D 11, DXVA2, 以及 OpenCL。macOS 平台通用的解码框架只有一个,也就是苹果自己的 VideoToolbox。Linux 平台的通用解码框架有 VAAPI 和 OpenCL。
显然,对于 Chrome 而言,为了更好的兼容性和稳定性,基于通用硬解框架实现硬解,更符合最小成本最大收益的目标,并提升了可维护性。
理解 Chromium 解码流程根据 Chromium Media 模块简介可知,浏览器将音视频播放一共抽象成三种类型,我们比较常见的有:Video Element 标签,MSE API。此外还有支持加密视频播放的 EME API,这三种在底层又存在多种复用关系。
(Chromium 的解码流程,图片来自 Chromium 代码仓库)
那么到了最底层的解码模块,整体逻辑大概可以简述为:
- 浏览器会从列表中依次按照顺序查找 Decoder,通常来说优先级最高的是硬解 Decoder, 然后会尝试软解 Decoder。
- 如有命中其中的某个 Decoder 则执行后续解码逻辑。
- 如没有命中的 Decoder,则解码失败,中止。
因此,为了实现 HEVC 硬解,我们首先需要找到各个平台的通用硬解 Decoder:
- 对于 Windows,根据操作系统以及显卡驱动版本,分为两种:D3D11VideoDecoder 和 VDAVideoDecoder,前者在大于 Windows8 且支持 D3D11 的系统默认被使用,后者则在前者不被使用时(比如 Windows 7)作为 Backup 方案被使用。
- 对于 macOS,为 VDAVideoDecoder。
- 对于 Linux,为 VAAPIVideoDecoder。
在了解了大致背景后,便可以开始探索实现 HEVC 硬解实现了,考虑到 Apple 其最新 Apple Silicon 芯片专门实现了支持 H.264、HEVC 和 ProRes 的专用编解码媒体处理引擎,看在 Apple 这么努力的份上,我首先挑选了 macOS 平台来进行尝试。
FFMPEG 方案的尝试虽然 Chrome 没有直接实现 HEVC 解码能力,但由于其实现了 FFMpegVideoDecoder,因此本质上任何 FFMPEG 可以播的视频,只要利用修改 Chromium 的方式为其添加 FFMPEG 解码器的入口,理论上均可以实现播放,此方案其实是本文硬解实现前开源社区最广为流传的一种方案,@斯杰的文章(https://www.infoq.cn/article/s65bFDPWzdfP9CQ6Wbw6)内已有详尽介绍,由于当时的版本是基于 Chromium 79,目前最新的 Chromium 版本号为 104,因此里面的一些实现有所变动,但整体逻辑并没有明显改变,通过修改 Chromium 104 依然可以实现软解。
优点有很多:由于是 CPU 软解且使用行业最标准的 FFMPEG 解码,最终结果是:不挑系统,容错性好,支持任何 CPU 架构、操作系统,性能虽比不过硬解,但依然比前端 WASM 方案性能更好,且原生支持 MSE 和 Video Element。
缺点也很明显:普通的四核笔记本电脑,即使分辨率只有 1080P,在快进或快退时也会感到明显的卡顿,同时伴随比较高的 CPU 占用,抢占渲染进程 CPU 资源,另外这种方法是否有版权有待评估,但可以确定一点,使用平台提供的解码是合规且没有版权风险的。
当分辨率达到 4K 甚至 8K 级别,8 核甚至更多核的 CPU 也会卡到掉帧。
( FFMPEG 的解码流程,图片来自知乎 @我是小北挖哈哈)
根据 FFMPEG 的解码流程如上图(参考:https://zhuanlan.zhihu.com/p/168240163?Futm_source=wechat_session&utm_medium=social&utm_oi=29396161265664),可知道,FFMPEG 除了实现了软解,其实已经完整实现了硬解功能,然而 Chromium 的 FFMpegVideoDecoder 并不支持硬解,因此,同组同学 @豪爽,首先尝试 FFMpegVideoDecoder 内尝试配置 hw_device_ctx,以开启其硬解能力,具体步骤如下:
开启硬解宏:
// third_party/ffmpeg/chromium/config/Chrome/mac/x64/config.h
#define CONFIG_VIDEOTOOLBOX 1
#define CONFIG_HEVC_VIDEOTOOLBOX_HWACCEL 1
#define HAVE_KCMVIDEOCODECTYPE_HEVC 1
设置硬件上下文:
// media/filters/ffmpeg_video_decoder.cc -> FFmpegVideoDecoder::ConfigureDecoder(const VideoDecoderConfig& config, bool low_delay)
if (decode_nalus_)
codec_context_->flags2 |= AV_CODEC_FLAG2_CHUNKS;
if (codec_context_->codec_id == AVCodecID::AV_CODEC_ID_HEVC) {
AVBufferRef *hw_device_ctx = NULL;
int err;
if ((err = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VIDEOTOOLBOX, NULL, NULL, 0)) >= 0) {
codec_context_->hw_device_ctx = av_buffer_ref(hw_device_ctx);
}
}
const AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
取出解码数据:
// media/ffmpeg/ffmpeg_common.cc -> AVPixelFormatToVideoPixelFormat(AVPixelFormat pixel_format)
case AV_PIX_FMT_YUV420P:
case AV_PIX_FMT_YUVJ420P:
case AV_PIX_FMT_VIDEOTOOLBOX: // hwaccel
return PIXEL_FORMAT_I420;
将硬件解码得到的数据取出,即 av_hwframe_transfer_data 函数:
// media/ffmpeg/ffmpeg_decoding_loop.cc
FFmpegDecodingLoop::DecodeStatus FFmpegDecodingLoop::DecodePacket(const AVPacket* packet, FrameReadyCB frame_ready_cb) {
AVFrame* tmp_frame = NULL;
AVFrame* sw_frame = av_frame_alloc();
bool sent_packet = false, frames_remaining = true, decoder_error = false;
while (!sent_packet || frames_remaining) {
......
if (frame_.get()->format == AV_PIX_FMT_VIDEOTOOLBOX) {
int ret = av_hwframe_transfer_data(sw_frame, frame_.get(), 0);
tmp_frame = sw_frame;
} else {
tmp_frame = frame_.get();
}
const bool frame_processing_success = frame_ready_cb.Run(tmp_frame);
av_frame_unref(tmp_frame);
- const bool frame_processing_success = frame_ready_cb.Run(frame_.get());
av_frame_unref(frame_.get());
if (!frame_processing_success)
return DecodeStatus::kFrameProcessingFailed;
}
return decoder_error ? DecodeStatus::kDecodeFrameFailed : DecodeStatus::kOkay;
}
如上,经过多次尝试后,通过活动监视器可以观察到点击< Video >标签播放按钮时 VTDecoderXPCService 进程(Videotoolbox 的解码进程)CPU 占有率有所上升,说明调用 VideoToolbox 硬件解码模块成功,但视频白屏说明解码失败。
探索过程中,阅读 Chromium Media 模块的文档后发现,使用 FFMpegVideoDecoder 不支持在 Sandboxed 的进程调用 VT 硬解框架,为了避免在错误的道路上投入过多精力,遂放弃。
在 GPU 进程实现上面的方式行不通,说明得换一种思路,需要看看正统的 H264 硬解流程是怎样的,通过使用 Chrome 的搜索引擎(https://source.chromium.org/),发现 macOS 的 H264 硬解实现均位于vt_video_decoder_accelerator.cc这个文件内。
VideoToolbox 简介由 FFmpeg 介绍可知,如我们想在 macOS 实现 HEVC 硬解,则一定需要使用苹果提供的媒体解码框架 VideoToolbox 来完成。
VideoToolbox is a low-level framework that provides direct access to hardware encoders and decoders. It provides services for video compression and decompression, and for conversion between raster image formats stored in CoreVideo pixel buffers. These services are provided in the form of session objects (compression, decompression, and pixel transfer), which are vended as Core Foundation (CF) types. Apps that don't need direct access to hardware encoders and decoders should not need to use VideoToolbox directly.
根据 Apple Developer 网站介绍(https://developer.apple.com/documentation/videotoolbox)可知,VideoToolbox 是苹果提供的直接用来进行编解码的底层框架,要实现硬解,大体解码流程可以理解为:Chromium -> VDAVideoDecoder -> VideoToolbox -> GPU -> VideoToolbox -> VDAVideoDecoder -> Chromium。
因此我们的目标就是正确按照 VideoToolbox 要求的方式,提交 Image Buffer,并等待 VT 将解码后的数据回传。
添加 Supported Profile根据 Chromium 解码流程 可知,Chromium 对于特定 Codec 的视频首先会尝试查找硬解 Decoder,如硬解 Decoder 不支持,则继续向后查找 Fallback 的软解 Decoder。
通过观察可发现,在 macOS 下,某种编码格式是否支持硬解,取决于硬解 Decoder 内的 SupportProfiles 是否包含这种编码格式,其代码如下:
// media/gpu/mac/vt_video_decode_accelerator_mac.cc
// 这个数组内包含了所有可能支持的Profile,但是否真正支持并不取决于这里
constexpr VideoCodecProfile kSupportedProfiles[] = {
H264PROFILE_BASELINE, H264PROFILE_EXTENDED, H264PROFILE_MAIN,
H264PROFILE_HIGH,
// macOS 11以上,会尝试对这两种格式进行硬解
VP9PROFILE_PROFILE0, VP9PROFILE_PROFILE2,
// macOS 11以上,支持的最主流的HEVC Main / Main10 Profile, 以及
// Main Still Picture / Main Rext 的硬、软解
// (Apple Silicon 机型支持硬解HEVC Rext, Intel 机型支持软解HEVC Rext)
// These are only supported on macOS 11 .
HEVCPROFILE_MAIN, HEVCPROFILE_MAIN10, HEVCPROFILE_MAIN_STILL_PICTURE,
HEVCPROFILE_REXT,
// TODO(sandersd): Hi10p fails during
// CMVideoFormatDescriptionCreateFromH264ParameterSets with
// kCMFormatDescriptionError_InvalidParameter.
//
// H264PROFILE_HIGH10PROFILE,
// TODO(sandersd): Find and test media with these profiles before enabling.
//
// H264PROFILE_SCALABLEBASELINE,
// H264PROFILE_SCALABLEHIGH,
// H264PROFILE_STEREOHIGH,
// H264PROFILE_MULTIVIEWHIGH,
};
Session 预热与引导逻辑
实现硬解,需要在 Sandboxed 的进程启用前创建解码 Session 预热,并根据系统版本与支持情况决定最终是否启用硬解:
// media/gpu/mac/vt_video_decode_accelerator_mac.cc
bool InitializeVideoToolbox() {
// 在GPU主进程调用时立刻执行,以确保Sandboxed/非Sandoxed进程均可硬解
static const bool succeeded = InitializeVideoToolboxInternal();
return succeeded;
}
// 在GPU Sandbox启用前通过创建Videotoolbox的Decompression Session预热,确保Sandboxed/非Sandoxed进程均可硬解
bool InitializeVideoToolboxInternal() {
VTDecompressionOutputCallbackRecord callback = {0};
base::ScopedCFTypeRef<VTDecompressionSessionRef> session;
gfx::Size configured_size;
// 创建H264硬解Session
const std::vector<uint8_t> sps_h264_normal = {
0x67, 0x64, 0x00, 0x1e, 0xac, 0xd9, 0x80, 0xd4, 0x3d, 0xa1, 0x00, 0x00,
0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x30, 0x8f, 0x16, 0x2d, 0x9a};
const std::vector<uint8_t> pps_h264_normal = {0x68, 0xe9, 0x7b, 0xcb};
if (!CreateVideoToolboxSession(
CreateVideoFormatH264(sps_h264_normal, std::vector<uint8_t>(),
pps_h264_normal),
/*require_hardware=*/true, /*is_hbd=*/false, &callback, &session,
&configured_size)) {
// 如果H264硬解Session创建失败,直接禁用整个硬解模块
DVLOG(1) << "Hardware H264 decoding with VideoToolbox is not supported";
return false;
}
session.reset();
// 创建H264软解Session
// 总结下,如果这台设备连H264硬/软解都不支持,则直接禁用硬解,解码完全走FFMpegVideoDecoder的软解
const std::vector<uint8_t> sps_h264_small = {
0x67, 0x64, 0x00, 0x0a, 0xac, 0xd9, 0x89, 0x7e, 0x22, 0x10, 0x00,
0x00, 0x3e, 0x90, 0x00, 0x0e, 0xa6, 0x08, 0xf1, 0x22, 0x59, 0xa0};
const std::vector<uint8_t> pps_h264_small = {0x68, 0xe9, 0x79, 0x72, 0xc0};
if (!CreateVideoToolboxSession(
CreateVideoFormatH264(sps_h264_small, std::vector<uint8_t>(),
pps_h264_small),
/*require_hardware=*/false, /*is_hbd=*/false, &callback, &session,
&configured_size)) {
DVLOG(1) << "Software H264 decoding with VideoToolbox is not supported";
// 如果H264软解 Decompression Session创建失败,直接禁用整个硬解模块
return false;
}
session.reset();
if (__builtin_available(macOS 11.0, *)) {
VTRegisterSupplementalVideoDecoderIfAvailable(kCMVideoCodecType_VP9);
// 当系统大于等于macOS Big Sur时,尝试创建VP9硬解Session
if (!CreateVideoToolboxSession(
CreateVideoFormatVP9(VideoColorSpace::REC709(), VP9PROFILE_PROFILE0,
absl::nullopt, gfx::Size(720, 480)),
/*require_hardware=*/true, /*is_hbd=*/false, &callback, &session,
&configured_size)) {
DVLOG(1) << "Hardware VP9 decoding with VideoToolbox is not supported";
// 如果创建session失败,说明不支持VP9硬解,跳过,但保持H264可继续硬解
}
}
// 按照Chromium的要求HEVC硬解相关的逻辑,均需要依赖ENABLE_HEVC_PARSER_AND_HW_DECODER宏定义开关,只有开启开关后才会将代码引入
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
// 即使编译时开启了HEVC硬解宏
// 当启动时传入`--enable-features=PlatformHEVCDecoderSupport`可启用HEVC硬解
if (base::FeatureList::IsEnabled(media::kPlatformHEVCDecoderSupport)) {
// 这里限制了至少是Big Sur系统的原因是,Catalina及以下系统使用
// CMVideoFormatDescriptionCreateFromHEVCParameterSets API创建解码Session
// 会失败
// 注:macOS自身问题,苹果承诺了10.13及以上系统即可使用这个API,然,实测结果并卵
// 但VLC和FFmpeg等使用的CMVideoFormatDescriptionCreate可以正常创建
// 但,这与硬解模块实现的风格和结构不符
if (__builtin_available(macOS 11.0, *)) {
session.reset();
// 创建HEVC硬解Session
// vps/sps/pps提取自bear-1280x720-hevc.mp4
const std::vector<uint8_t> vps_hevc_normal = {
0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60,
0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03,
0x00, 0x00, 0x03, 0x00, 0x5d, 0x95, 0x98, 0x09};
const std::vector<uint8_t> sps_hevc_normal = {
0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00,
0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5d, 0xa0, 0x02, 0x80, 0x80,
0x2d, 0x16, 0x59, 0x59, 0xa4, 0x93, 0x2b, 0xc0, 0x5a, 0x70, 0x80,
0x00, 0x01, 0xf4, 0x80, 0x00, 0x3a, 0x98, 0x04};
const std::vector<uint8_t> pps_hevc_normal = {0x44, 0x01, 0xc1, 0x72,
0xb4, 0x62, 0x40};
if (!CreateVideoToolboxSession(
CreateVideoFormatHEVC(vps_hevc_normal, sps_hevc_normal,
pps_hevc_normal),
/*require_hardware=*/true, /*is_hbd=*/false, &callback, &session,
&configured_size)) {
DVLOG(1) << "Hardware HEVC decoding with VideoToolbox is not supported";
// 同VP9逻辑,HEVC硬解预热失败不会禁用H264硬解能力
}
session.reset();
// 创建HEVC软解Session
// vps/sps/pps提取自bear-320x240-v_frag-hevc.mp4
const std::vector<uint8_t> vps_hevc_small = {
0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60,
0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03,
0x00, 0x00, 0x03, 0x00, 0x3c, 0x95, 0x98, 0x09};
const std::vector<uint8_t> sps_hevc_small = {
0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90,
0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x3c, 0xa0, 0x0a,
0x08, 0x0f, 0x16, 0x59, 0x59, 0xa4, 0x93, 0x2b, 0xc0, 0x40,
0x40, 0x00, 0x00, 0xfa, 0x40, 0x00, 0x1d, 0x4c, 0x02};
const std::vector<uint8_t> pps_hevc_small = {0x44, 0x01, 0xc1, 0x72,
0xb4, 0x62, 0x40};
if (!CreateVideoToolboxSession(
CreateVideoFormatHEVC(vps_hevc_small, sps_hevc_small,
pps_hevc_small),
/*require_hardware=*/false, /*is_hbd=*/false, &callback, &session,
&configured_size)) {
DVLOG(1) << "Software HEVC decoding with VideoToolbox is not supported";
// 同VP9逻辑,HEVC软解预热失败不会禁用H264硬解能力
}
}
}
#endif // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
return true;
}
// 实际的最终判断逻辑
VideoDecodeAccelerator::SupportedProfiles
VTVideoDecodeAccelerator::GetSupportedProfiles(
const gpu::GpuDriverBugWorkarounds& workarounds) {
SupportedProfiles profiles;
// H264硬/软解不支持时,禁用硬解模块
if (!InitializeVideoToolbox())
return profiles;
for (const auto& supported_profile : kSupportedProfiles) {
// 目前仅支持VP9 PROFILE0、2两种Profile
if (supported_profile == VP9PROFILE_PROFILE0 ||
supported_profile == VP9PROFILE_PROFILE2) {
// 所有GPU模块的解码都会先读取依赖GPU Workaround
// 比如需要禁用特定型号或厂商的GPU对特定Codec的硬解支持
// 则可利用GPU Workaround下发禁用配置
if (workarounds.disable_accelerated_vp9_decode)
continue;
if (!base::mac::IsAtLeastOS11())
// 系统版本不支持VP9硬解,跳过
continue;
if (__builtin_available(macOS 10.13, *)) {
if ((supported_profile == VP9PROFILE_PROFILE0 ||
supported_profile == VP9PROFILE_PROFILE2) &&
!VTIsHardwareDecodeSupported(kCMVideoCodecType_VP9)) {
// Profile不支持,或操作系统不支持VP9硬解,跳过
continue;
}
// 经过GPU workaround、操作系统版本、Profile、以及OS是否支持VP9硬解检查,最终确认支持VP9硬解,并接管解码权限
} else {
// 系统版本不支持VP9硬解,跳过
continue;
}
}
// 目前支持HEVC Main、Main10、MSP、Rext四种Profile
if (supported_profile == HEVCPROFILE_MAIN ||
supported_profile == HEVCPROFILE_MAIN10 ||
supported_profile == HEVCPROFILE_MAIN_STILL_PICTURE ||
supported_profile == HEVCPROFILE_REXT) {
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
if (!workarounds.disable_accelerated_hevc_decode &&
base::FeatureList::IsEnabled(kPlatformHEVCDecoderSupport)) {
if (__builtin_available(macOS 11.0, *)) {
// 经过GPU workaround、操作系统版本、Profile,编译开关,启动开关检查,最终确认支持HEVC硬解(软解我们也使用Videotoolbox来做,原因后面说),并接管解码权限
SupportedProfile profile;
profile.profile = supported_profile;
profile.min_resolution.SetSize(16, 16);
// HEVC最大可支持8k
profile.max_resolution.SetSize(8192, 8192);
profiles.push_back(profile);
}
}
#endif // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
continue;
}
// H264和VP9最大支持4k
SupportedProfile profile;
profile.profile = supported_profile;
profile.min_resolution.SetSize(16, 16);
profile.max_resolution.SetSize(4096, 4096);
profiles.push_back(profile);
}
return profiles;
}
如上,经过 GPU workaround、操作系统版本、Profile、编译开关、启动开关检查,最终如果校验通过,则 HEVC 解码逻辑会由 VideoToolbox 接管,并由 VTDecoderXPCService 进程最终实际负责解码。
理解 HEVC 的 NALU 类型NALU (network abstraction layer unit),即网络抽象层单元,是 H.264 / AVC 和 HEVC 视频编码标准的核心定义,按白话理解,就是 H264 / HEVC 为不同的视频单元定义了的不同的类型(参考),感兴趣可自行百科,这里不再赘述。对于 H264,存在 32 种,其中保留 Nalu 有 8 种。到了 HEVC,被扩展到了 64 种,保留 Nalu 有 16 种。
(H264 的 Nalu Unit 组成,图片来自 Apple)
// media/video/h265_nalu_parser.h
enum Type {
TRAIL_N = 0, // coded slice segment of a non TSA(Temporal Sub-layer Access)
// trailing picture
TRAIL_R = 1, // coded slice segment of a non TSA(Temporal Sub-layer Access)
// trailing picture
TSA_N = 2, // coded slice segment of a TSA(Temporal Sub-layer Access)
// trailing picture
TSA_R = 3, // coded slice segment of a TSA(Temporal Sub-layer Access)
// trailing picture
STSA_N = 4, // coded slice segment of a STSA(Step-wise Temporal Sub-layer
// Access) trailing picture
STSA_R = 5, // coded slice segment of a STSA(Step-wise Temporal Sub-layer
// Access) trailing picture
RADL_N = 6, // coded slice segment of a RADL(Random Access Decodable
// Leading) leading picture
RADL_R = 7, // coded slice segment of a RADL(Random Access Decodable
// Leading) leading picture
RASL_N = 8, // coded slice segment of a RASL(Random Access Skipped
// Leading)L leading picture
RASL_R = 9, // coded slice segment of a RASL(Random Access Skipped Leading)
// leading picture
RSV_VCL_N10 = 10, // reserved non-IRAP SLNR VCL
RSV_VCL_R11 = 11, // reserved non-IRAP sub-layer reference VCL
RSV_VCL_N12 = 12, // reserved non-IRAP SLNR VCL
RSV_VCL_R13 = 13, // reserved non-IRAP sub-layer reference VCL
RSV_VCL_N14 = 14, // reserved non-IRAP SLNR VCL
RSV_VCL_R15 = 15, // reserved non-IRAP sub-layer reference VCL
BLA_W_LP = 16, // coded slice segment of a BLA IRAP picture
BLA_W_RADL = 17, // coded slice segment of a BLA IRAP picture
BLA_N_LP = 18, // coded slice segment of a BLA IRAP picture
IDR_W_RADL = 19, // coded slice segment of an IDR IRAP picture
IDR_N_LP = 20, // coded slice segment of an IDR IRAP picture
CRA_NUT = 21, // coded slice segment of a CRA IRAP picture
RSV_IRAP_VCL22 = 22, // reserved IRAP(intra random access point) VCL
RSV_IRAP_VCL23 = 23, // reserved IRAP(intra random access point) VCL
RSV_VCL24 = 24, // reserved non-IRAP VCL
RSV_VCL25 = 25, // reserved non-IRAP VCL
RSV_VCL26 = 26, // reserved non-IRAP VCL
RSV_VCL27 = 27, // reserved non-IRAP VCL
RSV_VCL28 = 28, // reserved non-IRAP VCL
RSV_VCL29 = 29, // reserved non-IRAP VCL
RSV_VCL30 = 30, // reserved non-IRAP VCL
RSV_VCL31 = 31, // reserved non-IRAP VCL
VPS_NUT = 32, // vps(video parameter sets)
SPS_NUT = 33, // sps(sequence parameter sets)
PPS_NUT = 34, // pps(picture parameter sets)
AUD_NUT = 35, // access unit delimiter
EOS_NUT = 36, // end of sequence
EOB_NUT = 37, // end of bitstream
FD_NUT = 38, // filter Data
PREFIX_SEI_NUT = 39, // sei
SUFFIX_SEI_NUT = 40, // sei
RSV_NVCL41 = 41, // reserve
RSV_NVCL42 = 42, // reserve
RSV_NVCL43 = 43, // reserve
RSV_NVCL44 = 44, // reserve
RSV_NVCL45 = 45, // reserve
RSV_NVCL46 = 46, // reserve
RSV_NVCL47 = 47, // reserve
UNSPEC48 = 48, // unspecified
UNSPEC49 = 49, // unspecified
UNSPEC50 = 50, // unspecified
UNSPEC51 = 51, // unspecified
UNSPEC52 = 52, // unspecified
UNSPEC53 = 53, // unspecified
UNSPEC54 = 54, // unspecified
UNSPEC55 = 55, // unspecified
UNSPEC56 = 56, // unspecified
UNSPEC57 = 57, // unspecified
UNSPEC58 = 58, // unspecified
UNSPEC59 = 59, // unspecified
UNSPEC60 = 60, // unspecified
UNSPEC61 = 61, // unspecified
UNSPEC62 = 62, // unspecified
UNSPEC63 = 63, // unspecified
};
解析 SPS / PPS / VPS
如想实现 HEVC 解码,首先需要拿到视频的元数据,这就需要通过解析 NALU 类型为 32 (VPS_NUT), 33 (SPS_NUT), 34 (PPS_NUT)的 Nalu Header 来获取。
举个最基本的例子,如果我们希望获取视频的宽高,则需要解析SPS_NUT的 Nalu Header,并取sps->pic_width_in_luma_samples的值,以此类推。
通常媒体开发会使用一个叫做StreamAnalyzer的工具(链接:https://www.elecard.com/zh/products/video-analysis/stream-analyzer)快速解析视频 Nalu Header,我们要做的事其实和这个软件做的差不多: