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

news2024/11/27 9:49:33

直接上代码吧:

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_OBJECT

public:
    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;
    // 初始化 FFmpeg
    qDebug()<<"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;";
     // 配置AVCodecContext
    codecParameters = 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.coloradmin.cn/o/940577.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Java swing和mysql实现的学生选课管理系统(源码+数据库+运行指导视频)

一、项目简介 本项目是一套Java swing和mysql实现的学生选课管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、项目文档、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严…

浮点动作 转 布尔动作(FloatActionToBooleanAction)

文章目录 思维导图具体步骤1、“浮点动作”的创建2、“布尔动作”的创建3、用“FloatToBoolean.cs”把两个动作联系起来 注意 思维导图 具体步骤 1、“浮点动作”的创建 “右手柄 扳机键 按下”需借助“Input.UnityInputManager.1DAxisAction”预设体成为FloatAction&#xf…

Java 读取TIFF JPEG GIF PNG PDF

Java 读取TIFF JPEG GIF PNG PDF 本文解决方法基于开源 tesseract 下载适合自己系统版本的tesseract &#xff0c;官网链接&#xff1a;https://digi.bib.uni-mannheim.de/tesseract/ 2. 下载之后安装&#xff0c;安装的时候选择选择语言包&#xff0c;我选择了中文和英文 3.…

代码随想录打卡—day44—【DP】— 8.28 完全背包基础

1 完全背包基础 完全背包和01背包问题唯一不同的地方就是&#xff0c;每种物品有无限件。 而完全背包的物品是可以添加多次的&#xff0c;所以要内嵌的背包容量循环从小到大去遍历&#xff0c;即&#xff1a;&#xff08;至于为什么&#xff0c;内嵌背包容量循环从小到大遍历…

mysql 计算两点之间距离

先说一下我们可能会用到的一些场景&#xff0c;这样同学们可以先评估&#xff0c;该篇文章是否对你有帮助&#xff01; 场景&#xff1a; 假设 美团&#xff0c;我点外卖时&#xff0c;系统会让我先进行定位&#xff0c;比如我定位在了 A 点&#xff0c;系统就会给我推荐&…

八路参考文献:[八一新书]许少辉.乡村振兴战略下传统村落文化旅游设计[M]北京:中国建筑工业出版社,2022.

八路参考文献&#xff1a;&#xff3b;八一新书&#xff3d;许少辉&#xff0e;乡村振兴战略下传统村落文化旅游设计&#xff3b;&#xff2d;&#xff3d;北京&#xff1a;中国建筑工业出版社&#xff0c;&#xff12;&#xff10;&#xff12;&#xff12;&#xff0e;

多商户门店会员卡充值收款营销公众号开发

多商户门店会员卡充值收款营销公众号开发 一、功能特色&#xff1a; 专注于收款码支持商户入驻功能&#xff0c;降低运营成本资金可以直接到达商户&#xff0c;支持微信分账方式分配佣金&#xff0c;完全符合支付结算相关法规充值、充次支持给赠值设置有效期&#xff0c;促进…

防雷浪涌保护器选型方案

防雷浪涌保护器是一种用于保护电气设备免受雷电或其他电源干扰引起的过电压或过电流的装置。防雷浪涌保护器的选型应根据国家标准、设备要求和实际工程条件进行&#xff0c;以达到既满足防雷验收要求&#xff0c;又能有效保护设备的目的。地凯科技介绍一些常用的防雷浪涌保护器…

论文解读 | OmniObject3D:用于逼真感知、重建和生成的大词汇量3D对象数据集

原创 | 文 BFT机器人 这篇论文的主要目标是介绍和探索OmniObject3D数据集&#xff0c;该数据集包含大量真实扫描的3D物体&#xff0c;涵盖了190个类别&#xff0c;提供了多种丰富的注释&#xff0c;包括纹理3D网格、采样点云、多视图图像等。作者将OmniObject3D应用于多个3D视…

mysql和mybatisPlus实现:datetime类型的字段范围查询

前提说明 数据库在存储数据时,我们为了精确一下时间,便会把改时间类型的字段设置为datetime类型; 在过滤数据库数据时,我们又需要对该字段进行一个范围的过滤 由此,便出现了这篇博客 datetime数据类型 在MySQL中,datetime数据类型用于保存日期和时间的值。它的格式为Y…

ChatGPT 制作可视化柱形图突出显示第1名与最后1名

对比分析柱形图的用法。在图表中显示最大值与最小值。 像这样的动态图表的展示只需要给ChatGPT,AIGC,OpenAI 发送一个指令就可以了, 人工智能会快速的写出HTML与JS代码来实现。 请使用HTML,JS,Echarts完成一个对比分析柱形图,在图表中突出显示第1名和最后1名用单独一种不…

局域网远程软件Radmin

Radmin是一个快速且安全的远程控制和远程访问软件&#xff0c;通过它可以就像坐在远程计算机前一样&#xff0c;在远程计算机上工作&#xff0c;并可以从多个位置访问远程计算机。&#xff08;本例使用的版本是Radmin 3.5&#xff09; 下载Radmin 3.5安装包。 Radmin 3.5安装…

通信原理板块——基础知识(三)

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 7、能量信号的频谱密度 (1)能量信号…

直播时代下的海外网红营销:挑战与机遇并存

随着科技的迅猛发展&#xff0c;互联网正在以前所未有的速度改变着人们的生活方式和商业模式。其中&#xff0c;直播和网红营销作为数字时代的新生力量&#xff0c;迅速崛起并引领着市场的潮流。特别是在海外市场&#xff0c;直播时代为海外网红营销带来了前所未有的机遇和挑战…

ThinkPHP 通用的API格式封装

ThinkPHP 通用的API格式封装 1.创建status.php 用于设置通用的状态码返回枚举类2.将API返回格式统一封装3.重写BaseController中的__call方法4.在控制器下面新建Error控制器&#xff0c;然后添加__call方法 1.创建status.php 用于设置通用的状态码返回枚举类 <?phpreturn[…

[Agent]开发---ConversationalRetrievalAgent开发

参考资料[langchain官方文档]&#xff1a; tool retrieval agent::::https://python.langchain.com/docs/modules/agents/how_to/custom_agent_with_tool_retrieval retrieval memory:::https://python.langchain.com/docs/modules/memory/types/vectorstore_retriever_memory …

科技巨擘:探索中国机器人产业的腾飞之路

原创 | 文 BFT机器人 引言 今年4月&#xff0c;特斯拉宣布在德州建造全球最大的电池工厂&#xff0c;这一举措进一步实现了马斯克对于“机器制造机器”的大目标。这座工厂不像过去依靠传统的工人模式&#xff0c;而是采用大量机器人完成生产任务。 机器人已成「刚需」。 机器人…

Mybatis小记

目录 Mybatis第一个程序 xml文件 测试类 错误排查 Mybatis第一个程序 1.搭建实验数据库 2.导入MyBatis相关jar包 3.编写MyBatis核心配置文件 4.编写MyBatis工具类 5.创建实体类、 6.编写Mapper接口类 7.编写Mapper.xml配置文件 8.编写测试类 对象传参只引用需要的属性就可…

突破连接壁垒,火山引擎边缘云网络的先行之路

在万物互联时代&#xff0c;信息不局限于人与人之间的交流&#xff0c;大量的机器设备也需要进行信息交流。就在去年&#xff0c;我国率先迎来了“物超人”的历史性时刻&#xff0c;即物联网连接数超越了人联网连接数。边缘云的发展进入到“黄金十年”。 “要致富&#xff0c;…

多线程(一)

一.什么是线程 一个线程就是一个执行流&#xff0c;多个线程就是多个执行流&#xff0c;多线程就是让多个执行流分别执行自己的代码。 比如&#xff1a;利用多线程在控制台上循环交替打印A和B package demo; public class Main {static class SubThread extends Thread{Over…