linux+QT+FFmpeg 6.0,把多个QImage组合成一个视频

直接上代码吧:

RecordingThread.h#ifndef RECORDINGTHREAD_H
#define RECORDINGTHREAD_H
#include "QTimer"
#include <QObject>
#include <QImage>
#include <QQueue>extern "C"{//因为FFmpeg是c语言,QT里面调用的话需要extern "C"#include "libavcodec/avcodec.h"#include "libavformat/avformat.h"#include "libswscale/swscale.h"#include "libavdevice/avdevice.h"#include "libavformat/avio.h"#include "libavutil/imgutils.h"
}class RecordingThread : public QObject
{Q_OBJECTpublic:void FFmpegInit();void saveMp4(QImage image);void stopMp4();void setImage(QImage image);public slots:void recordInit();signals:void send(QString);private:AVFormatContext* formatContext;AVCodecParameters* codecParameters;const AVCodec* codec;AVCodecContext* codecContext;AVStream* stream;const AVPixelFormat* pixFmt;int num = 0;QQueue<QImage> gQdata;int isRecord = -1;};#endif // RECORDINGTHREAD_H
#include "recordingthread.h"
#include <QPainter>
#include <cmath>
#include <QPainterPath>
#include <QDebug>
#include <QTimer>
#include <QDateTime>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>void RecordingThread::FFmpegInit(){isRecord = -1;int ret;// 初始化 FFmpegqDebug()<<"avdevice_register_all()";avdevice_register_all(); //初始化所有设备qDebug()<<"formatContext = avformat_alloc_context()";formatContext = avformat_alloc_context();//分配format上下文qint64 timeT = QDateTime::currentMSecsSinceEpoch();//毫秒级时间戳QString outputFileName = QString("/sdcard/").append("ffmpeg").append(QString::number(timeT)).append(".mp4");//第三个参数可以直接使用nullptr 根据outputFileName的后缀自动识别qDebug()<<"avformat_alloc_output_context2(&formatContext, nullptr, \"mp4\", outputFileName.toUtf8().constData())";ret = avformat_alloc_output_context2(&formatContext, nullptr, nullptr, outputFileName.toUtf8().constData());qDebug()<<"ret===="<<ret;qDebug()<<"formatContext===="<<formatContext;qDebug()<<"formatContext->oformat = av_guess_format(nullptr, outputFileName.toUtf8().constData(), nullptr);";formatContext->oformat = av_guess_format(nullptr, outputFileName.toUtf8().constData(), nullptr);qDebug() << "avio_open(&formatContext->pb, outputFileName.toUtf8().constData(), AVIO_FLAG_WRITE) < 0";// 打开输出文件if (avio_open(&formatContext->pb, outputFileName.toUtf8().constData(), AVIO_FLAG_WRITE) < 0) {qDebug() << "Failed to open output file";return;}qDebug() << "AVStream* stream = avformat_new_stream(formatContext, nullptr);";// 创建一个AVStream对象stream = avformat_new_stream(formatContext, nullptr);if (!stream) {qDebug() << "Failed to create output stream";return;}qDebug() << "AVCodecParameters* codecParameters = stream->codecpar;";// 配置AVCodecContextcodecParameters = stream->codecpar;codecParameters->codec_type = AVMEDIA_TYPE_VIDEO;codecParameters->codec_id = AV_CODEC_ID_H264; // 使用H.264编码器codecParameters->width = 400;codecParameters->height = 400;qDebug() << " const AVCodec* codec = avcodec_find_encoder(codecParameters->codec_id);";// 打开编解码器codec = avcodec_find_encoder(codecParameters->codec_id);codecContext = avcodec_alloc_context3(codec);codecContext->width = 400;codecContext->height = 400;codecContext->pix_fmt = AV_PIX_FMT_YUV420P;codecContext->time_base = {1, 25}; // 设置编码器的时间基为 1秒/30帧codecContext->framerate = {25, 1}; // 设置编码器的帧率为 30fps//codecContext->thread_count = 4;qDebug() << "AV_PIX_FMT_YUV420P====="<<AV_PIX_FMT_YUV420P;qDebug() << "codecContext->pix_fmt====="<<codecContext->pix_fmt;qDebug() << "avcodec_open2(codecContext, codec, nullptr);";//设置完成编码格式以后要立刻打开,要不然调用avcodec_parameters_to_context的时候会重置编码ret = avcodec_open2(codecContext, codec, nullptr);if(ret < 0){qDebug() << "Failed to avcodec_open2";return;}qDebug() << "avcodec_parameters_to_context(codecContext, codecParameters);";// 将编码器参数复制到输出流avcodec_parameters_to_context(codecContext, codecParameters);// 检查编解码器支持的像素格式pixFmt = codec->pix_fmts;qDebug() << "while";while (*pixFmt != AV_PIX_FMT_NONE) {qDebug() << av_get_pix_fmt_name(*pixFmt);++pixFmt;}qDebug() << " avformat_write_header(formatContext, nullptr);";// 写入头部信息avformat_write_header(formatContext, nullptr);
}
void RecordingThread::saveMp4(QImage image){int imagewidth = image.width();int imageheight = image.height();int ret;//qDebug() << "  AVFrame* frame = av_frame_alloc();";// 逐个写入图像帧AVFrame* frame = av_frame_alloc();if (!frame) {qDebug() << "Failed to allocate frame.";return;}//qDebug() << "frame->format = AV_PIX_FMT_YUV420P";frame->format = AV_PIX_FMT_YUV420P;frame->width = imagewidth;frame->height = imageheight;// 在循环中设置每一帧的时间戳 如果没有这个可能就是0秒视频frame->pts = (((AV_TIME_BASE / 25))/10 * num);// qDebug() << "frame->pts===========" << frame->pts;if (av_frame_get_buffer(frame, 0) < 0) {qDebug() << "Failed to allocate frame buffer.";av_frame_free(&frame);return;}// 图像格式转换SwsContext* swsContext = sws_getContext(imagewidth, imageheight, AV_PIX_FMT_RGB32,frame->width, frame->height, AV_PIX_FMT_YUV420P,SWS_BICUBIC, nullptr, nullptr, nullptr);if (!swsContext) {qDebug() << "Failed to create SwsContext.";av_frame_free(&frame);return;}uint8_t* destData[4] = {frame->data[0], frame->data[1], frame->data[2], nullptr};int destLinesize[4] = {frame->linesize[0], frame->linesize[1], frame->linesize[2], 0};//av_image_fill_arrays(frame->data, frame->linesize, destData[0], codecContext->pix_fmt, codecContext->width, codecContext->height, 1);image = image.convertToFormat(QImage::Format_RGB32);const uchar* bits = image.constBits();int bytesPerLine = image.bytesPerLine();// 函数返回的值是转换后的图像的输出行数。输出的图像高度为图像像素。ret = sws_scale(swsContext, &bits, &bytesPerLine, 0, image.height(), destData, destLinesize);//qDebug() << "sws_scale ret==="<<ret;//函数用于释放由 sws_getContext 函数创建的图像格式转换上下文sws_freeContext(swsContext);//qDebug() << "AVPacket packet;";// 编码并写入视频帧AVPacket packet;av_init_packet(&packet);packet.data = nullptr;packet.size = 0;int code = -1;// 接收输出包while (code < 0) {ret = avcodec_send_frame(codecContext, frame);// qDebug() << "avcodec_send_frame ret===="<<ret;code = avcodec_receive_packet(codecContext, &packet);//qDebug() << "while avcodec_receive_packet====" << code;if(code == 0){// 处理输出包ret = av_interleaved_write_frame(formatContext, &packet);//  qDebug() << "av_interleaved_write_frame==================" << ret;av_packet_unref(&packet);  // 释放输出包} else if (code == AVERROR(EAGAIN)) {// 当输出队列为空时,需要重新发送帧进行编码continue;} else {qDebug() << "Error encoding frame: " << code;break;}}//qDebug() << "av_frame_free(&frame);";av_frame_free(&frame);//qDebug()<<"num==============================================="<<num;++num;
}
void RecordingThread::stopMp4(){isRecord = 0;
}void RecordingThread::setImage(QImage image){isRecord = 1;gQdata.push_back(image);
}void RecordingThread::recordInit(){while (1) {if(!gQdata.isEmpty() && gQdata.size()>0){QImage qimage = gQdata.dequeue();saveMp4(qimage);}else{if(isRecord == 0){isRecord = -1;num = 0;//写入尾部信息qDebug() << "av_write_trailer(formatContext)";	int ret = av_write_trailer(formatContext);qDebug() << "av_write_trailer(formatContext) ret==="<<ret;	emit send("stopRecode");}		}usleep(5000);}
}

我这里是专门搞了个类封装,我把这个类当成线程使用了,在启动程序的时候直接当线程启动recordInit():比如这样


 然后我在需要合成视频的时候先调用初始化:

mRecordingThread->FFmpegInit();

再传入QImage:

mRecordingThread->setImage(rotatedImage);
停止的时候再调用:
mRecordingThread->stopMp4();

这样就不会造成卡死主线程的情况

我在使用FFmpeg的时候主要出现两个比较明显的情况:

1.pix_fmt为-1的情况,原因是

设置完成编码格式以后要立刻打开,要不然调用avcodec_parameters_to_context的时候会重置编码

2.合成的视频只有一帧的情况

//主要是因为这个参数导致的,你们可以根据自己的需求微调
// 在循环中设置每一帧的时间戳 如果没有这个可能就是0秒视频 
frame->pts = ((baseTimestamp + (num * (AV_TIME_BASE / 30)))/10);

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/86531.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【【萌新的STM32学习-16中断的基本介绍1】】

萌新的STM32学习-16中断的基本介绍1 中断 什么是中断 中断是打断CPU执行正常的程序&#xff0c;转而处理紧急程序&#xff0c;然后返回原暂停的程序继续执行&#xff0c;就叫中断 中断的作用 实时控制 &#xff1a; 就像对温度进行控制 故障控制 &#xff1a; 第一时间对突发情…

ICS PA1

ICS PA1 init.shmake 编译加速ISA计算机是个状态机程序是个状态机准备第一个客户程序parse_argsinit_randinit_loginit_meminit_isa load_img剩余的初始化工作运行第一个客户程序调试&#xff1a;零断点TUI 基础设施单步执行打印寄存器状态扫描内存 表达式求值词法分析递归求值…

指针C语言

1指针方式 1.int a,*p&a; 2. int a;int *p&a;特点&#xff1a; 1.指针变量与类型无关&#xff0c;在TC占2字节&#xff0c;在VC下占四字节 2.指针变量的引用 1.直接引用 2.间接引用 注意*的运算对象必须为地址 *p1; //相当于取p指针指向的值然后&#xff0b;1 int …

Zabbix下载安装及SNMP Get使用

帮助文档&#xff1a;6. Zabbix Appliance 一、zabbix下载安装 1、获取Zabbix Appliance镜像 Download Zabbix appliance 2、使用该镜像创建虚拟机 3、打开虚拟机控制台自动安装&#xff0c;等待安装完成即可 默认配置 系统/数据库&#xff1a;root:zabbix Zabbix 前端&am…

django的简易的图书管理系统jsp书店进销存源代码MySQL

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 django的简易的图书管理系统 系统有1权限&#xff1a…

STM32移植ST77891.69寸屏幕并移植lvgl8.0.2(按键输入设备)一些心得

学习目标: 将ST7789(1.69寸圆角屏SPI)驱动移植+lvgl移植+按键当作输入设备 学习内容: 驱动移植lvgl移植按键移植软件使用正片开始: 先说说这块屏幕的介绍呗 ST7789屏幕是一种高性能的液晶显示屏,它具有高清晰度、高亮度、低功耗等优点。它采用了SPI接口通信,可以实现快速…

无涯教程-聚类算法 - K-Means

K-均值聚类算法计算质心并进行迭代&#xff0c;直到找到最佳质心为止&#xff0c;它假定群集的数目是已知的&#xff0c;它也称为扁平聚类算法。通过算法从数据中识别出的簇数以K均值中的" K"表示。 在该算法中&#xff0c;将数据点分配给群集&#xff0c;以使数据点…

python 模块BeautifulSoup 从HTML或XML文件中提取数据

一、安装 Beautiful Soup 是一个HTML/XML的解析器&#xff0c;主要的功能也是如何解析和提取 HTML/XML 数据。 lxml 只会局部遍历&#xff0c;而Beautiful Soup 是基于HTML DOM的&#xff0c;会载入整个文档&#xff0c;解析整个DOM树&#xff0c;因此时间和内存开销都会大很多…

206.Flink(一):flink概述,flink集群搭建,flink中执行任务,单节点、yarn运行模式,三种部署模式的具体实现

一、Flink概述 1.基本描述 Flink官网地址:Apache Flink — Stateful Computations over Data Streams | Apache Flink Flink是一个框架和分布式处理引擎,用于对无界和有界数据流进行有状态计算。 2.有界流和无界流 无界流(流): 有定义流的开始,没有定义结束。会无休止…

【Python】从入门到上头—Python基础(2)

文章目录 一.基础语法1.编码2.标识符3.保留字4.注释5.行与缩进6.多行语句7.数字(Number)类型8.字符串(String)9.空行10.等待用户输入11.同一行显示多条语句12.多个语句构成代码组13.print 输出14.import 与 from...import 二.基本数据类型1.变量和赋值2.多个变量赋值3.标准数据…

react-sortable-hoc 拖拽列表上oncick事件失效

const SortableItem SortableElement(({value, onChangePayment}) > {const onClickItem () > {// todo}return (<View className"-item" onClick{onClickItem}>xxxxxxx</View>) })问题&#xff1a;onClick 无效 解决&#xff1a;添加distance

Nexus私有仓库+IDEA配置远程推送

目录 一、docker安装nexus本地私服&#xff0c;Idea通过maven配置deploy本地jar包&#xff08;简单&#xff09; 二、docker push镜像到第三方nexus远程私服&#xff08;shell命令操作&#xff09; 三、springboot通过maven插件自动生成docker镜像并push到nexus私服&#xf…