集成Agora或者zego得都知道,这两者都支持自绘制或者让SDK绘制,SDK绘制有缺陷:

1.改变大小由于未能及时通知agora绘制得视频会闪烁

2.sdk同时只能在一个窗口绘制

拿到agora采集得数据自己绘制可以解决上面得缺陷。

声网支持两种方式,一种是继承接口方式,一种是设置一个回调接口。两种方式都一样。接口如下: (zego跟agora类似),如果接口返回的数据格式不是yuv,可以使用libyuv库(跨平台库)进行转化。

bool onCaptureVideoFrame(VideoFrame& videoFrame) override;

bool onRenderVideoFrame(unsigned int uid, VideoFrame& videoFrame) override;

VideoFrame中包含了视频数据,格式是yuv的。

这两个接口抛出来的数据在声网的线程,Qt不支持在非主线程中绘制窗体,所以需要将数据通过信号和槽抛到主线程,然后进行绘制,Agora的数据声网会自动销毁,所以这里要进行保存,使用QByteArray来进行数据的保存.

void copyFrameData(VideoFrame& videoFrame, QSharedPointer<QByteArray>& arrayStr)
{
    char* pNewYuvBuf = (char*)videoFrame.yBuffer;
    char* pNewUBuf = (char*)videoFrame.uBuffer;
    char* pNewVBuf = (char*)videoFrame.vBuffer;
    //分行拷贝
    if (videoFrame.yStride > videoFrame.width)
    {
        pNewYuvBuf = new char [videoFrame.width * videoFrame.height * 3 / 2];
        pNewUBuf = pNewYuvBuf + videoFrame.width * videoFrame.height;
        pNewVBuf = pNewUBuf + videoFrame.width * videoFrame.height / 4;

char* yBuf1 = (char*)pNewYuvBuf;
        char* yBuf2 = (char*)videoFrame.yBuffer;
        for (int h = 0; h < videoFrame.height; ++h)
        {
            if (h > 0)
            {
                memcpy(yBuf1, yBuf2, videoFrame.width);
            }
            yBuf1 += videoFrame.width;
            yBuf2 += videoFrame.yStride;
        }
        char* uBuf1 = (char*)pNewUBuf;
        char* uBuf2 = (char*)videoFrame.uBuffer;
        for (int h = 0; h < videoFrame.height / 2; ++h)
        {
            if (h > 0)
            {
                memcpy(uBuf1, uBuf2, videoFrame.width / 2);
            }
            uBuf1 += videoFrame.width / 2;
            uBuf2 += videoFrame.uStride;
        }

char* vBuf1 = (char*)pNewVBuf;
        char* vBuf2 = (char*)videoFrame.vBuffer;
        for (int h = 0; h < videoFrame.height / 2; ++h)
        {
            if (h > 0)
            {
                memcpy(vBuf1, vBuf2, videoFrame.width / 2);
            }
            vBuf1 += videoFrame.width / 2;
            vBuf2 += videoFrame.vStride;
        }
    }

arrayStr->append(pNewYuvBuf, videoFrame.width * videoFrame.height);
    arrayStr->append(pNewUBuf, videoFrame.width * videoFrame.height / 4);
    arrayStr->append(pNewVBuf, videoFrame.width * videoFrame.height / 4);

if (videoFrame.yStride > videoFrame.width)
    {
        delete[] pNewYuvBuf;
    }
}

然后将保存好的 QSharedPointer<QByteArray>& arrayStr数据通过信号发送主线程进行绘制。

代码如下:

头文件:

#ifndef VIDEORENDEROPENGL_H
#define VIDEORENDEROPENGL_H

#include <QtOpenGL>
#include <QOpenGLFunctions>

class VideoRenderOpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
    Q_OBJECT
public:
    explicit VideoRenderOpenGLWidget(QWidget *parent = 0);
    ~VideoRenderOpenGLWidget();

//设置视频的宽高(agora数据结构中返回的宽高)
    void setVideoParameters(int w, int h);
    //设置视频数据
    void setFrameData(const QSharedPointer<QByteArray>& data);
    //是否镜像
    void setMirror(bool bMirror);

protected:
    void bind();
    void bindPlane(int p);
    void initializeShader();
    void initTextures();
    virtual void initializeGL();
    virtual void paintGL();
    virtual void resizeGL(int w, int h);
    void clear();

private:
    bool update_res;
    bool upload_tex;
    int width;
    int height;

QByteArray m_data;

typedef struct
    {
        char* data;
        int stride;
        GLint internal_fmt;
        GLenum fmt;
        GLenum type;
        int bpp;
        QSize tex_size;
        QSize upload_size;
    } Plane;
    QVector<Plane> plane;

GLuint tex [3];
    int u_MVP_matrix, u_colorMatrix, u_Texture [3];
    QOpenGLShaderProgram *m_program;
    QMutex m_mutex;
    QMatrix4x4 m_mat;
    bool m_bMirror{false};
};

#endif // VIDEORENDEROPENGL_H

源文件:

#include "VideoRenderOpenGLWidget.h"

static const QMatrix4x4 yuv2rgb_bt601 =
    QMatrix4x4(
        1.0f, 0.000f, 1.402f, 0.0f,
        1.0f, -0.344f, -0.714f, 0.0f,
        1.0f, 1.772f, 0.000f, 0.0f,
        0.0f, 0.000f, 0.000f, 1.0f)
    *
    QMatrix4x4(
        1.0f, 0.0f, 0.0f, 0.0f,
        0.0f, 1.0f, 0.0f, -0.5f,
        0.0f, 0.0f, 1.0f, -0.5f,
        0.0f, 0.0f, 0.0f, 1.0f);

const GLfloat kVertices[] = {
    -1, 1,
    -1, -1,
    1, 1,
    1, -1,
};
// 原始矩阵
const GLfloat kTexCoordsMirror[] = {
    0, 0,
    0, 1,
    1, 0,
    1, 1,
};

//x轴镜像
const GLfloat kTexCoords[] = {
    1, 0,
    1, 1,
    0, 0,
    0, 1,
};

char const *const* BukaAttributes()
{
    static const char a0[] = {0x61, 0x5f, 0x50, 0x6f, 0x73, 0x0}; //a_Pos
    static const char a1[] = {0x61, 0x5f, 0x54, 0x65, 0x78, 0x0}; //a_Tex
    static const char a2[] = {0x00, 0x51, 0x74, 0x41, 0x56, 0x0}; //
    static const char* A[] = { a0, a1, a2};

return A;
}

typedef struct
{
    QImage::Format qfmt;
    GLint internal_fmt;
    GLenum fmt;
    GLenum type;
    int bpp;
} gl_fmt_entry_t;

#define glsl(x) #x
static const char kVertexShader[] = glsl(
    attribute vec4 a_Pos;
    attribute vec2 a_Tex;
    uniform mat4 u_MVP_matrix;
    varying vec2 v_TexCoords;
    void main()
{
    gl_Position = u_MVP_matrix * a_Pos;
    v_TexCoords = a_Tex;
}
    );

static const char kFragmentShader[] = glsl(
    uniform sampler2D u_Texture0;
    uniform sampler2D u_Texture1;
    uniform sampler2D u_Texture2;
    varying mediump vec2 v_TexCoords;
    uniform mat4 u_colorMatrix;
    void main()
{
    gl_FragColor = clamp(u_colorMatrix
        * vec4(
            texture2D(u_Texture0, v_TexCoords).r,
            texture2D(u_Texture1, v_TexCoords).r,
            texture2D(u_Texture2, v_TexCoords).r,
            1)
        , 0.0, 1.0);
}
    );
static const char kFragmentShaderRGB[] = glsl(
    uniform sampler2D u_Texture0;
    varying mediump vec2 v_TexCoords;
    void main()
{
    vec4 c = texture2D(u_Texture0, v_TexCoords);
    gl_FragColor = c.rgba;
}
    );
#undef glsl

VideoRenderOpenGLWidget::VideoRenderOpenGLWidget(QWidget *parent)
    : QOpenGLWidget(parent)
    , update_res(true)
    , upload_tex(true)
    , m_program(nullptr),
    width(1),height(1)
{
    QSurfaceFormat format;
    format.setDepthBufferSize(0);
    format.setStencilBufferSize(0);
    format.setSamples(4);
    setFormat(format);

memset(tex, 0, 3);
}

VideoRenderOpenGLWidget::~VideoRenderOpenGLWidget()
{
    makeCurrent();
    clear();
    if (m_program)
    {
        m_program->release();
        delete m_program;
        m_program = 0;
    }
    doneCurrent();
}

void VideoRenderOpenGLWidget::setFrameData(const QSharedPointer<QByteArray>& data)
{
    QMutexLocker lock(&m_mutex);
    Q_UNUSED(lock);
    upload_tex = true;
    m_data = *data.data();
    if (m_data.isEmpty())
    {
        return;
    }
    char* pData = m_data.data();
    if (pData == NULL){
        return;
    }
    if (plane.isEmpty())
    {
        return;
    }
    plane [0].data = pData;
    if (plane.size() > 2)
    {
        plane [1].data = plane [0].data + plane [0].stride * height;
        plane [2].data = plane [1].data + plane [1].stride * height / 2;
    }
    else {
    }
    //重绘
    update();
}

void VideoRenderOpenGLWidget::setMirror(bool bMirror)
{
    m_bMirror = bMirror;
    if (!m_data.isEmpty()) {
        update();
    }
}

void VideoRenderOpenGLWidget::bind()
{
    for (int i = 0; i < plane.size(); ++i)
    {
        bindPlane((i + 1) % plane.size());
    }
    upload_tex = false;
}

void VideoRenderOpenGLWidget::bindPlane(int p)
{
    glActiveTexture(GL_TEXTURE0 + p);
    glBindTexture(GL_TEXTURE_2D, tex [p]);
    if (!upload_tex)
    {
        return;
    }
    // This is necessary for non-power-of-two textures
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    const Plane &P = plane [p];
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, P.upload_size.width(), P.upload_size.height(), P.fmt, P.type, P.data);
}

void VideoRenderOpenGLWidget::initTextures()
{
    glDeleteTextures(3, tex);
    memset(tex, 0, 3);
    glGenTextures(plane.size(), tex);
    for (int i = 0; i < plane.size(); ++i)
    {
        const Plane &P = plane [i];
        glBindTexture(GL_TEXTURE_2D, tex [i]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        // This is necessary for non-power-of-two textures
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, P.internal_fmt, P.tex_size.width(), P.tex_size.height(), 0 /*border, ES not support*/, P.fmt, P.type, NULL);
        glBindTexture(GL_TEXTURE_2D, 0);
    }
}

void VideoRenderOpenGLWidget::setVideoParameters(int w, int h)
{
    if (width == w && height == h)
    {
        return;
    }
    QMutexLocker lock(&m_mutex);
    Q_UNUSED(lock);
    update_res = true;
    m_data.clear();
    width = w;
    height = h;
    plane.resize(3);
    Plane &p = plane [0];
    p.data = 0;
    p.stride = w;
    p.tex_size.setWidth(p.stride);
    p.upload_size.setWidth(p.stride);
    p.tex_size.setHeight(h);
    p.upload_size.setHeight(h);
    p.internal_fmt = p.fmt = GL_LUMINANCE;
    p.type = GL_UNSIGNED_BYTE;
    p.bpp = 1;
    for (int i = 1; i < plane.size(); ++i)
    {
        Plane &p = plane [i];
        p.stride =  w / 2;
        p.tex_size.setWidth(p.stride);
        p.upload_size.setWidth(p.stride);
        p.tex_size.setHeight(h / 2);
        p.upload_size.setHeight(h / 2);
        p.internal_fmt = p.fmt = GL_LUMINANCE;
        p.type = GL_UNSIGNED_BYTE;
        p.bpp = 1;
    }
}

void VideoRenderOpenGLWidget::paintGL()
{
    clear();
    if (!this->isVisible()){
        return QOpenGLWidget::paintGL();
    }
    for (GLenum err; (err = glGetError()) != GL_NO_ERROR;)
    {
    }
    QMutexLocker lock(&m_mutex);
    Q_UNUSED(lock);
    if (m_data.isEmpty()) {
        return QOpenGLWidget::paintGL();
    }
    if (!plane.isEmpty() && !plane [0].data)
    {
        return;
    }
    if (update_res || !tex [0])
    {
        initializeShader();
        initTextures();
        update_res = false;
    }
    bind();
    if(!m_program)
    {
        return;
    }
    if (!m_program->bind()){
        return;
    }
    if(!m_program->log().isEmpty()){
    }
    for (int i = 0; i < plane.size(); ++i)
    {
        m_program->setUniformValue(u_Texture [i], (GLint)i);
    }
    m_program->setUniformValue(u_colorMatrix, yuv2rgb_bt601);
    m_program->setUniformValue(u_MVP_matrix, m_mat);
    m_program->setAttributeArray(0, GL_FLOAT, kVertices, 2);
    if (m_bMirror){
        m_program->setAttributeArray(1, GL_FLOAT, kTexCoordsMirror, 2);
    }
    else {
        m_program->setAttributeArray(1, GL_FLOAT, kTexCoords, 2);
    }
    char const *const *attr = BukaAttributes();
    for (int i = 0; attr [i] [0]; ++i)
    {
        //TODO: in setActiveShader
        m_program->enableAttributeArray(i);
    }
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    for (int i = 0; attr [i] [0]; ++i)
    {
        //TODO: in setActiveShader
        if (m_program != NULL){
            m_program->disableAttributeArray(i);
        }
    }
    m_program->release();
}

void VideoRenderOpenGLWidget::initializeGL()
{
    static const struct {
        const char *extension;
        int major;
        int minor;
    } required_extensions[] = {
        { "GL_ARB_multitexture",             1, 3 },
        { "GL_ARB_vertex_buffer_object",     1, 5 }, //GLX_ARB_vertex_buffer_object
        { "GL_ARB_vertex_shader",            2, 0 },
        { "GL_ARB_fragment_shader",          2, 0 },
        { "GL_ARB_shader_objects",           2, 0 },
        { "GL_ARB_texture_non_power_of_two", 0, 0 },
        { "GL_EXT_unpack_subimage",          0, 0 },
        { NULL,                          0, 0 }
    };

static bool bChecked = false;
    initializeOpenGLFunctions();
    if (!bChecked)
    {
        bChecked = true;
        const char * version = (const char*)glGetString(GL_VERSION);
        if (version != NULL) {
        }
        const char * extensions = (const char*)glGetString(GL_EXTENSIONS);
        if (extensions != NULL) {
            QString str = QString::fromUtf8(extensions);
            for (int i = 0; required_extensions[i].extension; i++)
            {
                QString strExt = QString::fromUtf8(required_extensions[i].extension);
                if (str.contains(strExt)) {
                    strExt += " support";
                }
                else {
                    strExt += " unsupport";
                }
            }
        }
    }

int max_texture_size, max_viewport_width;
    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
    glGetIntegerv(GL_MAX_VIEWPORT_DIMS, &max_viewport_width);

}

void VideoRenderOpenGLWidget::resizeGL(int w, int h)
{
    if (h == 0)// 防止被零除
    {
        h = 1;// 将高设为1
    }
    if (w == 0)// 防止被零除
    {
        w = 1;// 将高设为1
    }
    glViewport(0, 0, w, qMax(0, h));
    m_mat.setToIdentity();
}

void VideoRenderOpenGLWidget::initializeShader()
{
    if (m_program)
    {
        m_program->release();
        delete m_program;
        m_program = 0;
    }
    m_program = new QOpenGLShaderProgram(this);
    if (!m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, kVertexShader)){
    }
    QByteArray frag;
    if (plane.size() > 1)
    {
        frag = QByteArray(kFragmentShader);
    }
    else
    {
        frag = QByteArray(kFragmentShaderRGB);
    }
    frag.prepend("#ifdef GL_ES\n"
        "precision mediump int;\n"
        "precision mediump float;\n"
        "#else\n"
        "#define highp\n"
        "#define mediump\n"
        "#define lowp\n"
        "#endif\n");
    if (m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, frag)){
    }

char const *const *attr = BukaAttributes();
    for (int i = 0; attr [i] [0]; ++i)
    {
        m_program->bindAttributeLocation(attr [i], i);
    }
    if (!m_program->link())
    {
    }

u_MVP_matrix = m_program->uniformLocation("u_MVP_matrix");
    // fragment shader
    u_colorMatrix = m_program->uniformLocation("u_colorMatrix");
    for (int i = 0; i < plane.size(); ++i)
    {
        QString tex_var = QString("u_Texture%1").arg(QString::number(i));
        u_Texture [i] = m_program->uniformLocation(tex_var);
    }
}

void VideoRenderOpenGLWidget::clear()
{
    //清理一下缓存
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

实现绘制代码在上面,可作为参考,另外这代码可以在windows和mac绘制。

注意:opengl绘制可能会影响到qt内部的opengl状态,偶现的,目前未找到问题。如有问题可以参照我前面的博客方式绘制视频。更加简单并且也是跨平台的

如有问题可以加:2043649236这个qq

Qt opengl自绘制Agora,zego(YUV)视频(支持Windows,mac)相关推荐

  1. Android OpenGL ES 学习(十一) –渲染YUV视频以及视频抖音特效

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

  2. qt opengl lesson5 绘制3d立体旋转图形

    继续上一篇lesson,先将3d立体各个面写完,然后进行旋转,感觉每个面单独写比较费时,这大概是3d比较麻烦和难以理解的地方,需要建立良好的空间想象力. #---------------------- ...

  3. linux的qt5.5,Qt 5.5 正式发布,完全支持 Windows 10

    Qt 5.5 正式发布!过去的 6 个月时间,修复了 1500 个报告的 Bug,实现了大量的性能改进.同时还完善了跨平台功能,并且优化了多媒体和蓝牙方面的功能. 完善跨平台功能 Windows:Qt ...

  4. 【专题3:电子工程师 之 上位机】 之 【47.使用QT Opengl显示YUV图像】

    嵌入式工程师成长之路 系列文章 总目录 希望本是无所谓有,无所谓无的,这正如脚下的路,其实地上本没有路,走的人多了,也便成了路 原创不易,文章会持续更新 文章会同步到作者个人公众号上,感谢扫码关注 所 ...

  5. 【QT项目:视频播放器——Qt opengl编程】通过shader完成显示yuv

    通过Qt opengl不是为了3D绘制,而是为了将视频绘制起来 使用opengl 可以极大降低yuv转rgb的转换开销 使用Opengl需要考虑三大问题: 1.QOpenGLWidget(与界面如何交 ...

  6. Android平台上基于OpenGl渲染yuv视频

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 更多音视频开发文章,请看:音视频开发专栏 介绍一个自己刚出炉的音视频播放录制开源项目 前言 这是我音视频专栏的第一篇实例解析,也算是 ...

  7. Android音视频学习系列(六) — 掌握视频基础知识并使用OpenGL ES 2.0渲染YUV数据

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  8. 【Qt for Android】OpenGL ES 绘制彩色立方体

    Qt 内置对OpenGL ES的支持.选用Qt进行OpenGL ES的开发是很方便的,很多辅助类都已经具备.从Qt 5.0開始添加了一个QWindow类,该类既能够使用OpenGL绘制3D图形,也能够 ...

  9. Qt OpenGL(三十六)——Qt OpenGL 核心模式-绘制雷达坐标系

    提示:本系列文章的索引目录在下面文章的链接里(点击下面可以跳转查看): Qt OpenGL 核心模式版本文章目录 Qt OpenGL(三十六)--Qt OpenGL 核心模式-绘制雷达坐标系 一.场景 ...

最新文章

  1. Codeforces Round #355 (Div. 2) B. Vanya and Food Processor 水题
  2. logm--求矩阵的对数
  3. 怎么计算网站高峰期并发量和所需的带宽?
  4. 审计某开源商城中的漏洞大礼包
  5. 管家婆SQL SERVER数据库“可能发生了架构损坏。请运行DBCC CHECKCATALOG”修复
  6. 【Linux内核之旅】eBPF C语言入门架构
  7. Mac adb 安装
  8. java赋值两个对象数组 clone_Java:类的两个相同对象数组的克隆问题
  9. 电脑表格软件哪个好用---办公软件推荐
  10. python输出矩阵_python输出矩阵
  11. 如何阅读看懂datasheet
  12. 轻松学python(一)
  13. docker部署time machine服务
  14. 【车间调度】基于matlab改进的帝国企鹅算法求解车间调度问题【含Matlab源码 2041期】
  15. 仿微信朋友圈图片上传
  16. 【科研】浅学Cross-attention?
  17. kubeadm reset重新初始化过程
  18. 王者荣耀:从程序员的角度解密王者荣耀,专业术语把院长看懵了
  19. I2C的ACK和NACK
  20. 服务器单独运行jar包方法

热门文章

  1. wpsppt页面卷曲在哪里_如何用PPT制作卷轴效果
  2. DDOS攻击需要怎么防御
  3. 用python写一个简单的网页游戏_python模拟点击玩游戏的实例讲解
  4. 微服务【分布式架构认识微服务SpringCloud】第1章
  5. 基于QTableView中的MVD代理添加总结
  6. 1999-2020年广东省21地级市地区生产总值(亿元)
  7. 弘辽科技:拼多多上架商品被驳回怎么办?为什么会被驳回?
  8. WebApi的安全性解决方案
  9. Abp框架Web站点的安全性提升
  10. @程序猿(媛),如何过一个不俗套的中秋节?