头文件:
xencode.h
cpp
#pragma once
#include <mutex>
#include<vector>
struct AVCodecContext;
struct AVPacket;
struct AVFrame;
class XEncode
{
public:
//
/// 创建编码上下文
/// @para codec_id 编码器ID号,对应ffmpeg
/// @return 编码上下文 ,失败返回nullptr
static AVCodecContext* Create(int codec_id);
//
/// 设置对象的编码器上下文 上下文传递到对象中,资源由XEncode维护
/// 加锁 线程安全
/// @para c 编码器上下文 如果c_不为nullptr,则先清理资源
void set_c(AVCodecContext* c);
/
/// 设置编码参数,线程安全
bool SetOpt(const char* key, const char* val);
bool SetOpt(const char* key, int val);
//
/// 打开编码器 线程安全
bool Open();
//
/// 编码数据 线程安全 每次新创建AVPacket
/// @para frame 空间由用户维护
/// @return 失败范围nullptr 返回的AVPacket用户需要通过av_packet_free 清理
AVPacket* Encode(const AVFrame* frame);
///
//根据AVCodecContext 创建一个AVFrame,需要调用者释放av_frame_free
AVFrame* CreateFrame();
//返回所有编码缓存中的AVPacket,解决丢帧问题
std::vector<AVPacket*> End();
private:
AVCodecContext* c_ = nullptr; //编码器上下文
std::mutex mux_; //编码器上下文锁
};
源文件:
xencode.cpp
cpp
#include "xencode.h"
#include <iostream>
using namespace std;
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
}
//预处理指令导入库
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"avutil.lib")
static void PrintErr(int err)
{
char buf[1024] = { 0 };
av_strerror(err, buf, sizeof(buf) - 1);
cerr << buf << endl;
}
//
/// 创建编码上下文
/// @para codec_id 编码器ID号,对应ffmpeg
/// @return 编码上下文 ,失败返回nullptr
AVCodecContext* XEncode::Create(int codec_id)
{
//1 找到编码器
auto codec = avcodec_find_encoder((AVCodecID)codec_id);
if (!codec)
{
cerr << "avcodec_find_encoder failed!" << codec_id << endl;
return nullptr;
}
//创建上下文
auto c = avcodec_alloc_context3(codec);
if (!c)
{
cerr << "avcodec_alloc_context3 failed!" << codec_id << endl;
return nullptr;
}
//设置参数默认值
c->time_base = { 1,25 };
c->pix_fmt = AV_PIX_FMT_YUV420P;
c->thread_count = 16;
return c;
}
//
/// 设置对象的编码器上下文 上下文传递到对象中,资源由XEncode维护
/// 加锁 线程安全
/// @para c 编码器上下文 如果c_不为nullptr,则先清理资源
void XEncode::set_c(AVCodecContext* c)
{
unique_lock<mutex>lock(mux_);
if (c_)
{
avcodec_free_context(&c_);
}
this->c_ = c;
}
bool XEncode::SetOpt(const char* key, const char* val)
{
unique_lock<mutex>lock(mux_);
if (!c_)return false;
auto re = av_opt_set(c_->priv_data, key, val, 0);
if (re != 0)
{
cerr << "av_opt_set failed!";
PrintErr(re);
}
return true;
}
bool XEncode::SetOpt(const char* key, int val)
{
unique_lock<mutex>lock(mux_);
if (!c_)return false;
auto re = av_opt_set_int(c_->priv_data, key, val, 0);
if (re != 0)
{
cerr << "av_opt_set failed!";
PrintErr(re);
}
return true;
}
//
/// 打开编码器 线程安全
bool XEncode::Open()
{
unique_lock<mutex>lock(mux_);
if (!c_)return false;
auto re = avcodec_open2(c_, NULL, NULL);
if (re != 0)
{
PrintErr(re);
return false;
}
return true;
}
//
/// 编码数据 线程安全 每次新创建AVPacket
/// @para frame 空间由用户维护
/// @return 失败范围nullptr 返回的AVPacket用户需要通过av_packet_free 清理
AVPacket* XEncode::Encode(const AVFrame* frame)
{
if (!frame)return nullptr;
unique_lock<mutex>lock(mux_);
if (!c_)return nullptr;
//发送到编码线程
auto re = avcodec_send_frame(c_, frame);
if (re != 0)return nullptr;
auto pkt = av_packet_alloc();
//接收编码线程数据
re = avcodec_receive_packet(c_, pkt);
if (re == 0)
{
return pkt;
}
av_packet_free(&pkt);
if (re == AVERROR(EAGAIN) || re == AVERROR_EOF)
{
return nullptr;
}
if (re < 0)
{
PrintErr(re);
}
return nullptr;
}
//返回所有编码缓存中的AVPacket,解决丢帧问题
std::vector<AVPacket*> XEncode::End()
{
std::vector<AVPacket*> res;//类似一个数组,存的是AVPacket 对象指针
unique_lock<mutex>lock(mux_);
if (!c_)return res;
auto re = avcodec_send_frame(c_, NULL);//发送NULL到编码器中,获取编码器中的缓存区
if (re != 0)return res;
while (re >= 0)
{
auto pkt = av_packet_alloc();
re = avcodec_receive_packet(c_,pkt);//avpacket从编码器c_中读取编码数据。
if (re != 0)
{
av_packet_free(&pkt);
break;
}
res.push_back(pkt);
}
return res;
}
///
//根据AVCodecContext 创建一个AVFrame,需要调用者释放av_frame_free
AVFrame* XEncode::CreateFrame()
{
unique_lock<mutex>lock(mux_);
if (!c_)return nullptr;
auto frame = av_frame_alloc();
frame->width = c_->width;
frame->height = c_->height;
frame->format = c_->pix_fmt;
auto re = av_frame_get_buffer(frame, 0);
if (re != 0)
{
av_frame_free(&frame);
PrintErr(re);
return nullptr;
}
return frame;
}
main.cpp
cpp
#include <iostream>
#include <fstream>
#include "xencode.h"
using namespace std;
extern "C" { //指定函数是c语言函数,函数名不包含重载标注
//引用ffmpeg头文件
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
}
int main(int argc, char* argv[])
{
string filename = "400_300_25_preset";
AVCodecID codec_id = AV_CODEC_ID_H264;
if (argc > 1)
{
string codec = argv[1];
if (codec == "h265" || codec == "hevc")
{
codec_id = AV_CODEC_ID_HEVC;
}
}
if (codec_id == AV_CODEC_ID_H264)
{
filename += ".h264";
}
else if (codec_id == AV_CODEC_ID_HEVC)
{
filename += ".h265";
}
//输出文件
ofstream ofs;
ofs.open(filename, ios::binary);
XEncode en;
auto c = en.Create(codec_id);
c->width = 400;
c->height = 300;
en.set_c(c);
en.SetOpt("crf", 18); //恒定速率因子(CRF)
en.Open();
auto frame = en.CreateFrame();
int count = 0;//写入文件的帧数,看是否有500,SPS PPS IDR放在一帧中
for (int i = 0; i < 500; i++)
{
//生成AVFrame 数据 每帧数据不同
//Y
for (int y = 0; y < c->height; y++)
{
for (int x = 0; x < c->width; x++)
{
frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
}
}
//UV
for (int y = 0; y < c->height / 2; y++)
{
for (int x = 0; x < c->width / 2; x++)
{
frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;
frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;
}
}
frame->pts = i;//显示的时间
auto pkt = en.Encode(frame);
if (pkt)
{
count++;
ofs.write((char*)pkt->data, pkt->size);
av_packet_free(&pkt);
}
}
auto missing_pkt = en.End();
for (auto pkt : missing_pkt)
{
count++;
ofs.write((char*)pkt->data, pkt->size);
av_packet_free(&pkt);
}
ofs.close();
en.set_c(nullptr);
cout << "encode" << count << endl;
getchar();
return 0;
}
讨论:
1.上一篇文章提到的丢帧问题得到了解决,原因是编码器缓存不够
2.封装使得代码更加简洁