写本篇文章,是因为本人初学FFMPEG,便于日后复习使用
decodevideo.h
#ifndef DECODEVIDEO_H
#define DECODEVIDEO_H
#include <QThread>
#include <QImage>
#include <QDebug>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavdevice/avdevice.h"
#include "libavfilter/avfilter.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libpostproc/postprocess.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
}
class DecodeVideo : public QThread
{
public:
DecodeVideo();
// QThread interface
void decoding();
private:
QImage img;
protected:
void run();
};
#endif // DECODEVIDEO_H
decodevideo.cpp
#include "decodevideo.h"
DecodeVideo::DecodeVideo()
{
}
//解码
void DecodeVideo::decoding()
{
//1、注册组件
av_register_all();
//2、打开视频文件
AVFormatContext *formatContext;
formatContext=avformat_alloc_context();
int res=avformat_open_input(&formatContext,"../video/Warcraft3_End.avi",nullptr,nullptr);
if(res!=0)
{
qDebug()<<"open video failed";
}
else
{
qDebug()<<"open video success";
}
//3、获取视频信息
res=avformat_find_stream_info(formatContext,nullptr);
if(res<0)
{
qDebug()<<"avformat_find_stream_info failed";
}
else
{
qDebug()<<"avformat_find_stream_info success";
}
//4、判断是否有视频流
int video_input=-1;
for(int i=0;i<formatContext->nb_streams;i++)
{
if(formatContext->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
{
//判断为视频
//记录流的下标
video_input=i;
break;
}
}
if(video_input==-1)
{
//没有流
qDebug()<<"video not found";
}
else
{
//有流
qDebug()<<"总帧数"<<formatContext->streams[video_input]->nb_frames;//视频流总帧数
qDebug()<<"video_input"<<video_input;
}
//5、查找解码器
AVCodecContext *codeContext=formatContext->streams[video_input]->codec;
//通过文件信息中文件编码id找到解码器
AVCodec *decoder=avcodec_find_decoder(codeContext->codec_id);
if(decoder==nullptr)
{
qDebug()<<"avcodec_find_decoder failed";
}
else
{
qDebug()<<"avcodec_find_decoder success";
}
//6、打开解码器
res=avcodec_open2(codeContext,decoder,nullptr);
if(res!=0)
{
qDebug()<<"avcodec_open2 failed";
}
else
{
qDebug()<<"avcodec_open2 success";
}
/******************************AVPacket准备工作开始***************************************/
//7、读取一帧压缩数据
AVPacket *pkt;
pkt=(AVPacket*)malloc(sizeof(AVPacket));
//视频大小
int size=codeContext->width*codeContext->height;
res=av_new_packet(pkt,size);
if(res!=0)
{
qDebug()<<"av_new_packet failed";
}
else
{
qDebug()<<"av_new_packet success";
}
/******************************AVPacket准备工作完成***************************************/
/******************************AVFrame RGB准备工作***************************************/
AVFrame *pictureRGB;
AVFrame *picture;
picture=av_frame_alloc();
pictureRGB=av_frame_alloc();
//解码画面信息必须要按原图进行设置
pictureRGB->width=codeContext->width;
pictureRGB->height=codeContext->height;
pictureRGB->format=codeContext->pix_fmt;
//设置缓冲区
int imgByteRGB=avpicture_get_size(AV_PIX_FMT_RGB32,codeContext->width,codeContext->height);
uint8_t *bufferRGB=(uint8_t*)av_malloc(imgByteRGB*sizeof (uint8_t));
//填充缓冲区
avpicture_fill((AVPicture*)pictureRGB,bufferRGB,AV_PIX_FMT_RGB32,codeContext->width,codeContext->height);
//制定一个图像规则
SwsContext *SwsContextRGB=sws_getContext(codeContext->width,codeContext->height,codeContext->pix_fmt,
codeContext->width,codeContext->height,
AV_PIX_FMT_RGB32,SWS_BICUBIC,
nullptr,nullptr,nullptr);
/*****************************AVFrame RGB准备工作完成**************************************/
//读取一帧解码一帧
int x=0;
while(av_read_frame(formatContext,pkt)==0)
{
if(pkt->stream_index==video_input)
{
//压缩数据为图像压缩数据
int get_picture_ptr=-1;
avcodec_decode_video2(codeContext,picture,&get_picture_ptr,pkt);
if(get_picture_ptr!=0)
{
//删除无效数据,转RGB存储方式
sws_scale(SwsContextRGB,picture->data,picture->linesize,0,picture->height,
pictureRGB->data,pictureRGB->linesize);
this->img=QImage((uchar*)bufferRGB,codeContext->width,codeContext->height,
QImage::Format_RGB32);
//保存
this->img.save(QString("../fileout/%1.jpg").arg(x));
x++;
}
}
av_packet_unref(pkt);
}
//释放解码器
avcodec_close(codeContext);
//释放关闭文件
avformat_close_input(&formatContext);
}
void DecodeVideo::run()
{
decoding();
}
main.cpp
#include <QApplication>
#include <QDebug>
#include "decodevideo.h"
extern "C"{
#include "libavcodec/avcodec.h"
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
DecodeVideo *dv=new DecodeVideo();
dv->start();
return a.exec();
}
注意:DedcodeVideo继承于QThread是因为不能将延时操作放在正常代码顺序中,不然资源过大就会未响应
代码功能概述
这段代码的主要功能是读取一个视频文件,对视频流进行解码,将解码后的视频帧转换为 RGB 格式,并将每一帧保存为 PNG 图像文件。
步骤笔记
1. 组件注册
- 操作:调用
av_register_all()
注册所有的 FFmpeg 组件,这是使用 FFmpeg 库的基础准备工作。 - 记忆点:初始化 FFmpeg,就像启动一个工具箱,先把所有工具准备好。
2. 打开视频文件
- 操作:
- 分配
AVFormatContext
结构体,用于存储视频文件的格式信息。 - 调用
avformat_open_input
打开指定的视频文件。
- 分配
- 记忆点:先准备一个 “文件信息盒子”(
AVFormatContext
),然后用它去打开视频文件。
3. 获取视频信息
- 操作:调用
avformat_find_stream_info
获取视频文件的流信息。 - 记忆点:打开文件后,查看里面的详细信息,了解视频的构成。
4. 判断是否有视频流
- 操作:
- 遍历
AVFormatContext
中的所有流,找到视频流的下标。 - 如果没有找到视频流,输出错误信息;否则,输出视频流的总帧数和下标。
- 遍历
- 记忆点:在众多流中找到视频流,就像在一堆物品中找出视频这个 “宝贝”。
5. 查找解码器
- 操作:
- 获取视频流的
AVCodecContext
,用于存储编解码器的上下文信息。 - 根据
AVCodecContext
中的codec_id
查找对应的解码器。
- 获取视频流的
- 记忆点:根据视频的编码规则,找到能解开它的 “钥匙”(解码器)。
6. 打开解码器
- 操作:调用
avcodec_open2
打开找到的解码器。 - 记忆点:用找到的 “钥匙” 打开视频编码的 “锁”。
7. 读取一帧压缩数据
- 操作:
- 分配
AVPacket
结构体,用于存储压缩的视频数据。 - 调用
av_new_packet
为AVPacket
分配内存。
- 分配
- 记忆点:准备一个 “包裹”(
AVPacket
)来装一帧压缩的视频数据。
8. 准备 AVFrame 和 RGB 转换
- 操作:
- 分配
AVFrame
结构体,分别用于存储解码后的视频帧和 RGB 格式的视频帧。 - 设置
AVFrame
的参数,如宽度、高度和像素格式。 - 分配 RGB 缓冲区,并填充到
AVFrame
中。 - 创建
SwsContext
用于图像格式转换。
- 分配
- 记忆点:准备两个 “相框”(
AVFrame
),一个装解码后的原始图像,一个装转换后的 RGB 图像,同时准备好转换工具(SwsContext
)。
9. 循环读取和解码视频帧
- 操作:
- 调用
av_read_frame
循环读取视频文件中的每一帧压缩数据。 - 如果读取的是视频流的数据,调用
avcodec_decode_video2
进行解码。 - 如果解码成功,调用
sws_scale
将解码后的帧转换为 RGB 格式。 - 将 RGB 数据保存为 PNG 图像文件。
- 释放
AVPacket
中的数据。
- 调用
- 记忆点:一帧一帧地读取视频数据,解码后转换为 RGB 格式,然后保存为图片,最后清空 “包裹”(
AVPacket
)。
10. 释放资源
- 操作:
- 关闭解码器。
- 关闭视频文件。
- 记忆点:使用完工具后,记得关闭和解开连接,收拾好 “工具”。
代码示例总结
1. 注册组件:av_register_all()
2. 打开视频文件:avformat_alloc_context() + avformat_open_input()
3. 获取视频信息:avformat_find_stream_info()
4. 判断是否有视频流:遍历查找视频流下标
5. 查找解码器:avcodec_find_decoder()
6. 打开解码器:avcodec_open2()
7. 读取一帧压缩数据:av_new_packet()
8. 准备 AVFrame 和 RGB 转换:av_frame_alloc() + 缓冲区设置 + sws_getContext()
9. 循环读取和解码视频帧:av_read_frame() + avcodec_decode_video2() + sws_scale() + 保存图片
10. 释放资源:avcodec_close() + avformat_close_input()