FFmpeg/opencv + C++ 实现直播拉流和直播推流(对视频帧进行处理)
第1章 基础知识
1.1 封装和解封装
原始图像序列 (frame₀, frame₁, frame₂, ..., frameₙ) ↓ [H.264 编码器](编码器本质是一种帧与帧之间,以及帧内的一种压缩技术) ↓ H.264 视频编码流(包含 I 帧、P 帧、B 帧) ↓ [MP4 封装器] ↓ MP4 文件(movie.mp4) ├── 视频流(H.264 编码) ├── 音频流(可选,如 AAC 编码) └── 元数据(时间戳、分辨率等)1.2 编码与解码
MP4 文件(movie.mp4) ↓ [MP4 解封装器] ↓ 分离出的流 ├── H.264 视频流(包含 I 帧、P 帧、B 帧) └── 音频流(如果有) ↓ [H.264 解码器] ↓ 重建图像序列 (recon₀, recon₁, recon₂, ..., reconₙ)1.3 硬件加速编解码
1.4 工具介绍
FFmpeg “八大金刚”核心开发库。
- AVUtil
核心工具库,用于辅助实现可移植的多媒体编程。包含安全的可移植的字符串函数,随机数生成器,附加的数学函数,密码学和多媒体相关的功能函数。 - AVFormat 协议及容器封装库。该库封装了协议层(Protocol 协议,包括文件的数据来源,I/O操作方式等)和媒体格式容器层(Muxer/Demuxer 复用/解复用,媒体文件的封装和解封装方式)。AVFormat库支持多种输入和输出协议(FILE、HTTP、UDP、RTMP等),以及多种媒体容器格式(MP4,RMVB等)。
- AVCodec
编解码库,封装了编解码层(Codec),提供了一个通用的编码/解码框架,包含了用于音频、视频和字幕流的多个编码器和解码器。也可以将其他的第三方的编解码器以插件形式加入(如x264,x265等)。 - AVFilter
滤镜库,提供了一个通用的可对音频/视频进行过滤处理的框架,其中包含过滤器,数据源和接收器的概念。 - Postproc
后处理库,包括对于音频/视频文件进行后期处理的常用操作例如隔行滤波,去噪滤波、锐化滤波等,可以用于增强视频的清晰度、减少噪点和伪影等。常与AVFilter库一起使用,新版本已经移除。 - AVDevice
设备库,提供了一个通用框架,用于对许多常见的多媒体I/O设备进行抓取和渲染。例如:需要操作:【/dev/videoX】 - SWresample
音频处理库,用于处理音频,可以执行音频的重采样、重矩阵化和样本格式转换等操作。 - SWscale
图像转换库,用于处理图像,可以执行图像缩放,色彩空间和像素格式转换等操作。例如:需要将FFmpeg中的YUV格式转换为OpenCV中的BGR格式。
第2章 工程项目实践
工程目标:从一个 RTMP 输入流(如rtmp://.../live/456)拉取视频流,使用 FFmpeg 解码后,再通过 OpenCV 进行可能的图像处理(目前注释掉了),然后重新编码为 H.264 视频流,并推送到另一个 RTMP 地址(如rtmp://.../live/dj/1ZNBJ7C00C009X)。
功能分解说明
1. 输入源使用 FFmpeg 的libavformat/libavcodec打开并读取一个 RTMP 流(也可以是本地文件或其它协议)。自动探测流信息,找到视频流(AVMEDIA_TYPE_VIDEO)。使用对应的解码器(如 H.264)进行解码,得到原始帧(AVFrame)。
2. 图像处理(可选)将解码后的AVFrame转换为 OpenCV 的cv::Mat(BGR 格式)。注释中提到可以在此处添加:叠加文字/Logo目标检测(如 YOLO)图像增强等当前未启用任何处理逻辑。
3. 重新编码将处理后的cv::Mat(BGR)转换为 YUV420P 格式的AVFrame(通过CVMatToAVFrame函数)。使用 H.264 编码器(AV_CODEC_ID_H264)对每一帧进行编码。设置了编码参数:码率:400 kbps(50 * 1024 * 8实际是 400,000 bps)GOP = 30(每30帧一个关键帧)分辨率、帧率继承自输入流
4. 输出推流使用 FLV 封装格式(RTMP 标准封装)。通过avformat_write_header和av_interleaved_write_frame将编码后的 H.264 数据推送到目标 RTMP 地址。时间戳(PTS/DTS)经过正确重缩放(av_rescale_q),保证播放同步。
2.1 格式转换
2.1.1 AVFrame 转 Mat
Mat是OpenCV的图像格式,颜色空间为BGR,对应FFmpeg格式为AV_PIX_FMT_BGR24。AVFrame一般为YUV420P,以此格式为例。 这个通过FFmpeg的格式转换函数就可以解决。转换代码如下:
cv::Mat AVFrameToCVMat(AVFrame *yuv420Frame) { //得到AVFrame信息 int srcW = yuv420Frame->width; int srcH = yuv420Frame->height; SwsContext *swsCtx = sws_getContext(srcW, srcH, (AVPixelFormat)yuv420Frame->format, srcW, srcH, (AVPixelFormat)AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL); //生成Mat对象 cv::Mat mat; mat.create(cv::Size(srcW, srcH), CV_8UC3); //格式转换,直接填充Mat的数据data AVFrame *bgr24Frame = av_frame_alloc(); av_image_fill_arrays(bgr24Frame->data, bgr24Frame->linesize, (uint8_t *)mat.data, (AVPixelFormat)AV_PIX_FMT_BGR24, srcW, srcH, 1); sws_scale(swsCtx,(const uint8_t* const*)yuv420Frame->data, yuv420Frame->linesize, 0, srcH, bgr24Frame->data, bgr24Frame->linesize); //释放 av_frame_free(bgr24Frame); sws_freeContext(swsCtx); return mat; } 2.1.2 Mat 转 AVFrame
借鉴上步,首先也是想利用FFmpeg的转换函数进行转换,但是总有问题,没有定位到具体原因。不过理解原理后,也可以自己处理数据。基本思路是将Mat转换为YUV420格式,将Y、U、V分量分别填充到对应的AVFrame里面就可以了。
AVFrame *CVMatToAVFrame(cv::Mat &inMat) { //得到Mat信息 AVPixelFormat dstFormat = AV_PIX_FMT_YUV420P; int width = inMat.cols; int height = inMat.rows; //创建AVFrame填充参数 注:调用者释放该frame AVFrame *frame = av_frame_alloc(); frame->width = width; frame->height = height; frame->format = dstFormat; //初始化AVFrame内部空间 int ret = av_frame_get_buffer(frame, 32); if (ret < 0) { qDebug() << "Could not allocate the video frame data" ; return nullptr; } ret = av_frame_make_writable(frame); if (ret < 0) { qDebug() << "Av frame make writable failed."; return nullptr; } //转换颜色空间为YUV420 cv::cvtColor(inMat, inMat, cv::COLOR_BGR2YUV_I420); //按YUV420格式,设置数据地址 int frame_size = width * height; unsigned char *data = inMat.data; memcpy(frame->data[0], data, frame_size); memcpy(frame->data[1], data + frame_size, frame_size/4); memcpy(frame->data[2], data + frame_size * 5/4, frame_size/4); return frame; } 2.2 工作流程
RTSP 输入流 ↓ av_read_frame() ← 拉流 ↓ avcodec_send_packet() avcodec_receive_frame() ← GPU 硬解 (h264_cuvid) 得到 GPU 帧(NV12 on CUDA)// 需要支持软解 ↓ av_hwframe_transfer_data() ← 下载到 CPU 内存 (NV12) ↓ sws_scale() → BGR ← 转 OpenCV 的BGR格式 ↓ cv::Mat → 自定义处理(AI视频识别等) ↓ sws_scale() → YUV420P 或 NV12 ← 转编码器所需格式(CPU) ↓ avcodec_send_frame() ← 送入编码器(可选 GPU 编码:h264_nvenc) ↓ avcodec_receive_packet() ↓ av_write_frame() ← 推送到 RTMP / RTSP / 文件2.3 工程实现
extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #include <libavutil/error.h> #include <libavutil/mem.h> #include <libswscale/swscale.h> #include <libavdevice/avdevice.h> #include <libavutil/time.h> } #include <opencv2/imgproc/imgproc_c.h> #include <opencv2/core/opengl.hpp> #include <opencv2/cudacodec.hpp> #include <opencv2/freetype.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/opencv.hpp> #include <iostream> #include <queue> #include <string> #include <vector> /// Mat 转 AVFrame AVFrame *CVMatToAVFrame(cv::Mat &inMat) { //得到Mat信息 AVPixelFormat dstFormat = AV_PIX_FMT_YUV420P; int width = inMat.cols; int height = inMat.rows; //创建AVFrame填充参数 注:调用者释放该frame AVFrame *frame = av_frame_alloc(); frame->width = width; frame->height = height; frame->format = dstFormat; //初始化AVFrame内部空间 int ret = av_frame_get_buffer(frame, 64); if (ret < 0) { return nullptr; } ret = av_frame_make_writable(frame); if (ret < 0) { return nullptr; } //转换颜色空间为YUV420 cv::cvtColor(inMat, inMat, cv::COLOR_BGR2YUV_I420); //按YUV420格式,设置数据地址 int frame_size = width * height; unsigned char *data = inMat.data; memcpy(frame->data[0], data, frame_size); memcpy(frame->data[1], data + frame_size, frame_size/4); memcpy(frame->data[2], data + frame_size * 5/4, frame_size/4); return frame; } void rtmpPush2(std::string inputUrl, std::string outputUrl){ // 视频流下标 int videoindex = -1; // 注册相关服务,可以不用 av_register_all(); avformat_network_init(); const char *inUrl = inputUrl.c_str(); const char *outUrl = outputUrl.c_str(); // 使用opencv解码 VideoCapture capture; cv::Mat frame; // 像素转换上下文 SwsContext *vsc = NULL; // 输出的数据结构 AVFrame *yuv = NULL; // 输出编码器上下文 AVCodecContext *outputVc = NULL; // rtmp flv 封装器 AVFormatContext *output = NULL; // 定义描述输入、输出媒体流的构成和基本信息 AVFormatContext *input_ctx = NULL; AVFormatContext * output_ctx = NULL; /// 1、加载视频流 // 打开一个输入流并读取报头 int ret = avformat_open_input(&input_ctx, inUrl, 0, NULL); if (ret < 0) { cout << "avformat_open_input failed!" << endl; return; } cout << "avformat_open_input success!" << endl; // 读取媒体文件的数据包以获取流信息 ret = avformat_find_stream_info(input_ctx, 0); if (ret != 0) { return; } // 终端打印相关信息 av_dump_format(input_ctx, 0, inUrl, 0); //如果是输入文件 flv可以不传,可以从文件中判断。如果是流则必须传 ret = avformat_alloc_output_context2(&output_ctx, NULL, "flv", outUrl); if (ret < 0) { cout << "avformat_alloc_output_context2 failed!" << endl; return; } cout << "avformat_alloc_output_context2 success!" << endl; cout << "nb_streams: " << input_ctx->nb_streams << endl; unsigned int i; for (i = 0; i < input_ctx->nb_streams; i++) { AVStream *in_stream = input_ctx->streams[i]; // 根据输入流创建输出流 AVStream *out_stream = avformat_new_stream(output_ctx, in_stream->codec->codec); if (!out_stream) { cout << "Failed to successfully add audio and video stream" << endl; ret = AVERROR_UNKNOWN; } //将输入编解码器上下文信息 copy 给输出编解码器上下文 ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar); if (ret < 0) { printf("copy 编解码器上下文失败\n"); } out_stream->codecpar->codec_tag = 0; out_stream->codec->codec_tag = 0; if (output_ctx->oformat->flags & AVFMT_GLOBALHEADER) { out_stream->codec->flags = out_stream->codec->flags | AV_CODEC_FLAG_GLOBAL_HEADER; } } //查找到当前输入流中的视频流,并记录视频流的索引 for (i = 0; i < input_ctx->nb_streams; i++) { if (input_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videoindex = i; break; } } //获取视频流中的编解码上下文 AVCodecContext *pCodecCtx = input_ctx->streams[videoindex]->codec; //4.根据编解码上下文中的编码id查找对应的解码 AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id); // pCodec = avcodec_find_decoder_by_name("flv"); if (pCodec == NULL) { std::cout << "找不到解码器" << std::endl; return; } //5.打开解码器 if (avcodec_open2(pCodecCtx, pCodec,NULL)<0) { std::cout << "解码器无法打开" << std::endl; return; } //推流每一帧数据 AVPacket pkt; //获取当前的时间戳 微妙 long long start_time = av_gettime(); long long frame_index = 0; int count = 0; int indexCount = 1; int numberAbs = 1; bool flag; int VideoCount = 0; std::queue<std::string> VideoList; /// 定义解码过程中相关参数 AVFrame *pFrame = av_frame_alloc(); int got_picture; int frame_count = 0; AVFrame* pFrameYUV = av_frame_alloc(); uint8_t *out_buffer; out_buffer = new uint8_t[avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height)]; avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); try{ /// opencv 解码 // capture.open(inUrl); // while (!capture.isOpened()){ // capture.open(inUrl); // std::cout << "直播地址无法打开" << endl; // } // int inWidth = capture.get(CAP_PROP_FRAME_WIDTH); // int inHeight = capture.get(CAP_PROP_FRAME_HEIGHT); // int fps = capture.get(CAP_PROP_FPS); /// TODO: ffmpeg 解码 /// 2 初始化格式转换上下文 vsc = sws_getCachedContext(vsc, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB24, // 源宽、高、像素格式 pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, // 目标宽、高、像素格式 SWS_BICUBIC, // 尺寸变化使用算法 0, 0, 0); if (!vsc) { throw logic_error("sws_getCachedContext failed!"); // 转换失败 } /// 3 初始化输出的数据结构 yuv = av_frame_alloc(); yuv->format = AV_PIX_FMT_YUV420P; yuv->width = pCodecCtx->width; yuv->height = pCodecCtx->height; yuv->pts = 0; // 分配yuv空间 int ret = av_frame_get_buffer(yuv, 32); if (ret != 0) { char buf[1024] = {0}; av_strerror(ret, buf, sizeof(buf) - 1); throw logic_error(buf); } /// 4 初始化编码上下文 // a 找到编码器 AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264); if (!codec) { throw logic_error("Can`t find h264 encoder!"); // 找不到264编码器 } // b 创建编码器上下文 outputVc = avcodec_alloc_context3(codec); if (!outputVc) { throw logic_error("avcodec_alloc_context3 failed!"); // 创建编码器失败 } // c 配置编码器参数 outputVc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; // 全局参数 outputVc->codec_id = codec->id; outputVc->thread_count = 8; outputVc->bit_rate = 50 * 1024 * 8; // 压缩后每秒视频的bit位大小为50kb outputVc->width = pCodecCtx->width; outputVc->height = pCodecCtx->height; outputVc->time_base = {1, pCodecCtx->time_base.den}; outputVc->framerate = {pCodecCtx->time_base.den, 1}; /// TODO 以下参数可以控制直播画质和清晰度 // 画面组的大小,多少帧一个关键帧 outputVc->gop_size = 30; outputVc->max_b_frames = 1; outputVc->qmax = 51; outputVc->qmin = 10; outputVc->pix_fmt = AV_PIX_FMT_YUV420P; // d 打开编码器上下文 ret = avcodec_open2(outputVc, 0, 0); if (ret != 0) { char buf[1024] = {0}; av_strerror(ret, buf, sizeof(buf) - 1); throw logic_error(buf); } cout << "avcodec_open2 success!" << endl; /// 5 输出封装器和视频流配置 // a 创建输出封装器上下文 ret = avformat_alloc_output_context2(&output, 0, "flv", outUrl); if (ret != 0) { char buf[1024] = {0}; av_strerror(ret, buf, sizeof(buf) - 1); throw logic_error(buf); } // b 添加视频流 AVStream *vs = avformat_new_stream(output, codec); if (!vs) { throw logic_error("avformat_new_stream failed"); } vs->codecpar->codec_tag = 0; // 从编码器复制参数 avcodec_parameters_from_context(vs->codecpar, outputVc); av_dump_format(output, 0, outUrl, 1); // 写入封装头 ret = avio_open(&output->pb, outUrl, AVIO_FLAG_WRITE); ret = avformat_write_header(output, NULL); if (ret != 0) { cout << "ret:" << ret << endl; char buf[1024] = {0}; av_strerror(ret, buf, sizeof(buf) - 1); throw logic_error(buf); } AVPacket pack; memset(&pack, 0, sizeof(pack)); int vpts = 0; bool imgFrameFlag = false; int timeCount = 0; while (1) { /// 读取rtsp视频帧(opencv 读取方式, 如使用opencv方式、则不需要前期的针对输入流的相关处理) /* if (!capture.grab()) { continue; } /// yuv转换为rgb if (!capture.retrieve(frame)) { continue; } if (frame.empty()){ capture.open(inUrl); while (!capture.isOpened()){ capture.open(inUrl); std::cout << "直播地址无法打开" << endl; } imgFrameFlag = true; } if (imgFrameFlag){ imgFrameFlag = false; continue; } */ ret = av_read_frame(input_ctx, &pkt); if (ret < 0) { continue; } //延时 if (pkt.stream_index == videoindex) { /// TODO: 获取frame //7.解码一帧视频压缩数据,得到视频像素数据 auto start = std::chrono::system_clock::now(); ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &pkt); auto end = std::chrono::system_clock::now(); // cout << "The run time is: " <<(double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl; // cout << "Detection time of old version is: " <<std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << endl; if (ret < 0) { cout << ret << endl; std::cout << "解码错误" << std::endl; av_frame_free(&yuv); av_free_packet(&pack); continue; } //为0说明解码完成,非0正在解码 if (got_picture) { SwsContext *img_convert_ctx; img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL); sws_scale(img_convert_ctx, (const uint8_t *const *) pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); frame_count++; cv::Mat img; img = cv::Mat(pCodecCtx->height, pCodecCtx->width, CV_8UC3); img.data = pFrameYUV->data[0]; /// TODO: 图像处理模块 /* 此处可根据需要进行相关处理,往视频叠加元素、视频检测 */ /// rgb to yuv yuv = CVMatToAVFrame(img); /// h264编码 yuv->pts = vpts; vpts++; ret = avcodec_send_frame(outputVc, yuv); av_frame_free(&yuv); if (ret != 0){ av_frame_free(&yuv); av_free_packet(&pack); continue; } ret = avcodec_receive_packet(outputVc, &pack); if (ret != 0 || pack.size > 0) {} else { av_frame_free(&yuv); av_free_packet(&pack); continue; } int firstFrame = 0; if (pack.dts < 0 || pack.pts < 0 || pack.dts > pack.pts || firstFrame) { firstFrame = 0; pack.dts = pack.pts = pack.duration = 0; } // 推流 pack.pts = av_rescale_q(pack.pts, outputVc->time_base, vs->time_base); // 显示时间 pack.dts = av_rescale_q(pack.dts, outputVc->time_base, vs->time_base); // 解码时间 pack.duration = av_rescale_q(pack.duration, outputVc->time_base, vs->time_base); // 数据时长 ret = av_interleaved_write_frame(output, &pack); if (ret < 0) { printf("发送数据包出错\n"); av_frame_free(&yuv); av_free_packet(&pack); continue; } av_frame_free(&yuv); av_free_packet(&pack); } } } }catch (exception e){ if (vsc) { sws_freeContext(vsc); vsc = NULL; } if (outputVc) { avio_closep(&output->pb); avcodec_free_context(&outputVc); } cerr << e.what() << endl; } } int main() { av_log_set_level(AV_LOG_TRACE); rtmpPush2("rtmp://39.170.104.236:28081/live/456","rtmp://39.170.104.237:28081/live/dj/1ZNBJ7C00C009X"); } 2.4 常用工具类封装
ffmpegheader.h
#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_Hffmpegheader.cpp
#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; }