XVideoView.cpp
bool XVideoView::drawAVFrame(AVFrame* frame)
{
bool ret = (frame != nullptr);
if (ret)
{
m_count++;
if (m_beginMs == 0)
{
m_beginMs = NowMs(); // 开始计时
}
else if ((NowMs() - m_beginMs >= 1000)) // 1s 刷新了多少帧
{
m_renderFps = m_count;
m_count = 0;
m_beginMs = NowMs();
}
switch (frame->format)
{
case AV_PIX_FMT_ARGB:
case AV_PIX_FMT_RGBA:
case AV_PIX_FMT_BGRA:
case AV_PIX_FMT_RGB24:
case AV_PIX_FMT_BGR24:
ret = draw(frame->data[0], frame->linesize[0]);
break;
case AV_PIX_FMT_NV12:
if (m_cache == nullptr)
{
m_cache = new unsigned char[4096 * 2000 * 1.5]; // 分配足够大的空间,使之适应于绝大多数帧,不需要每次都重新分配空间
}
if (frame->linesize[0] == frame->width)
{
memcpy(m_cache, frame->data[0], frame->linesize[0] * frame->height);
memcpy(m_cache + frame->linesize[0] * frame->height, frame->data[1], frame->linesize[1] * frame->height / 2);
}
else // 需要处理字节对齐的问题, 逐行复制
{
for (int i = 0; i < frame->height; i++)
{
memcpy(m_cache + i * frame->width, frame->data[0] + i * frame->linesize[0], frame->width);
}
for (int i = 0; i < frame->height / 2; i++)
{
memcpy(m_cache + frame->width * frame->height + i * frame->width, frame->data[1] + i * frame->linesize[1], frame->width);
}
}
ret = draw(m_cache, frame->linesize[0]);
break;
case AV_PIX_FMT_YUV420P:
ret = draw(frame->data[0], frame->linesize[0], frame->data[1], frame->linesize[1], frame->data[2], frame->linesize[2]);
break;
default:
ret = false;
break;
}
}
return ret;
}
119_test_decode_view _hw.cpp
#include
#include
#include "XVideoView.h"
#include "XSDL.h"
using namespace std;
extern "C" // 指定函数是 C 语言函数,函数目标名不包含重载标识,C++ 中调用 C 函数需要使用 extern "C"
{
// 引用 ffmpeg 头文件
#include "libavcodec/avcodec.h"
}
// 预处理指令导入库
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avutil.lib")
int main()
{
//1 分割h264 存入AVPacket
// ffmpeg -i v1080.mp4 -s 400x300 test.h264
string file_name = "test.h264";
ifstream ifs;
AVCodec* codec = nullptr;
AVCodecID codec_id = AV_CODEC_ID_H264;
AVCodecContext* context = nullptr;
AVCodecParserContext* parser = nullptr;
AVPacket* packet = nullptr;
AVFrame* frame = nullptr;
AVFrame* hw_frame = nullptr;
AVFrame* p_frame = nullptr; // 为了同时支持硬解码和软解码
unsigned char inbuf[4096] = { 0 };
int inbuf_len = 0;
int ret = 0;
long long begin = 0;
int cnt = 0; // 解码的帧率
XVideoView* view = XVideoView::Create();
bool first_init = false;
AVHWDeviceType dev_type = AV_HWDEVICE_TYPE_DXVA2; // 硬件加速格式 DXVA2
AVBufferRef* ctx_ref = nullptr;
ifs.open(file_name, ios::in | ios::binary);
if (!ifs)
{
return -1;
}
//1 找解码器
codec = avcodec_find_decoder(codec_id);
if (codec == nullptr)
{
cerr << "avcodec_find_decoder failed!" << endl;
return -1;
}
//2 创建上下文
context = avcodec_alloc_context3(codec);
if (context == nullptr)
{
cerr << "avcodec_alloc_context3 failed!" << endl;
return -1;
}
context->thread_count = 16;
/// 打印所有支持的硬件加速方式
for (int i = 0; ; i++)
{
auto config = avcodec_get_hw_config(codec, i);
if (config == nullptr)
{
break;
}
if (config->device_type)
{
cout << av_hwdevice_get_type_name(config->device_type) << endl;
}
}
// 初始化硬件加速上下文
ret = av_hwdevice_ctx_create(&ctx_ref, dev_type, nullptr, nullptr, 0);
if (ret < 0)
{
cerr << "av_hwdevice_ctx_create failed!" << endl;
return -1;
}
// 设定硬件GPU加速
context->hw_device_ctx = av_buffer_ref(ctx_ref);
//3 打开上下文
ret = avcodec_open2(context, nullptr, nullptr);
if (ret < 0)
{
cerr << "avcodec_open2 failed!" << endl;
return -1;
}
// 分割上下文
parser = av_parser_init(codec_id);
if (parser == nullptr)
{
cerr << "av_parser_init failed!" << endl;
return -1;
}
packet = av_packet_alloc();
frame = av_frame_alloc();
hw_frame = av_frame_alloc();
p_frame = frame;
begin = NowMs();
while (!ifs.eof())
{
int len = 0;
unsigned char* data = inbuf;
ifs.read((char*)inbuf, sizeof(inbuf));
len = ifs.gcount(); // 读取的字节数
if (ifs.eof())
{
ifs.clear();
ifs.seekg(0, ios::beg);
}
while (len > 0)
{
// 通过 0001 截断输出到AVPacket 如果满足一个 AVPackret 则返回这个 AVPacket 的大小 不足一个AVPactet 则返回余下的数据大小 不足一个AVPactet的数据会被缓存起来
int in_len = av_parser_parse2(parser, context,
&packet->data, &packet->size, // 输出
data, len, // 输入
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0
);
data += in_len;
len -= in_len;
if (packet->size > 0)
{
// cout << packet->size << " " << flush;
//发送packet到解码线程
ret = avcodec_send_packet(context, packet);
if (ret != 0)
{
cerr << "avcodec_send_packet failed!" << endl;
break;
}
/* 获取多帧解码数据 */
ret = avcodec_receive_frame(context, frame);
while (ret == 0)
{
long long curr = NowMs();
cnt++;
if (curr - begin >= 1000)
{
cout << "FPS = " << cnt << endl;
cnt = 0;
begin = curr;
}
if (context->hw_device_ctx != nullptr) // 硬解码
{
// 硬解码转换GPU =》CPU 显存=》内存
// AV_PIX_FMT_NV12, ///< planar YUV 4:2:0, 12bpp, 1 plane for Y and 1 plane for the UV components, which are interleaved (first byte U and the following byte V)
av_hwframe_transfer_data(hw_frame, frame, 0);
p_frame = hw_frame;
}
if (!first_init)
{
view->init(p_frame->width, p_frame->height, static_cast(p_frame->format));
first_init = true;
}
view->drawAVFrame(p_frame); // 渲染
//每次会调用av_frame_unref
ret = avcodec_receive_frame(context, frame);
}
}
}
}
/* 取出缓存数据 */
avcodec_send_packet(context, nullptr);
ret = avcodec_receive_frame(context, frame);
while (ret == 0)
{
view->drawAVFrame(p_frame); // 渲染
ret = avcodec_receive_frame(context, frame);
}
av_frame_free(&hw_frame);
av_frame_free(&frame);
av_packet_free(&packet);
av_parser_close(parser);
avcodec_free_context(&context);
ifs.close();
view->close();
delete view;
return 0;
}
硬件解码出来的 frame 不在内存中,而是在显存中,所以需要使用 av_hwframe_transfer_data 函数将 frame 拷贝到内存中去,此时的 frame 的像素格式为 NV12,我们给 XVideoView 新增了 NV12 的像素格式,使之支持 NV12 的渲染。
使用硬件解码所使用的资源如下图所示:
可以看出硬件解码不使用 CPU 的资源,而是使用 GPU 的资源。
使用软件解码所使用的资源如下图所示:
可以看出软件解码使用的是 CPU 的资源。
使用硬件解码 400x300 分辨率的帧率如下图所示:
使用软件解码 400x300 分辨率 16线程数 的帧率如下图所示:
可以看出使用16线程软件解码的速度比硬件解码的速度快,但是硬件解码不占用 CPU 的资源。