概述
本文记录一个最简单的基于ffmpeg的H.264视频编码器;直接调用ffmpeg完成编码。因此项目的体积非常小巧。该编码器可以将输入的YUV数据编码为H.264码流文件。
流程中主要的函数如下所示:
av_register_all():注册FFmpeg所有编解码器;
avcodec_find_encoder():查找编码器;
avcodec_alloc_context3():初始化输出码流的AVFormatContext;
avcodec_open2():打开编码器;
avcodec_encode_video2():编码一帧视频。即将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据);
avcodec_close():关闭编码器;
源代码介绍
基于ffmpeg的H.264视频编码器封装成simplest_encoder_FFmpeg类;里面包含对外暴露接口如下:
1、long Init();
这个是用于申请ffmpeg 的编码资源; 返回:0代表成功。
2、long SetEncoderParam(ffmpeg_video_encoder_param* param);
这个函数用于编码处理的;参数解释如下:
param:设置编码参数,具体参考:ffmpeg_video_encoder_param;
函数返回值:0代表成功;否则失败。
3、long StartEncoder();
这个函数表示开始编码,在这个函数内部会进行ffmpeg 参数初始化;
函数返回值:0代表成功;否则失败。
4、long Encoder(ffmpeg_video_frame* in_frame, ffmpeg_video_frame* out_frame);
这个函数表示编码功能,在这个函数内部会进行ffmpeg 的编码接口,具体参数如下:
in_frame:表示输入的YUV数据,结构体内容参考ffmpeg_video_frame;
out_frame:表示编码输出的编码数据,结构体内容参考ffmpeg_video_frame;
函数返回值:0代表成功;否则失败。
5、long AdjustBitrate(int bitrate);
这个函数用于编码处理的;参数解释如下:
bitrate:更换的比特率,单位应该对应比特率*1000;
函数返回值:0代表成功;否则失败。
6、 long MandatoryKeyFrame();
函数解释:这个代表强制输出关键帧,即IDR帧;
函数返回值:0代表成功;否则失败。
7、long Flush(ffmpeg_video_frame* out_frame);
这个函数表示刷新编码器内部缓存帧,参数如下:
out_frame:表示编码输出的编码数据,结构体内容参考ffmpeg_video_frame;
函数返回值:0代表成功;否则失败。
8、long StopEncoder();
这个函数表示停止编码,在这个函数内部会进行ffmpeg 释放相关编码函数;
函数返回值:0代表成功;否则失败。
9、long UnInit();
函数解释:代表释放ffmpeg 相关资源;
函数返回值:0代表成功;否则失败。
对应源码头文件:simplest_encoder_FFmpeg.h
/**
* 简单的ffmpeg视频编码器
*Simple ffmpeg video encoder
*
* 梁启东 qidong.liang
* 18088708700@163.com
* https://blog.csdn.net/u011645307
*
*
* 本程序实现了简单的ffmpeg视频编码功能;
* 支持强制输出关键帧
* 支持动态修改码率
* 目前只开放了I420数据格式编码
*This program realizes a simple ffmpeg video coding function;
*Support forced output of key frames
*Support dynamic modification of bit rate
*At present, only i420 data format coding is open
*
*/
#ifndef SIMPLEST_ENCODER_FFMPEG
#define SIMPLEST_ENCODER_FFMPEG
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavutil/imgutils.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#ifdef __cplusplus
};
#endif
#endif
#include<map>
enum ffmpeg_video_frame_type
{
FFmpegVideoFrame_UNKNOWN = 0,
FFmpegVideoFrame_H264,
FFmpegVideoFrame_H265,
FFmpegVideoFrame_I420,
FFmpegVideoFrame_NV12,
FFmpegVideoFrame_GRAY8,
FFmpegVideoFrame_YUV422P,
FFmpegVideoFrame_YUV444P,
FFmpegVideoFrame_YUYV422,
FFmpegVideoFrame_RGB24,
FFmpegVideoFrame_BGR24,
FFmpegVideoFrame_BGRA,
FFmpegVideoFrame_PNG,
FFmpegVideoFrame_JPG,
};
struct ffmpeg_video_frame
{
ffmpeg_video_frame_type pix_fmt = FFmpegVideoFrame_H264;
char* payload = nullptr;
unsigned int payloadLen = 0;
unsigned int width = 0;
unsigned int height = 0;
unsigned long long pts = 0;
unsigned long long dts = 0;
};
struct ffmpeg_video_encoder_param
{
unsigned int bitRate = 0;
unsigned int width = 0;
unsigned int height = 0;
unsigned int gopSize = 0;
unsigned int frameRate = 0;
bool openBFrame = 0;
unsigned int sliceCount = 0;
unsigned int threadCount = 1;
ffmpeg_video_frame_type pix_fmt = FFmpegVideoFrame_I420;
};
class simplest_encoder_FFmpeg
{
public:
simplest_encoder_FFmpeg();
~simplest_encoder_FFmpeg();
long Init();
long SetEncoderParam(ffmpeg_video_encoder_param* param);
long StartEncoder();
long Encoder(ffmpeg_video_frame* in_frame, ffmpeg_video_frame* out_frame);
long AdjustBitrate(int bitrate);
long MandatoryKeyFrame();
long Flush(ffmpeg_video_frame* out_frame);
long StopEncoder();
long UnInit();
private:
std::map<ffmpeg_video_frame_type, AVPixelFormat> m_PixelFormatMap;
AVCodecContext* m_pCodecCtx;
AVCodec* m_pCodec;
AVFrame* m_pFrame;
AVCodecID m_codecId;
AVPacket pkt;
unsigned int m_bitRate;
float m_videoBuffMultiple;
bool m_mandatoryKeyFrame;
};
#endif//SIMPLEST_ENCODER_FFMPEG
对应源码头文件:simplest_encoder_FFmpeg.cpp
#include "simplest_encoder_FFmpeg.h"
simplest_encoder_FFmpeg::simplest_encoder_FFmpeg()
: m_codecId(AV_CODEC_ID_H264)
{
avcodec_register_all();
}
simplest_encoder_FFmpeg::~simplest_encoder_FFmpeg()
{
}
long simplest_encoder_FFmpeg::Init()
{
m_pCodecCtx = nullptr;
m_pCodec = nullptr;
m_mandatoryKeyFrame = false;
m_PixelFormatMap.insert(std::pair<ffmpeg_video_frame_type, AVPixelFormat>(FFmpegVideoFrame_I420, AV_PIX_FMT_YUV420P));
m_pCodec = avcodec_find_encoder(m_codecId);
if (!m_pCodec) {
return -1;
}
m_pCodecCtx = avcodec_alloc_context3(m_pCodec);
if (!m_pCodecCtx) {
return -2;
}
m_pFrame = av_frame_alloc();
if (!m_pFrame) {
return -3;
}
return 0;
}
long simplest_encoder_FFmpeg::SetEncoderParam(ffmpeg_video_encoder_param* param)
{
//m_pCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;//表示代表头
m_pCodecCtx->bit_rate = param->bitRate;
m_pCodecCtx->width = param->width;
m_pCodecCtx->height = param->height;
m_pCodecCtx->gop_size = param->frameRate;// param->gopSize * 4;
m_pCodecCtx->max_b_frames = param->openBFrame;
m_pCodecCtx->time_base.den = param->frameRate;
m_pCodecCtx->time_base.num = 1;
m_pCodecCtx->thread_count = param->threadCount; // more than one threads seem to increase delay
/* 根据分辨率设定多线程编码 */
if (param->threadCount == 0)
{
if (m_pCodecCtx->width > 1080)
{
m_pCodecCtx->thread_count = 8; // auto or 8
}
else if (m_pCodecCtx->width > 720 && m_pCodecCtx->width <= 1080)
{
m_pCodecCtx->thread_count = 4;
}
else if (m_pCodecCtx->width > 360 && m_pCodecCtx->width <= 720)
{
m_pCodecCtx->thread_count = 2;
}
else
{
m_pCodecCtx->thread_count = 1;
}
}
av_opt_set(m_pCodecCtx->priv_data, "preset", "superfast", 0);
av_opt_set(m_pCodecCtx->priv_data, "tune", "zerolatency", 0);
av_opt_set(m_pCodecCtx->priv_data, "profile", "baseline", 0);
av_opt_set(m_pCodecCtx->priv_data, "crf", "29", 0);
if (param->sliceCount > 0)
{
m_pCodecCtx->slices = param->sliceCount;
}
else
{
av_opt_set(m_pCodecCtx->priv_data, "slice-max-size", "1260", 0);
}
m_pCodecCtx->refs = 3;
m_pCodecCtx->qmin = 1;
m_pCodecCtx->qmax = 50;
m_pCodecCtx->me_range = 16; //32
m_pCodecCtx->me_subpel_quality = 3; // 5
m_pCodecCtx->qcompress = 0.6;
// 码率控制
m_pCodecCtx->rc_min_rate = m_pCodecCtx->bit_rate;
m_pCodecCtx->rc_max_rate = m_pCodecCtx->bit_rate;
m_pCodecCtx->bit_rate_tolerance = m_pCodecCtx->bit_rate;
m_pCodecCtx->rc_buffer_size = m_pCodecCtx->bit_rate * 1.2;// *2 / 3;
m_pCodecCtx->rc_initial_buffer_occupancy = m_pCodecCtx->rc_buffer_size * 3 / 4;
m_pCodecCtx->scenechange_threshold = 0;
m_pCodecCtx->flags &= AV_CODEC_FLAG_CLOSED_GOP; // intra-refresh
m_bitRate = m_pCodecCtx->bit_rate;
auto itor = m_PixelFormatMap.find(param->pix_fmt);
if (m_PixelFormatMap.end() == itor)
{
return -1;
}
m_pCodecCtx->pix_fmt = itor->second;
m_pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
if (AV_PIX_FMT_YUV420P == m_pCodecCtx->pix_fmt)
{
m_videoBuffMultiple = 1.5f;
}
m_pCodecCtx->pix_fmt = itor->second;
return 0;
}
long simplest_encoder_FFmpeg::StartEncoder()
{
int ret = avcodec_open2(m_pCodecCtx, m_pCodec, NULL);
if (ret < 0) {
return -2;
}
m_pFrame->format = m_pCodecCtx->pix_fmt;
m_pFrame->width = m_pCodecCtx->width;
m_pFrame->height = m_pCodecCtx->height;
int picture_size = avpicture_get_size(m_pCodecCtx->pix_fmt, m_pCodecCtx->width, m_pCodecCtx->height);
uint8_t* picture_buf = (uint8_t*)av_malloc(picture_size);
avpicture_fill((AVPicture*)m_pFrame, picture_buf, m_pCodecCtx->pix_fmt, m_pCodecCtx->width, m_pCodecCtx->height);
av_new_packet(&pkt, picture_size);
return 0;
}
long simplest_encoder_FFmpeg::Encoder(ffmpeg_video_frame* in_frame, ffmpeg_video_frame* out_frame)
{
int gotOutput = 0;
long long i = 0;
int ret = 0;
//I420
int y_size = in_frame->height * in_frame->width;
memcpy(m_pFrame->data[0], in_frame->payload, y_size);
memcpy(m_pFrame->data[1], in_frame->payload + y_size, y_size / 4);
memcpy(m_pFrame->data[2], in_frame->payload + y_size + y_size / 4, y_size / 4);
m_pFrame->pts = in_frame->pts;
if (m_bitRate != m_pCodecCtx->bit_rate)
{
avcodec_close(m_pCodecCtx);
m_pCodecCtx->bit_rate = m_bitRate;
ret = avcodec_open2(m_pCodecCtx, m_pCodec, NULL);
if (ret < 0) {
return -1;
}
}
av_init_packet(&pkt);
pkt.data = NULL; // packet data will be allocated by the encoder
pkt.size = 0;
if (m_mandatoryKeyFrame)
{
m_pFrame->pict_type = AV_PICTURE_TYPE_I;
m_pFrame->key_frame = 1;
m_mandatoryKeyFrame = false;
}
else
{
m_pFrame->pict_type = AV_PICTURE_TYPE_NONE;
}
/* encode the image */
ret = avcodec_encode_video2(m_pCodecCtx, &pkt, m_pFrame, &gotOutput);
if (ret < 0) {
return -2;
}
if (!gotOutput) {
return -3;
}
bool frameType_I = (pkt.flags & AV_PKT_FLAG_KEY) ? true : false;
char* buff = (char*)pkt.data;
unsigned int buffLen = pkt.size;
if (nullptr != out_frame)
{
out_frame->width = m_pFrame->width;
out_frame->height = m_pFrame->height;
out_frame->pts = pkt.pts;
out_frame->dts = pkt.dts;
out_frame->payload = (char*)buff;
out_frame->payloadLen = buffLen;
out_frame->pix_fmt = FFmpegVideoFrame_H264;
}
av_free_packet(&pkt);
return 0;
}
long simplest_encoder_FFmpeg::AdjustBitrate(int bitrate)
{
m_bitRate = bitrate;
return 0;
}
long simplest_encoder_FFmpeg::MandatoryKeyFrame()
{
m_mandatoryKeyFrame = true;
return 0;
}
long simplest_encoder_FFmpeg::Flush(ffmpeg_video_frame* out_frame)
{
//Flush Encoder 立刻编码输出
int gotOutput = 1;
av_init_packet(&pkt);
int ret = avcodec_encode_video2(m_pCodecCtx, &pkt, NULL, &gotOutput);
if (ret <= 0) {
return -1;
}
if (!gotOutput) {
return -1;
}
bool frameType_I = (pkt.flags & AV_PKT_FLAG_KEY) ? true : false;
char* buff = (char*)pkt.data;
unsigned int buffLen = pkt.size;
if (nullptr != out_frame)
{
out_frame->width = m_pFrame->width;
out_frame->height = m_pFrame->height;
out_frame->pts = pkt.pts;
out_frame->dts = pkt.dts;
out_frame->payload = (char*)buff;
out_frame->payloadLen = buffLen;
out_frame->pix_fmt = FFmpegVideoFrame_H264;
}
av_free_packet(&pkt);
return 0;
}
long simplest_encoder_FFmpeg::StopEncoder()
{
if (nullptr != m_pCodecCtx)
{
avcodec_close(m_pCodecCtx);
av_free(m_pCodecCtx);
}
m_pCodecCtx = nullptr;
if (nullptr != m_pFrame && nullptr != m_pFrame->data[0])
{
av_freep(&m_pFrame->data[0]);
m_pFrame->data[0] = nullptr;
}
if (nullptr != m_pFrame)
{
av_frame_free(&m_pFrame);
}
m_pFrame = nullptr;
return 0;
}
long simplest_encoder_FFmpeg::UnInit()
{
StopEncoder();
return 0;
}
对应的测试函数 main.cpp
/**
* 简单的ffmpeg视频编码器测试程序
*Simple ffmpeg video encoder
*
* 梁启东 qidong.liang
* 18088708700@163.com
* https://blog.csdn.net/u011645307
*
*
* 本程序测试simplest_encoder_ffmpeg视频编码功能;
* 测试simplest_encoder_ffmpeg强制输出关键帧的功能
* 测试simplest_encoder_ffmpeg动态修改码率的功能
* 测试simplest_encoder_ffmpeg I420数据格式编码的功能
* This program tests simplest_encoder_ffmpeg video coding function;
* Test simplest_encoder_ffmpeg forced output keyframe function
* Test simplest_encoder_ffmpeg4 function of dynamically modifying code rate
* Test simplest_encoder_ffmpeg i420 data format coding function
*
*/
#include "simplest_encoder_ffmpeg.h"
#include <iostream>
int main()
{
FILE* out_file = nullptr;
FILE* in_file = nullptr;
int w = 0;
int h = 0;
int buffLen = 0;
fopen_s(&in_file, "../inData/i420_480x272.yuv", "rb+");
fopen_s(&out_file, "../outData/i420_480x272.h264", "wb+");
w = 480;
h = 272;
buffLen = w * h * 3 / 2;
if (!in_file)
{
printf("in_file is nullptr\n");
exit(-1);
}
ffmpeg_video_encoder_param cfg;
simplest_encoder_FFmpeg* ffmpeg = new (std::nothrow)simplest_encoder_FFmpeg;
if (!ffmpeg)
{
printf("ffmpeg is nullptr\n");
delete ffmpeg;
ffmpeg = nullptr;
if (in_file)
{
fclose(in_file);
}
in_file = nullptr;
if (out_file)
{
fclose(out_file);
}
out_file = nullptr;
exit(-1);
}
cfg.bitRate = 200000000;
cfg.pix_fmt = FFmpegVideoFrame_I420;
cfg.height = h;
cfg.width = w;
cfg.openBFrame = 0;
cfg.frameRate = 25;
ffmpeg->Init();
if (0 != ffmpeg->SetEncoderParam(&cfg))
{
printf("ffmpeg.SetEncoderParam error\n");
exit(-1);
}
char* inbuff = new char[buffLen];
char* outbuff = new char[buffLen];
long long i = 0;
long long d = 0;
int outLen = buffLen;
ffmpeg_video_frame* in_frame = new (std::nothrow)ffmpeg_video_frame;
ffmpeg_video_frame* out_frame = new (std::nothrow)ffmpeg_video_frame;
if (!in_frame || !out_frame)
{
printf("ffmpeg.in_frame :%p, out_frame:%p error\n", in_frame, out_frame);
exit(-1);
}
out_frame->dts = 0;
out_frame->height = cfg.height;
out_frame->payload = outbuff;
out_frame->payloadLen = outLen;
out_frame->pix_fmt = FFmpegVideoFrame_H264;
out_frame->pts = 0;
out_frame->width = cfg.width;
in_frame->payload = inbuff;
in_frame->payloadLen = buffLen;
in_frame->dts = 0;
in_frame->height = cfg.height;
in_frame->pix_fmt = FFmpegVideoFrame_I420;
in_frame->pts = 0;
in_frame->width = cfg.width;
ffmpeg->StartEncoder();
while (true)
{
i++;
outLen = buffLen;
if (fread(inbuff, 1, buffLen, in_file) != buffLen)
{
break;
}
if (100 == i)
{
ffmpeg->AdjustBitrate(150000000);
}
if (i == 10)
{
ffmpeg->MandatoryKeyFrame();
}
ffmpeg->Encoder(in_frame, out_frame);
fwrite(out_frame->payload, 1, out_frame->payloadLen, out_file);
}
while (true)
{
if (0 != ffmpeg->Flush(out_frame))
{
break;
}
fwrite(out_frame->payload, 1, out_frame->payloadLen, out_file);
}
ffmpeg->StopEncoder();
if (0 != ffmpeg->UnInit())
{
printf("ffmpeg.UnInit error\n");
}
delete ffmpeg;
ffmpeg = nullptr;
if (in_file)
{
fclose(in_file);
}
in_file = nullptr;
if (out_file)
{
fclose(out_file);
}
out_file = nullptr;
std::cout << "Hello World!\n";
}
代码路径
csdn: