FFmpeg基础知识
Qt的FFmpeg环境配置
https://blog.csdn.net/weixin_39308337/article/details/100127079
音视频基础知识
视频编码
通过特定的压缩技术,将某个视频格式文件转换成另一种视频格式文件的方式。
帧率
帧率(Frame Rate)是用于测量显示帧数的量度。测量单位为每一秒显示帧数。
码率
即比特率,比特率是单位时间播放连续的媒体的比特数量。
文件大小 = 码率 * 时间
视频帧以及音频帧
I帧
I帧表示关键帧,可以理解为这一帧画面的完整保留。解码的时候只需要本帧数据就可以完成解码。
P帧
帧间预测编码帧,需参考前面的I帧才能进行编码。表示当前帧与前一个I帧或者P帧的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。
B帧
双向预测编码帧,记录的是本帧与前后帧的差别。解码时不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面与本帧数据的叠加取得最终画面
备注
I(关键帧)帧只考虑本帧;P(帧间预测编码帧)帧记录与前一帧(前一个I(关键帧)帧或者P(帧间预测编码帧)帧)的差别;B(双向预测编码帧)帧记录的是前一帧及后一帧的差别
DTS以及PTS
DTS
即 Decode Time Stamp。解码时间戳。主要用于告诉播放器该在什么时候解码这一帧的数据。
PTS
即 Presentation Time Stamp 。显示时间戳。即告诉播放器该在什么时候显示这一帧的数据。
注
当视频流中无B帧时,通常DTS和PTS的顺序是一致的
音频中无B帧,所以音频的DTS和PTS顺序是一致的
FFmpeg解码流程
①初始化FFmpeg环境以及上下文
②打开一个视频文件,且寻找到视频流
③根据寻找到的视频流查找并打开视频流解码器。
④从视频流中读取数据帧
⑤若是视频帧未曾读取完毕,则跳转到 ④
⑥处理视频帧数据
⑦跳转到④
⑧释放申请的FFmpeg资源
FFmpeg基础数据结构介绍
AVFormatContext
是一个贯穿始终的数据结构,它是FFmpeg解封装功能的结构体,
基本组成:
struct AVInputFormat *iformat:输入数据的封装格式
AVIOContext *pb:输入数据的缓存
unsigned int nb_streams:视音频流的个数
AVStream **streams:视音频流
char filename[1024]:文件名
int64_t duration:时长(单位:微秒us,转换为秒需要除以1000000)
int bit_rate:比特率(单位bps,转换为kbps需要除以1000)
AVDictionary *metadata:元数据
AVCodecContext
解码器的上下文,解码时候需要用到
AVCodec
是存储编解码器信息的结构体
AVFrame
用来存储解码后的(或原始)音频或视频数据
AVPacket
保存的是解码前的数据,也就是压缩后的数据
代码
/打开一个视频文件,并且保存视频帧图片/
header.h
#ifndef HEADER_H
#define HEADER_H
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/pixfmt.h"
#include "libswscale/swscale.h"
#include <SDL.h>
#include <SDL_audio.h>
#include <SDL_types.h>
#include <SDL_name.h>
#include <SDL_main.h>
#include <SDL_config.h>
}
#include<QString>
#include<QDebug>
#include<QImage>
#define FLOG qDebug()<<"[FILE:"<<__FILE__<<",LINE"<<__LINE__<<",FUNC"<<__FUNCTION__;
#define SDL_AUDIO_BUFFER_SIZE 1024
#define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio
#endif // HEADER_H
videoplayer.h
#ifndef VIDEOPLAYER_H
#define VIDEOPLAYER_H
#include"header.h"
#include <QObject>
#include<QThread>
class VideoPlayer : public QThread
{
Q_OBJECT
public:
explicit VideoPlayer(QObject *parent = nullptr);
void setFilePath(const QString &);
QString getFilePath()const;
QImage avFrameToQImage(AVFrame *pFrame,int width,int height);
private:
void SaveFrame(AVFrame *pFrame, int width, int height,int index);
bool findVideoDecoder();
signals:
public slots:
void setPause(bool);
void setStop(bool flag);
protected:
virtual void run() override ;
private:
bool _isPause;
bool _isStop;
QString _videoFileName;
AVFormatContext *_pFormatCtx;//资源上下文
AVCodecContext * _pCodecCtxOrig;//原解码器上下文
AVCodecContext *_pCodecCtx;//解码器上下文副本
AVCodec *_pCodec;//解码器
int _videoStreamIndex = - 1;
}; #endif // VIDEOPLAYER_H
`
videoplayer.cpp
#include "videoplayer.h"
VideoPlayer::VideoPlayer(QObject *parent) :
QThread(parent),
_pFormatCtx(nullptr),
_isPause(false),
_isStop(false)
{
}
void VideoPlayer::setFilePath(const QString & path)
{
_videoFileName = path;;
}
QString VideoPlayer::getFilePath() const
{
return _videoFileName;
}
void VideoPlayer::SaveFrame(AVFrame *pFrame, int width, int height, int index)
{
FILE *pFile;
char szFilename[32];
int y;
// Open file
sprintf(szFilename, "data/frame%d.ppm", index);
pFile=fopen(szFilename, "wb");
if(pFile==nullptr)
return;
// Write header
fprintf(pFile, "P6\n%d %d\n255\n", width, height);
// Write pixel data
for(y=0; y<height; y++)
{
fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
}
// Close file
fclose(pFile);
}
/*获取视频解码器*/
bool VideoPlayer::findVideoDecoder()
{
}
void VideoPlayer::setPause(bool flag)
{
_isPause = flag;
}
void VideoPlayer::setStop(bool flag)
{
_isStop = flag;
}
void VideoPlayer::run()
{
av_register_all();
_pFormatCtx = avformat_alloc_context();
if (avformat_open_input(&_pFormatCtx, _videoFileName.toStdString().c_str(), NULL, NULL) != 0) {
printf("can't open the file. \n");
FLOG;
return ;
}
//寻找流信息
if (avformat_find_stream_info(_pFormatCtx, NULL) < 0) {
printf("Could't find stream infomation.\n");
FLOG;
return ;
}
_videoStreamIndex = -1;
for (int i = 0; i < _pFormatCtx->nb_streams; i++) {
if (_pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
_videoStreamIndex = i;
}
}
///如果videoStream为-1 说明没有找到视频流
if (_videoStreamIndex == -1) {
printf("Didn't find a video stream.\n");
FLOG;
return;
}
///查找解码器
_pCodecCtx = _pFormatCtx->streams[_videoStreamIndex]->codec;//解码器上下文
_pCodec = avcodec_find_decoder(_pCodecCtx->codec_id);//寻找解码器
if (_pCodec == nullptr) {
printf("Codec not found.\n");
FLOG;
return ;
}
///打开解码器
if (avcodec_open2(_pCodecCtx, _pCodec, NULL) < 0) {
printf("Could not open codec.\n");
FLOG;
return ;
}
//
AVFrame * _pFrame = nullptr;//每一帧 存储的是解码后的原始音视频数据
_pFrame = av_frame_alloc();
AVFrame * _pFrameRGB;//显示的RGB数据
_pFrameRGB = av_frame_alloc(); // 这个函数只是分配AVFrame结构体,但data指向的内存并没有分配
static struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(_pCodecCtx->width, _pCodecCtx->height,
_pCodecCtx->pix_fmt, _pCodecCtx->width, _pCodecCtx->height,
AV_PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
//每一帧的大小
int numBytes = avpicture_get_size(AV_PIX_FMT_RGB24, _pCodecCtx->width,_pCodecCtx->height);//
uint8_t *out_buffer = nullptr;
out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
//avpicture_fill 用来将 out_buffer 与 _pFrameRGB 进行关联
avpicture_fill((AVPicture *) _pFrameRGB, out_buffer, AV_PIX_FMT_RGB24,//填充转换RGB的内存区
_pCodecCtx->width, _pCodecCtx->height);//原始图片数据转化为 RGB
//此处预先分配是为了多次重用AVFrame
int y_size = _pCodecCtx->width * _pCodecCtx->height;
auto packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
av_new_packet(packet, y_size); //分配packet的数据//解码前的数据
int got_picture;
int index = 0;
while(!_isStop)
{
if(_isPause)
continue;
auto ret =av_read_frame(_pFormatCtx, packet);
if(ret < 0)
{//视频帧读取完毕
_isStop = true;
}
if (packet->stream_index == _videoStreamIndex) {
ret = avcodec_decode_video2(_pCodecCtx, _pFrame, &got_picture,packet);//视频帧解码
if (ret < 0) {
printf("decode error.\n");
return ;
}
if (got_picture) {
sws_scale(img_convert_ctx,
(uint8_t const * const *) _pFrame->data,//视频帧图像数据
_pFrame->linesize, 0, _pCodecCtx->height, _pFrameRGB->data,
_pFrameRGB->linesize);
SaveFrame(_pFrameRGB, _pCodecCtx->width,_pCodecCtx->height,index++); //保存图片
if(index > 50)
{
_isStop = true;
break;
}
}
}
av_free_packet(packet);
}
av_free(out_buffer);
av_free(_pFrameRGB);
avcodec_close(_pCodecCtx);
avformat_close_input(&_pFormatCtx);
sws_freeContext(img_convert_ctx);
}
main.cpp
#include<QCoreApplication>
#include"header.h"
#include"videoplayer.h"
#undef main//引用SDL的时候加上
int main(int argc, char* argv[])
{
QCoreApplication app(argc,argv);
const char * videoFile = "test.mp4";
VideoPlayer player;
player.setFilePath(videoFile);
player.start();
player.quit();
player.wait();
return app.exec();
}`