说明
记录下个人在开发中使用到的FFmpeg常用功能,避免相同功能代码的重复编写,使用时直接复制提升效率。由于音视频处理的场景众多,无法编写完全通用的方法接口,可能需根据实际场景进行一定的修改,本文章中的代码也将持续更新优化。
代码
这里提供ffmpegheader.h,ffmpegheader.cpp。配置好基本的FFmpeg库环境后,直接导入上述两个文件,即可直接使用对应功能。
更新记录
2023.05.29 优化编码时间戳为 固定累加+实时矫正
2023.06.13 优化编码类;新增时间戳处理类
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> /************************************* 常用函数封装 ************************************/ //获取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; }
标签:封装,FFmpeg,记录,int,frame,ret,int64,videoEnCodecCtx,include From: https://www.cnblogs.com/lidabo/p/17631228.html