XCodec 封装重构
XDecode 封装
XCodec.h
#pragma once
#include <mutex>
#include <vector>
struct AVCodec;
struct AVCodecContext;
struct AVFrame;
struct AVPacket;
void PrintError(int err);
class XCodec
{
public:
XCodec();
//
/// 创建编码上下文
/// @para codec_id 编码器ID号,对应ffmpeg
/// @return 编码上下文 ,失败返回nullptr
static AVCodecContext* Create(int codec_id, bool is_encoder = true);
//
/// 设置对象的编码器上下文 上下文传递到对象中,资源由XEncode维护
/// 加锁 线程安全
/// @para context 编码器上下文 如果context不为nullptr,则先清理资源
void setContext(AVCodecContext* context);
/
/// 设置编码参数,线程安全
bool setOpt(const char* key, const char* value);
bool setOpt(const char* key, int value);
//
/// 打开编码器 线程安全
bool open();
///
//根据AVCodecContext 创建一个AVFrame,需要调用者释放av_frame_free
AVFrame* createFrame();
protected:
std::mutex m_mtx; // 编码器上下文锁
AVCodecContext* m_context; // 编码器上下文
};
XCodec.cpp
#include "XCodec.h"
#include <iostream>
extern "C" // 指定函数是 C 语言函数,函数目标名不包含重载标识,C++ 中调用 C 函数需要使用 extern "C"
{
// 引用 ffmpeg 头文件
#include "libavcodec/avcodec.h"
#include "libavutil/opt.h"
}
// 预处理指令导入库
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avutil.lib")
using namespace std;
void PrintError(int err)
{
char buf[1024] = { 0 };
av_strerror(err, buf, sizeof(buf) - 1);
cerr << buf << endl;
}
XCodec::XCodec()
{
m_context = nullptr;
}
AVCodecContext* XCodec::Create(int codec_id, bool is_encoder)
{
AVCodec* codec = nullptr;
AVCodecContext* context = nullptr;
if (is_encoder)
{
codec = avcodec_find_encoder(static_cast<AVCodecID>(codec_id));
}
else
{
codec = avcodec_find_decoder(static_cast<AVCodecID>(codec_id));
}
if (codec != nullptr)
{
context = avcodec_alloc_context3(codec);
if (context != nullptr)
{
context->pix_fmt = AV_PIX_FMT_YUV420P; // 源数据像素格式,与编码算法相关
context->time_base = { 1, 25 }; // 帧时间戳的时间单位 pts*time_base = 播放时间(秒) 分数 1/25
context->thread_count = 16; // 编码线程数,可以通过调用系统接口获取cpu核心数量
}
else
{
cerr << "avcodec_alloc_context3 failed!" << endl;
}
}
else
{
cerr << "avcodec_find_encoder failed! " << "avcodec_id = " << codec_id << endl;
}
return context;
}
void XCodec::setContext(AVCodecContext* context)
{
if (context != nullptr)
{
unique_lock<mutex> context_mtx(m_mtx);
if (m_context != nullptr)
{
avcodec_free_context(&m_context);
}
m_context = context;
}
}
bool XCodec::setOpt(const char* key, const char* value)
{
bool ret = false;
int err = 0;
unique_lock<mutex> context_mtx(m_mtx);
if ((m_context != nullptr) && (key != nullptr) && (value != nullptr))
{
err = av_opt_set(m_context->priv_data, key, value, 0);
if (err == 0)
{
ret = true;
}
else
{
PrintError(err);
}
}
return ret;
}
bool XCodec::setOpt(const char* key, int value)
{
bool ret = false;
int err = 0;
unique_lock<mutex> context_mtx(m_mtx);
if ((m_context != nullptr) && (key != nullptr))
{
err = av_opt_set_int(m_context->priv_data, key, value, 0);
if (err == 0)
{
ret = true;
}
else
{
PrintError(err);
}
}
return ret;
}
bool XCodec::open()
{
bool ret = false;
int err = 0;
unique_lock<mutex> context_mtx(m_mtx);
if (m_context != nullptr)
{
err = avcodec_open2(m_context, nullptr, nullptr);;
if (err == 0)
{
ret = true;
}
else
{
PrintError(err);
}
}
return ret;
}
AVFrame* XCodec::createFrame()
{
AVFrame* ret = nullptr;
int err = 0;
unique_lock<mutex> context_mtx(m_mtx);
if (m_context != nullptr)
{
ret = av_frame_alloc();
ret->width = m_context->width;
ret->height = m_context->height;
ret->format = m_context->pix_fmt;
err = av_frame_get_buffer(ret, 0);
if (err != 0)
{
PrintError(err);
av_frame_free(&ret);
}
}
return ret;
}
XEncode.h
#pragma once
#include "XCodec.h"
struct AVCodec;
struct AVCodecContext;
struct AVFrame;
struct AVPacket;
class XEncode : public XCodec
{
public:
//
/// 编码数据 线程安全 每次新创建AVPacket
/// @para frame 空间由用户维护
/// @return 失败范围nullptr 返回的AVPacket用户需要通过av_packet_free 清理
AVPacket* encode(AVFrame* frame);
//
//返回所有编码缓存中AVPacket
std::vector<AVPacket*> end();
};
XEncode.cpp
#include "XEncode.h"
#include <iostream>
extern "C" // 指定函数是 C 语言函数,函数目标名不包含重载标识,C++ 中调用 C 函数需要使用 extern "C"
{
// 引用 ffmpeg 头文件
#include "libavcodec/avcodec.h"
#include "libavutil/opt.h"
}
using namespace std;
AVPacket* XEncode::encode(AVFrame* frame)
{
AVPacket* ret = nullptr;
int err = 0;
unique_lock<mutex> context_mtx(m_mtx);
if ((m_context != nullptr))
{
err = avcodec_send_frame(m_context, frame);
if (err == 0)
{
ret = av_packet_alloc();
err = avcodec_receive_packet(m_context, ret);
// 没能够获取编码数据
if (err != 0)
{
if ((err != AVERROR(EAGAIN)) && (err != AVERROR_EOF))
{
PrintError(err);
}
av_packet_free(&ret);
}
}
else
{
PrintError(err);
}
}
return ret;
}
vector<AVPacket*> XEncode::end()
{
vector<AVPacket*> ret;
int err = 0;
unique_lock<mutex> context_mtx(m_mtx);
if (m_context != nullptr)
{
AVPacket* packet = nullptr;
err = avcodec_send_frame(m_context, nullptr); // 发送NULL 获取缓冲
if (err == 0)
{
while (err == 0)
{
packet = av_packet_alloc();
err = avcodec_receive_packet(m_context, packet);
if (err == 0)
{
ret.push_back(packet);
}
else
{
if ((err != AVERROR(EAGAIN)) && (err != AVERROR_EOF))
{
PrintError(err);
}
av_packet_free(&packet);
}
}
}
else
{
PrintError(err);
}
}
return ret;
}
XDecode.h
#pragma once
#include "XCodec.h"
#include <vector>
struct AVFrame;
struct AVPacket;
class XDecode : public XCodec
{
public:
初始化硬件加速 4 AV_HWDEVICE_TYPE_DXVA2
bool initHW(int type = 4);
bool send(AVPacket* packet);
bool recv(AVFrame* frame);
std::vector<AVFrame*> end();
};
XDecode.cpp
#include "XDecode.h"
#include <iostream>
extern "C" // 指定函数是 C 语言函数,函数目标名不包含重载标识,C++ 中调用 C 函数需要使用 extern "C"
{
// 引用 ffmpeg 头文件
#include "libavcodec/avcodec.h"
#include "libavutil/opt.h"
}
using namespace std;
bool XDecode::initHW(int type)
{
bool ret = false;
int err = 0;
AVBufferRef* ctx_ref = nullptr;
unique_lock<mutex> context_mtx(m_mtx);
err = av_hwdevice_ctx_create(&ctx_ref, static_cast<AVHWDeviceType>(type), nullptr, nullptr, 0);
if (err == 0)
{
m_context->hw_device_ctx = ctx_ref;
}
else
{
PrintError(err);
}
return ret;
}
bool XDecode::send(AVPacket* packet)
{
bool ret = false;
int err = 0;
unique_lock<mutex> context_mtx(m_mtx);
if (m_context != nullptr)
{
err = avcodec_send_packet(m_context, packet);
if (err == 0)
{
ret = true;
}
else
{
PrintError(err);
}
}
return ret;
}
bool XDecode::recv(AVFrame* frame)
{
bool ret = false;
int err = 0;
unique_lock<mutex> context_mtx(m_mtx);
if ((m_context != nullptr) && (frame != nullptr))
{
if (m_context->hw_device_ctx == nullptr)
{
err = avcodec_receive_frame(m_context, frame);
}
else // GPU 解码
{
AVFrame* tmp = av_frame_alloc();
err = avcodec_receive_frame(m_context, tmp);
if (err == 0)
{
err = av_hwframe_transfer_data(frame, tmp, 0); // 显存转内存 GPU =》 CPU
}
av_frame_free(&tmp);
}
if (err == 0)
{
ret = true;
}
else
{
if ((err != AVERROR(EAGAIN)) && (err != AVERROR_EOF))
{
PrintError(err);
}
}
}
return ret;
}
std::vector<AVFrame*> XDecode::end()
{
vector<AVFrame*> ret;
int err = 0;
unique_lock<mutex> context_mtx(m_mtx);
if (m_context != nullptr)
{
AVFrame* frame = nullptr;
err = avcodec_send_packet(m_context, nullptr); // 发送NULL 获取缓冲
if (err == 0)
{
while (err == 0)
{
frame = av_frame_alloc();
if (m_context->hw_device_ctx == nullptr)
{
err = avcodec_receive_frame(m_context, frame);
}
else // GPU 解码
{
AVFrame* tmp = av_frame_alloc();
err = avcodec_receive_frame(m_context, tmp);
if (err == 0)
{
err = av_hwframe_transfer_data(frame, tmp, 0);
}
av_frame_free(&tmp);
}
if (err == 0)
{
ret.push_back(frame);
}
else
{
if ((err != AVERROR(EAGAIN)) && (err != AVERROR_EOF))
{
PrintError(err);
}
av_frame_free(&frame);
}
}
}
else
{
PrintError(err);
}
}
return ret;
}
120_test_xdecode.cpp
#include <iostream>
#include <fstream>
#include "XVideoView.h"
#include "XSDL.h"
#include "XDecode.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;
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
XDecode de;
vector<AVFrame*> vec;
ifs.open(file_name, ios::in | ios::binary);
if (!ifs)
{
return -1;
}
context = de.Create(codec_id, false);
if (context != nullptr)
{
de.setContext(context);
de.initHW();
de.open();
}
// 分割上下文
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();
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到解码线程
if (!de.send(packet))
{
cerr << "avcodec_send_packet failed!" << endl;
break;
}
/* 获取多帧解码数据 */
while (de.recv(frame))
{
long long curr = NowMs();
cnt++;
if (curr - begin >= 1000)
{
cout << "FPS = " << cnt << endl;
cnt = 0;
begin = curr;
}
if (!first_init)
{
view->init(frame->width, frame->height, static_cast<XVideoView::PixelFormat>(frame->format));
first_init = true;
}
view->drawAVFrame(frame); // 渲染
}
}
}
}
/* 取出缓存数据 */
vec = de.end();
for (int i = 0; i < vec.size(); i++)
{
view->drawAVFrame(vec[i]);
av_frame_free(&vec[i]);
}
av_frame_free(&frame);
av_packet_free(&packet);
av_parser_close(parser);
avcodec_free_context(&context);
ifs.close();
view->close();
delete view;
return 0;
}
该测试程序将 .h264 文件解析出一个个 AVPacket,然后解码出 AVFrame,并进行渲染。