QT-VLC ,获取VLC每一帧并渲染到Qwidget

1,800 阅读3分钟

这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战

QT + VS2015 ,获取VLC每一帧并渲染到Qwidget

1、依赖下载

1.1 首先下载VLC依赖动态库和静态库,官网下载(VLC-3.0.4,64位),如需下载32位去 win32 目录下载即可。

download.videolan.org/pub/videola…

另外我上传到了csdn:方便大家下载:

download.csdn.net/download/u0…

1.2 下载完成后解压,拷贝出其中的 plugins 文件夹,libvlc.dll 文件,libvlccore.dll 文件,和sdk文件夹下的 include 和 lib 文件夹,总共5项。

1.3 然后将 plugins 文件夹和 libvlc.dll 和 libvlccore.dll 文件拷贝到exe的同级目录,同时将 include 和 lib 文件夹拷贝到 cpp 代码的同级目录。

ibvcxf.png

2、链接配置

2.1 打开VS2015,右键项目,打开属性页,选择C/C++ 标签,选择常规, 附加包含目录,添加一项 .\include

ibvhZQ.png

2.2 切换到链接器标签,选择常规,附加库目录,添加一项 .\lib


3、代码展示

渲染思路: 将回调的每一帧转为 QImage,然后将 QImage 放入 list 末尾,然后使用槽函数通知 paintEvent 从 list 的头部取出一帧图像,使用 drawPixmap 方法进行绘制,绘制完成后从list头部移除渲染过的 image 图像。

// 头文件 

#pragma once
#include <QtWidgets/QWidget>
#include <QPaintEvent>

class Vedio : public QWidget
{
    Q_OBJECT

public:
    Vedio(QWidget *parent = Q_NULLPTR);
    void updatePicture(const QImage &image);
    static Vedio *pThis;  //声明 pThis 对象方便我们在静态函数中调用成员函数
protected:
    virtual void paintEvent(QPaintEvent *event);
signals:
    void showImage();

private:
    std::list<QImage> lists;
};

正常情况下我们播放 VLC 视频只需要调用 libvlc_media_player_set_hwnd(mp, (void *)this->winId()); 传入窗口句柄即可播放视频,但是当需要获取每一帧自己去渲染的时候,此方法就失效了,需要通过 libvlc_video_set_callbacks(mp, lock, unlock, display, ctx); 方法在 VLC 提供的三个回调方法中处理数据,手动去渲染每一帧,这三个回调方法为 lock,unlock 和 display 方法。

// cpp文件

#include "vedio.h"
#include <include/vlc/vlc.h>  
#pragma comment(lib, "libvlc.lib")
#pragma comment(lib, "libvlccore.lib")
#include <QPixmap>
#include <QImage>
#include <QPainter>
#include <QMutex>
using namespace std;

// 定义输出视频的分辨率
#define VIDEO_WIDTH   1920
#define VIDEO_HEIGHT  1280


struct context {
    QMutex mutex;
    uchar *pixels;
};

static void *lock(void *opaque, void **planes)
{
    struct context *ctx = (context *)opaque;
    ctx->mutex.lock();
    *planes = ctx->pixels;
    return NULL;
}

static void unlock(void *opaque, void *picture, void *const *planes)
{
       struct context *ctx = (context *)opaque;
       unsigned char *data = (unsigned char *)*planes; // planes即为帧数据
       QImage image(data, VIDEO_WIDTH, VIDEO_HEIGHT, QImage::Format_RGBA8888);// 指定生成的图片格式为 RGBA 4通道
       Vedio::pThis->updatePicture(image);  // 
       ctx->mutex.unlock();
}

static void display(void *opaque, void *picture)
{
    (void)opaque;
}
Vedio* Vedio::pThis = nullptr;

Vedio::Vedio(QWidget *parent)
    : QWidget(parent)
{
    pThis = this;
    //
    connect(this, SIGNAL(showImage()), this, SLOT(update()));

    libvlc_instance_t * inst;
    libvlc_media_player_t *mp;
    libvlc_media_t *m;

    context *ctx = new context;
    ctx->pixels = new uchar[VIDEO_WIDTH * VIDEO_HEIGHT * 4]; // 申请大小也为4通道的像素
    memset(ctx->pixels, 0, VIDEO_WIDTH * VIDEO_HEIGHT * 4);

    libvlc_time_t length;
    int width;
    int height;
    inst = libvlc_new(0, NULL);
    m = libvlc_media_new_path(inst, u8"C:\\Users\\HiWin10\\Desktop\\4K.mp4");
    mp = libvlc_media_player_new_from_media(m);
    //libvlc_media_player_set_hwnd(mp, (void *)this->winId());
    libvlc_video_set_callbacks(mp, lock, unlock, display, ctx);
    libvlc_video_set_format(mp, "RGBA", VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_WIDTH * 4);
    libvlc_media_release(m);
    libvlc_media_player_play(mp);
}

void Vedio::updatePicture(const QImage &image)
{
    lists.push_back(image);
    emit showImage();
}


void Vedio::paintEvent(QPaintEvent *event)
{
    if (lists.empty())
    {
        return;
    }
    QPainter painter(this);
    painter.drawPixmap(this->rect(), QPixmap::fromImage(lists.front()));
    lists.pop_front();
}

经过上面的步骤我们已经完成了整个视频的渲染,由于 paintEvent 方法使用的是cpu计算,且我们不停的生成的是 QImage 对象,因此对 CPU 负荷较高。

其实在绘制这块我们可以使用 Opencv 或者 openGL 去绘制,利用 GPU 减轻 CPU 计算负荷。