我们看的电视剧和电影那些都是由看的视频文件,字幕文件以及听的音频文件共同组成。我们可以利用ffmpeg来进行对音视频文件的处理。下面我来讲解一下怎么利用ffmpeg来提取出视频文件。
具体流程图如下:
1.注册组键
我们通过调用av_register_all()函数来注册muxer(合并文件)和demuxer(muxer的逆过程)。muxer是指将视频文件,字幕文件以及音频文件合并成某一个视频格式。
demuxer是muxer的逆过程,是将某一个视频格式的文件拆分到视频文件,字幕文件和音频文件下。
//1.注册组件
av_register_all();
2.打开视频文件,获得解码或编码的多媒体格式的上下文对象
我们需要创建一个AVFormatContext类的对象来存储视频文件的信息。
//准备一个AVFormatContext对象
AVFormatContext *pformatCtx = avformat_alloc_context();
//打开对象
int res = avformat_open_input(&pformatCtx, "../video/Warcraft3_End.avi", nullptr, nullptr);
if(res != 0)
{
qDebug() << "avformat_open_input error";
return 0;
}
qDebug() << "avformat_open_input ok";
1.avformat_alloc_context 是 FFmpeg 库中用于分配和初始化 AVFormatContext 结构的函数
2.AVFormatContext 是一个非常重要的结构体,包含了媒体文件或媒体流的格式信息和输入输出的上下文
3.在 FFmpeg 中,“上下文”通常指的是 AVFormatContext、AVCodecContext、AVIOContext 等结构体。这些结构体包含了处理多媒体数据所需的所有信息和状态。
AVFormatContext:
包含了输入或输出多媒体文件的格式信息。
记录了文件或流的整体信息,如流的数量、类型、编解码器、时长等。
用于管理文件或流的读取和写入操作。
AVCodecContext:
包含了编解码器的相关信息和状态。
记录了编解码器的参数,如比特率、分辨率、帧率等。
用于处理具体的编码或解码操作。
AVIOContext:
包含了输入输出操作的状态和缓冲区信息。
管理底层的 I/O 操作,如文件读写、网络流读取等。
4.avformat_open_input 是 FFmpeg 库中的一个函数,用于打开一个输入媒体文件或媒体流,并将其与指定的 AVFormatContext 关联起来。这是处理媒体文件或流的第一步,通常在调用 avformat_find_stream_info 获取流信息之前使用。(即将对应的媒体文件信息与AVFormatContext 对象产生关联)
avformat_open_input 是 FFmpeg 中用于打开输入媒体文件或流的关键函数,通过它可以初始化并填充 AVFormatContext 结构体,为后续的媒体处理操作做准备。了解其参数和使用方法对于有效使用 FFmpeg 处理多媒体数据至关重要。
3.从AVFormatContext解析出流媒体信息
res = avformat_find_stream_info(pformatCtx, nullptr);
if(res != 0)
{
qDebug() << "avformat_find_stream_info error";
return 0;
}
qDebug() << "avformat_find_stream_info ok";
avformat_find_stream_info 是 FFmpeg 库中的一个函数,用于分析并填充 AVFormatContext 结构体中的流信息。在成功打开媒体文件或流之后,调用此函数可以获取文件或流中的详细信息,如视频和音频流的编解码器参数、时间基准、比特率等。
4.从流信息中取出视频流
int videoIndex = -1;//用于保存视频流对应的下标
//pformatCtx->nb_streams流的数量
for(int i = 0; i < pformatCtx->nb_streams; ++i)
{
if(pformatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoIndex = i;
break;
}
}
if(videoIndex == -1)
{
qDebug() << "gain video stream error";
return 0;
}
qDebug() << "gain video stream success" << "下标:" << videoIndex;
5.获取合适的编解码器
AVCodecContext *pcodecCtx = pformatCtx->streams[videoIndex]->codec;
AVCodec *decoder = avcodec_find_decoder(pcodecCtx->codec_id);
if(decoder == nullptr)
{
qDebug() << "not find suitable decoder";
return 0;
}
qDebug() << "can find suitable decoder";
AVCodec 是 FFmpeg 库中用于表示编解码器(encoder/decoder)的结构体。编解码器是负责将数据编码成某种格式或从某种格式解码数据的软件组件。
6.打开编码器
res = avcodec_open2(pcodecCtx, decoder, nullptr);
if(res != 0)
{
qDebug() << "open decoder error";
return 0;
}
qDebug() << "open decoder success";
7.解码(从视频码流中提取一帧帧的原始像素信息)
FILE *fp = fopen("../video/temp.h264", "wb");
AVPacket *pkt = (AVPacket *)malloc(sizeof(AVPacket));
//从上下文容器中读取一帧帧的视频流数据保存到pkt中
//初始化AVFrame 需要两个 一个是压缩的图像码流 第二个是解压后的图像码流(去除无用像素信息)
AVFrame *picture = av_frame_alloc();//用于保存压缩的图像码流信息
AVFrame *rgbPicture = av_frame_alloc();//用于保存解压后的图像码流信息
//初始化格式
picture->width = pcodecCtx->width;
picture->height = pcodecCtx->height;
picture->format = pcodecCtx->pix_fmt;
rgbPicture->width = pcodecCtx->width;
rgbPicture->height = pcodecCtx->height;
rgbPicture->format = pcodecCtx->pix_fmt;
//获取最终要生成的每一张图片的尺寸
int numbyte = avpicture_get_size(AV_PIX_FMT_RGB32, picture->width, picture->height);
//创建图片缓冲区
uint8_t *buffer = (uint8_t *)av_malloc(numbyte * sizeof (uint8_t));
//初始化图片,用于保存最终生成的图片
avpicture_fill((AVPicture*)rgbPicture, buffer, AV_PIX_FMT_RGB32,
pcodecCtx->width, pcodecCtx->height);
//设置过滤规则(转置)
SwsContext *swsCtx = sws_getContext(pcodecCtx->width, pcodecCtx->height, pcodecCtx->pix_fmt,
pcodecCtx->width, pcodecCtx->height, AV_PIX_FMT_RGB32,
SWS_BICUBIC, nullptr, nullptr,nullptr);
int count = 0;
char imgPath[256] = { 0 };
while(av_read_frame(pformatCtx, pkt) == 0)
{
if(pkt->stream_index == videoIndex)//判断本帧是否为视频数据流
{
fwrite(pkt->data, pkt->size, 1, fp);//将一帧的码流数据写入文件
//让每帧画面转换原始像素图片,在Qt窗口中展示
int got_picture_ptr = -1;//操作的状态信息 不为0表示解码成功
//从一帧视频码流信息中解析出图像信息
avcodec_decode_video2(pcodecCtx, picture, &got_picture_ptr, pkt);
if(got_picture_ptr != 0)//解码成功
{
//剔除图像中的无效信息,生成最终的原始图像
sws_scale(swsCtx, picture->data, picture->linesize,0, picture->height,
rgbPicture->data, rgbPicture->linesize);
++count;
//将图片转存到QImage中
QImage img = QImage((uchar*)buffer, pcodecCtx->width, pcodecCtx->height,
QImage::Format_RGB32);
sprintf(imgPath, "../image/img%d.jpg",count);
//保存成图片文件
img.save(imgPath);
}
}
}
fclose(fp);
qDebug() << "decoder is finish";
AVPacket 是 FFmpeg 库中的一个结构体,用于存储压缩编码的数据(packet)。
主要作用:
存储压缩数据:AVPacket 用于存储从多媒体文件或流中读取的压缩数据,或者即将写入多媒体文件或流的压缩数据。
传递数据:它在解码和编码过程中用来传递数据,例如从解码器中接收压缩数据,然后将其传递给解码器,或者从编码器中接收已编码的数据并传递给输出文件或流。
AVFrame 是 FFmpeg 库中的一个结构体,用于存储解码后的原始音视频数据。它在音视频处理过程中扮演着关键角色,尤其是在解码、编码、过滤和显示数据时。
主要作用:
存储解码数据:AVFrame 用于存储从压缩数据包(AVPacket)解码后的原始数据。
传递数据:在解码器、编码器和过滤器之间传递原始数据。
avpicture_get_size 是 FFmpeg 库中用于计算图像数据大小的一个函数。
avpicture_fill 是 FFmpeg 库中的一个函数,用于将图像数据指针和缓冲区链接到一个 AVPicture 结构体
SwsContext 是 FFmpeg 库中用于图像缩放和像素格式转换的结构体。它在图像处理过程中起着关键作用,特别是在视频处理、编辑和转码中。通过配置和使用 SwsContext,可以高效地在不同的分辨率和像素格式之间进行转换。
sws_getContext 是 FFmpeg 库中用于创建和初始化图像缩放和像素格式转换上下文的函数。这个上下文由 SwsContext 结构体表示,用于在不同像素格式、大小和色彩空间之间进行转换。
sws_scale 是 FFmpeg 库中的一个函数,用于在图像缩放和像素格式转换过程中进行实际的处理操作。它依赖于之前通过 sws_getContext 创建和初始化的 SwsContext 结构体。sws_scale 将源图像数据转换为目标图像数据,执行的操作包括尺寸调整和像素格式转换。
主要作用:
图像缩放:将图像从一种分辨率缩放到另一种分辨率。
像素格式转换:将图像从一种像素格式转换为另一种像素格式。
结尾
至此我们将一个视频的视频流提取出来放入了img文件夹中。完整代码如下:
#include "cwidget.h"
#include <QApplication>
#include <QDebug>
#include <QImage>
// extern "C"是为了实现类C和C++的混合编程
// 因为ffmpeg是用C写的,故而在C++引入ffmpeg的C函数库需加此声明
extern "C"
{
#include "libavcodec/avcodec.h" //编解码库
#include "libavdevice/avdevice.h" //输入输出设备库;读取摄像头的
#include "libavfilter/avfilter.h" //音视频滤镜库;进行音视频处理与编辑
#include "libavformat/avformat.h" //文件格式和协议库
#include "libavutil/avutil.h" //音视频处理
#include "libswresample/swresample.h" //音频重采样
#include "libswscale/swscale.h" //图像进行格式转换
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//1.注册组件
av_register_all();
/*2.打开视频文件,获得解码或编码的多媒体格式的上下文对象
* AVFormatContext用来保存视频信息的核心对象*/
//准备一个AVFormatContext对象
AVFormatContext *pformatCtx = avformat_alloc_context();
//打开对象
int res = avformat_open_input(&pformatCtx, "../video/Warcraft3_End.avi", nullptr, nullptr);
if(res != 0)
{
qDebug() << "avformat_open_input error";
return 0;
}
qDebug() << "avformat_open_input ok";
//3.从AVFormatContext解析出流媒体信息
res = avformat_find_stream_info(pformatCtx, nullptr);
if(res != 0)
{
qDebug() << "avformat_find_stream_info error";
return 0;
}
qDebug() << "avformat_find_stream_info ok";
//4.从流信息中取出视频流
int videoIndex = -1;//用于保存视频流对应的下标
//pformatCtx->nb_streams流的数量
for(int i = 0; i < pformatCtx->nb_streams; ++i)
{
if(pformatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoIndex = i;
break;
}
}
if(videoIndex == -1)
{
qDebug() << "gain video stream error";
return 0;
}
qDebug() << "gain video stream success" << "下标:" << videoIndex;
//5.获取合适的编解码器
AVCodecContext *pcodecCtx = pformatCtx->streams[videoIndex]->codec;
AVCodec *decoder = avcodec_find_decoder(pcodecCtx->codec_id);
if(decoder == nullptr)
{
qDebug() << "not find suitable decoder";
return 0;
}
qDebug() << "can find suitable decoder";
//6.打开解码器
res = avcodec_open2(pcodecCtx, decoder, nullptr);
if(res != 0)
{
qDebug() << "open decoder error";
return 0;
}
qDebug() << "open decoder success";
//7.解码(从视频码流中提取一帧帧的原始像素信息)
FILE *fp = fopen("../video/temp.h264", "wb");
AVPacket *pkt = (AVPacket *)malloc(sizeof(AVPacket));
//从上下文容器中读取一帧帧的视频流数据保存到pkt中
//初始化AVFrame 需要两个 一个是压缩的图像码流 第二个是解压后的图像码流(去除无用像素信息)
AVFrame *picture = av_frame_alloc();//用于保存压缩的图像码流信息
AVFrame *rgbPicture = av_frame_alloc();//用于保存解压后的图像码流信息
//初始化格式
picture->width = pcodecCtx->width;
picture->height = pcodecCtx->height;
picture->format = pcodecCtx->pix_fmt;
rgbPicture->width = pcodecCtx->width;
rgbPicture->height = pcodecCtx->height;
rgbPicture->format = pcodecCtx->pix_fmt;
//获取最终要生成的每一张图片的尺寸
int numbyte = avpicture_get_size(AV_PIX_FMT_RGB32, picture->width, picture->height);
//创建图片缓冲区
uint8_t *buffer = (uint8_t *)av_malloc(numbyte * sizeof (uint8_t));
//初始化图片,用于保存最终生成的图片
avpicture_fill((AVPicture*)rgbPicture, buffer, AV_PIX_FMT_RGB32,
pcodecCtx->width, pcodecCtx->height);
//设置过滤规则(转置)
SwsContext *swsCtx = sws_getContext(pcodecCtx->width, pcodecCtx->height, pcodecCtx->pix_fmt,
pcodecCtx->width, pcodecCtx->height, AV_PIX_FMT_RGB32,
SWS_BICUBIC, nullptr, nullptr,nullptr);
int count = 0;
char imgPath[256] = { 0 };
while(av_read_frame(pformatCtx, pkt) == 0)
{
if(pkt->stream_index == videoIndex)//判断本帧是否为视频数据流
{
fwrite(pkt->data, pkt->size, 1, fp);//将一帧的码流数据写入文件
//让每帧画面转换原始像素图片,在Qt窗口中展示
int got_picture_ptr = -1;//操作的状态信息 不为0表示解码成功
//从一帧视频码流信息中解析出图像信息
avcodec_decode_video2(pcodecCtx, picture, &got_picture_ptr, pkt);
if(got_picture_ptr != 0)//解码成功
{
//剔除图像中的无效信息,生成最终的原始图像
sws_scale(swsCtx, picture->data, picture->linesize,0, picture->height,
rgbPicture->data, rgbPicture->linesize);
++count;
//将图片转存到QImage中
QImage img = QImage((uchar*)buffer, pcodecCtx->width, pcodecCtx->height,
QImage::Format_RGB32);
sprintf(imgPath, "../image/img%d.jpg",count);
//保存成图片文件
img.save(imgPath);
}
}
}
fclose(fp);
qDebug() << "decoder is finish";
return a.exec();
}