之前的章节中使用软解(CPU)进行音视频解码,软解通用性强,无需考虑设备硬件;但对于高分辨率(4k、8k)视频,使用软解会导致解码时间过长,CPU资源消耗过大,影响用户体验。因此,硬件解码应运而生,硬件解码可以提高解码效率,提升用户体验。
1.硬件解码
硬件解码(Hardware Decoding)是指使用硬件加速设备(如GPU、专用的视频解码芯片等)来解码文件的过程,这些硬件通常经过优化,能够以更高效的方式进行计算密集型的解码任务,不仅可以提高解码速度,而且可以减少系统资源消耗。因此,硬件解码广泛应用于高清视频播放、视频流媒体处理、实时视频会议以及视频编辑等场景。
硬件解码具有明显的优势:
2.硬解解码的应用
2.1解码器选择
如前文所述,硬件解码需要专门的硬件加速设备,并且不同的机型上的硬件加速设备不一定相同,如Intel的QuickSync、AMD的VCE、NVIDIA的CUDA等。为此,使用硬件解码时,需要先确定当前设备是否支持硬件解码,如果支持,则选择合适的硬件解码器进行解码。
在硬解的设备选择过程中,FFmpeg提供了如下接口:
函数的原型如下:
//枚举设备类型
//参数:prev:上一个设备类型,通常为AV_HWDEVICE_TYPE_NONE。
//返回值:返回下一个可用的设备类型,如果没有更多设备类型,则返回AV_HWDEVICE_TYPE_NONE。
enum AVHWDeviceType av_hwdevice_iterate_types(enum AVHWDeviceType prev);
//创建硬件设备上下文
//参数:device_ctx:指向AVBufferRef结构体的指针,用于存储创建的硬件设备上下文。
// type:硬件设备类型,例如AV_HWDEVICE_TYPE_CUDA、AV_HWDEVICE_TYPE_VAAPI等。
// device:特定于硬件设备的字符串,例如CUDA设备名称、VAAPI设备名称等,通常为nullptr
// opts:用于设置硬件设备上下文的选项,可以为NULL。
// flags:用于设置硬件设备上下文的标志,可以为0。
//返回值:成功返回0,失败返回负值。
int av_hwdevice_ctx_create(AVBufferRef device_ctx, const AVHWDeviceType type,
const char *device, AVDictionary *opts, int flags);
//获取硬件配置信息
//参数:codec:指向AVCodec结构体的指针,用于指定要获取硬件配置信息的解码器。
// index:指定要获取的硬件配置信息的索引,通常为0。
//返回值:成功返回指向AVCodecHWConfig结构体的指针,失败返回NULL。
const AVCodecHWConfig *avcodec_get_hw_config(const AVCodec *codec, int index);所以设备类型的选择可以通过如下代码实现:
enum AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;//设备类型
AVBufferRef *hw_device_ctx = nullptr;//硬件设备上下文
enum AVPixelFormat hw_pix_fmt{ AV_PIX_FMT_NONE };//硬件纹理格式
while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE) {
int ret = av_hwdevice_ctx_create(&hw_device_ctx, type, nullptr, nullptr, 0);
if (ret >= 0)
{
break;//找到硬件解码的支持类型,退出循环
}
}
if(type == AV_HWDEVICE_TYPE_NONE)
{
std::cerr << "No suitable hardware device found" << std::endl;
if(hw_device_ctx)
{
av_buffer_unref(&hw_device_ctx);
}
return -1;
}
for (int i = 0;; i++)
{
const AVCodecHWConfig* config = avcodec_get_hw_config(decoder, i);
if (!config)
{
fprintf(stderr, "Decoder %s does not support device type %s.\n",
decoder->name, av_hwdevice_get_type_name(hw_device_type));
return;
}
if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&
config->device_type == hw_device_type)
{
hw_pix_fmt = config->pix_fmt;
break;
}
}
//now can use device type and hw_device_ctx to decode2.2解码器上下文
在真正解码过程中无论是发送数据包还是获取解码完的数据帧,都需要的是解码器上下文,因此,无论是硬件解码还是软件解码均需要创建解码器上下文并进行参数的复制。只是区别于软解,硬解需要多做如下工作:
具体代码如下:
static enum AVPixelFormat get_hw_format(AVCodecContext* ctx,
const enum AVPixelFormat* pix_fmts)
{
const enum AVPixelFormat* p;
for (p = pix_fmts; *p != -1; p++) {
if (*p == hw_pix_fmt)//hw_pix_fmt为上文获取的硬件的配置信息中的纹理格式
return *p;
}
fprintf(stderr, "Failed to get HW surface format.\n");
return AV_PIX_FMT_NONE;
}
if (hw_device_ctx)
{
dec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
dec_ctx->get_format = get_hw_format;
}至此已完成硬解相关的解码器上下文的设置,可以直接参照软解的流程进行解码。读取文件数据包,发送数据包,获取帧数据的流程与软解的流程是一样的。
2.3数据转换
只是此时获取的数据帧是硬件帧(存储在硬件相关的缓冲区),数据格式和特性是由硬件设备本身决定的,与软件帧(存储在系统内存的常规缓冲区)格式不同,不符合后续处理的要求。所以需要数据转换的环节——将硬件帧转换为软件帧。
FFmmpeg提供了一个函数av_hwframe_transfer_data用于将硬件帧转换为软件帧。该函数的原型如下:
//数据转换
//参数:
// dst:指向目标AVFrame结构体的指针,用于存储转换后的软件帧。
// src:指向源AVFrame结构体的指针,用于存储硬件帧。
// flags:用于设置转换的标志,可以为0。
//返回值:成功返回0,失败返回负值。
int av_hwframe_transfer_data(AVFrame *dst, const AVFrame *src, int flags);借助如上函数获得的软件帧,即可进行后续的处理。
如上已列出硬解区别于软解的关键接口,可自行书写硬件解码的代码
我本将全部的代码写到了本文中,奈何阅读体验不佳,如果需要如上代码的话,可加好友获取。
3.总结
本文主要介绍了FFmpeg中硬件解码的基本流程,包括硬件设备的选择、解码器上下文的创建、数据转换等关键步骤。通过硬件解码,可以大大提升视频解码的效率,减少系统负担,提高视频播放的流畅性。