图片合并成视频 img2video ffmpeg

  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->streamsvIdx;

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;

}

  1. 打开输出文件

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->streamsvIdx;

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;

}

  1. 初始化图片转换器

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);
  1. 图片重新编码输入输出文件

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;

}

cpp 复制代码
#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;
}
cpp 复制代码
#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
相关推荐
A13345556 小时前
视频特效字幕怎么翻译?保姆级AI字幕与外挂字幕教程
人工智能·音视频
ViiTor_AI18 小时前
从去字幕到 AI 配音:完整视频本地化流程
人工智能·音视频
明德扬1 天前
多路MIPI摄像头如何同步聚合?一文看懂FPGA视频汇聚方案
fpga开发·音视频
HY小宝F1 天前
树莓派 FFmpeg 实战笔记(二):命令行、源码编译与硬件编码的真相
学习·职场和发展·ffmpeg
折枝吖1 天前
音视频开发-PCM 原理与 AI 模块实战
音视频·pcm
HySpark1 天前
熙瑾·会悟 ASR 实战:Qwen-ASR 漏字导致转写不完整,如何从音频分段到二次校验解决?
音视频·熙瑾会悟
qq_316837751 天前
uniapp + Vite + ffmpeg-wasm 0.12.x 集成示例
ffmpeg·uni-app·wasm
qingmiaozhuan1 天前
电脑录屏黑屏?只有声音没画面?从原理到排查一篇搞定
ffmpeg·电脑·音视频开发·录屏技术·电脑录屏
大鹅办公1 天前
酷狗音乐怎么转换MP3格式?4种方法亲测对比+避坑指南(附FFmpeg命令行教程)
ffmpeg·音视频·音频格式