当前位置:   article > 正文

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

ffmpeg 6.0 + qt

直接上代码吧:

  1. RecordingThread.h
  2. #ifndef RECORDINGTHREAD_H
  3. #define RECORDINGTHREAD_H
  4. #include "QTimer"
  5. #include <QObject>
  6. #include <QImage>
  7. #include <QQueue>
  8. extern "C"{
  9. //因为FFmpeg是c语言,QT里面调用的话需要extern "C"
  10. #include "libavcodec/avcodec.h"
  11. #include "libavformat/avformat.h"
  12. #include "libswscale/swscale.h"
  13. #include "libavdevice/avdevice.h"
  14. #include "libavformat/avio.h"
  15. #include "libavutil/imgutils.h"
  16. }
  17. class RecordingThread : public QObject
  18. {
  19. Q_OBJECT
  20. public:
  21. void FFmpegInit();
  22. void saveMp4(QImage image);
  23. void stopMp4();
  24. void setImage(QImage image);
  25. public slots:
  26. void recordInit();
  27. signals:
  28. void send(QString);
  29. private:
  30. AVFormatContext* formatContext;
  31. AVCodecParameters* codecParameters;
  32. const AVCodec* codec;
  33. AVCodecContext* codecContext;
  34. AVStream* stream;
  35. const AVPixelFormat* pixFmt;
  36. int num = 0;
  37. QQueue<QImage> gQdata;
  38. int isRecord = -1;
  39. };
  40. #endif // RECORDINGTHREAD_H
  1. #include "recordingthread.h"
  2. #include <QPainter>
  3. #include <cmath>
  4. #include <QPainterPath>
  5. #include <QDebug>
  6. #include <QTimer>
  7. #include <QDateTime>
  8. #include <stdio.h>
  9. #include <string.h>
  10. #include <stdlib.h>
  11. #include <unistd.h>
  12. void RecordingThread::FFmpegInit(){
  13. isRecord = -1;
  14. int ret;
  15. // 初始化 FFmpeg
  16. qDebug()<<"avdevice_register_all()";
  17. avdevice_register_all(); //初始化所有设备
  18. qDebug()<<"formatContext = avformat_alloc_context()";
  19. formatContext = avformat_alloc_context();//分配format上下文
  20. qint64 timeT = QDateTime::currentMSecsSinceEpoch();//毫秒级时间戳
  21. QString outputFileName = QString("/sdcard/").append("ffmpeg").append(QString::number(timeT)).append(".mp4");
  22. //第三个参数可以直接使用nullptr 根据outputFileName的后缀自动识别
  23. qDebug()<<"avformat_alloc_output_context2(&formatContext, nullptr, \"mp4\", outputFileName.toUtf8().constData())";
  24. ret = avformat_alloc_output_context2(&formatContext, nullptr, nullptr, outputFileName.toUtf8().constData());
  25. qDebug()<<"ret===="<<ret;
  26. qDebug()<<"formatContext===="<<formatContext;
  27. qDebug()<<"formatContext->oformat = av_guess_format(nullptr, outputFileName.toUtf8().constData(), nullptr);";
  28. formatContext->oformat = av_guess_format(nullptr, outputFileName.toUtf8().constData(), nullptr);
  29. qDebug() << "avio_open(&formatContext->pb, outputFileName.toUtf8().constData(), AVIO_FLAG_WRITE) < 0";
  30. // 打开输出文件
  31. if (avio_open(&formatContext->pb, outputFileName.toUtf8().constData(), AVIO_FLAG_WRITE) < 0) {
  32. qDebug() << "Failed to open output file";
  33. return;
  34. }
  35. qDebug() << "AVStream* stream = avformat_new_stream(formatContext, nullptr);";
  36. // 创建一个AVStream对象
  37. stream = avformat_new_stream(formatContext, nullptr);
  38. if (!stream) {
  39. qDebug() << "Failed to create output stream";
  40. return;
  41. }
  42. qDebug() << "AVCodecParameters* codecParameters = stream->codecpar;";
  43. // 配置AVCodecContext
  44. codecParameters = stream->codecpar;
  45. codecParameters->codec_type = AVMEDIA_TYPE_VIDEO;
  46. codecParameters->codec_id = AV_CODEC_ID_H264; // 使用H.264编码器
  47. codecParameters->width = 400;
  48. codecParameters->height = 400;
  49. qDebug() << " const AVCodec* codec = avcodec_find_encoder(codecParameters->codec_id);";
  50. // 打开编解码器
  51. codec = avcodec_find_encoder(codecParameters->codec_id);
  52. codecContext = avcodec_alloc_context3(codec);
  53. codecContext->width = 400;
  54. codecContext->height = 400;
  55. codecContext->pix_fmt = AV_PIX_FMT_YUV420P;
  56. codecContext->time_base = {1, 25}; // 设置编码器的时间基为 1/30
  57. codecContext->framerate = {25, 1}; // 设置编码器的帧率为 30fps
  58. //codecContext->thread_count = 4;
  59. qDebug() << "AV_PIX_FMT_YUV420P====="<<AV_PIX_FMT_YUV420P;
  60. qDebug() << "codecContext->pix_fmt====="<<codecContext->pix_fmt;
  61. qDebug() << "avcodec_open2(codecContext, codec, nullptr);";
  62. //设置完成编码格式以后要立刻打开,要不然调用avcodec_parameters_to_context的时候会重置编码
  63. ret = avcodec_open2(codecContext, codec, nullptr);
  64. if(ret < 0){
  65. qDebug() << "Failed to avcodec_open2";
  66. return;
  67. }
  68. qDebug() << "avcodec_parameters_to_context(codecContext, codecParameters);";
  69. // 将编码器参数复制到输出流
  70. avcodec_parameters_to_context(codecContext, codecParameters);
  71. // 检查编解码器支持的像素格式
  72. pixFmt = codec->pix_fmts;
  73. qDebug() << "while";
  74. while (*pixFmt != AV_PIX_FMT_NONE) {
  75. qDebug() << av_get_pix_fmt_name(*pixFmt);
  76. ++pixFmt;
  77. }
  78. qDebug() << " avformat_write_header(formatContext, nullptr);";
  79. // 写入头部信息
  80. avformat_write_header(formatContext, nullptr);
  81. }
  82. void RecordingThread::saveMp4(QImage image){
  83. int imagewidth = image.width();
  84. int imageheight = image.height();
  85. int ret;
  86. //qDebug() << " AVFrame* frame = av_frame_alloc();";
  87. // 逐个写入图像帧
  88. AVFrame* frame = av_frame_alloc();
  89. if (!frame) {
  90. qDebug() << "Failed to allocate frame.";
  91. return;
  92. }
  93. //qDebug() << "frame->format = AV_PIX_FMT_YUV420P";
  94. frame->format = AV_PIX_FMT_YUV420P;
  95. frame->width = imagewidth;
  96. frame->height = imageheight;
  97. // 在循环中设置每一帧的时间戳 如果没有这个可能就是0秒视频
  98. frame->pts = (((AV_TIME_BASE / 25))/10 * num);
  99. // qDebug() << "frame->pts===========" << frame->pts;
  100. if (av_frame_get_buffer(frame, 0) < 0) {
  101. qDebug() << "Failed to allocate frame buffer.";
  102. av_frame_free(&frame);
  103. return;
  104. }
  105. // 图像格式转换
  106. SwsContext* swsContext = sws_getContext(imagewidth, imageheight, AV_PIX_FMT_RGB32,
  107. frame->width, frame->height, AV_PIX_FMT_YUV420P,
  108. SWS_BICUBIC, nullptr, nullptr, nullptr);
  109. if (!swsContext) {
  110. qDebug() << "Failed to create SwsContext.";
  111. av_frame_free(&frame);
  112. return;
  113. }
  114. uint8_t* destData[4] = {frame->data[0], frame->data[1], frame->data[2], nullptr};
  115. int destLinesize[4] = {frame->linesize[0], frame->linesize[1], frame->linesize[2], 0};
  116. //av_image_fill_arrays(frame->data, frame->linesize, destData[0], codecContext->pix_fmt, codecContext->width, codecContext->height, 1);
  117. image = image.convertToFormat(QImage::Format_RGB32);
  118. const uchar* bits = image.constBits();
  119. int bytesPerLine = image.bytesPerLine();
  120. // 函数返回的值是转换后的图像的输出行数。输出的图像高度为图像像素。
  121. ret = sws_scale(swsContext, &bits, &bytesPerLine, 0, image.height(), destData, destLinesize);
  122. //qDebug() << "sws_scale ret==="<<ret;
  123. //函数用于释放由 sws_getContext 函数创建的图像格式转换上下文
  124. sws_freeContext(swsContext);
  125. //qDebug() << "AVPacket packet;";
  126. // 编码并写入视频帧
  127. AVPacket packet;
  128. av_init_packet(&packet);
  129. packet.data = nullptr;
  130. packet.size = 0;
  131. int code = -1;
  132. // 接收输出包
  133. while (code < 0) {
  134. ret = avcodec_send_frame(codecContext, frame);
  135. // qDebug() << "avcodec_send_frame ret===="<<ret;
  136. code = avcodec_receive_packet(codecContext, &packet);
  137. //qDebug() << "while avcodec_receive_packet====" << code;
  138. if(code == 0){
  139. // 处理输出包
  140. ret = av_interleaved_write_frame(formatContext, &packet);
  141. // qDebug() << "av_interleaved_write_frame==================" << ret;
  142. av_packet_unref(&packet); // 释放输出包
  143. } else if (code == AVERROR(EAGAIN)) {
  144. // 当输出队列为空时,需要重新发送帧进行编码
  145. continue;
  146. } else {
  147. qDebug() << "Error encoding frame: " << code;
  148. break;
  149. }
  150. }
  151. //qDebug() << "av_frame_free(&frame);";
  152. av_frame_free(&frame);
  153. //qDebug()<<"num==============================================="<<num;
  154. ++num;
  155. }
  156. void RecordingThread::stopMp4(){
  157. isRecord = 0;
  158. }
  159. void RecordingThread::setImage(QImage image){
  160. isRecord = 1;
  161. gQdata.push_back(image);
  162. }
  163. void RecordingThread::recordInit(){
  164. while (1) {
  165. if(!gQdata.isEmpty() && gQdata.size()>0){
  166. QImage qimage = gQdata.dequeue();
  167. saveMp4(qimage);
  168. }else{
  169. if(isRecord == 0){
  170. isRecord = -1;
  171. num = 0;
  172. //写入尾部信息
  173. qDebug() << "av_write_trailer(formatContext)";
  174. int ret = av_write_trailer(formatContext);
  175. qDebug() << "av_write_trailer(formatContext) ret==="<<ret;
  176. emit send("stopRecode");
  177. }
  178. }
  179. usleep(5000);
  180. }
  181. }

我这里是专门搞了个类封装,我把这个类当成线程使用了,在启动程序的时候直接当线程启动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);

嵌入式编译FFmpeg6.0版本并且组合x264_想取一个与众不同的名字好难的博客-CSDN博客

嵌入式编译x264源码_想取一个与众不同的名字好难的博客-CSDN博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/182029
推荐阅读
相关标签
  

闽ICP备14008679号