ffmpeg编解码器包装类
Posted 顾文繁
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ffmpeg编解码器包装类相关的知识,希望对你有一定的参考价值。
对视频处理过程中,有编码,解码过程,两个过程在ffmpeg实现过程中有相同的处理函数,为了降低代码冗余,封装两个过程显得很有必要。
编码,解码两个过程的处理函数有:
- avcodec_find_encoder() 找到编码器
- avcodec_find_encoder() 找到解码器
- avcodec_alloc_context() 生成编码器上下文
- avcodec_open2打开编码器上下文
- avcodec_send_frame() 发送帧到线程中压缩(编码)
- avcodec_send_packet() 发送帧到线程中压缩(解码)
- avcodec_receive_packet() 接受缓冲中接受压缩后的packet(编码)
- avcodec_receive_frame() 接受缓冲中接受压缩后的packet(解码)
还有申请两个编解码数据载体,AVFrame和AVPacket
- av_frame_alloc() 申请AVFrame对象
- av_frame_get_buffer() 给AVFrame对象分配内部空间
- av_packet_alloc() 申请AVPacket对象
同时别忘记很重要的内存释放
- av_packet_free();
- av_frame_free();
- 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 命令查询一 ( 版本 | 编译配置 | 复用格式 | 编解码器 )