ffmpeg在解码过程中,一般使用的是软件解码,即使用CPU去解码; 但如果需要解码4k、8k等分辨率的视频时,再使用软件解码,就会使得CPU占用率非常大,如果CPU跟不上可能会出现某些异常情况; 所以,需要借助硬件解码去处理此类问题,即GPU解码;
流程:
cpp
1. 准备硬件
└─ av_hwdevice_ctx_create() → 创建硬件设备
2. 初始化解码器并绑定硬件
└─ avcodec_find_decoder() → avcodec_alloc_context3() → 绑定hw_device_ctx → avcodec_open2()
3. 解析裸流,循环解码
├─ av_parser_parse2() → 提取一帧压缩数据(AVPacket)
├─ avcodec_send_packet() → 送压缩数据
└─ avcodec_receive_frame() → 取出解码帧(硬件格式)
4. 硬件帧 → 内存(关键)
└─ av_hwframe_transfer_data() → 将GPU数据拷贝到CPU(sw_frame)
5. 渲染显示
├─ 将sw_frame的NV12数据拷贝到nv12data
└─ SDL_UpdateTexture() → SDL_RenderCopy() → SDL_RenderPresent()
6. 冲刷解码器(发送NULL包,取完剩余帧)
7. 释放资源(解码器、设备、SDL等)
头文件:#include "libavutil/hwcontext.h"
首先查看电脑支持的硬件:
cpp
// 查找解码器
const AVCodec *decodec = avcodec_find_decoder(AV_CODEC_ID_H264);
// 打印所有硬件
for (int i = 0; ; ++i) {
const AVCodecHWConfig *config = avcodec_get_hw_config(decodec, i);
if (!config) {
break;
}
if (config->device_type) {
std::cout << av_hwdevice_get_type_name(config->device_type) << std::endl;
}
}
如下是我电脑所打印结果:
bash
dxva2
d3d11va
d3d12va
cuda
vaapi
vulkan
学习就使用DXVA2,因为它兼容最广,不容易失败。
ffmpeg也有与之相对应的枚举(AVHWDeviceType),用于查找硬件设备;
bash
dxva2 AVHWDeviceType::AV_HWDEVICE_TYPE_DXVA2
d3d11va AVHWDeviceType::AV_HWDEVICE_TYPE_D3D11VA
d3d12va AVHWDeviceType::AV_HWDEVICE_TYPE_D3D12VA
cuda AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA
vaapi AVHWDeviceType::AV_HWDEVICE_TYPE_VAAPI
vulkan AVHWDeviceType::AV_HWDEVICE_TYPE_VULKAN
当硬件解码出来的一帧AVFrame,他的格式也是有与之相对应的硬件类型(AVPixelFormat);
bash
dxva2 AVPixelFormat::AV_PIX_FMT_DXVA2_VLD
d3d11va AVPixelFormat::AV_PIX_FMT_D3D11
d3d12va AVPixelFormat::AV_PIX_FMT_D3D12
cuda AVPixelFormat::AV_PIX_FMT_CUDA
vaapi AVPixelFormat::AV_PIX_FMT_VAAPI
vulkan AVPixelFormat::AV_PIX_FMT_VULKAN
当然,硬件解码出来的AVFrame,是在显存中的,即在GPU中,需要将其拷贝到内存中进行处理渲染处理才能显示,即拷贝到CPU中;(也可以在GPU中直接渲染显示的,这是高级用法了)
通过 av_hwframe_transfer_data 函数,可以将AVFrame从GPU拷贝到CPU中; 拷贝到GPU后的AVFrame,格式为AVPixelFormat::AV_PIX_FMT_NV12类型;
关键的步骤是,第一,硬件设备初始化;第二,将硬件绑定到解码器中;第三,显存拷贝到内存,即GUP拷贝到CPU;下方⭐️标注着。
1. av_hwdevice_ctx_create
av_hwdevice_ctx_create 是 FFmpeg 中用于初始化硬件设备上下文的核心函数。它封装了复杂的硬件初始化过程,是使用 FFmpeg 硬件加速的起点。
①函数原型
cpp
int av_hwdevice_ctx_create(
AVBufferRef **pdevice_ref, // 输出:硬件设备上下文(引用计数管理)
enum AVHWDeviceType type, // 硬件设备类型
const char *device, // 设备名称(如 "0" 表示第一个GPU)
AVDictionary *opts, // 可选参数字典(通常 NULL)
int flags // 标志位(通常 0)
);
②参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
| pdevice_ref | AVBufferRef ** | 输出参数,指向创建好的硬件设备上下文。通过 AVBuffer 引用计数管理内存,必须用 av_buffer_unref() 释放 |
| type | enum AVHWDeviceType | 硬件设备类型(见下表) |
| device | const char * | 设备标识字符串。如 "0" 表示第一个设备,NULL 表示默认设备 |
| opts | AVDictionary * | 设备特定的选项参数(如指定 CUDA 设备 ID),不需要填 NULL |
| flags | int | 标志位,通常填 0 |
AVHWDeviceType类型:
| 类型 | 说明 | 平台 |
|---|---|---|
| AV_HWDEVICE_TYPE_CUDA | NVIDIA GPU(NVDEC/NVENC) | Windows / Linux |
| AV_HWDEVICE_TYPE_DXVA2 | DirectX Video Acceleration 2 | Windows |
| AV_HWDEVICE_TYPE_D3D11VA | Direct3D 11 Video Acceleration | Windows |
| AV_HWDEVICE_TYPE_D3D12VA | Direct3D 12 Video Acceleration | Windows 10+ |
| AV_HWDEVICE_TYPE_QSV | Intel Quick Sync Video | Windows / Linux |
| AV_HWDEVICE_TYPE_VAAPI | VAAPI(Intel/AMD) | Linux |
| AV_HWDEVICE_TYPE_VIDEOTOOLBOX | Apple VideoToolbox | macOS / iOS |
| AV_HWDEVICE_TYPE_MEDIACODEC | Android MediaCodec | Android |
| AV_HWDEVICE_TYPE_VULKAN | Vulkan 视频解码 | 跨平台 |
③返回值
- 成功:返回 0
- 失败:返回一个负值的 AVERROR 错误码
④使用示例
cpp
AVHWDeviceType hwtype = AV_HWDEVICE_TYPE_DXVA2;
AVBufferRef *hw_device_ctx = nullptr;
// 创建硬件设备上下文
int ret = av_hwdevice_ctx_create(&hw_device_ctx, hwtype, nullptr, nullptr, 0);
if (ret < 0) {
std::cout << "av_hwdevice_ctx_create failed!" << std::endl;
PrintErr(ret);
return -1;
}
注意,硬件设备需要绑定到解码器上下文中:decodec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
2. av_hwframe_transfer_data
av_hwframe_transfer_data 是 FFmpeg 中用于在硬件(GPU)和系统内存(CPU)之间传输视频帧数据的核心函数。
简单来说,当使用硬件加速解码后,解码出的 AVFrame 数据可能依然存放在 GPU 显存中。这个函数就是负责将数据从 GPU 显存"搬运"到 CPU 可访问的系统内存中,以便进行后续处理(如保存为文件、通过软件渲染器显示等)。
①函数原型
cpp
int av_hwframe_transfer_data(AVFrame *dst, const AVFrame *src, int flags);
②参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
| dst | AVFrame * | 目标帧(接收数据),通常是软件帧(CPU 内存) |
| src | const AVFrame * | 源帧(提供数据),通常是硬件帧(GPU 显存),由 avcodec_receive_frame() 解码得到 |
| flags | int | 标志位,通常填 0 |
③返回值
- 成功:返回 0
- 失败:返回一个负值的 AVERROR 错误码
④使用示例
cpp
// 显存拷贝到内存 gpu->cpu
re = av_hwframe_transfer_data(sw_frame, hw_frame, 0);
if (re < 0) {
std::cout << "av_hwframe_transfer_data failed!" << std::endl;
PrintErr(re);
return -1;
}
bash
GPU 显存(硬件帧) ──av_hwframe_transfer_data──▶ CPU 内存(软件帧)
hw_frame (format=AV_PIX_FMT_CUDA) sw_frame (format=AV_PIX_FMT_NV12)
3. 编码流程
3.1 打开视频文件
cpp
std::ifstream ifs;
ifs.open("800_600_25.h264", std::ios::binary);
if (!ifs.is_open()) {
std::cout << "ifs open file failed!\n";
std::cout << "Error code: " << errno << std::endl;
std::cout << "Error message: " << strerror(errno) << std::endl;
return -1;
}
3.2 初始化解码器
cpp
// 查找解码器
const AVCodec *decodec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!decodec) {
std::cout << "avcodec_find_decoder failed!" << std::endl;
return -1;
}
// 创建解码器上下文
AVCodecContext *decodec_ctx = avcodec_alloc_context3(decodec);
if (!decodec_ctx) {
std::cout << "avcodec_alloc_context3 failed!" << std::endl;
return -1;
}
// 注意这里暂时还不能打开
3.3 初始化硬件设备 ⭐️
cpp
AVHWDeviceType hwtype = AV_HWDEVICE_TYPE_DXVA2;
AVBufferRef *hw_device_ctx = nullptr;
// 创建硬件设备上下文
int ret = av_hwdevice_ctx_create(&hw_device_ctx, hwtype, nullptr, nullptr, 0);
if (ret < 0) {
std::cout << "av_hwdevice_ctx_create failed!" << std::endl;
PrintErr(ret);
return -1;
}
3.4 硬件设备上下文绑定到解码器上下文 ⭐️
cpp
decodec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
3.5 打开解码器上下文
cpp
ret = avcodec_open2(decodec_ctx, decodec, 0);
if (0 != ret) {
std::cout << "avcodec_open2 failed!" << std::endl;
PrintErr(ret);
return -1;
}
3.6 初始化音视频码流解析器
cpp
AVCodecParserContext *parser_ctx = av_parser_init(AV_CODEC_ID_H264);
if (!parser_ctx) {
std::cout << "av_parser_init failed!" << std::endl;
return -1;
}
3.7 初始化SDL部分
注意,因为硬件解码出来再转到cpu中的类型是NV12,所以这里创建纹理时,也是指定了SDL_PIXELFORMAT_NV12去创建,否则sdl渲染会失败; 当然也可以指定YUV420P或RGB24等类型,需要配合sws_scale进行像素格式转换,将NV12类型转换为YUV420P或RGB24等类型即可。
cpp
SDL_Init(SDL_INIT_VIDEO);
// 创建窗口
SDL_Window *sdl_win = SDL_CreateWindow("xxxxxxxx", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
800, 600, SDL_WINDOW_OPENGL);
// 创建渲染器
SDL_Renderer *render = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_TARGETTEXTURE);
// 创建 纹理|材质
SDL_Texture *texture = SDL_CreateTexture(render, SDL_PIXELFORMAT_NV12, SDL_TEXTUREACCESS_STREAMING, 800, 600);
// 设置线性插值(抗锯齿)
SDL_SetTextureScaleMode(texture, SDL_ScaleModeLinear);
3.8 读取文件数据
cpp
unsigned char buf[4096] = { 0 };
int size_in = 0;
while (1) {
// 读取文件数据
ifs.read((char *)buf, sizeof(buf));
size_in = ifs.gcount();
// 读取失败或读取完毕
if (0 >= size_in) {
std::cout << "ifs.read end!" << std::endl;
break;
}
// other code ...
}
3.9 解析音视频裸流数据
cpp
data_in = buf;
while (0 < size_in) {
// 解析音视频裸流数据,从中分离出一帧一帧的压缩编码数据
int re = av_parser_parse2(parser_ctx, decodec_ctx, &pkt->data, &pkt->size,
data_in, size_in, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
data_in += re; // 处理了多少,指针移位多少
size_in -= re; // 处理了多少,减去多少,小于等于0,则说明读取的字节已经处理完毕
if (pkt->size) {
// other code ...
}
}
3.10 解码
cpp
// 发送解码
ret = avcodec_send_packet(decodec_ctx, pkt);
if (0 != ret) {
std::cout << "avcodec_send_packet failed! ret = " << ret << std::endl;
PrintErr(ret);
goto FAILED;
}
while (0 <= ret) {
// 接收解码
ret = avcodec_receive_frame(decodec_ctx, hw_frame);
if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) {
av_packet_unref(pkt);
break;
}
if (0 > ret) {
std::cout << "avcodec_receive_frame failed! ret = " << ret << std::endl;
PrintErr(ret);
av_packet_unref(pkt);
goto FAILED;
}
// other code ...
}
3.11 GPU显存拷贝到CPU内存 ⭐️
cpp
AVFrame *display_frame = nullptr;
// 判断是否是硬件解码
if (hw_frame->format == AV_PIX_FMT_DXVA2_VLD ||
hw_frame->format == AV_PIX_FMT_D3D11 ||
hw_frame->format == AV_PIX_FMT_VULKAN ||
hw_frame->format == AV_PIX_FMT_VAAPI ||
hw_frame->format == AV_PIX_FMT_CUDA ||
hw_frame->format == AV_PIX_FMT_D3D12) {
// 显存拷贝到内存 gpu->cpu
re = av_hwframe_transfer_data(sw_frame, hw_frame, 0);
if (re < 0) {
std::cout << "av_hwframe_transfer_data failed!" << std::endl;
PrintErr(re);
return -1;
}
display_frame = sw_frame;
} else {
display_frame = hw_frame;
}
3.11 SDL渲染
注意,这里改用了SDL_UpdateNVTexture,因为是NV12类型的。
cpp
SDL_UpdateNVTexture(texture, NULL, display_frame->data[0], display_frame->linesize[0],
display_frame->data[1], display_frame->linesize[1]);
SDL_RenderClear(render);
SDL_RenderCopy(render, texture, NULL, NULL);
SDL_RenderPresent(render);
当然,也可以将display_frame的内容拷贝到unsigned char *中,然后再进行渲染; 如下代码:
cpp
unsigned char *nv12data = (unsigned char *)malloc(4096 * 2160);
memset(nv12data, 0, 4096 * 2160);
// 处理NV12类型数据
if (display_frame->format == AV_PIX_FMT_NV12) {
// 如果相等,则全部拷贝
if (display_frame->width == display_frame->linesize[0]) {
// 拷贝Y
memcpy(nv12data, display_frame->data[0], display_frame->linesize[0] * display_frame->height);
// 拷贝UV
memcpy(nv12data + display_frame->width * display_frame->height,
display_frame->data[1], display_frame->linesize[1] * display_frame->height/2);
} else {
// 逐行复制 Y 和 UV 平面的有效数据,剔除每行末尾的填充字节
// 拷贝Y
for (int i = 0; i < display_frame->height; ++i) {
memcpy(nv12data + i * display_frame->width,
display_frame->data[0] + i * display_frame->linesize[0],
display_frame->width);
}
// 拷贝UV
auto p = nv12data + display_frame->height * display_frame->width;
for (int i = 0; i < display_frame->height/2; ++i) {
memcpy(p + i * display_frame->width,
display_frame->data[1] + i * display_frame->linesize[1],
display_frame->width);
}
}
}
SDL_UpdateTexture(texture, NULL, nv12data, display_frame->width);
SDL_RenderClear(render);
SDL_RenderCopy(render, texture, NULL, NULL);
SDL_RenderPresent(render);
除了这样,还可以进行像素格式转换sws_scale后再渲染显示。
3.12 冲刷解码器
cpp
ret = avcodec_send_packet(decodec_ctx, NULL);
if (0 != ret) {
std::cout << "avcodec_send_packet failed! ret = " << ret << std::endl;
PrintErr(ret);
goto FAILED;
}
while (0 <= ret) {
ret = avcodec_receive_frame(decodec_ctx, hw_frame);
if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) {
break;
}
if (0 > ret) {
std::cout << "avcodec_receive_packet failed! ret = " << ret << std::endl;
PrintErr(ret);
goto FAILED;
}
// 判断是否是硬件解码
if (hw_frame->format == AV_PIX_FMT_DXVA2_VLD ||
hw_frame->format == AV_PIX_FMT_D3D11 ||
hw_frame->format == AV_PIX_FMT_VULKAN ||
hw_frame->format == AV_PIX_FMT_VAAPI ||
hw_frame->format == AV_PIX_FMT_CUDA ||
hw_frame->format == AV_PIX_FMT_D3D12) {
std::cout << "type = " << hw_frame->format << std::endl;
av_hwframe_transfer_data(sw_frame, hw_frame, 0);
SDL_UpdateNVTexture(texture, NULL, sw_frame->data[0], sw_frame->linesize[0],
sw_frame->data[1], sw_frame->linesize[1]);
SDL_RenderClear(render);
SDL_RenderCopy(render, texture, NULL, NULL);
SDL_RenderPresent(render);
}
std::cout << pts++ << "- ";
}
3.12 内存回收
cpp
FAILED:
// 内存回收
ifs.close();
av_frame_free(&sw_frame);
av_frame_free(&hw_frame);
av_packet_free(&pkt);
avcodec_free_context(&decodec_ctx);
av_parser_close(parser_ctx);
free(nv12data);
av_buffer_unref(&hw_device_ctx); // 释放硬件设备上下文
if (texture) {
SDL_DestroyTexture(texture);
}
if (render) {
SDL_DestroyRenderer(render);
}
if (sdl_win) {
SDL_DestroyWindow(sdl_win);
}
SDL_Quit();
4. 代码合集
读取本地h.264视频文件,通过硬件加速解码后,再通过SDL2渲染;
cpp
#include <iostream>
#include <fstream>
#include <thread>
#include <chrono>
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavutil/avutil.h"
#include "libswscale/swscale.h"
#include "libavutil/hwcontext.h"
}
#include "SDL.h"
// 打印错误信息
static void PrintErr(int ret) {
char buf[1024] = { 0 };
av_strerror(ret, buf, sizeof(buf));
std::cout << buf << std::endl;
}
#undef main
int main(void)
{
std::cout << av_version_info() << std::endl;
// 打开视频文件
std::ifstream ifs;
ifs.open("800_600_25.h264", std::ios::binary);
if (!ifs.is_open()) {
std::cout << "ifs open file failed!\n";
std::cout << "Error code: " << errno << std::endl;
std::cout << "Error message: " << strerror(errno) << std::endl;
return -1;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// 初始化解码器
// 查找解码器
const AVCodec *decodec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!decodec) {
std::cout << "avcodec_find_decoder failed!" << std::endl;
return -1;
}
// 创建解码器上下文
AVCodecContext *decodec_ctx = avcodec_alloc_context3(decodec);
if (!decodec_ctx) {
std::cout << "avcodec_alloc_context3 failed!" << std::endl;
return -1;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////
/// 初始化硬件设备
// 打印所有硬件
for (int i = 0; ; ++i) {
const AVCodecHWConfig *config = avcodec_get_hw_config(decodec, i);
if (!config) {
break;
}
if (config->device_type) {
std::cout << av_hwdevice_get_type_name(config->device_type) << std::endl;
}
}
/*
* dxva2
d3d11va
d3d12va
cuda
vaapi
vulkan
*/
AVHWDeviceType hwtype = AV_HWDEVICE_TYPE_DXVA2;
// AVHWDeviceType hwtype = AV_HWDEVICE_TYPE_D3D12VA;
AVBufferRef *hw_device_ctx = nullptr;
// 创建硬件设备上下文
int ret = av_hwdevice_ctx_create(&hw_device_ctx, hwtype, nullptr, nullptr, 0);
if (ret < 0) {
std::cout << "av_hwdevice_ctx_create failed!" << std::endl;
PrintErr(ret);
return -1;
}
// 将硬件设备上下文绑定到解码器上下文
decodec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
/////////////////////////////////////////////////////////////////////////////////////////////////
// 打开解码器上下文
ret = avcodec_open2(decodec_ctx, decodec, 0);
if (0 != ret) {
std::cout << "avcodec_open2 failed!" << std::endl;
PrintErr(ret);
return -1;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/// 初始化音视频码流解析器
AVCodecParserContext *parser_ctx = av_parser_init(AV_CODEC_ID_H264);
if (!parser_ctx) {
std::cout << "av_parser_init failed!" << std::endl;
return -1;
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// 初始化SDL部分
SDL_Init(SDL_INIT_VIDEO);
// 创建窗口
SDL_Window *sdl_win = SDL_CreateWindow("xxxxxxxx", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
800, 600, SDL_WINDOW_OPENGL);
// 创建渲染器
SDL_Renderer *render = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_TARGETTEXTURE);
// 创建 纹理|材质
SDL_Texture *texture = SDL_CreateTexture(render, SDL_PIXELFORMAT_NV12, SDL_TEXTUREACCESS_STREAMING, 800, 600);
// 设置线性插值(抗锯齿)
SDL_SetTextureScaleMode(texture, SDL_ScaleModeLinear);
int pts = 0;
unsigned char buf[4096] = { 0 };
unsigned char *data_in = nullptr;
int size_in = 0;
AVPacket *pkt = av_packet_alloc();
AVFrame *hw_frame = av_frame_alloc();
AVFrame *sw_frame = av_frame_alloc();
unsigned char *nv12data = (unsigned char *)malloc(4096 * 2160);
memset(nv12data, 0, 4096 * 2160);
while (1) {
// 读取文件数据
ifs.read((char *)buf, sizeof(buf));
size_in = ifs.gcount();
// 读取失败或读取完毕
if (0 >= size_in) {
std::cout << "ifs.read end!" << std::endl;
break;
}
data_in = buf;
while (0 < size_in) {
// 解析音视频裸流数据,从中分离出一帧一帧的压缩编码数据
int re = av_parser_parse2(parser_ctx, decodec_ctx, &pkt->data, &pkt->size,
data_in, size_in, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
data_in += re; // 处理了多少,指针移位多少
size_in -= re; // 处理了多少,减去多少,小于等于0,则说明读取的字节已经处理完毕
if (pkt->size) {
// 发送解码
ret = avcodec_send_packet(decodec_ctx, pkt);
if (0 != ret) {
std::cout << "avcodec_send_packet failed! ret = " << ret << std::endl;
PrintErr(ret);
goto FAILED;
}
while (0 <= ret) {
// 接收解码
ret = avcodec_receive_frame(decodec_ctx, hw_frame);
if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) {
av_packet_unref(pkt);
break;
}
if (0 > ret) {
std::cout << "avcodec_receive_frame failed! ret = " << ret << std::endl;
PrintErr(ret);
av_packet_unref(pkt);
goto FAILED;
}
av_packet_unref(pkt);
AVFrame *display_frame = nullptr;
// 判断是否是硬件解码
if (hw_frame->format == AV_PIX_FMT_DXVA2_VLD ||
hw_frame->format == AV_PIX_FMT_D3D11 ||
hw_frame->format == AV_PIX_FMT_VULKAN ||
hw_frame->format == AV_PIX_FMT_VAAPI ||
hw_frame->format == AV_PIX_FMT_CUDA ||
hw_frame->format == AV_PIX_FMT_D3D12) {
// 显存拷贝到内存 gpu->cpu
re = av_hwframe_transfer_data(sw_frame, hw_frame, 0);
if (re < 0) {
std::cout << "av_hwframe_transfer_data failed!" << std::endl;
PrintErr(re);
return -1;
}
display_frame = sw_frame;
} else {
display_frame = hw_frame;
}
std::cout << pts++ << " ";
// 渲染NV12类型
// if (display_frame->format == AV_PIX_FMT_NV12) {
// // 如果相等,则全部拷贝
// if (display_frame->width == display_frame->linesize[0]) {
// // 拷贝Y
// memcpy(nv12data, display_frame->data[0], display_frame->linesize[0] * display_frame->height);
// // 拷贝UV
// memcpy(nv12data + display_frame->width * display_frame->height,
// display_frame->data[1], display_frame->linesize[1] * display_frame->height/2);
// } else {
// // 逐行复制 Y 和 UV 平面的有效数据,剔除每行末尾的填充字节
// // 拷贝Y
// for (int i = 0; i < display_frame->height; ++i) {
// memcpy(nv12data + i * display_frame->width,
// display_frame->data[0] + i * display_frame->linesize[0],
// display_frame->width);
// }
// // 拷贝UV
// auto p = nv12data + display_frame->height * display_frame->width;
// for (int i = 0; i < display_frame->height/2; ++i) {
// memcpy(p + i * display_frame->width,
// display_frame->data[1] + i * display_frame->linesize[1],
// display_frame->width);
// }
// }
// }
// SDL_UpdateTexture(texture, NULL, nv12data, display_frame->width);
SDL_UpdateNVTexture(texture, NULL, display_frame->data[0], display_frame->linesize[0],
display_frame->data[1], display_frame->linesize[1]);
SDL_RenderClear(render);
SDL_RenderCopy(render, texture, NULL, NULL);
SDL_RenderPresent(render);
// 睡眠 40 毫秒
// std::this_thread::sleep_for(std::chrono::milliseconds(40));
}
}
}
}
// 冲刷解码器
ret = avcodec_send_packet(decodec_ctx, NULL);
if (0 != ret) {
std::cout << "avcodec_send_packet failed! ret = " << ret << std::endl;
PrintErr(ret);
goto FAILED;
}
while (0 <= ret) {
ret = avcodec_receive_frame(decodec_ctx, hw_frame);
if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) {
break;
}
if (0 > ret) {
std::cout << "avcodec_receive_packet failed! ret = " << ret << std::endl;
PrintErr(ret);
goto FAILED;
}
// 判断是否是硬件解码
if (hw_frame->format == AV_PIX_FMT_DXVA2_VLD ||
hw_frame->format == AV_PIX_FMT_D3D11 ||
hw_frame->format == AV_PIX_FMT_VULKAN ||
hw_frame->format == AV_PIX_FMT_VAAPI ||
hw_frame->format == AV_PIX_FMT_CUDA ||
hw_frame->format == AV_PIX_FMT_D3D12) {
std::cout << "type = " << hw_frame->format << std::endl;
av_hwframe_transfer_data(sw_frame, hw_frame, 0);
SDL_UpdateNVTexture(texture, NULL, sw_frame->data[0], sw_frame->linesize[0],
sw_frame->data[1], sw_frame->linesize[1]);
SDL_RenderClear(render);
SDL_RenderCopy(render, texture, NULL, NULL);
SDL_RenderPresent(render);
}
std::cout << pts++ << "- ";
}
FAILED:
// 内存回收
ifs.close();
av_frame_free(&sw_frame);
av_frame_free(&hw_frame);
av_packet_free(&pkt);
avcodec_free_context(&decodec_ctx);
av_parser_close(parser_ctx);
free(nv12data);
av_buffer_unref(&hw_device_ctx); // 释放硬件设备上下文
if (texture) {
SDL_DestroyTexture(texture);
}
if (render) {
SDL_DestroyRenderer(render);
}
if (sdl_win) {
SDL_DestroyWindow(sdl_win);
}
SDL_Quit();
return 0;
}
5. 视频效果截图
最后自己可以在尝试一下别的硬件解码类型,看看会有什么不同效果。
bash
dxva2
d3d11va
d3d12va
cuda
vaapi
vulkan