目录
1、视频解码流程分析
2、FFmpeg相关解码函数
3、创建一个类专门用来视频解码
.h文件
构造函数
打开视频文件
获取视频文件流信息并查找是否有视频流
查找视频流对应的解码器
打开解码器
循环读取每一帧并生成.h264、.yuv文件
关闭解码用到的组件
主函数测试
4、测试效果
运行效果
查看h264文件
查看yuv文件
一个视频包含有音频流、视频流、字幕流等信息,下面为视频流的解码过程以及代码实现。
/*解码流程分析:
* 1.注册所有组件
* 2.打开文件
* 3.获取文件信息
* 4.看看有没有相关视频信息
* 5.有视频信息,找他对应的解码器
* 6.打开解码器
* 7.获取一帧视频流(码流)信息
* 8.读取,解码的动作
* 9.得到像素数据
* 重复7、8、9的动作,直到没有码流数据
* 10.关闭解码器
* 11.关闭视频流文件
*
* */
av_register_all():注册所有组件。
avformat_open_input():打开输入视频文件。
avformat_find_stream_info():获取视频文件信息。
avcodec_find_decoder():查找解码器。
avcodec_open2():打开解码器。
av_read_frame():从输入文件读取一帧压缩数据。
avcodec_decode_video2():解码一帧压缩数据。
avcodec_close():关闭解码器。
avformat_close_input():关闭输入视频文件。
#include
extern "C"
{
#include
#include
#include
#include
#include
#include
}
class fDecode
{
public:
fDecode();
void openMvFile(QString mvPath);//打开视频文件
void findVideoStream();//查找视频流
void findDecoder();//查找对应的解码器
void openDecoder();//打开解码器
void readFrame();//读取每一帧
void closeAll();//关闭用到的组件
private:
AVFrame *in_frame,*out_frame;//保存解码后的像素数据
int avcodec_index;//视频流所在输入视频的AVStream []数组的索引
AVFormatContext* avformat_context;//封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息
AVCodecContext* avcodec_context;//编码器上下文结构体,保存了视频(音频)编解码相关信息
AVCodec* avcodec;//每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体
};
fDecode::fDecode()
{
//注册组件
avcodec_register_all();
qDebug()<<"————注册所有组件————";
qDebug()<<"注册成功!";
}
void fDecode::openMvFile(QString mvPath)
{
qDebug()<<"————打开视频文件————";
avformat_context = avformat_alloc_context();//封装格式上下文结构体开空间
int res = avformat_open_input(&avformat_context,mvPath.toStdString().c_str(),NULL,NULL);//return 0 on success
if (res != 0)
{
qDebug()<<"打开失败";
exit(0);
}
else
{
qDebug()<<"打开成功!";
}
}
void fDecode::findVideoStream()
{
qDebug()<<"————获取文件信息————";
this->avcodec_index = -1;
int res = avformat_find_stream_info(avformat_context, NULL);//return >=0 if OK
if (res < 0)
{
qDebug()<<"获取文件信息失败";
exit(0);
}
else
{
qDebug()<<"获取文件信息成功!";
qDebug()<<"视频时长为:"<duration/1000000.0<<"秒";
qDebug()<<"视频平均混合码率为:"<bit_rate/1000<<"Kbps";
qDebug()<<"————获取文件视频流信息————";
//nb_streams :输入视频的AVStream 个数
for (int i = 0; i < avformat_context->nb_streams; i++)//遍历流
{
//找到视频流
if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
this->avcodec_index = i;
qDebug()<<"获取文件视频流信息成功!";
break;
}
}
if (-1 == this->avcodec_index)
{
qDebug()<<"没有找到视频流";
exit(0);
}
}
return;
}
void fDecode::findDecoder()
{
qDebug()<<"————查找对应的解码器————";
//avcodec_index为视频流所在输入视频的AVStream []数组的索引,通过上一步查找视频流得到的
//avcodec_context 编码器上下文结构体,保存了视频(音频)编解码相关信息
avcodec_context = avformat_context->streams[this->avcodec_index]->codec;
//根据解码器id查找解码器对应的结构体
avcodec = NULL;
avcodec = avcodec_find_decoder(avcodec_context->codec_id);
if (NULL == avcodec)
{
qDebug()<<"没有找到视频解码器";
exit(0);
}
else
{
qDebug()<<"找到对应的解码器为:"<name;
}
}
void fDecode::openDecoder()
{
qDebug()<<"————打开解码器————";
//avcodec_context、 avcodec 为上一步得到的
int res = avcodec_open2(avcodec_context,avcodec,NULL);//zero on success, a negative value on error
if (0 != res)
{
qDebug()<<"打开解码器失败";
exit(0);
}
else
{
qDebug()<<"打开解码器成功!";
}
}
void fDecode::readFrame()
{
qDebug()<<"————读取每一帧————";
AVPacket *pkt = (AVPacket *)malloc(sizeof(AVPacket));//存储一帧压缩编码数据
in_frame= av_frame_alloc();//保存解码后的像素数据
out_frame = av_frame_alloc();//保存解码后剔除损坏数据后的像素数据
//一帧码流数据解码后得到的像素数据有多大
int numByte = avpicture_get_size(AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height);
//开的空间用来保存像素数据
uint8_t *buf = (uint8_t *)av_malloc(numByte * sizeof(uint8_t));
//像素数据的填充
avpicture_fill((AVPicture *)out_frame, buf, AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height);
//保存转换规则
SwsContext *sws_content = nullptr;
//转换规则的设置
sws_content = sws_getContext(avcodec_context->width, avcodec_context->height ,avcodec_context->pix_fmt,
avcodec_context->width, avcodec_context->height ,AV_PIX_FMT_YUV420P,
SWS_BICUBIC,nullptr,nullptr,nullptr);
FILE *fp_H264=NULL;
FILE *fp_yuv=NULL;
//以二进制格式创建或打开文件
fp_H264=fopen("test.h264","wb+");
fp_yuv=fopen("test.yuv","wb+");
if(fp_H264 == NULL || fp_yuv == NULL)
{
qDebug()<<"file open fail!";
exit(0);
}
int size = avcodec_context->width * avcodec_context->height;
av_new_packet(pkt,size);
//循环读取每一帧
while (av_read_frame(avformat_context,pkt) == 0) //return 0 if OK, < 0 on error or end of file
{
if(pkt->stream_index == this->avcodec_index) //如果这一帧是视频流
{
fwrite(pkt->data,pkt->size,1,fp_H264);//写入h264文件
//解码——>得到yuv AVFrame
//got_picture_ptr Zero if no frame could be decompressed, otherwise, it is nonzero.
int got_picture_ptr = -1;
avcodec_decode_video2(avcodec_context, in_frame, &got_picture_ptr, pkt);
if(got_picture_ptr != 0)//做解码的操作
{
//把解码得到的损坏的数据剔除
sws_scale(sws_content, in_frame->data, in_frame->linesize, 0, in_frame->height, //原数据
out_frame->data, out_frame->linesize); //输出数据
fwrite(out_frame->data[0], size, 1, fp_yuv);//写入y数据
fwrite(out_frame->data[1], size/4, 1, fp_yuv);//写入u数据
fwrite(out_frame->data[2], size/4, 1, fp_yuv);//写入v数据
}
}
}
av_packet_unref(pkt);//清空
}
//关闭h264、yuv文件
fclose(fp_H264);
fclose(fp_yuv);
qDebug()<<"写入成功";
}
void fDecode::closeAll()
{
qDebug()<<"————关闭所有组件————";
av_frame_free(&in_frame);
av_frame_free(&out_frame);
avcodec_close(avcodec_context);
avformat_free_context(avformat_context);
qDebug()<<"关闭成功";
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
qDebug()<<"ffmpeg版本号:"<
当前目录下会生成test.h264文件和test.yuv文件
H.264压缩方法比较复杂。包含了帧内预测、帧间预测、熵编码、环路滤波等环节构成。可以将图像数据压缩100倍以上,所以可以发现未压缩的像素数据yuv文件比压缩后的码流数据h624文件大非常多。