FFmpeg4入门系列教程7:Qt播放视频

Posted by

索引地址:系列教程索引地址

前两篇介绍了视频帧解码和帧数据解码保存,都是不够实时和直观,本篇介绍使用Qt作为界面来显示解码后的数据。

使用ffmpeg解码视频每一帧,因为比较耗时,所以独立一个线程。解码完成后的数据发送给界面,界面渲染显示图像数据,界面显示一个线程。

流程如下:

play

解码部分

解码部分和之前的一样,不过需要调整一下。

像初始化变量、打开文件、分配解码器上下文、打开解码器等等,这些操作只需要一次,并且耗时很短,不需要放在独立线程里面。而发送数据给解码器、解码、接收解码器解码结果、格式转换这些操作会一直持续直到视频处理结束,此部分最耗时,放在独立线程中。

qt的多线程结构的一种为:

class FFmpegVideo:public QThread{
public:
    ...
protected:
    void run();
private:
    ...
}

继承QThread之后,重写run()函数,所有耗时操作在此函数中,且只有此函数的内容是在其他线程中。

void FFmpegVideo::run()
{
    if(!openFlag){//是否有过初始化、打开文件的操作
        qDebug()<<"Please open file first.";
        return;
    }

    while(av_read_frame(fmtCtx,pkt)>=0){//读取一帧
        if(pkt->stream_index == videoStreamIndex){//视频流
            if(avcodec_send_packet(videoCodecCtx,pkt)>=0){//发送帧给解码器
                int ret;
                while((ret=avcodec_receive_frame(videoCodecCtx,yuvFrame))>=0){//接收解码之后的结果
                    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                        return;
                    else if (ret < 0) {
                        fprintf(stderr, "Error during decoding\n");
                        exit(1);
                    }
                    //格式转换
                    sws_scale(img_ctx,
                              yuvFrame->data,yuvFrame->linesize,
                              0,videoCodecCtx->height,
                              rgbFrame->data,rgbFrame->linesize);

                    //将数据处理为QImage格式
                    QImage img(out_buffer,
                               videoCodecCtx->width,videoCodecCtx->height,
                               QImage::Format_RGB32);
                    emit sendQImage(img);//发送结果数据信号
                    QThread::msleep(30);//延时30ms
                }
            }
            av_packet_unref(pkt);
        }
    }
}

显示部分

一帧解码完成之后会将数据转换为QImage的格式,然后发送信号emit sendQImage(const QImage img);,将打包好的QImage图像发送出来。在显示界面有个对应的处理槽receiveQImage(const QImage img);将接收到的图像数据赋值给界面类的全局变量。

void FFmpegWidget::receiveQImage(const QImage &rImg)
{
    img = rImg.scaled(this->size());
    update();
}

重写qt的paintEvent函数,界面会循环调用这个函数来在界面上绘图。

void FFmpegWidget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.drawImage(0,0,img);
}

界面为:

ui

效果为:

result

cpu占用率为:

cpu rate

源码在Githubffmpeg_video_decode_qt_play_cpu下。

赞赏

微信赞赏支付宝赞赏

Leave a Reply

邮箱地址不会被公开。