您的位置:首页 > 财经 > 金融 > ffmpeg视频编码原理和实战-(5)对编码过程进行封装并解决丢帧问题

ffmpeg视频编码原理和实战-(5)对编码过程进行封装并解决丢帧问题

2024/12/23 8:34:58 来源:https://blog.csdn.net/m0_51386664/article/details/139508410  浏览:    关键词:ffmpeg视频编码原理和实战-(5)对编码过程进行封装并解决丢帧问题

头文件:

xencode.h

#pragma once
#include <mutex>
#include<vector>
struct AVCodecContext;
struct AVPacket;
struct AVFrame;
class XEncode
{
public:///// 创建编码上下文/// @para codec_id 编码器ID号,对应ffmpeg/// @return 编码上下文 ,失败返回nullptrstatic 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_freeAVFrame* CreateFrame();//返回所有编码缓存中的AVPacket,解决丢帧问题std::vector<AVPacket*> End();private:AVCodecContext* c_ = nullptr;  //编码器上下文std::mutex mux_;               //编码器上下文锁
};

源文件:

xencode.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


#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 数据 每帧数据不同//Yfor (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;}}//UVfor (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.封装使得代码更加简洁

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com