目录
一、视频播放器的实现框架
1.解封装(Demuxing)
将输入的封装格式的数据,分离成为音频流压缩编码数据和视频流压缩编码数据。封装格式种类很多,例如 MP4,MKV,RMVB,TS,FLV,AVI 等,它的作用就是将已经压缩编码的视频数据和音频数据按照一定的格式放到一起。例如,FLV 格式的数据,经过解封装操作后,输出 H.264 编码的视频码流和 AAC 编码的音频码流;
2.解码(Decode)
将视频/音频压缩编码数据,解码成为非压缩的视频/音频原始数据。音频的压缩编码标准包含 AAC,MP3 等,视频的压缩编码标准则包含 H.264,MPEG2 等。解码是整个系统中最重要也是最复杂的一个环节。通过解码,压缩编码的视频数据输出成为非压缩的颜色数据,例如 YUV、RGB 等等;压缩编码的音频数据输出成为非压缩的音频抽样数据,例如 PCM 数据
3.音视频同步
根据解封装模块处理过程中获取到的参数信息,同步解码出来的音频和视频数据,并将音视频频数据送至系统的显卡和声卡播放出来(Render)。
二、FFmpeg解码视频流程
1.FFmpeg解码视频流程图
音频解码后面会专门出一期介绍,这边就先不说了;
2. 代码实现
头文件中类的数据成员和信号
//视频文件上下文格式
AVFormatContext* avformat_context;
//编解码器上下文格式
AVCodecContext* avcodec_context;
//解码器上下文格式
AVCodec* avcodec;
//数据包
AVPacket* av_packet;
//保存视频流的索引
int av_stream_index;
//帧数据
AVFrame *pFramein;
AVFrame *pFrameRGB;
uint8_t * pOutbuffer;
int ret;
QImage m_image;
signals:
//发送解码得到的每一帧像素数据--保存的图片信息
void sigGetOneFrame(QImage image);
(1)注册所有组件
av_register_all();
(2)打开视频输入文件
qDebug()<<"2.打开视频输入文件";
//给avformat_context开空间
avformat_context = avformat_alloc_context();
//avformat_open_input参数一:视频文件格式上下文->AVFormatContext->包含了视频信息(视频名、时长、大小等)
//参数二:文件路径
qDebug()<<"打开"<<filename<<"视频文件进行播放";
int avformat_open_result = avformat_open_input(&avformat_context,filename.toStdString().c_str(),NULL,NULL);
if (avformat_open_result != 0)
{
//获取异常信息
char* error_info = new char[32];
av_strerror(avformat_open_result, error_info, 100);
qDebug()<<QString("异常信息 %1").arg(error_info);
};
(3)查找视频流
qDebug()<<"3.查找视频流信息";
//参数一:视频封装格式上下文->AVFormatContext
//参数二:配置
//返回值:>=0找到,否则失败
int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context,NULL);
if (avformat_find_stream_info_result < 0)
{
//获取失败
char* error_info = new char[32];
av_strerror(avformat_find_stream_info_result, error_info, 100);
qDebug()<<QString("异常信息 %1").arg(error_info);
}
av_stream_index = -1;
for (int i = 0; i < avformat_context->nb_streams; i++)
{
//循环遍历每一流找到视频流
if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
//找到了
av_stream_index = i;
break;
}
}
if (av_stream_index == -1)
{
qDebug()<<QString("没有找到视频流");
}
4.根据视频流查找对应的解码器
qDebug()<<"4.查找解码器";
//第一点:获取当前解码器是属于什么类型解码器->找到了视频流
//第二点:根据视频流->查找到视频解码器上下文->视频压缩数据
avcodec_context = avformat_context->streams[av_stream_index]->codec;//编解码器上下文
//第三点:根据解码器上下文->获取解码器ID
avcodec = avcodec_find_decoder(avcodec_context->codec_id);
if (avcodec == NULL)
{
qDebug()<<QString("没有找到视频解码器");
}
5.打开对应解码器
如果不打开解码器,会出现返回值-22异常错误
qDebug()<<"5.打开解码器";
int avcodec_open2_result = avcodec_open2(avcodec_context,avcodec,NULL);
if (avcodec_open2_result != 0)
{
char* error_info = new char[32];
av_strerror(avformat_find_stream_info_result, error_info, 100);
qDebug()<<QString("异常信息 %1").arg(error_info);
}
以上就是解码的准备工作,解码和转码的所有参数都可以在这里获取
可以打印视频的详细信息,如名称、时长、分辨率、解码器等等
获取的时长单位是微秒,换算成秒除以1000000
上图最后一行命令可以打印所有详细信息
接下来进行循环解码操作
6. 循环解码
1-5的代码可以放在构造函数中实现
循环解码可以在线程中实现,后续在窗口中播放时可以边解码边播放,更方便一些;
qDebug()<<"6.循环解码";
//packet开空间
av_packet = (AVPacket*)av_malloc(sizeof(AVPacket));
//输入->环境一帧数据->缓冲区->类似于一张图
pFramein = av_frame_alloc();
//输出->帧数据->数据格式->RGB
pFrameRGB = av_frame_alloc();
//只有指定了AVFrame的像素格式、画面大小才能真正分配内存
//缓冲区分配内存
pOutbuffer = (uint8_t *)av_malloc(avpicture_get_size(
AV_PIX_FMT_RGB32, avcodec_context->width, avcodec_context->height));
//初始化缓冲区 类似于memset
avpicture_fill((AVPicture *)pFrameRGB, pOutbuffer,
AV_PIX_FMT_RGB32, avcodec_context->width, avcodec_context->height);
//解码的状态类型(0:表示解码完毕,非0:表示正在解码)
// int current_frame_index = 0;
//用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
//准备一个视频像素数据格式上下文
//参数一:输入帧数据宽
//参数二:输入帧数据高
//参数三:输入帧数据格式
//参数四:输出帧数据宽
//参数五:输出帧数据高
//参数六:输出帧数据格式->AV_PIX_FMT_RGB32
//参数七:视频像素数据格式转换算法类型
//参数八:字节对齐类型(C/C++里面)->提高读取效率
SwsContext* pSwsContext = sws_getContext(avcodec_context->width,
avcodec_context->height,
avcodec_context->pix_fmt,
avcodec_context->width,
avcodec_context->height,
AV_PIX_FMT_RGB32,
SWS_BICUBIC,NULL,NULL,NULL);
int ret;
//解码的状态类型(0:表示解码完毕,非0:表示正在解码)
int current_frame_index = 0;//计算解码的帧数
while (m_stop == false)
{
//>=0:说明有数据,继续读取 <0:说明读取完毕,结束
//从视频文件上下文中读取包--- 有数据就一直读取
if (av_read_frame(avformat_context,av_packet) >= 0)
{
//解码什么类型流(视频流、音频流、字幕流等等...)
if (av_packet->stream_index == av_stream_index)
{
//发送一个包数据进行解码
avcodec_send_packet(avcodec_context, av_packet);
//接收一个包数据,解压成一帧
ret = avcodec_receive_frame(avcodec_context,pFramein);
if (ret == 0)
{
//图片的转换 输入 输出
sws_scale(pSwsContext, (const unsigned char* const*)pFramein->data, pFramein->linesize, 0, avcodec_context->height,
pFrameRGB->data, pFrameRGB->linesize);
QImage *tmpImg = new QImage((uchar *)pOutbuffer, avcodec_context->width,
avcodec_context->height,QImage::Format_RGB32);
QImage image=tmpImg->copy();
qDebug()<<"接收图片信号"<<image;
emit sigGetOneFrame(image);//每保存一帧图片,发送一次信号
//遍历每一帧的信息进行打印
current_frame_index++;
//发送信号
//延时操作 1秒显示25帧--1000/25=40
QThread::msleep(40);
//获取的视频信息
qDebug()<<QString("当前遍历第 %1 帧").arg(current_frame_index);
}
}
}
av_free_packet(av_packet);
}
7.关闭缓冲区
qDebug()<<"7.关闭所有解码组件";
av_packet_free(&av_packet);
//关闭流和文件
avcodec_close(avcodec_context);
avformat_free_context(avformat_context);
感谢观看!!!!
以上就是全部内容,如果对您有帮助,欢迎点赞评论,或者发现有哪里写错的,欢迎指正!