1. 打开图片文件bool open_input_file(int seq, const QString filePath){int ret avformat_open_input(in_fmtCtx, filePath.toStdString().c_str(), nullptr, nullptr);if(ret 0){qDebug() 打开文件;return false;}ret avformat_find_stream_info(in_fmtCtx, nullptr);if(ret 0){qDebug() 信息获取失败;return false;}int vIdx av_find_best_stream(in_fmt, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);AVStream *vStream in_fmt-streams[vIdx];const AVCodec* codec avcodec_find_decoder(vStream-codecpar-codec_id);in_decodec avcodec_alloc_context3(codec);avcodec_parameters_to_context(in_decodec, vStream-codecpar);ret avcodec_open2(in_decodec, codec, nullptr);if(ret 0){qDebug() 打开解码器;return false;}return ture;}2. 打开输出文件bool open_output_file(const QString filePath){int ret avformat_alloc_output_context2(out_fmtCtx, nullptr, nullptr, filePath.toStdString().c_str());if(ret 0){qDebug() 打开输出文件失败;return false;}ret avio_open(out_fmtCtx-pb, filePath.toStdString().c_str(), AVIO_FLAG_WRITE);if(ret 0){qDebug() 打开输出流失败;return false;}AVStream *vStream out_fmtCtx-streams[vIdx];const AVCodec* codec avcodec_find_encoder(AV_CODEC_ID_H264);AVCodecContext *m_codecCtx avcodec_alloc_context3(codec);m_codecCtx-pix_fmt AV_PIX_FMT_YUV420P;m_codecCtx-width vStream-width;m_codecCtx-height vStream-height;m_codecCtx-time_best vStream-time_best;m_codecCtx-framereate AVRational{1.25};m_codecCtx-gop_size 12;m_codecCtx-max_b_frames 0; // 关闭B帧ret avcodec_open2(m_codecCtx, codec, nullptr);if(ret 0){qDebug() 编码器打开失败;return false;}dest_video avformat_new_stream(m_codecCtx, codec);dest_video-time_base m_codecCtx-time_base;avcodec_parameters_from_context(dest_video-codecpat, m_codecCtx);dest_video-codecpat-codec_tag 0;ret avformat_write_header(out_fmtCtx, nullptr);if(ret 0){qDebug() 写文件头;return false;}return true;}3. 初始化图片转换器bool init_sws_context(){enum AVPixelFormat target_format AV_PIX_FMT_YUV420P;swsContex sws_getContext(in decodecCtx-width, in_decodecCtx-height, m_codecCtx-pix_fmt,out_encodecCtx-width, in_decodecCtx-height, target_format,SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);vframe av_frame_alloc();vframe-format target_format;vframe-width out_encodecCtx-width;vframe-height out_encodecCtx-height;ret av_image_alloc(vframe-data, vframe-linesizes,out_encodecCtx-width, out_encodecCtx-height,target_format, 1);if(ret 0){qDebug() 分配失败;return false;}return true;}//一次性分配图像原始数据缓冲区 int av_image_alloc(uint8_t *pointers[4], int linesizes[4], int w, int h, enum AVPixelFormat pix_fmt, int align);4. 图片重新编码输入输出文件bool decodec_video(int seq, AVPacket* packet, AVFrame* frame){int ret avcodec_send_packet(in_decodec, packet);if(ret 0){qDebug() 发送packet失败;return false;}while(true){ret avcodec_receive_frame(in_decodec, frame);if(ret 0){qDebug() receive frame error;}sws_scale(m_swsCtx, (const uint8_t*)frame-data, frame-linesize,0, frame-height,vframe-data, vframe-linesize);for(int i 0; i 100; i){vframe-pts packet_index;encodec_video(vframe);}}}bool encodec_video(AVFrame *frame){int ret avcodec_send_frame(out_encodecCtx, frame);if(ret 0){qDebug() 发送失败;return false;}while(1){AVPacket *packet av_packet_alloc();ret avcodec_receive_packet(out_encidecCtx, packet);if(ret 0){qDebug() 获取packet失败;return false;}av_packet_rescale_ts(packet, src_video-time_base, dest_video-time_base);packet-stream_index 0;ret av_write_frame(out_fmtCtx, packet);if(ret 0){qDebug() 写入失败;return false;}av_packet_unref(packet);}return true;}#include widget.h #include QDebug Widget::Widget(QWidget *parent) : QWidget(parent) { QString img1 3.jpg; QString img2 4.jpg; QString filePath 1.mp4; if(!open_input_file(0,img1)){ qDebug() 打开img1失败; return; } if(!open_input_file(1, img2)){ qDebug() 打开img2失败; return; } if(!open_output_file(filePath.toStdString().c_str())){ qDebug() 打开输出文件失败; return; } if(!init_sws_context(0)){ qDebug() 初始化失败; return; } if(!init_sws_context(1)){ qDebug() 初始化失败1; return; } AVPacket* packet av_packet_alloc(); AVFrame *frame av_frame_alloc(); while (av_read_frame(in_fmtCtx[0], packet) 0) { if(packet-stream_index video_index[0]){ decodec_video(0, packet, frame); } av_packet_unref(packet); } packet-data nullptr; packet-size 0; decodec_video(0, packet, frame); while (av_read_frame(in_fmtCtx[1],packet) 0) { if(packet-stream_index video_index[1]){ decodec_video(1,packet, frame); } av_packet_unref(packet); } packet-data nullptr; packet-size 0; decodec_video(0,packet, frame); encodec_video(nullptr); av_write_trailer(out_fmtCtx); av_frame_free(frame); av_frame_free(vframe[0]); av_frame_free(vframe[1]); av_packet_free(packet); avio_close(out_fmtCtx-pb); avcodec_free_context(in_decodecCtx[0]); avcodec_free_context(in_decodecCtx[1]); avcodec_free_context(out_encodecCtx); sws_freeContext(swsContex[0]); sws_freeContext(swsContex[1]); avformat_free_context(out_fmtCtx); avformat_close_input(in_fmtCtx[0]); avformat_close_input(in_fmtCtx[1]); } Widget::~Widget() { } bool Widget::open_input_file(int seq, const QString filePath) { // 1. 打开文件 int ret avformat_open_input(in_fmtCtx[seq], filePath.toStdString().c_str(), nullptr, nullptr); if( ret 0){ qDebug() 打开文件失败; return false; } // 2. 获取文件流信息 ret avformat_find_stream_info(in_fmtCtx[seq], nullptr); if( ret 0 ){ qDebug() 获取文件信息流失败; return false; } // 3. 获取视频流索引 video_index[seq] av_find_best_stream(in_fmtCtx[seq], AVMEDIA_TYPE_VIDEO, -1,-1,nullptr,0); if(video_index[seq] 0 ){ // 4. 获取视频流结构体 src_video in_fmtCtx[seq]-streams[video_index[seq]]; // 5. 获取解码器 const AVCodec *codec avcodec_find_decoder(src_video-codecpar-codec_id); // 6. 根据解码器获取解码器上下文结构体 in_decodecCtx[seq] avcodec_alloc_context3(codec); // 7. 复制解码参数到视频流结构体 ret avcodec_parameters_to_context(in_decodecCtx[seq], src_video-codecpar); if(ret 0){ qDebug() 复制失败; return false; } // 8. 打开解码器 ret avcodec_open2(in_decodecCtx[seq], codec, nullptr); if(ret 0){ qDebug() 打开解码器失败; return false; } } return true; } bool Widget::open_output_file(const QString filePath) { int ret avformat_alloc_output_context2(out_fmtCtx, nullptr, nullptr, filePath.toStdString().c_str()); if(ret 0){ qDebug() avformat_alloc_output_context2 fail; return false; } ret avio_open(out_fmtCtx-pb, filePath.toStdString().c_str(), AVIO_FLAG_WRITE); if(ret 0){ qDebug() avio_open fail; return false; } if(video_index[0] 0){ src_video in_fmtCtx[0]-streams[video_index[0]]; // ✅ 查找H264编码器 const AVCodec* codec avcodec_find_encoder(AV_CODEC_ID_H264); if(!codec){ qDebug() 找不到H264编码器请确认ffmpeg带libx264; return false; } out_encodecCtx avcodec_alloc_context3(codec); out_encodecCtx-pix_fmt AV_PIX_FMT_YUV420P; out_encodecCtx-width src_video-codecpar-width; out_encodecCtx-height src_video-codecpar-height; out_encodecCtx-time_base AVRational{1,25}; out_encodecCtx-framerate AVRational{25,1}; out_encodecCtx-gop_size 12; out_encodecCtx-max_b_frames 0; // 图片合成建议关闭B帧提升兼容性 ret avcodec_open2(out_encodecCtx,codec, nullptr); if(ret 0){ qDebug() 编码器打开失败; return false; } dest_video avformat_new_stream(out_fmtCtx, codec); dest_video-time_base out_encodecCtx-time_base; avcodec_parameters_from_context(dest_video-codecpar, out_encodecCtx); dest_video-codecpar-codec_tag 0; } ret avformat_write_header(out_fmtCtx, nullptr); if(ret 0){ qDebug() 写文件头失败; return false; } return true; } bool Widget::encodec_video(AVFrame *frame) { int ret avcodec_send_frame(out_encodecCtx,frame); if(ret 0){ qDebug() 发送失败; return false; } while (1) { AVPacket *packet av_packet_alloc(); ret avcodec_receive_packet(out_encodecCtx, packet); if(ret AVERROR(EAGAIN) || ret AVERROR_EOF){ qDebug() 获取pack 失败; return false; } av_packet_rescale_ts(packet, src_video-time_base, dest_video-time_base); packet-stream_index 0; ret av_write_frame(out_fmtCtx, packet); if(ret 0){ qDebug() 写入失败; return false; } av_packet_unref(packet); } return true; } bool Widget::decodec_video(int seq, AVPacket *packet, AVFrame *frame) { int ret avcodec_send_packet(in_decodecCtx[seq], packet); if(ret 0){ char err[AV_ERROR_MAX_STRING_SIZE]; av_strerror(ret, err, sizeof(err)); qDebug() send packet error: err; return false; } while (true) { ret avcodec_receive_frame(in_decodecCtx[seq], frame); if (ret AVERROR(EAGAIN) || ret AVERROR_EOF) { break; // 需要更多数据 / 解码结束正常退出循环 } if (ret 0) { char err[AV_ERROR_MAX_STRING_SIZE]; av_strerror(ret, err, sizeof(err)); qDebug() receive frame error: err; return false; } // 成功拿到 frame执行 sws_scale sws_scale(swsContex[seq], (const uint8_t **)frame-data, frame-linesize, 0, frame-height, vframe[seq]-data, vframe[seq]-linesize); // 编码 N 帧控制图片停留时长 for(int i 0; i 100; i) { vframe[seq]-pts packet_index; encodec_video(vframe[seq]); } } return true; } bool Widget::init_sws_context(int seq) { enum AVPixelFormat target_format AV_PIX_FMT_YUV420P; swsContex[seq] sws_getContext( in_decodecCtx[seq]-width, in_decodecCtx[seq]-height, in_decodecCtx[seq]-pix_fmt, out_encodecCtx-width, out_encodecCtx-height, target_format, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr ); if(swsContex[seq] nullptr){ qDebug() 初始化swsContext 失败; return false; } vframe[seq] av_frame_alloc(); vframe[seq]-format target_format; vframe[seq]-width out_encodecCtx-width; vframe[seq]-height out_encodecCtx-height; av_image_alloc(vframe[seq]-data, vframe[seq]-linesize, out_encodecCtx-width, out_encodecCtx-height, target_format, 1); return true; }#ifndef WIDGET_H #define WIDGET_H #include QWidget extern C { #include libavcodec/avcodec.h #include libavformat/avformat.h #include libswscale/swscale.h #include libavdevice/avdevice.h #include libavutil/time.h #include libavutil/mathematics.h #include libavutil/imgutils.h } class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent nullptr); ~Widget(); bool open_input_file(int seq, const QString filePath); bool open_output_file(const QString filePath); bool encodec_video(AVFrame *frame); // 编码 bool decodec_video(int seq, AVPacket* packet, AVFrame *frame); // bool init_sws_context(int seq); private: AVFormatContext* in_fmtCtx[2] {nullptr, nullptr}; AVFormatContext* out_fmtCtx nullptr; AVCodecContext* in_decodecCtx[2] {nullptr, nullptr}; AVCodecContext* out_encodecCtx nullptr; AVStream *src_video nullptr; AVStream *dest_video nullptr; SwsContext *swsContex[2] {nullptr,nullptr}; AVFrame* vframe[2] {nullptr, nullptr}; int video_index[2] {-1,-1}; int packet_index 0; }; #endif // WIDGET_H