FFmpeg开发常用功能封装

开发中使用到的FFmpeg常用功能,避免相同功能代码的重复编写,使用时直接复制提升效率。由于音视频处理的场景众多,无法编写完全通用的方法接口,可能需根据实际场景进行一定的修改,本文章中的代码也将持续更新优化

代码

这里提供ffmpegheader.h,ffmpegheader.cpp。配置好基本的FFmpeg库环境后,直接导入上述两个文件,即可直接使用对应功能。

1、头文件

#ifndef FFMPEGHEADER_H
#define FFMPEGHEADER_H
/**
 * 封装常用的ffmpeg方法以及类 只需要引入文件即可直接使用 避免重复轮子
 * By ZXT
 */
extern "C"
{
#include "./libavcodec/avcodec.h"
#include "./libavformat/avformat.h"
#include "./libavformat/avio.h"
#include "./libavutil/opt.h"
#include "./libavutil/time.h"
#include "./libavutil/imgutils.h"
#include "./libswscale/swscale.h"
#include "./libswresample/swresample.h"
#include "./libavutil/avutil.h"
#include "./libavutil/ffversion.h"
#include "./libavutil/frame.h"
#include "./libavutil/pixdesc.h"
#include "./libavutil/imgutils.h"
#include "./libavfilter/avfilter.h"
#include "./libavfilter/buffersink.h"
#include "./libavfilter/buffersrc.h"
#include "./libavdevice/avdevice.h"
}
#include <QDebug>

本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

/************************************* 常用函数封装 ************************************/

//获取ffmpeg报错信息

char *getAVError(int errNum);

//根据pts计算实际时间us

int64_t getRealTimeByPTS(int64_t pts, AVRational timebase);

//pts转换为us差时进行延时

void calcAndDelay(int64_t startTime, int64_t pts, AVRational timebase);

//十六进制字节数组转十进制

int32_t hexArrayToDec(char *array, int len);

/************************************* 常用类封装 *************************************/

//视频图像转换类

class VideoSwser
{
public:
VideoSwser();
~VideoSwser();

//初始化转换器

bool initSwsCtx(int srcWidth, int srcHeight, AVPixelFormat srcFmt, int dstWidth, int dstHeight, AVPixelFormat dstFmt);
void release();

//返回转换格式后的AVFrame

AVFrame *getSwsFrame(AVFrame *srcFrame);
private:
bool hasInit;
bool needSws;
int dstWidth;
int dstHeight;
AVPixelFormat dstFmt;

//格式转换

SwsContext *videoSwsCtx;
};

//视频编码器类

class VideoEncoder
{
public:
VideoEncoder();
~VideoEncoder();

//初始化编码器

bool initEncoder(int width, int height, AVPixelFormat fmt, int fps);
void release();

//返回编码后AVpacket

AVPacket *getEncodePacket(AVFrame *srcFrame);
AVPacket *flushEncoder();

//返回编码器上下文

AVCodecContext *getCodecContent();
private:
bool hasInit;

//编码器

AVCodecContext *videoEnCodecCtx;
};

//音频重采样类

class AudioSwrer
{
public:
AudioSwrer();
~AudioSwrer();

//初始化转换器

bool initSwrCtx(int inChannels, int inSampleRate, AVSampleFormat inFmt, int outChannels, int outSampleRate, AVSampleFormat outFmt);
void release();

//返回转换格式后的AVFrame

AVFrame *getSwrFrame(AVFrame *srcFrame);

//返回转换格式后的AVFrame srcdata为一帧源格式的数据

AVFrame *getSwrFrame(uint8_t *srcData);
private:
bool hasInit;
bool needSwr;
int outChannels;
int outSampleRate;
AVSampleFormat outFmt;

//格式转换

SwrContext *audioSwrCtx;
};

//音频编码器类

class AudioEncoder
{
public:
AudioEncoder();
~AudioEncoder();

//初始化编码器

bool initEncoder(int channels, int sampleRate, AVSampleFormat sampleFmt);
void release();

//返回编码后AVpacket

AVPacket *getEncodePacket(AVFrame *srcFrame);
AVPacket *flushEncoder();

//返回编码器上下文

AVCodecContext *getCodecContent();
private:
bool hasInit;

//编码器

AVCodecContext *audioEnCodecCtx;
};

//实时采集场景时间戳处理类

class AVTimeStamp
{
public:

//累加帧间隔 优点:时间戳稳定均匀 缺点:实际采集帧率可能不稳定,固定累加或忽略小数会累加误差造成不同步

//实时时间戳 优点:时间戳保持实时及正确 缺点:存在帧间隔不均匀,极端情况不能正常播放

//累加+实时矫正 优点:时间戳实时且较为均匀 缺点:纠正时间戳的某一时刻可能画面或声音卡顿

enum PTSMode
{
PTS_RECTIFY = 0, //默认矫正类型 保持帧间隔尽量均匀
PTS_REALTIME //实时pts
};
public:
AVTimeStamp();
~AVTimeStamp();

//初始化时间戳参数

void initAudioTimeStampParm(int sampleRate, PTSMode mode = PTS_RECTIFY);
void initVideoTimeStampParm(int fps, PTSMode mode = PTS_RECTIFY);

//开始时间戳记录

void startTimeStamp();

//返回pts

int64_t getAudioPts();
int64_t getVideoPts();
private:

//当前模式

PTSMode aMode;
PTSMode vMode;

//时间戳相关记录 均us单位

int64_t startTime;
int64_t audioTimeStamp;
int64_t videoTimeStamp;
double audioDuration;
double videoDuration;
};
#endif // FFMPEGHEADER_H

2、实现文件

#include "ffmpegheader.h"
char *getAVError(int errNum)
{
static char msg[32] = {0};
av_strerror(errNum, msg, 32);
return msg;
}
int64_t getRealTimeByPTS(int64_t pts, AVRational timebase)
{
   
   

//pts转换为对应us值

AVRational timebase_q = {1, AV_TIME_BASE};
int64_t ptsTime = av_rescale_q(pts, timebase, timebase_q);
return ptsTime;
}
void calcAndDelay(int64_t startTime, int64_t pts, AVRational timebase)
{
int64_t ptsTime = getRealTimeByPTS(pts, timebase);

//计算startTime到此刻的时间差值

int64_t nowTime = av_gettime() - startTime;
int64_t offset = ptsTime - nowTime;

//大于2秒一般时间戳存在问题 延时无法挽救

if(offset > 1000 && offset < 2*1000*1000)
av_usleep(offset);
}
int32_t hexArrayToDec(char *array, int len)
{
   
   

//目前限制四字节长度 超过则注意返回类型 防止溢出

if(array == nullptr || len > 4)
return -1;
int32_t result = 0;
for(int i=0; i<len; i++)
result = result * 256 + (unsigned char)array[i];
return result;
}
VideoSwser::VideoSwser()
{
videoSwsCtx = nullptr;
hasInit = false;
needSws = false;
}
VideoSwser::~VideoSwser()
{
release();
}
bool VideoSwser::initSwsCtx(int srcWidth, int srcHeight, AVPixelFormat srcFmt, int dstWidth, int dstHeight, AVPixelFormat dstFmt)
{
release();
if(srcWidth == dstWidth && srcHeight == dstHeight && srcFmt == dstFmt)
{
needSws = false;
}
else
{
   
   

//设置转换上下文 srcFmt 到 dstFmt(一般为AV_PIX_FMT_YUV420P)的转换

videoSwsCtx = sws_getContext(srcWidth, srcHeight, srcFmt, dstWidth, dstHeight, dstFmt, SWS_BILINEAR, NULL, NULL, NULL);
if (videoSwsCtx == NULL)
{
qDebug() << "sws_getContext error";
return false;
}
this->dstFmt = dstFmt;
this->dstWidth = dstWidth;
this->dstHeight = dstHeight;
needSws = true;
}
hasInit = true;
return true;
}
void VideoSwser::release()
{
if(videoSwsCtx)
{
sws_freeContext(videoSwsCtx);
videoSwsCtx = nullptr;
}
hasInit = false;
needSws = false;
}
AVFrame *VideoSwser::getSwsFrame(AVFrame *srcFrame)
{
if(!hasInit)
{
qDebug() << "Swser未初始化";
return nullptr;
}
if(!srcFrame)
return nullptr;
if(!needSws)
return srcFrame;
AVFrame *frame = av_frame_alloc();
frame->format = dstFmt;
frame->width = dstWidth;
frame->height = dstHeight;
int ret = av_frame_get_buffer(frame, 0);
if (ret != 0)
{
qDebug() << "av_frame_get_buffer swsFrame error";
return nullptr;
}
ret = av_frame_make_writable(frame);
if (ret != 0)
{
qDebug() << "av_frame_make_writable swsFrame error";
return nullptr;
}
sws_scale(videoSwsCtx, (const uint8_t *const *)srcFrame->data, srcFrame->linesize, 0, dstHeight, frame->data, frame->linesize);
return frame;
}
VideoEncoder::VideoEncoder()
{
videoEnCodecCtx = nullptr;
hasInit = false;
}
VideoEncoder::~VideoEncoder()
{
release();
}
bool VideoEncoder::initEncoder(int width, int height, AVPixelFormat fmt, int fps)
{
   
   

//重置编码信息

release();

//设置编码器参数 默认AV_CODEC_ID_H264

AVCodec *videoEnCoder = avcodec_find_encoder(AV_CODEC_ID_H264);
if(!videoEnCoder)
{
qDebug() << "avcodec_find_encoder AV_CODEC_ID_H264 error";
return false;
}
videoEnCodecCtx = avcodec_alloc_context3(videoEnCoder);
if(!videoEnCodecCtx)
{
qDebug() << "avcodec_alloc_context3 AV_CODEC_ID_H264 error";
return false;
}

//重要!编码参数设置 应根据实际场景修改以下参数

videoEnCodecCtx->bit_rate = 2*1024*1024; //1080P:4Mbps 720P:2Mbps 480P:1Mbps 默认中等码率可适当增大
videoEnCodecCtx->width = width;
videoEnCodecCtx->height = height;
videoEnCodecCtx->framerate = {fps, 1};
videoEnCodecCtx->time_base = {1, AV_TIME_BASE};
videoEnCodecCtx->gop_size = fps;
videoEnCodecCtx->max_b_frames = 0;
videoEnCodecCtx->pix_fmt = fmt;
videoEnCodecCtx->thread_count = 2;
videoEnCodecCtx->thread_type = FF_THREAD_FRAME;

//设置QP最大和最小量化系数,取值范围为0~51 越大编码质量越差

videoEnCodecCtx->qmin = 10;
videoEnCodecCtx->qmax = 30;

//若设置此项 则sps、pps将保存在extradata;否则放置于每个I帧前

videoEnCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

//预设参数 编码速度与压缩率的平衡 如编码快选择的算法就偏简单 压缩率低

//由慢到快veryslow slower slow medium fast faster veryfast superfast ultrafast 默认medium

int ret = av_opt_set(videoEnCodecCtx->priv_data, "preset", "ultrafast", 0);
if(ret != 0)
qDebug() << "av_opt_set preset error";

//偏好设置 进行视觉优化

//film电影 animation动画片 grain颗粒物 stillimage静止图片 psnr ssim图像评价指标 fastdecode快速解码 zerolatency零延迟

ret = av_opt_set(videoEnCodecCtx->priv_data, "tune", "zerolatency", 0);
if(ret != 0)
qDebug() << "av_opt_set preset error";

//画质设置 可能自动改变 如编码很快很难保证高画质会自动降级

//baseline实时通信 extended使用较少 main流媒体 high广电、存储

ret = av_opt_set(videoEnCodecCtx->priv_data, "profile", "main", 0);
if(ret != 0)
qDebug() << "av_opt_set preset error";
ret = avcodec_open2(videoEnCodecCtx, videoEnCoder, NULL);
if(ret != 0)
{
qDebug() << "avcodec_open2 video error";
return false;
}
hasInit = true;
return true;
}
void VideoEncoder::release()
{
if(videoEnCodecCtx)
{
avcodec_free_context(&videoEnCodecCtx);
videoEnCodecCtx = nullptr;
}
hasInit = false;
}
AVPacket *VideoEncoder::getEncodePacket(AVFrame *srcFrame)
{
if(!hasInit)
{
qDebug() << "VideoEncoder no init";
return nullptr;
}
if(!srcFrame)
return nullptr;
if(srcFrame->width != videoEnCodecCtx->width
|| srcFrame->height != videoEnCodecCtx->height
|| srcFrame->format != videoEnCodecCtx->pix_fmt)
{
qDebug() << "srcFrame不符合视频编码器设置格式";
return nullptr;
}

//应保证srcFrame pts为us单位

srcFrame->pts = av_rescale_q(srcFrame->pts, AVRational{1, AV_TIME_BASE}, videoEnCodecCtx->time_base);
int ret = avcodec_send_frame(videoEnCodecCtx, srcFrame);
if (ret != 0)
return nullptr;

//接收者负责释放packet

AVPacket *packet = av_packet_alloc();
ret = avcodec_receive_packet(videoEnCodecCtx, packet);
if (ret != 0)
{
av_packet_free(&packet);
return nullptr;
}
return packet;
}
AVPacket *VideoEncoder::flushEncoder()
{
if(!hasInit)
{
qDebug() << "VideoEncoder no init";
return nullptr;
}
int ret = avcodec_send_frame(videoEnCodecCtx, NULL);
if (ret != 0)
return nullptr;

//接收者负责释放packet

AVPacket *packet = av_packet_alloc();
ret = avcodec_receive_packet(videoEnCodecCtx, packet);
if (ret != 0)
{
av_packet_free(&packet);
return nullptr;
}
return packet;
}
AVCodecContext *VideoEncoder::getCodecContent()
{
return videoEnCodecCtx;
}
AudioSwrer::AudioSwrer()
{
audioSwrCtx = nullptr;
hasInit = false;
needSwr = false;
}
AudioSwrer::~AudioSwrer()
{
release();
}
bool AudioSwrer::initSwrCtx(int inChannels, int inSampleRate, AVSampleFormat inFmt, int outChannels, int outSampleRate, AVSampleFormat outFmt)
{
release();
if(inChannels == outChannels && inSampleRate == outSampleRate && inFmt == outFmt)
{
needSwr = false;
}
else
{
audioSwrCtx = swr_alloc_set_opts(NULL, av_get_default_channel_layout(outChannels), outFmt, outSampleRate,
av_get_default_channel_layout(inChannels), inFmt, inSampleRate, 0, NULL);
if (!audioSwrCtx)
{
qDebug() << "swr_alloc_set_opts failed!";
return false;
}
int ret = swr_init(audioSwrCtx);
if (ret != 0)
{
qDebug() << "swr_init error";
swr_free(&audioSwrCtx);
return false;
}
this->outFmt = outFmt;
this->outChannels = outChannels;
this->outSampleRate = outSampleRate;
needSwr = true;
}
hasInit = true;
return true;
}
void AudioSwrer::release()
{
if(audioSwrCtx)
{
swr_free(&audioSwrCtx);
audioSwrCtx = nullptr;
}
hasInit = false;
needSwr = false;
}
AVFrame *AudioSwrer::getSwrFrame(AVFrame *srcFrame)
{
if(!hasInit)
{
qDebug() << "Swrer未初始化";
return nullptr;
}
if(!srcFrame)
return nullptr;
if(!needSwr)
return srcFrame;
AVFrame *frame = av_frame_alloc();
frame->format = outFmt;
frame->channels = outChannels;
frame->channel_layout = av_get_default_channel_layout(outChannels);
frame->nb_samples = 1024; //默认aac
int ret = av_frame_get_buffer(frame, 0);
if (ret != 0)
{
qDebug() << "av_frame_get_buffer audio error";
return nullptr;
}
ret = av_frame_make_writable(frame);
if (ret != 0)
{
qDebug() << "av_frame_make_writable swrFrame error";
return nullptr;
}
const uint8_t **inData = (const uint8_t **)srcFrame->data;
swr_convert(audioSwrCtx, frame->data, frame->nb_samples, inData, frame->nb_samples);
return frame;
}
AVFrame *AudioSwrer::getSwrFrame(uint8_t *srcData)
{
if(!hasInit)
{
qDebug() << "Swrer未初始化";
return nullptr;
}
if(!srcData)
return nullptr;
if(!needSwr)
return nullptr;
AVFrame *frame = av_frame_alloc();
frame->format = outFmt;
frame->channels = outChannels;
frame->sample_rate = outSampleRate;
frame->channel_layout = av_get_default_channel_layout(outChannels);
frame->nb_samples = 1024; //默认aac
int ret = av_frame_get_buffer(frame, 0);
if (ret != 0)
{
qDebug() << "av_frame_get_buffer audio error";
return nullptr;
}
ret = av_frame_make_writable(frame);
if (ret != 0)
{
qDebug() << "av_frame_make_writable swrFrame error";
return nullptr;
}
const uint8_t *indata[AV_NUM_DATA_POINTERS] = {0};
indata[0] = srcData;
swr_convert(audioSwrCtx, frame->data, frame->nb_samples, indata, frame->nb_samples);
return frame;
}
AudioEncoder::AudioEncoder()
{
audioEnCodecCtx = nullptr;
hasInit = false;
}
AudioEncoder::~AudioEncoder()
{
release();
}
bool AudioEncoder::initEncoder(int channels, int sampleRate, AVSampleFormat sampleFmt)
{
release();

//初始化音频编码器相关 默认AAC

AVCodec *audioEnCoder = avcodec_find_encoder(AV_CODEC_ID_AAC);
if (!audioEnCoder)
{
qDebug() << "avcodec_find_encoder AV_CODEC_ID_AAC failed!";
return false;
}
audioEnCodecCtx = avcodec_alloc_context3(audioEnCoder);
if (!audioEnCodecCtx)
{
qDebug() << "avcodec_alloc_context3 AV_CODEC_ID_AAC failed!";
return false;
}

//ffmpeg -h encoder=aac 自带编码器仅支持AV_SAMPLE_FMT_FLTP 大多数AAC编码器都采用平面布局格式 提高数据访问效率和缓存命中率 加快编码效率

//音频数据量偏小 设置较为简单

audioEnCodecCtx->bit_rate = 64*1024;
audioEnCodecCtx->time_base = AVRational{1, sampleRate};
audioEnCodecCtx->sample_rate = sampleRate;
audioEnCodecCtx->sample_fmt = sampleFmt;
audioEnCodecCtx->channels = channels;
audioEnCodecCtx->channel_layout = av_get_default_channel_layout(channels);
audioEnCodecCtx->frame_size = 1024;

//打开音频编码器

int ret = avcodec_open2(audioEnCodecCtx, audioEnCoder, NULL);
if (ret != 0)
{
qDebug() << "avcodec_open2 audio error" << getAVError(ret);
return false;
}
hasInit = true;
return true;
}
void AudioEncoder::release()
{
if(audioEnCodecCtx)
{
avcodec_free_context(&audioEnCodecCtx);
audioEnCodecCtx = nullptr;
}
hasInit = false;
}
AVPacket *AudioEncoder::getEncodePacket(AVFrame *srcFrame)
{
if(!hasInit)
{
qDebug() << "AudioEncoder no init";
return nullptr;
}
if(!srcFrame)
return nullptr;
if(srcFrame->channels != audioEnCodecCtx->channels
|| srcFrame->sample_rate != audioEnCodecCtx->sample_rate
|| srcFrame->format != audioEnCodecCtx->sample_fmt)
{
qDebug() << "srcFrame不符合音频编码器设置格式";
return nullptr;
}

//应保证srcFrame pts为us单位

srcFrame->pts = av_rescale_q(srcFrame->pts, AVRational{1, AV_TIME_BASE}, audioEnCodecCtx->time_base);

//进行音频编码得到编码数据AVPacket

int ret = avcodec_send_frame(audioEnCodecCtx, srcFrame);
if (ret != 0)
return nullptr;

//接收者负责释放packet

AVPacket *packet = av_packet_alloc();
ret = avcodec_receive_packet(audioEnCodecCtx, packet);
if (ret != 0)
{
av_packet_free(&packet);
return nullptr;
}
return packet;
}
AVPacket *AudioEncoder::flushEncoder()
{
if(!hasInit)
{
qDebug() << "AudioEncoder no init";
return nullptr;
}
int ret = avcodec_send_frame(audioEnCodecCtx, NULL);
if (ret != 0)
return nullptr;

//接收者负责释放packet

AVPacket *packet = av_packet_alloc();
ret = avcodec_receive_packet(audioEnCodecCtx, packet);
if (ret != 0)
{
av_packet_free(&packet);
return nullptr;
}
return packet;
}
AVCodecContext *AudioEncoder::getCodecContent()
{
return audioEnCodecCtx;
}
AVTimeStamp::AVTimeStamp()
{
aMode = PTS_RECTIFY;
vMode = PTS_RECTIFY;
startTime = 0;
audioTimeStamp = 0;
videoTimeStamp = 0;

//默认视频264编码 25帧

videoDuration = 1000000 / 25;

//默认音频aac编码 44100采样率

audioDuration = 1000000 / (44100 / 1024);
}
AVTimeStamp::~AVTimeStamp()
{
}
void AVTimeStamp::initAudioTimeStampParm(int sampleRate, AVTimeStamp::PTSMode mode)
{
aMode = mode;
audioDuration = 1000000 / (sampleRate / 1024);
}
void AVTimeStamp::initVideoTimeStampParm(int fps, AVTimeStamp::PTSMode mode)
{
vMode = mode;
videoDuration = 1000000 / fps;
}
void AVTimeStamp::startTimeStamp()
{
audioTimeStamp = 0;
videoTimeStamp = 0;
startTime = av_gettime();
}
int64_t AVTimeStamp::getAudioPts()
{
if(aMode == PTS_RECTIFY)
{
int64_t elapsed = av_gettime() - startTime;
uint32_t offset = qAbs(elapsed - (audioTimeStamp + audioDuration));
if(offset < (audioDuration * 0.5))
audioTimeStamp += audioDuration;
else
audioTimeStamp = elapsed;
}
else
{
audioTimeStamp = av_gettime() - startTime;
}
return audioTimeStamp;
}
int64_t AVTimeStamp::getVideoPts()
{
if(vMode == PTS_RECTIFY)
{
int64_t elapsed = av_gettime() - startTime;
uint32_t offset = qAbs(elapsed - (videoTimeStamp + videoDuration));
if(offset < (videoDuration * 0.5))
videoTimeStamp += videoDuration;
else
videoTimeStamp = elapsed;
}
else
{
videoTimeStamp = av_gettime() - startTime;
}
return videoTimeStamp;
}

本文福利, 免费领取C++音视频学习资料包、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

猜你喜欢

转载自blog.csdn.net/m0_60259116/article/details/132477189