当前位置: 首页 > article >正文

FFmpeg 4.3 音视频-多路H265监控录放C++开发十三.3:将AVFrame转换成AVPacket.封装。代码改动

请封装,保留ffmpeg结构体

现成安全处理

获取编码缓冲区数据

xencoder.h

#pragma once

#include <mutex>
#include <vector>

struct AVCodecContext;
struct AVFrame;
struct AVPacket;
class XEncoder
{

public:
	AVCodecContext * Create(int code_id);

	//
	/// 设置对象的编码器上下文 上下文传递到对象中,资源由XEncode维护
	/// 加锁 线程安全
	/// @para avcodecContext 编码器上下文 如果  _avcodecContext 不为nullptr,则先清理资源
	/// 问题是:为什么要这么设计呢?
	/// 我们要把通过Create方法创建的avcodecContext 添加到xencoder对象中去。
	void SetAVCodecContext(AVCodecContext* avcodecContext);


	/
/// 设置编码参数,线程安全
	bool SetAVCodecContextOpt(const char* key, const char* val);
	bool SetAVCodecContextOptInt(const char* key, int val);

	//
/// 打开编码器 线程安全
	bool Open();

	///
//根据AVCodecContext 创建一个AVFrame,需要调用者释放av_frame_free
	AVFrame* CreateFrame();


	//
/// 编码数据 线程安全 每次新创建AVPacket
/// @para frame 空间由用户维护
/// @return 失败范围nullptr 返回的AVPacket用户需要通过av_packet_free 清理
	std::vector<AVPacket*>  Encode(const AVFrame* frame);


	/// 编码数据 线程安全 每次新创建AVPacket
/// @para frame 空间由用户维护
/// @return 失败范围nullptr 返回的AVPacket用户需要通过av_packet_free 清理
	std::vector<AVPacket*>  FlushEncode();

private:
	std::mutex _mutex;
	AVCodecContext* _avcodecContext = nullptr;
};

xencoder.cpp

#include "xencoder.h"
#include <iostream>
using namespace std;

extern "C" {
#include "libavcodec/avcodec.h"
#include "libavutil/opt.h"
}

void printErr(int errorcode) {
	char buf[1024] = { 0 };
	av_strerror(errorcode, buf, sizeof(buf) - 1);
	cout << buf << endl;
}
AVCodecContext* XEncoder::Create(int code_id)
{
	AVCodec* avcodec = avcodec_find_encoder((AVCodecID)code_id);
	if (avcodec == nullptr) {
		cout << "AVCodecContext* XEncoder::Create error avcodec_find_encoder == nullptr code_id = " << code_id << endl;
		return nullptr;
	}
	AVCodecContext * avcodecContext = avcodec_alloc_context3(avcodec);
	if (avcodecContext == nullptr) {
		cout << "AVCodecContext* XEncoder::Create error avcodecContext == nullptr" << endl;
		return nullptr;
	}
	//如果存在,则设定一些默认参数
	avcodecContext->time_base = {1,25};
	avcodecContext->pix_fmt = AV_PIX_FMT_YUV420P;
	avcodecContext->thread_count = 8;// 这个一般要从 CPU的数量获得

	return avcodecContext;
}

void XEncoder::SetAVCodecContext(AVCodecContext* avcodecContext)
{
	unique_lock<mutex> lock(_mutex);
	//如果在这之前有存储 avcodecContext到 this,那么要保证之前存储的avcodecContext被释放了,不然会有问题
	if (this->_avcodecContext != nullptr) {
		avcodec_free_context(&this->_avcodecContext);
	}
	//保存现在传递进来的
	this->_avcodecContext = avcodecContext;
}

bool XEncoder::SetAVCodecContextOpt(const char* key, const char* val)
{
	int ret = 0;
	unique_lock<mutex> lock(_mutex);
	if (this->_avcodecContext == nullptr) {
		cout << "SetAVCodecContextOpt return false because this->_avcodecContext == nullptr key = " << key <<"  val = " << val << endl;
		return false;
	}
	ret = av_opt_set(this->_avcodecContext->priv_data, key, val, 0);
	if (ret != 0) {
		cout << "av_opt_set error key = " << key << "val = " << val << endl;
		///说明有error
		printErr(ret);
		return false;
	}
	return true;
}

bool XEncoder::SetAVCodecContextOptInt(const char* key, int val)
{
	int ret = 0;
	unique_lock<mutex> lock(_mutex);
	if (this->_avcodecContext == nullptr) {
		cout << "SetAVCodecContextOptInt return false because this->_avcodecContext == nullptr key = " << key << "  val = " << val << endl;
		return false;
	}
	ret = av_opt_set_int(this->_avcodecContext->priv_data, key, val, 0);
	if (ret != 0) {
		cout << "av_opt_set_int error key = " << key << "val = " << val << endl;
		///说明有error
		printErr(ret);
		return false;
	}
	return true;
}

bool XEncoder::Open()
{
	int ret = 0;
	unique_lock<mutex> lock(_mutex);
	if (this->_avcodecContext == nullptr) {
		cout << "Open return false because this->_avcodecContext == nullptr " <<  endl;
		return false;
	}
	auto re = avcodec_open2(this->_avcodecContext, NULL, NULL);
	if (re != 0)
	{
		printErr(re);
		return false;
	}
	return true;
}

AVFrame* XEncoder::CreateFrame()
{
	unique_lock<mutex>lock(_mutex);
	if (this->_avcodecContext == nullptr) {
		cout << "CreateFrame return false because this->_avcodecContext == nullptr "  << endl;
		return nullptr;
	}
	auto frame = av_frame_alloc();
	frame->width = this->_avcodecContext->width;
	frame->height = this->_avcodecContext->height;
	frame->format = this->_avcodecContext->pix_fmt;
	auto re = av_frame_get_buffer(frame, 0);
	if (re != 0)
	{
		av_frame_free(&frame);
		printErr(re);
		return nullptr;
	}
	return frame;
}

vector<AVPacket*> XEncoder::Encode(const AVFrame* frame)
{

	vector<AVPacket*> vecpacket;
	int ret = 0;
	if (frame == nullptr) {
		cout << "Encode return false because frame == nullptr " << endl;
		return vecpacket;
	}
	unique_lock<mutex>lock(_mutex);
	if (this->_avcodecContext == nullptr) {
		cout << "Encode return false because this->_avcodecContext == nullptr " << endl;
		return vecpacket;
	}

	ret = avcodec_send_frame(this->_avcodecContext, frame);
	if (ret != 0) {
		//说明发送到 编码器的时候就有问题.那么就让继续读取下一帧,也可以直接退出。
		return vecpacket;
	}

	while (true) {
		AVPacket* avpacket = av_packet_alloc();
		ret = avcodec_receive_packet(this->_avcodecContext, avpacket);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
			//在失败后要记得 释放avpacket
			//cout << "avcodec_receive_packet ret == AVERROR(EAGAIN) || ret == AVERROR_EOF RET = " << ret << endl;

			av_packet_free(&avpacket);
			return vecpacket;
		}
		if (ret < 0) {
			cout << "avcodec_receive_packet error" << endl;
			//在失败后要记得 释放avpacket
			av_packet_free(&avpacket);
			printErr(ret);
			return vecpacket;
		}
		if (ret == 0) {
			vecpacket.push_back(avpacket);
			//cout << "debug point11" << endl;
		}
	}

	return vecpacket;
}


vector<AVPacket*> XEncoder::FlushEncode()
{
	int ret = 0;
	vector<AVPacket*> vecpacket;
	unique_lock<mutex>lock(_mutex);
	if (this->_avcodecContext == nullptr) {
		cout << "FlushEncode return false because this->_avcodecContext == nullptr " << endl;
		return vecpacket;
	}

	ret = avcodec_send_frame(this->_avcodecContext, NULL);
	if (ret != 0) {
		//说明发送到 编码器的时候就有问题.那么就让继续读取下一帧,也可以直接退出。
		return vecpacket;
	}

	while (true) {
		AVPacket* avpacket = av_packet_alloc();
		ret = avcodec_receive_packet(this->_avcodecContext, avpacket);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
			//在失败后要记得 释放avpacket
			//cout << "avcodec_receive_packet ret == AVERROR(EAGAIN) || ret == AVERROR_EOF RET = " << ret << endl;

			av_packet_free(&avpacket);
			return vecpacket;
		}
		if (ret < 0) {
			cout << "avcodec_receive_packet error" << endl;
			//在失败后要记得 释放avpacket
			av_packet_free(&avpacket);
			printErr(ret);
			return vecpacket;
		}
		if (ret == 0) {
			vecpacket.push_back(avpacket);
			cout << "debug point 2" << endl;
		}
	}

	return vecpacket;
}

mian.cpp

#include <iostream>
#include <fstream>
#include "xencoder.h"
extern "C" {
	#include "libavcodec/avcodec.h"
	#include "libavcodec/codec.h"
    #include "libavutil/opt.h"
	#include "libavutil/imgutils.h"
}
using namespace std;

char* yuvfilename = (char *)"400_300_25.yuv";
//char* h264filename = (char*)"400_300_25preset_ultrafast.h264";
char* h264filename = (char*)"400_300_25xencoder.h264";
ifstream fin;
ofstream fout;

AVCodecID codec_id_h264 = AV_CODEC_ID_H264;
AVCodecID codec_id_h265 = AV_CODEC_ID_H265;
AVCodecID codec_id_h265_1 = AV_CODEC_ID_HEVC;
AVCodec* avcodec = nullptr;
AVCodecContext* avcodecContext = nullptr;
AVFrame* avframe = nullptr;
uint8_t* frame_bytes_buf = nullptr;
void freeAndClose();
/***
*
* 目的是从一个YUV文件中,读取YUV数据到AVFrame
* 然后通过 H264编码器将AVFrame 转换成AVPacket
* 将每一帧的AVPacket 直接写入 xxx.h264文件,编码后的数据有带startcode
* h264文件可以通过 VLC 播放器播放,
***/
int main(int argc, char * argv[]) {
	cout << "014ChangeAVFrameToAVPakcet start" << endl;
	int ret = 0;

	//打开要读取的YUV 文件
	fin.open(yuvfilename, ios_base::binary);
	if (!fin) {
		ret = -1;
		cout << "open yuv file error yuvfilename = " << yuvfilename << endl;
		freeAndClose();
		return ret;
	}
	//打开要写入的h264文件
	fout.open(h264filename, ios_base::binary);
	if (!fout) {
		ret = -2;
		cout << "open h264 file error h264filename = " << h264filename << endl;
		freeAndClose();
		return ret;
	}
	XEncoder en;
	avcodecContext = en.Create(codec_id_h264);
	avcodecContext->width = 400;
	avcodecContext->height = 300;
	en.SetAVCodecContext(avcodecContext);
	en.SetAVCodecContextOptInt("crf", 18);
	en.Open();

	avframe = en.CreateFrame();

	//计算一张图片的大小
	//先计算出一张图片的大小
	int frame_bytes_bufsize = av_image_get_buffer_size(
		(enum AVPixelFormat)avframe->format,
		avframe->width,
		avframe->height,
		1);
	//我们在分配对应大小的空间
	frame_bytes_buf = (uint8_t*)malloc(frame_bytes_bufsize);
	//开始读取文件
	int posnum = 1;
	while (!fin.eof()) {
		
		memset(frame_bytes_buf, 0, frame_bytes_bufsize);
		fin.read((char *)frame_bytes_buf, frame_bytes_bufsize);
		//将 frame_bytes_buf中的数据填充到avframe中,返回值为实际填充的大小
		int need_size = av_image_fill_arrays(avframe->data,
			avframe->linesize,
			frame_bytes_buf,
			(enum AVPixelFormat)avframe->format,
			avframe->width,
			avframe->height,
			1);
		//如果实际填充的大小 和 需要填充的大小不相等,说明有问题
		if (need_size != frame_bytes_bufsize) {
			continue;
		}
		else {
			avframe->pts = posnum++;
			//说明正确的读取到的数据
			vector<AVPacket*> vectorAVPacket = en.Encode(avframe);
			for (size_t i = 0; i < vectorAVPacket.size(); i++)
			{
				fout.write((char*)vectorAVPacket[i]->data, vectorAVPacket[i]->size);
				av_packet_free(&vectorAVPacket[i]);
				//cout << "debug point 123" << endl;
			}
		}
	}

	vector<AVPacket*> vectorFlushAVPacket = en.FlushEncode();
	for (size_t i = 0; i < vectorFlushAVPacket.size(); i++)
	{
		fout.write((char*)vectorFlushAVPacket[i]->data, vectorFlushAVPacket[i]->size);
		av_packet_free(&vectorFlushAVPacket[i]);
		//cout << "debug point 123" << endl;
	}

	freeAndClose();
	return ret;
}

void freeAndClose() {
	if (frame_bytes_buf!=nullptr) {
		free(frame_bytes_buf);
		frame_bytes_buf = nullptr;
	}

	if (avframe != nullptr) {
		av_frame_free(&avframe);
	}
	if (avcodecContext != nullptr) {
		avcodec_free_context(&avcodecContext);
	}
	if (!fout) {
		fout.close();
	}

	if (!fin) {
		fin.close();
	}

}


http://www.kler.cn/a/396805.html

相关文章:

  • 无人机检测车辆——多目标检测
  • docker与大模型(口语化原理和实操讲解)
  • 如何保证MySQL与Redis缓存的数据一致性?
  • 关系型数据库和非关系型数据库详解
  • 【C语言】实现二维数组按列排序
  • 除了 TON, 哪些公链在争夺 Telegram 用户?数据表现如何?
  • 深入理解 MySQL 大小写敏感性:配置、问题与实践指南20241115
  • 每日小题--买股票的最佳时机
  • vue2.x elementui 固定顶部、左侧菜单与面包屑,自适应 iframe 页面布局
  • flutter字体大小切换案例 小字体,标准字体,大字体,超大字体案例
  • 基于Java Springboot图书馆管理系统
  • (一)- DRM架构
  • 微信小程序设置屏幕安全距离
  • 表单自动化填写-JavaScript脚本
  • TypeORM在Node.js中的高级应用
  • 深度学习的核心思想
  • 【freertos】FreeRTOS时间管理
  • 代码随想录算法训练营第三十一天| 56. 合并区间 、738.单调递增的数字 。c++转java
  • 右键添加获取可供WSL使用的路径,对windows文件夹也适用,即获取符合Linux规范的路径内容给WSL
  • 搭建高效稳定的ChatGPT网络环境:从网络专线到IP地址管理的全流程解析
  • SQL 处理数列
  • C++中特殊类设计/单例模式
  • Javascript_设计模式(二)
  • 将Excel文件的两个表格经过验证后分别读取到Excel表和数据库
  • HTML之图片和超链接的学习记录
  • 124. 二叉树中的最大路径和【 力扣(LeetCode) 】