音视频/FFmpeg #Qt
Qt-FFmpeg开发-打开本地摄像头录制视频【软解码+ OpenGL显示YUV】
目录
- 音视频/FFmpeg #Qt
-
Qt-FFmpeg开发-打开本地摄像头录制视频【软解码+ OpenGL显示YUV】
- 1、概述
- 2、实现效果
- 3、FFmpeg录制视频编码流程
- 4、主要代码
- 5、完整源代码
更多精彩内容 |
---|
👉个人内容分类汇总 👈 |
👉音视频开发 👈 |
1、概述
- 最近研究了一下FFmpeg开发,功能实在是太强大了,网上ffmpeg3、4的文章还是很多的,但是学习嘛,最新的还是不能放过,就选了一个最新的ffmpeg n5.1.2版本,和3、4版本api变化还是挺大的;
- 在这个Demo里主要使用Qt + FFmpeg开发一个摄像头【录像机】,这里主要使用的是【软解码】,需要使用硬解码的可以看之前的文章;
- 在之前的文章中使用了QPainter进行绘制显示,也讲了使用OpenGL显示RGB、YUV图像方式;
- 由于FFmpeg解码得到的像素格式为YUVJ422P,将YUVJ422P转换为RGB或者YUV420p都很麻烦,并且会消耗CPU资源,所以这里直接使用OpenGL显示YUVJ422P图像,(将YUVJ422P转RGB的步骤放到了GPU中进行)。
- 关于打开摄像头部分请看上一章,整理不重复说明,这里主要讲述录像功能。
开发环境说明
- 系统:Windows10、Ubuntu20.04
- Qt版本:V5.12.5
- 编译器:MSVC2017-64、GCC/G++64
- FFmpeg版本:n5.1.2
- 官方下载
- 我使用的库
2、实现效果
- 使用ffmpeg音视频库【软解码】打开本地摄像头【录制视频】保存到本地;
- 采用【OpenGL显示YUV】图像,支持自适应窗口缩放,支持使用QOpenGLWidget、QOpenGLWindow显示;
- 将YUV转RGB的步骤由CPU转换改为使用GPU转换,降低CPU占用率;
- 支持Windows、Linux打开本地摄像头;
- 支持使用【静态帧率】、【动态帧率】录制视频;
- 视频解码、线程控制、显示各部分功能分离,低耦合度。
- 采用最新的5.1.2版本ffmpeg库进行开发,超详细注释信息,将所有踩过的坑、解决办法、注意事项都得很写清楚。
- 使用CPU软解码 + OpenGL绘制 + CPU软编码录制
3、FFmpeg录制视频编码流程
- 白色部分主要为创建、设置信息步骤,蓝色部分主要为写入数据步骤。
4、主要代码
-
啥也不说了,直接上代码,一切有注释
-
videosave.h文件
/****************************************************************************** * @文件名 videosave.h * @功能 将视频编码后保存到文件中 * * @开发者 mhf * @邮箱 1603291350@qq.com * @时间 2022/11/29 * @备注 *****************************************************************************/ #ifndef VIDEOSAVE_H #define VIDEOSAVE_H #include
#include struct AVCodecParameters; struct AVFormatContext; struct AVCodecContext; struct AVStream; struct AVFrame; struct AVPacket; struct AVOutputFormat; class VideoSave { public: VideoSave(); ~VideoSave(); bool open(AVStream *inStream, const QString& fileName); void write(AVFrame* frame); void close(); private: void showError(int err); private: AVFormatContext* m_formatContext = nullptr; AVCodecContext * m_codecContext = nullptr; // 编码器上下文 AVStream * m_videoStream = nullptr; AVPacket * m_packet = nullptr; // 数据包 int m_index = 0; bool m_writeHeader = false; // 是否写入头 QMutex m_mutex; }; #endif // VIDEOSAVE_H -
videosave.cpp文件
#include "videosave.h" #include
extern "C" { // 用C规则编译指定的代码 #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libavutil/avutil.h" #include "libswscale/swscale.h" #include "libavutil/imgutils.h" #include "libavdevice/avdevice.h" } #define ERROR_LEN 1024 // 异常信息数组长度 #define PRINT_LOG 1 #define USE_H264 0 // 使用H264编码器 VideoSave::VideoSave() { } VideoSave::~VideoSave() { close(); } /** * @brief 显示ffmpeg函数调用异常信息 * @param err */ void VideoSave::showError(int err) { #if PRINT_LOG static char m_error[ERROR_LEN]; // 保存异常信息 memset(m_error, 0, ERROR_LEN); // 将数组置零 av_strerror(err, m_error, ERROR_LEN); qWarning() codecpar->codec_id)->name; // 获取编码器名称 int ret = avformat_alloc_output_context2(&m_formatContext, nullptr, strName.toStdString().data(), fileName.toStdString().data()); // 这里使用和解码一样的编码器,防止保存的图像颜色出问题 #endif if(ret pb, fileName.toStdString().data(), AVIO_FLAG_WRITE); if(ret oformat->video_codec); if(!codec) { close(); showError(AVERROR(ENOMEM)); return false; } // 分配AVCodecContext并将其字段设置为默认值。 m_codecContext = avcodec_alloc_context3(codec); if(!m_codecContext) { close(); showError(AVERROR(ENOMEM)); return false; } // 设置编码器上下文参数 m_codecContext->width = inStream->codecpar->width; // 图片宽度/高度 m_codecContext->height = inStream->codecpar->height; #if USE_H264 m_codecContext->pix_fmt = AV_PIX_FMT_YUV420P; #else m_codecContext->pix_fmt = AVPixelFormat(inStream->codecpar->format); // 像素格式,也可以使用codec->pix_fmts[0]或AV_PIX_FMT_YUVJ422P(【注意】摄像头解码的图像格式为yuvj422p,如果这里不一样可能保存会出问题,或者后面进行格式转换) #endif m_codecContext->time_base = {1, 10}; //设置时间基,20为分母,1为分子,表示以1/20秒时间间隔播放一帧图像 m_codecContext->framerate = {10, 1}; m_codecContext->bit_rate = 4000000; // 目标的码率,即采样的码率;显然,采样码率越大,视频大小越大,画质越高 m_codecContext->gop_size = 10; // I帧间隔 m_codecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; // m_codecContext->max_b_frames = 1; // 非B帧之间的最大B帧数(有些格式不支持) // m_codecContext->qmin = 1; // m_codecContext->qmax = 5; // m_codecContext->colorspace = AVCOL_SPC_BT470BG; // m_codecContext->color_range = AVCOL_RANGE_JPEG; // m_codecContext->color_primaries = AVCOL_PRI_BT709; // m_codecContext->bits_per_coded_sample = 24; // m_codecContext->bits_per_raw_sample = 8; // av_opt_set(m_codecContext->priv_data, "preset", "placebo", 0); // qDebug() pix_fmt; // 打开编码器 ret = avcodec_open2(m_codecContext, nullptr, nullptr); #if USE_H264 ret = avcodec_open2(m_codecContext, codec, nullptr); // 使用h264时第一次打不开,第二次可以打卡,不知道什么原因 #endif if(ret codecpar,m_codecContext); if(ret pts = m_index; // 注意:每一帧视频显示时间从0递增,否则录制的视频显示/时长会不对 m_index++; } // 将图像传入编码器 avcodec_send_frame(m_codecContext, frame); // 循环读取所有编码完的帧 while (true) { // 从编码器中读取图像帧 int ret = avcodec_receive_packet(m_codecContext, m_packet); if(ret time_base, m_videoStream->time_base); av_write_frame(m_formatContext, m_packet); // 将数据包写入输出媒体文件 av_packet_unref(m_packet); } } /** * @brief 关闭保存数据 */ void VideoSave::close() { write(nullptr); // 传入空帧,读取所有编码数据 QMutexLocker locker(&m_mutex); // 如果不加锁可能在点击关闭时,write函数正在写入数据,导致崩溃 if(m_formatContext) { // 写入文件尾 if(m_writeHeader) { m_writeHeader = false; int ret = av_write_trailer(m_formatContext); if(ret pb); if(ret
5、完整源代码
- github
- gitee
机房租用,北京机房托管,大带宽租用,IDC机房服务器主机租用托管-价格及服务咨询 www.e1idc.net