FFMPEG Qt视频播放器
- 本文是根据PaintEvent事件处理函数不停在组件中绘制视频帧数据
做过图像界面开发的都知道,任何耗时的操作都不能放在主线程进行,一旦主线程阻塞了,那么体现出来的就是界面卡了。 而我们读取视频和解码视频是一个非常耗时的操作,因此需要另外开辟一个线程来专门做这件事
项目准备工作:
- 安装和配置Qt;
- ffmpeg配置
具体步骤读者可以直接百度搜索;
项目具体步骤如下:
- ffmpeg解码视频文件得到的yuv数据
- 将yuv数据转为RGB数据
- 用QImage加载RGB数据
- 传输至组件
- PaintEvent函数绘制图像
项目代码实现
- 新建一个线程类,继承自QThread
class VideoPlayer : public QThread
- 在新建的线程类中重载其run函数,把解码耗时操作全部都在run函数里面执行。
解码具体流程如下(引用雷博士博客中的图片,致敬,连接:https://blog.csdn.net/leixiaohua1020/article/details/8652605)
视频解码就是要到视频文件中寻找视频流,找到后对流逐帧解码
代码如下所示
//1 初始化FFMPEG
av_register_all(); //调用了这个才能正常适用编码器和解码器
//2 分配AVFormatContext
AVFormatContext *pFormatCtx = avformat_alloc_context();//FFMPEG所有的操作都要通过这个AVFormatContext来进行
char *file_path = "join.avi";
//3. 打开视频文件
if (avformat_open_input(&pFormatCtx, file_path, NULL, NULL) != 0) {//打开视频文件
qDebug()<<"can't open the file. ";
return ; }
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {//因为视频文件比较复杂所以打开后还需要读取详细的流信息
qDebug()<<"Could't find stream infomation.";
return ;
}
// 4. 找文件中的视频流
///循环查找视频中包含的流信息,直到找到视频类型的流
///便将其记录下来保存到videoStream变量中
///这里我们现在只处理视频流 音频流先不管他
int videoStream=-1;
qDebug()<<QObject::tr("视频流个数:")<<pFormatCtx->nb_streams;
for (int i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
}
}
///如果videoStream为说明没有找到视频流
if (videoStream == -1) {
qDebug()<<"Didn't find a video stream.";
return ;
}
//5 根据视频流 打开一个解码器来解码:
///查找解码器
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
//5.1 找到视频流相对应的解码器
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
qDebug()<<"Codec not found. ";
return ;
}
///5.2 打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
qDebug()<<"Could not open codec.";
return ;}
//6 读取视频
AVFrame *pFrame, *pFrameRGB;
int numBytes; uint8_t *out_buffer;
pFrame = av_frame_alloc();
pFrameRGB = av_frame_alloc();
static struct SwsContext *img_convert_ctx;
//将解码后的YUV数据转换成RGB32
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);
out_buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB32,
pCodecCtx->width, pCodecCtx->height);
int y_size = pCodecCtx->width * pCodecCtx->height;
AVPacket *packet = (AVPacket *) malloc(sizeof(AVPacket)); //6.1 分配一个packet AVPacket是存储压缩编码数据相关信息的一个结构体。
av_new_packet(packet, y_size); //分配packet的数据
av_dump_format(pFormatCtx, 0, file_path, 0); //输出视频信息
int index = 0;
int ret, got_picture;
while (1)
{
if (av_read_frame(pFormatCtx, packet) < 0)
{
break; //这里认为视频读取完了
}
if (packet->stream_index == videoStream) {
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);//7.解码
if (ret < 0) {
qDebug()<<"decode error.";
return ;
}
if (got_picture) {//8. YUV420数据>RGB
sws_scale(img_convert_ctx,
(uint8_t const * const *) pFrame->data,
pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
pFrameRGB->linesize);
}
}
av_free_packet(packet);
msleep(40); //延时ms使其帧率=1000/40
}
- 发送QImage数据到界面
由于我们不能够在子线程中操作界面,(操作界面只能在主线程中进行,几乎所有的图形界面开发都是这样设定),因此我们只能给主线程发送信号,信号带上这个QIMage,让主线程帮忙把这个图像显示出来。所以需要在 YUV420数据>RGB 后加入以下代码
//把这个RGB数据用QImage加载
QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
QImage image = tmpImg.copy(); //把图像复制一份传递给界面显示
emit sig_GetOneFrame(image); //发送信号
- 槽函数调用update函数,自动调事件处理函数
void CameraClient::slotGetOneFrame(QImage img){
mImage = img;
update(); //调用update将执行paintEvent函数
}
- 事件处理函数绘制
void CameraClient::paintEvent(QPaintEvent *event){
QPainter painter(this);
painter.setBrush(Qt::black);
painter.drawRect(0, 0, this->width(), this->height()); //先画成黑色
if (mImage.size().width() <= 0) return;
///将图像按比例缩放成和窗口一样大小
QImage img = mImage.scaled(this->size(),Qt::KeepAspectRatio);
int x = this->width() - img.width();
int y = this->height() - img.height();
x /= 2;
y /= 2;
painter.drawImage(QPoint(x,y),img); //画出图像
}
这样就完成了简单的视频显示
完整代码:
https://download.csdn.net/download/a18796007675/10454097
参考:http://blog.yundiantech.com/?log=blog&id=9
FFMPEG Qt视频播放器的更多相关文章
- 音视频处理之FFmpeg+SDL视频播放器20180409
一.FFmpeg视频解码器 1.视频解码知识 1).纯净的视频解码流程 压缩编码数据->像素数据. 例如解码H.264,就是“H.264码流->YUV”. 2).一般的视频解码流程 视频码 ...
- qt 视频播放器错误解决方法
DirectShowPlayerService::doRender: Unresolved error code 0x80040266 () 当你发布的qmlproject包含QtMultimedia ...
- Qt 视频播放器
#include <phonon/VideoPlayer> #include <phonon/SeekSlider> #include <phonon/MediaObje ...
- 最简单的基于FFMPEG+SDL的视频播放器 ver2 (採用SDL2.0)
===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...
- 最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)
===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...
- 100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)【转】
转自:http://blog.csdn.net/leixiaohua1020/article/details/8652605 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] ...
- 最简单的基于FFMPEG+SDL的视频播放器:拆分-解码器和播放器
===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...
- 基于ffmpeg网络播放器的教程与总结
基于ffmpeg网络播放器的教程与总结 一. 概述 为了解决在线无广告播放youku网上的视频.(youku把每个视频切换成若干个小视频). 视频资源解析可以从www.flvcd. ...
- 【转】100行代码实现最简单的基于FFMPEG+SDL的视频播放器
FFMPEG工程浩大,可以参考的书籍又不是很多,因此很多刚学习FFMPEG的人常常感觉到无从下手.我刚接触FFMPEG的时候也感觉不知从何学起. 因此我把自己做项目过程中实现的一个非常简单的视频播放器 ...
随机推荐
- ES6 函数的扩展-rest参数
ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了.rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中. functio ...
- 基于区域的OSPF简单认证
实验要求:掌握OSPF区域简单认证配置 拓扑如下: 配置如下: R1enable configure terminal interface s0/0/0ip address 192.168.1.1 2 ...
- maven修改本地仓库,远程仓库与中央仓库
什么是Maven仓库 在不用Maven的时候,比如说以前我们用Ant构建项目,在项目目录下,往往会看到一个名为/lib的子目录,那里存放着各类第三方依赖jar文件,如 log4j.jar,junit. ...
- PaddlePaddle Perceptron Example
.caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px so ...
- Android内核sys_setresuid() Patch提权(CVE-2012-6422)
让我们的Android ROOT,多一点套路. 一.简单套路 CVE-2012-6422的漏洞利用代码,展示了另一种提权方法.(见附录) 这也是一个mmap驱动接口校验导致映射任意内核地址的洞.将内核 ...
- Redis分布式锁实现
redis分布式锁的一个简单直接的实现方法就是用 SET NX 命令设置一个设定了存活周期 TTL 的 Key 来获取锁,通过删除 Key 来释放锁,通过存活周期来避免死锁.不过这个方法存在单点故障风 ...
- Groovy实现代码热载的机制和原理
前言: 真的很久没在博客园上更新博客了, 现在趁这段空闲的时间, 对之前接触的一些工程知识做下总结. 先来讲下借用Groovy如何来实现代码的热载, 以及其中涉及到的原理和需要注意的点. 总的来说, ...
- [LeetCode&Python] Problem 453. Minimum Moves to Equal Array Elements
Given a non-empty integer array of size n, find the minimum number of moves required to make all arr ...
- xdoj 1241--余神的rp机(区间dp)
xdoj 1241---余神的rp机 核
- hdu3613 Best Reward manacher+贪心+前缀和
After an uphill battle, General Li won a great victory. Now the head of state decide to reward him w ...