zoukankan      html  css  js  c++  java
  • FFMPEG Qt视频播放器

    • 本文是根据PaintEvent事件处理函数不停在组件中绘制视频帧数据

    做过图像界面开发的都知道,任何耗时的操作都不能放在主线程进行,一旦主线程阻塞了,那么体现出来的就是界面卡了。 而我们读取视频和解码视频是一个非常耗时的操作,因此需要另外开辟一个线程来专门做这件事

    项目准备工作:

    1. 安装和配置Qt;
    2. ffmpeg配置
      具体步骤读者可以直接百度搜索;

    项目具体步骤如下:

    1. ffmpeg解码视频文件得到的yuv数据
    2. 将yuv数据转为RGB数据
    3. 用QImage加载RGB数据
    4. 传输至组件
    5. 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

  • 相关阅读:
    a标签href不跳转 禁止跳转
    重新安装 tcp/ip协议
    痤疮的治疗
    tuxedo 强制重启
    山西企业主要指标稳步回升 运行渐入平稳轨道
    unix/linux 环境软件调试笔记
    连连看消重算法
    oracle sql developer guide
    取某字段最大值所在的的记录
    食指的《相信未来》
  • 原文地址:https://www.cnblogs.com/zhaobinyouth/p/9124960.html
Copyright © 2011-2022 走看看