ffmpeg编解码器包装类

Posted 顾文繁

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ffmpeg编解码器包装类相关的知识,希望对你有一定的参考价值。

对视频处理过程中,有编码,解码过程,两个过程在ffmpeg实现过程中有相同的处理函数,为了降低代码冗余,封装两个过程显得很有必要。

编码,解码两个过程的处理函数有:

  1. avcodec_find_encoder() 找到编码器
  2. avcodec_find_encoder() 找到解码器
  3. avcodec_alloc_context() 生成编码器上下文
  4. avcodec_open2打开编码器上下文
  5. avcodec_send_frame() 发送帧到线程中压缩(编码)
  6. avcodec_send_packet() 发送帧到线程中压缩(解码)
  7. avcodec_receive_packet() 接受缓冲中接受压缩后的packet(编码)
  8. avcodec_receive_frame() 接受缓冲中接受压缩后的packet(解码)

还有申请两个编解码数据载体,AVFrame和AVPacket

  1. av_frame_alloc() 申请AVFrame对象
  2. av_frame_get_buffer() 给AVFrame对象分配内部空间
  3. av_packet_alloc() 申请AVPacket对象

同时别忘记很重要的内存释放

  1. av_packet_free();
  2. av_frame_free();
  3. 12.avcodec_free_context();
    对于AVPacket,avcodec_receive_packet()后,输入的AVPacket会有数据,在内部会申请空间,这需要调用av_packet_unref()擦除AVPacket数据,不然会导致内存泄漏。

编解码的基本使用

编码器的使用

#pragma once
extern "C"
{
#include <libavcodec/avcodec.h>
}

#include <iostream>
#include <fstream>
using namespace std;

#define H264_FILE "..\\\\..\\\\resources\\\\generate_YUV.h264"
#define H265_FILE "..\\\\..\\\\resources\\\\generate_YUV.h265"
#define YUV_FILE "..\\\\..\\\\resources\\\\generate_YUV.yuv"

void encoderFrame()
{
	int ret;
	int width = 640, height = 480;

	ofstream out_file;
	out_file.open(H265_FILE, ios::binary);
	if(!out_file.is_open())
	{
		cout << "Failed to open H265_FILE" << endl;
		return;
	}
	ofstream out_yuv_file;
	out_yuv_file.open(YUV_FILE, ios::binary);
	if (!out_yuv_file.is_open())
	{
		cout << "Failed to open out_yuv_file" << endl;
		return;
	}

	//查找编码器
	AVCodec* codec = avcodec_find_encoder(AVCodecID::AV_CODEC_ID_H265);
	if (!codec)
	{
		cerr << "avcodec_alloc_context3 failed!" << endl;
		return;
	}

	//生成编码器上下文
	AVCodecContext* context = avcodec_alloc_context3(codec);
	context->width = width;
	context->height = height;
	// pts 显示时间戳,这个时间戳用来告诉播放器该在什么时候显示这一帧的数据。
	// dts 解码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这一帧的数据。
	context->time_base = { 1, 25 };  // 播放时间=pts*time_base,pts是刻度尺
	context->pix_fmt = AV_PIX_FMT_YUV420P;// 元数据像素格式,与编码算法相关
	context->thread_count = 16; //编码线程数,可以通过调用系统接口获取cpu核心数量

	//打开编码器
	ret = avcodec_open2(context, codec, NULL);
	if (ret < 0)
	{
		char buf[512] = { 0 };
		av_strerror(ret, buf, sizeof buf - 1);
		return ;
	}

	cout << "Has aleady open avcodec." << endl;

	//创建AVFrame,将未压缩数据置入AVFrame中
	AVFrame* frame = av_frame_alloc();
	frame->width = context->width;
	frame->height = context->height;
	frame->format = context->pix_fmt;

	ret = av_frame_get_buffer(frame, 0);
	if (ret < 0)
	{
		char buf[512] = { 0 };
		av_strerror(ret, buf, sizeof(buf) - 1);
		return;
	}
	
	AVPacket* packet = av_packet_alloc();
	if (!packet)
	{
		cout << "Failed to av_packet_alloc" << endl;
		return ;
	}

	//准备YUV数据
	//生成250帧的YUV,fps=25,播放十秒
	for (int i = 0; i < 250; i++)
	{
		//生成AVFrame 数据 每帧数据不同
		//Y
		for (int y = 0; y < context->height; y++)
		{
			for (int x = 0; x < context->width; x++)
			{
				frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
			}
		}
		//UV
		for (int y = 0; y < context->height / 2; y++)
		{
			for (int x = 0; x < context->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;
			}
		}
		cout << "linesize = " << frame->linesize[0] << endl;
		cout << "linesize = " << frame->linesize[1] << endl;
		cout << "linesize = " << frame->linesize[2] << endl;
		out_yuv_file.write((char*)frame->data[0], frame->linesize[0] * height);
		out_yuv_file.write((char*)frame->data[1], frame->linesize[1] * height / 2);
		out_yuv_file.write((char*)frame->data[2], frame->linesize[2] * height / 2);


		frame->pts = i;

		//发送帧到线程中压缩
		ret = avcodec_send_frame(context, frame);
		cout << "avcodec_send_frame" << endl;
		if (ret != 0)
		{
			char buf[512] = { 0 };
			av_strerror(ret, buf, sizeof buf - 1);
			break;
		}

		while (ret >= 0)
		{
			//编码,接受压缩帧,一遍前几针会返回空,会缓冲,每次编码会在单独的线程当中
			ret = avcodec_receive_packet(context, packet);
			if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			{
				break;
			}
			if (ret < 0)
			{
				char buf[1024] = { 0 };
				av_strerror(ret, buf, sizeof(buf) - 1);
				cout << "Failed to receive packet!" << endl;
				break;
			}
			cout << "packet size : " << packet->size << endl;
			out_file.write((char*)packet->data, packet->size);
			av_packet_unref(packet);
		}

	}
	out_yuv_file.close();
	out_file.close();
	av_packet_free(&packet);
	av_frame_free(&frame);
	avcodec_free_context(&context);
}

解码器的基本使用

#pragma once
#include <iostream>
#include <fstream>
#include <sstream>
#include "my_video_view.h"
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
}

#define H264_FILE "..\\\\..\\\\resources\\\\Wildlife.h264"
#define DECODE_RESULT "..\\\\..\\\\resources\\\\decode_result.yuv"
using namespace std;
#define SHOWERROR(id, msg) if(id != 0){cerr << msg << endl;}
 
void test_encode()
{

	MyVideoView* view = MyVideoView::Create();
	ifstream ifs;
	ifs.open(H264_FILE, ios::binary);
	if (!ifs.is_open())
	{
		cerr << "Failed to open H264_FILE file" << endl;
		return;
	}

	AVCodecID codec_id = AV_CODEC_ID_H264;


	//1 找到解码器
	AVCodec* codec = avcodec_find_decoder(codec_id);

	//2 创建上下文
	AVCodecContext* context = avcodec_alloc_context3(codec);
	//3 打开上下文
	avcodec_open2(context, NULL, NULL);

	//parse 上下文
	AVCodecParserContext* parseContext = av_parser_init(codec_id);
	AVPacket* packet = av_packet_alloc();
	AVFrame* frame = av_frame_alloc();
	AVFrame* hw_frame = av_frame_alloc();

	//硬件加速格式 DXVA2
	auto hw_type = AV_HWDEVICE_TYPE_DXVA2;

	//打印所有支持的硬件加速方式
	for (int i = 0;; i++)
	{
		const AVCodecHWConfig* config = avcodec_get_hw_config(codec, i);
		if (!config)break;
		if (config->device_type)
		{
			cout << av_hwdevice_get_type_name(config->device_type) << endl;
		}
	}

	//初始化硬件加速上下文
	AVBufferRef* hw_context = nullptr;
	av_hwdevice_ctx_create(&hw_context, hw_type, NULL, NULL, 0);

	//设定GPU加速
	context->hw_device_ctx = hw_context;
	context->thread_count = 16;

	//打印帧率
	int fps = 0;
	long long begin = MyVideoView::NowMs();
	bool is_first_init = true;

	unsigned char buf[4096] = { 0 };
	while (true)
	{
		ifs.read((char*)buf, sizeof buf);
		int data_size = ifs.gcount();
		if (data_size == 0)
			break;
		unsigned char* data = buf;
		while (data_size > 0)  //一次读入多帧
		{
			int ret = av_parser_parse2(
				parseContext, context, &packet->data, &packet->size,//输出数据
				data, data_size,//输入数据
				AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0
			);
			data += ret;
			data_size -= ret;

			//读入一个片段
			if (packet->size)
			{
				//cout << packet->size << " ";
				//发送packet到解码器
				ret = avcodec_send_packet(context, packet);
				if (ret < 0)
				{
					break;
				}
				//获取多帧解码结果frame
				while (ret >= 0)
				{
					ret = avcodec_receive_frame(context, frame);
					if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
					{
						break;
					}
					if (ret < 0)
					{
						char buf[1024] = { 0 };
						av_strerror(ret, buf, sizeof(buf) - 1);
						cout << "Failed to receive frame!" << endl;
						break;
					}
					AVFrame* pframe = frame;
					if (context->hw_device_ctx)
					{
						//硬解码:GPU显存-》CPU显存-》内存
						//这里的format为:AV_PIX_FMT_NV12
						av_hwframe_transfer_data(hw_frame, frame, 0);
						pframe = hw_frame; //统一AVFrame指针,同时支持软硬渲染
					}


					if (is_first_init)
					{
						view->Init(pframe->width, pframe->height, (MyVideoView::Format)pframe->format);
						is_first_init = false;
					}
					fps++;
					view->DrawFrame(pframe);
					//MSleep(40);
					long long now = clock();
					if (now - begin >= 1000)
					{
						stringstream ss;
						ss << "[fps = " << fps <<"]";
						cout << ss.str() << endl;
						begin = now;
						fps = 0;
					}
				
				}
			}

		}
		
		
	}

	//缓冲中可能还有数据,发送NULL,获取frame
	int ret = avcodec_send_packet(context, NULL);
	while (ret >= 0)
	{
		ret = avcodec_receive_frame(context, frame);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
		{
			break;
		}
		if (ret < 0)
		{
			char buf[1024] = { 0 };
			av_strerror(ret, buf, sizeof(buf) - 1);
			cout << "Failed to receive frame!" << endl;
			break;
		}
		cout << "[" << frame->format << "] ";
	}
	av_frame_free(&hw_frame);
	av_frame_free(&frame);
	av_packet_free(&packet);
	avcodec_free_context(&context);
	av_parser_close(parseContext);
	ifs.close();
}

在解码时,将编码好的数据读入buf中,这个buf可能不是一个帧的整数倍,可能是几帧数据的小数倍,然后将这些帧数据使用avcodec_send_packet(),将packet发送到多线程中进行解码到缓冲中,这里使用的是生产者,消费者模式,在avcodec_receive_frame(),接受到的AVFrame是一个独立的帧,因此对于读到半帧数据要放入缓冲中,等待其他数据到达了缓冲中时,再从缓冲中取出一个完整的帧。所以这里产生了一个三层while去完成解码操作。

封装编解码器

my_codec.h

#pragma once
#include <mutex>
#include <vector>
#include <string>
struct AVCodecContext;
struct AVFrame;
struct AVPacket;

class MyCodec
{
public:
	static void ShowError(int errorId);
	static void ShowError(const char* msg);

	//创建编解码器上下文
	AVCodecContext* Create(int codecId, bool is_encoder);
	//设置编解码器上下文,加锁,线程安全
	bool SetContext(AVCodecContext* context);
	//给编解码器甚至参数,加锁,线程安全
	bool SetOption(const char* key, const char* val);
	bool SetOption(const char* key, int val);
	//打开编解码器,加锁,线程安全
	bool Open();
	//创建的frame需要释放,加锁,线程安全
	AVFrame* CreateFrame();

protected:
	AVCodecContext* context_;
	std::mutex context_mtx_;

};

my_encoder.h

#pragma once
#include "my_codec.h"
#include <mutex>
#include <vector>
#include <string>
struct AVCodecContext;
struct AVFrame;
struct AVPacket;
class MyEncoder : public MyCodec
{
public:
	MyEncoder();
	//返回的packet需要释放,线程安全
	AVPacket* Encode(const AVFrame* frame);
	std::vector<AVPacket*> GetBufferPacket()ffmpeg里有x264么

FFmpegffmpeg 命令查询一 ( 版本 | 编译配置 | 复用格式 | 编解码器 )

FFmpegffmpeg 命令查询一 ( 版本 | 编译配置 | 复用格式 | 编解码器 )

FFmpeg 支持的所有编解码器和格式是啥?

音视频编解码——解码:代码实现

ffmpeg/Libavcodec 找不到编解码器