QT使用FFmpeg把QImage合成视频只有一帧

我使用FFmpeg6.0+QT去把QImage合成视频.但是我不管怎么循环去保存,都只有一个一帧的视频

qDebug()<<"================================================================start======================================================================";
    int ret;
    QImage image("/sdcard/photo/20210101130446468.jpg");
    int imagewidth = image.width();
    int imageheight = image.height();
    // 初始化 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对象
    AVStream* stream = avformat_new_stream(formatContext, nullptr);
    if (!stream) {
        qDebug() << "Failed to create output stream";
        return;
    }
    
    qDebug() << "AVCodecParameters* codecParameters = stream->codecpar;";
     // 配置AVCodecContext
    AVCodecParameters* codecParameters = stream->codecpar;
    codecParameters->codec_type = AVMEDIA_TYPE_VIDEO;
    codecParameters->codec_id = AV_CODEC_ID_H264; // 使用H.264编码器
    codecParameters->width = imagewidth;
    codecParameters->height = imageheight;
    qDebug() << " const AVCodec* codec = avcodec_find_encoder(codecParameters->codec_id);";
    
    qDebug() << "const AVCodec* codec = avcodec_find_encoder(codecParameters->codec_id);";
     // 打开编解码器
    const AVCodec* codec = avcodec_find_encoder(codecParameters->codec_id);
    AVCodecContext* codecContext = avcodec_alloc_context3(codec);
    codecContext->width = imagewidth;
    codecContext->height = imageheight;
    codecContext->pix_fmt = AV_PIX_FMT_YUV420P;
    codecContext->time_base = {1, 30}; // 设置编码器的时间基为 1秒/30帧
    codecContext->framerate = {30, 1}; // 设置编码器的帧率为 30fps
    
    
    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);
    // 检查编解码器支持的像素格式
    const AVPixelFormat* 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);
    
    
    
    int num = 0;
    while (num < 1200) {
        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;
        
        frame->pts = av_rescale_q(stream->nb_frames, stream->time_base, codecContext->time_base);
        
        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_BGR32,
                                                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};
        
        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);
            }
        }
        
        qDebug() << "av_frame_free(&frame);";
        av_frame_free(&frame);
        qDebug()<<"num==============================================="<<num;
        ++num;
    }
    
    
    //写入尾部信息
    ret = av_write_trailer(formatContext);
    qDebug() << "av_write_trailer(formatContext) ret==="<<ret;
    //av_frame_free(&frame);
    qDebug()<<"=============================================================stop=========================================================================";

img


这是我的视频:love.sunchip-ad.com/qt/ffmpeg1609507548763.mp4

需要在循环中为每一帧创建一个新的QImage对象,而不是使用同一个QImage对象,可以使用QDir类来遍历存储图片的文件夹,并使用QImage的构造函数来从文件名创建QImage对象

试试

qDebug()<<"start";
int ret;
int imagewidth = 0;
int imageheight = 0;

avdevice_register_all();

formatContext = avformat_alloc_context();

qint64 timeT = QDateTime::currentMSecsSinceEpoch();
QString outputFileName = QString("/sdcard/").append("ffmpeg").append(QString::number(timeT)).append(".mp4");

ret = avformat_alloc_output_context2(&formatContext, nullptr, nullptr, outputFileName.toUtf8().constData());

formatContext->oformat = av_guess_format(nullptr, outputFileName.toUtf8().constData(), nullptr);

if (avio_open(&formatContext->pb, outputFileName.toUtf8().constData(), AVIO_FLAG_WRITE) < 0) {
  return;
}

AVStream* stream = avformat_new_stream(formatContext, nullptr);
if (!stream) {
  return; 
}

AVCodecParameters* codecParameters = stream->codecpar;
codecParameters->codec_type = AVMEDIA_TYPE_VIDEO;
codecParameters->codec_id = AV_CODEC_ID_H264;

const AVCodec* codec = avcodec_find_encoder(codecParameters->codec_id);
AVCodecContext* codecContext = avcodec_alloc_context3(codec);

codecContext->pix_fmt = AV_PIX_FMT_YUV420P;
codecContext->time_base = {1, 30};
codecContext->framerate = {30, 1};

ret = avcodec_open2(codecContext, codec, nullptr);
if(ret < 0){
  return;
}

ret = avcodec_parameters_from_context(codecParameters, codecContext);
if (ret < 0) {
  return;
}

const AVPixelFormat* pixFmt = codec->pix_fmts;
while (*pixFmt != AV_PIX_FMT_NONE) {
  ++pixFmt;
}

avformat_write_header(formatContext, nullptr);  

int num = 0;

QDir dir("/sdcard/photo");
QStringList imageFiles = dir.entryList(QStringList() << "*.jpg" << "*.png", QDir::Files);

while (num < imageFiles.size()) {

  QImage image(dir.filePath(imageFiles.at(num)));
  
  imagewidth = image.width();
  imageheight = image.height();
  
  codecContext->width = imagewidth;
  codecContext->height = imageheight;

  AVFrame* frame = av_frame_alloc();
  if (!frame) {
    return;
  }

  frame->format = AV_PIX_FMT_YUV420P;
  frame->width = imagewidth;
  frame->height = imageheight;
        
  frame->pts = av_rescale_q(stream->nb_frames, stream->time_base, codecContext->time_base);
        
  if (av_frame_get_buffer(frame, 0) < 0) {
    av_frame_free(&frame);
    return;
  }

  SwsContext* swsContext = sws_getContext(imagewidth, imageheight, AV_PIX_FMT_BGR32,
                                          frame->width, frame->height, AV_PIX_FMT_YUV420P,
                                          SWS_BICUBIC, nullptr, nullptr, nullptr);
  if (!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};
        
  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);
        
  sws_freeContext(swsContext);

  AVPacket packet;
  av_init_packet(&packet);
  packet.data = nullptr;
  packet.size = 0;
        
  int code = -1;
  while (code < 0) {
    ret = avcodec_send_frame(codecContext, frame);
    code = avcodec_receive_packet(codecContext, &packet);
    if(code == 0){
      ret = av_interleaved_write_frame(formatContext, &packet);
      av_packet_unref(&packet);
    }
  }

  av_frame_free(&frame);

  ++num;
}

ret = av_write_trailer(formatContext);

avcodec_free_context(&codecContext);
avformat_free_context(formatContext);

avio_close(formatContext->pb);

qDebug()<<"stop";

你的代码里面视频帧的写入位置不正确。(你上面代码的第134行-147行这段代码写的又问题)
在循环中,你应该在av_interleaved_write_frame之前调用av_write_frame来写入视频帧。然后,在循环结束后,再调用av_write_trailer来写入尾部信息。

【相关推荐】




如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^

这个地方


int num = 0;
while (num < 1200) {
    // ...其他代码...
    
    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_EOF) {
        // 手动发送 nullptr 完成编码
        ret = avcodec_send_frame(codecContext, nullptr);
        code = avcodec_receive_packet(codecContext, &packet);
        if (code == 0) {
            // 处理输出包
            ret = av_interleaved_write_frame(formatContext, &packet);
            qDebug() << "av_interleaved_write_frame==================" << ret;
            av_packet_unref(&packet);
        }
        break; // 退出循环
    }
    
    // ...其他代码...
    
    ++num;
}


qDebug()<<"start";
int ret;
int imagewidth = 0;
int imageheight = 0;
avdevice_register_all();
formatContext = avformat_alloc_context();
qint64 timeT = QDateTime::currentMSecsSinceEpoch();
QString outputFileName = QString("/sdcard/").append("ffmpeg").append(QString::number(timeT)).append(".mp4");
ret = avformat_alloc_output_context2(&formatContext, nullptr, nullptr, outputFileName.toUtf8().constData());
formatContext->oformat = av_guess_format(nullptr, outputFileName.toUtf8().constData(), nullptr);
if (avio_open(&formatContext->pb, outputFileName.toUtf8().constData(), AVIO_FLAG_WRITE) < 0) {
  return;
}
AVStream* stream = avformat_new_stream(formatContext, nullptr);
if (!stream) {
  return; 
}
AVCodecParameters* codecParameters = stream->codecpar;
codecParameters->codec_type = AVMEDIA_TYPE_VIDEO;
codecParameters->codec_id = AV_CODEC_ID_H264;
const AVCodec* codec = avcodec_find_encoder(codecParameters->codec_id);
AVCodecContext* codecContext = avcodec_alloc_context3(codec);
codecContext->pix_fmt = AV_PIX_FMT_YUV420P;
codecContext->time_base = {1, 30};
codecContext->framerate = {30, 1};
ret = avcodec_open2(codecContext, codec, nullptr);
if(ret < 0){
  return;
}
ret = avcodec_parameters_from_context(codecParameters, codecContext);
if (ret < 0) {
  return;
}
const AVPixelFormat* pixFmt = codec->pix_fmts;
while (*pixFmt != AV_PIX_FMT_NONE) {
  ++pixFmt;
}
avformat_write_header(formatContext, nullptr);  
int num = 0;
QDir dir("/sdcard/photo");
QStringList imageFiles = dir.entryList(QStringList() << "*.jpg" << "*.png", QDir::Files);
while (num < imageFiles.size()) {
  QImage image(dir.filePath(imageFiles.at(num)));
  imagewidth = image.width();
  imageheight = image.height();
  codecContext->width = imagewidth;
  codecContext->height = imageheight;
  AVFrame* frame = av_frame_alloc();
  if (!frame) {
    return;
  }
  frame->format = AV_PIX_FMT_YUV420P;
  frame->width = imagewidth;
  frame->height = imageheight;
  frame->pts = av_rescale_q(stream->nb_frames, stream->time_base, codecContext->time_base);
  if (av_frame_get_buffer(frame, 0) < 0) {
    av_frame_free(&frame);
    return;
  }
  SwsContext* swsContext = sws_getContext(imagewidth, imageheight, AV_PIX_FMT_BGR32,
                                          frame->width, frame->height, AV_PIX_FMT_YUV420P,
                                          SWS_BICUBIC, nullptr, nullptr, nullptr);
  if (!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};
  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);
  sws_freeContext(swsContext);
  AVPacket packet;
  av_init_packet(&packet);
  packet.data = nullptr;
  packet.size = 0;
  int code = -1;
  while (code < 0) {
    ret = avcodec_send_frame(codecContext, frame);
    code = avcodec_receive_packet(codecContext, &packet);
    if(code == 0){
      ret = av_interleaved_write_frame(formatContext, &packet);
      av_packet_unref(&packet);
    }
  }
  av_frame_free(&frame);
  ++num;
}
ret = av_write_trailer(formatContext);
avcodec_free_context(&codecContext);
avformat_free_context(formatContext);
avio_close(formatContext->pb);
qDebug()<<"stop";

引用chatgpt内容作答:
你的代码中有一些问题可能导致视频只有一个帧。以下是需要注意的地方:

1、流的时间基准匹配问题:确保流的时间基准(stream->time_base)与编解码器上下文的时间基准(codecContext->time_base)匹配。帧的显示时间戳(frame->pts)应基于流的时间基准来计算。

2、帧的显示时间戳问题:在循环中编码帧时,你使用stream->nb_frames来计算显示时间戳(frame->pts)。然而,stream->nb_frames 可能没有被正确更新,这会导致所有帧具有相同的时间戳。你应该手动递增显示时间戳以确保每帧都有不同的时间戳。

frame->pts = av_rescale_q(num, codecContext->time_base, stream->time_base);

3、释放数据包:在使用 av_interleaved_write_frame() 写入数据包之后,你需要使用 av_packet_unref(&packet) 来释放数据包。

4、帧数据更新:似乎你在循环中重用了 image 变量,但在循环内没有更新图像数据。确保在每帧之前更新图像数据。

5、正确释放资源:确保在使用完毕后释放资源,如编解码器上下文、格式上下文以及任何分配的帧。未正确释放资源可能会导致内存泄漏。

6、编解码器支持问题:确保所选的编解码器(AV_CODEC_ID_H264)在你使用的 FFmpeg 配置中得到支持。你可以检查 avcodec_open2() 的返回值是否有错误。

7、检查返回值:始终检查 FFmpeg 函数的返回值是否有错误。如果函数返回负值,表示出现了错误。

8、帧率问题:确保编解码器上下文中设置的帧率(codecContext->framerate)与实际提供的帧率匹配。你已将其设置为 30fps,但确保提供的帧确实以该帧率提供。

同步帧时间基准:显示时间戳(frame->pts)应该基于编解码器的时间基准计算,而不是流的时间基准。因此,在计算中使用 codecContext->time_base 而不是 stream->time_base。

参考gpt:
结合自己分析给你如下建议:
流的时间基准匹配问题:确保流的时间基准(stream->time_base)与编解码器上下文的时间基准(codecContext->time_base)匹配。帧的显示时间戳(frame->pts)应基于流的时间基准来计算。
帧的显示时间戳问题:在循环中编码帧时,你使用 stream->nb_frames 来计算显示时间戳(frame->pts)。然而,stream->nb_frames 可能没有被正确更新,这会导致所有帧具有相同的时间戳。你应该手动递增显示时间戳以确保每帧都有不同的时间戳。
编码器参数复制问题:你在打开编解码器之后才调用 avcodec_parameters_to_context 函数来复制编码器参数到输出流。这可能导致一些参数被重置或丢失。你应该在打开编解码器之前调用该函数,并检查返回值是否成功。
编码器输出包处理问题:你在循环中调用 avcodec_send_frame 和 avcodec_receive_packet 函数来编码和接收输出包。然而,你没有正确处理返回值,例如 AVERROR(EAGAIN) 或 AVERROR_EOF。这可能导致一些包被丢弃或无法写入输出文件。