FFmpeg学习一

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();  
}`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一路初心向前

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值