FFmpeg之视频解码
第一次写CSDN,先熟悉熟悉FFmpeg
常用结构体
1. AVFormatContext; //为封装上下文;
2. AVCodecContext; //为解码器上下文;
3. AVStream; //为存放的是各种流,如:音频流,视频流,字母等;
4. SwsContext; //为转换上下文,主要用于将原始数据转换成目标格式的数据;如:YUV或RGB;
5. AVCodec; //为解码器;
6. AVpacket; //为数据包,用于将编码数据发送给解码器的;
7. AVFrame; //存放一般存放解码后的数据;
常用方法函数
1. av_register_all();
2. avformat_open_input();
3. av_find_best_stream( ); //找到最你指定的那个流的序号;
4. avformat_find_stream_info( ); //该函数主要用于给每个媒体流(音频/视频)的AVStream结构体赋值
5. avcodec_find_decoder(); //找到音频或视频流对应的编码格式的解码器;
6. avcodec_parameters_to_context(); //将解码器参数辅导解码器上下文中
7. avcodec_open2(); //打开解码器;
8. sws_getContext(); //设置原始数据(YUV)转换成其他格式( RGB )的转换上下文,并返回换上下文。
9. av_read_frame(); //视频是一帧的,音频可以是多帧。
10. avcodec_decode_video2(); //对视频解码
11. sws_scale(); //将一帧原始数据(YUV)转换成我们指定格式 (RGB )。
视频解码的一些基础知识:
-
视频流是按一定的顺序排列 I 帧,P 帧 和 B 帧的。
1. I 帧:为关键帧,它为帧内压缩,解码后为一个完整的画面,这是解码的关键; 2. P 帧:为前向预测帧,它自己解码是无法完成一帧画面,它的解码需要依赖上一次解码的 I 帧(或P帧); 3. B 帧:为双向预测内插编码帧,它以前面的I或P帧和后面的P帧为参考帧。
因此,重要性:I 帧 > P 帧 > B 帧。由于不同类型的帧的重要性不同,这意味着我们要按播放连贯的视频,就必须按照一定规定来显示这些帧,这就引来了两个时间概念:解码时间戳(DTS)和显示时间戳(PTS)。
音频同步的时候,也是以显示时间戳为标准的。
-
视频格式(AVPixelFormat)
AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUYV422, AV_PIX_FMT_RGB24, AV_PIX_FMT_RGBA, AV_PIX_FMT_NONE = -1, AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUYV422, AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, AV_PIX_FMT_YUV422P, …… //格式还有很多种,这里不在叙述。
代码示例,Qt Creator中运行
主要思想:用主线程显示视频(即widget.h和.cpp),用MyFFmpeg线程来实现文件的解码;
widget.h
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void init_MyFFmpeg();
void paintEvent(QPaintEvent* e);
private:
MyFFmpeg *myFFmpeg;
QPixmap pix;
private:
Ui::Widget *ui;
};
widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
myFFmpeg=new MyFFmpeg();
//收到myFFmpeg线程对象发来的图片,就绘到widget上
connect(myFFmpeg,&MyFFmpeg::emitImage,[=](QImage image,int time){
pix = QPixmap::fromImage(image,Qt::AutoColor);
qDebug()<<"time:"<<time;
update(); //更新paintEvent时间
});
//启动线程
myFFmpeg->start();
}
Widget::~Widget(){
delete ui;
}
void Widget::paintEvent(QPaintEvent* e){
QPainter painter(this);
pix.scaled(this->size(),Qt::IgnoreAspectRatio);
int x = this->width() - pix.width();
int y = this->height() - pix.height();
painter.drawPixmap(QPoint(x,y),pix);
}
MyFFmpeg.h文件
class MyFFmpeg: public QThread
{
Q_OBJECT
public:
MyFFmpeg();
~MyFFmpeg();
void run() override;
void init_MyFFmpeg();
signals:
void emitImage(QImage image);
private:
//ffmpeg相关变量预先定义与分配
AVFormatContext *m_pAVFormatCtx; //解封装上下文
SwsContext *m_pSwsCtx; //视频转码上下文
AVCodecContext *avCodecCtx; //解码上下文
AVFrame *m_pAVFrame = 0; // 解码后的原始数据Frame
AVFrame *m_pAVFrameRGB32 = 0; // 转换后的目标格式Frame
AVPacket *m_pAVPacket = 0; // ffmpag单帧数据包
int gotPicture = 0; // 解码时数据是否解码成功
int outBuffer_size = 0; // 解码后的数据长度
uchar *outBuffer = 0; // 解码后的数据存放缓存区
char m_errorBuff[1024]; //打开时发生的错误信息
int m_totalMs; //总时长
int m_videoStreamId; //视频流
int m_fps; //每秒的视频帧数
int m_pts; //获得当前解码帧的时间
};
MyFFmpeg.cpp文件文章来源:https://www.toymoban.com/news/detail-405302.html
MyFFmpeg::MyFFmpeg()
{
init_MyFFmpeg();
av_register_all();
}
void MyFFmpeg::run(){
const char* pathUrl="mmm.mkv";
//打开文件,解封装
int ret = avformat_open_input(&m_pAVFormatCtx,pathUrl,NULL,NULL);
if(ret!=0){
av_strerror(ret,m_errorBuff,sizeof(m_errorBuff));
qDebug()<<"Open failed: "<<m_errorBuff;
exit(0);
}
m_totalMs = m_pAVFormatCtx->duration / (AV_TIME_BASE); //获取视频的总时间
//寻找视频流序号
// m_videoStreamId = av_find_best_stream(m_pAVFormatCtx,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
m_videoStreamId = -1;
for(int i=0;i<m_pAVFormatCtx->nb_streams;i++)
{
if(m_pAVFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO)
{
m_videoStreamId=i;
break;
}
}
if(m_videoStreamId ==-1){
qDebug()<<"can not find videoStream";
exit(0);
}
//寻找解码器
AVCodec *avCodec = avcodec_find_decoder( m_pAVFormatCtx->streams[m_videoStreamId]->codecpar->codec_id);
//avCodecCtx = avcodec_alloc_context3(avCodec);
avCodecCtx=m_pAVFormatCtx->streams[m_videoStreamId]->codec;
//打开编码器
if(avcodec_open2(avCodecCtx,avCodec,NULL)<0){
qDebug()<<"can not open codec";
exit(0);
}
//进行格式转换上下文设置,一般的pix_fmt都是AV_PIX_FMT_YUV420P
avCodecCtx->pix_fmt=AV_PIX_FMT_YUV420P; //这里我们手动给pix_fmt赋值AV_PIX_FMT_YUV420P,
// 如果不赋值,下面sws_getContext就会崩掉,因为没有这句话 pix_fmt=-1;
m_pSwsCtx = sws_getContext(avCodecCtx->width, avCodecCtx->height, avCodecCtx->pix_fmt,
avCodecCtx->width, avCodecCtx->height, AV_PIX_FMT_RGBA, SWS_FAST_BILINEAR,
NULL, NULL, NULL);
outBuffer_size = av_image_get_buffer_size(AV_PIX_FMT_RGBA, avCodecCtx->width, avCodecCtx->height,1);
outBuffer=(unsigned char*)malloc(outBuffer_size);
//将m_pAVFrameRGB32赋值, 并将m_pAVFrameRGB32的data变量指向outBuffer
av_image_fill_arrays(m_pAVFrameRGB32->data,m_pAVFrameRGB32->linesize, outBuffer,
AV_PIX_FMT_RGBA, avCodecCtx->width, avCodecCtx->height, 1);
//开始逐帧解码视频和音频,这里我们之解码视频
while(av_read_frame(m_pAVFormatCtx, m_pAVPacket)>=0)
{
//找到视频流
if(m_pAVPacket->stream_index==m_videoStreamId){
if(avcodec_decode_video2(avCodecCtx, m_pAVFrame, &gotPicture, m_pAVPacket)<0){
qDebug()<<"解码视频失败";
exit(0);
}
if(gotPicture){
//对原始数据进行格式转化
sws_scale(m_pSwsCtx, (const uint8_t* const *)m_pAVFrame->data, m_pAVFrame->linesize,0,
avCodecCtx->height, m_pAVFrameRGB32->data, m_pAVFrameRGB32->linesize);
//接下来就是将m_pAVFrameRGB32上的数据在设备上显示出来。
QImage imageshow(*m_pAVFrameRGB32->data, avCodecCtx->width, avCodecCtx->height,
QImage::Format_RGB32);
//显示当前帧的时间,并转化成秒
m_dts=m_pAVFrame->pts * av_q2d(m_pAVFormatCtx->streams[m_videoStreamId]->time_base);
//将线程中解码好的图片和显示时间发送到主线程中
emitImage(imageshow, m_dts);
QThread::msleep(30);//延时使得画面播放流畅
}
}
av_packet_unref(m_pAVPacket); //引用计数减1;
}
//正常来讲这里是要执行下面着几句话的,但是释放内存的,这个线程就会出现冲突,所以我们将这些话放在了析构函数中
//avformat_free_context(m_pAVFormatCtx);
//avcodec_free_context(&avCodecCtx);
//av_frame_free(&m_pAVFrame);
//av_frame_free(&m_pAVFrameRGB32);
//av_packet_free(&m_pAVPacket);
//qDebug()<<"音频解码结束";
}
MyFFmpeg::~MyFFmpeg()
{ //释放内存
avformat_free_context(m_pAVFormatCtx);
avcodec_free_context(&avCodecCtx);
av_frame_free(&m_pAVFrame);
av_frame_free(&m_pAVFrameRGB32);
av_packet_free(&m_pAVPacket);
qDebug()<<"音频解码结束";
}
void MyFFmpeg::init_MyFFmpeg()
{
m_pAVFormatCtx = avformat_alloc_context();
m_pAVFrame = av_frame_alloc();
m_pAVPacket = av_packet_alloc();
m_pAVFrameRGB32 = av_frame_alloc();
}
//
总结一下,这里也才了一个坑,就是
问题就是sws_getContext()中, avCodecCtx->pix_fmt=-1,导致系统崩溃,文章来源地址https://www.toymoban.com/news/detail-405302.html
avCodecCtx->pix_fmt=AV_PIX_FMT_YUV420P; //必需在这里可以赋值 AV_PIX_FMT_YUV420P
m_pSwsCtx = sws_getContext(avCodecCtx->width, avCodecCtx->height, avCodecCtx->pix_fmt,
avCodecCtx->width, avCodecCtx->height, AV_PIX_FMT_RGBA, SWS_FAST_BILINEAR,
NULL, NULL, NULL);
第一次写博客,参考了别人的一些经验,然后自己整理加自己的理解,主要是给自己记笔记,当然也供小伙伴们参考,如果有错误,请谅解!!!
到了这里,关于FFmpeg之视频解码的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!