- 打开图片文件
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;
}
- 打开输出文件
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;
}
- 初始化图片转换器
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);
- 图片重新编码输入输出文件
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