XEncode.h
#pragma once
#include <mutex>
#include <vector>
struct AVCodec;
struct AVCodecContext;
struct AVFrame;
struct AVPacket;
class XEncode
{
public:
//
/// 创建编码上下文
/// @para codec_id 编码器ID号,对应ffmpeg
/// @return 编码上下文 ,失败返回nullptr
static AVCodecContext* Create(int codec_id);
//
/// 设置对象的编码器上下文 上下文传递到对象中,资源由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();
//
/// 编码数据 线程安全 每次新创建AVPacket
/// @para frame 空间由用户维护
/// @return 失败范围nullptr 返回的AVPacket用户需要通过av_packet_free 清理
AVPacket* encode(AVFrame* frame);
///
//根据AVCodecContext 创建一个AVFrame,需要调用者释放av_frame_free
AVFrame* createFrame();
//
//返回所有编码缓存中AVPacket
std::vector<AVPacket*> end();
private:
std::mutex m_mtx; // 编码器上下文锁
AVCodecContext* m_context; // 编码器上下文
};
XEncode.cpp
#include "XEncode.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;
static void PrintError(int err)
{
char buf[1024] = { 0 };
av_strerror(err, buf, sizeof(buf) - 1);
cerr << buf << endl;
}
AVCodecContext* XEncode::Create(int codec_id)
{
AVCodec* codec = nullptr;
AVCodecContext* context = nullptr;
codec = avcodec_find_encoder(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 XEncode::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 XEncode::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 XEncode::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 XEncode::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;
}
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;
}
AVFrame* XEncode::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;
}
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;
}
116_test_xencode.cpp
#include <iostream>
#include <fstream>
#include "XEncode.h"
using namespace std;
extern "C" // 指定函数是 C 语言函数,函数目标名不包含重载标识,C++ 中调用 C 函数需要使用 extern "C"
{
// 引用 ffmpeg 头文件
#include "libavcodec/avcodec.h"
#include "libavutil/opt.h"
}
int main(int argc, char* argv[])
{
XEncode en;
AVCodecContext* context = nullptr;
AVFrame* frame = nullptr;
AVPacket* packet = nullptr;
vector<AVPacket*> vec;
AVCodecID codec_id = AV_CODEC_ID_H264;
ofstream ofs;
string file_name = "400_300_25_preset";
int cnt = 0; // 写入文件的帧数 SPS PPS IDR放在一帧中
int r = 0;
if (argc > 1)
{
string suffix = argv[1];
cout << "suffix = " << suffix << endl;
if ((suffix == "h265") || (suffix == "hevc"))
{
codec_id = AV_CODEC_ID_HEVC;
}
}
if (codec_id == AV_CODEC_ID_H264)
{
file_name += ".h264";
}
else if (codec_id == AV_CODEC_ID_HEVC)
{
file_name += ".h265";
}
ofs.open(file_name, ios::out | ios::binary);
if (!ofs)
{
cout << "open " << file_name << " failed!" << endl;
return -1;
}
context = en.Create(codec_id);
context->width = 400;
context->height = 300;
en.setContext(context);
en.setOpt("crf", 18); // 恒定速率因子(CRF)
if (!en.open())
{
cout << "open context failed!" << endl;
}
frame = en.createFrame();
if (frame == nullptr)
{
return -1;
}
/* 十秒视频 250帧 */
for (int i = 0; i < 250; i++)
{
// 生成AVFrame 数据 每帧数据不同
// Y
for (int h = 0; h < frame->height; h++)
{
for (int w = 0; w < frame->width; w++)
{
frame->data[0][frame->linesize[0] * h + w] = h + w + i * 3;
}
}
// UV
for (int h = 0; h < frame->height / 2; h++)
{
for (int w = 0; w < frame->width / 2; w++)
{
frame->data[1][frame->linesize[1] * h + w] = 128 + h + i * 2;
frame->data[2][frame->linesize[2] * h + w] = 64 + w + i * 5;
}
}
frame->pts = i; // 显示的时间
packet = en.encode(frame);
if (packet != nullptr)
{
cnt++;
ofs.write((char*)packet->data, packet->size);
av_packet_free(&packet);
}
}
vec = en.end(); // 获取之前还没编码好的 AVPacket
for (int i = 0; i < vec.size(); i++)
{
packet = vec[i];
cnt++;
ofs.write((char*)packet->data, packet->size);
av_packet_free(&packet);
}
cout << "cnt = " << cnt << endl;
ofs.close();
av_frame_free(&frame);
avcodec_free_context(&context);
return 0;
}
我们使用了 XEncode 类对 ffmpeg 的编码进行了简单的封装。
测试结果
我们成功将250帧的原始数据进行编码,然后将编码后的码流数据写入到文件中。