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

如何使用 Qt 开发音视频通话应用 作者:单辉,声网 Agora 高级开发工程师。 众所周知,Qt 是一个跨平台的 C++ 图形用户界面应用程序开发框架,它具有跨平台、丰富的 API、支持 2D/3D 图形渲染、支持 OpenGL、开源等优秀的特性。很多市面上常见的应用或者游戏,例如说 VLC、WPS Office、极品飞车等,都是基于 Qt 开发。 本文将介绍如何使用 Qt 开发一个音视频通话应用。 1 使用 Qt Quick... 阅读详情

集成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音视频开发之视频播放——使用QOpenGLWidget显示 在上述代码中,updateVideo函数用于更新视频纹理,其中,data为视频帧数据。paintGL函数用于绘制OpenGL场景,其中,需要通过OpenGL函数设置渲染纹理。在代码中,需要定义一个OpenGL窗口类,继承自QOpenGLWidget,并重写其initializeGL、resizeGL和paintGL函数。其中,initializeGL函数用于初始化OpenGL,resizeGL函数用于处理窗口大小的改变,paintGL函数用于绘制OpenGL场景。 阅读详情

相关推荐

Qt/C++音视频开发20-vlc内核动态保存录像文件(不需要重新编译源码)

在vlc默认提供的保存文件方式中,通过打开的时候传入指定的参数来保存文件,直到关闭播放生成文件,这种方式简单暴力,但是不适用大部分的场景,大部分时候需要的是提供开始录制和停止录制的功能,也就是在播放过程中能够动态录制多个文件,想录制的时候就录制,要结束的时候就结束录制,很遗憾vlc并没有提供这样的函数接口。

Qt/C++视频监控/推流/物联网/大屏系统/自定义控件/UI定制/输入法 2414

Golang 数组

Golang数组 数组是具有固定长度且拥有零个或多个相同数据元素的序列。 数组存储的类型可以是内置类型,比如整型或者字符串,也可以是自定义的数据结构。因为是连续的,所以索引比较好计算,所以我们可以很快的索引数组中的任何数据。 数组的声明和初始化 var array [5]int 以上我们声明了一个数组array,但是我们还没有对他进行初始化,这时候数组array里面的值,是对应元素类型的零值。 数组的初始化 var array [5]int = [5]int{1,2,3,4,5} array := [5

weixin_46272577的博客 985

利用 QOpenGLWidget 实现 GPU 加速视频帧绘制

初始化 OpenGL 上下文利用 QOpenGLWidget 创建一个 OpenGL 上下文,设置背景色、视口和正交投影。纹理上传使用 QOpenGLWidget 内置的方法将 QImage 上传为 OpenGL 纹理。Qt 内部会处理格式转换和缓存,避免重复上传相同内容。固定函数管线绘制利用 OpenGL 固定函数管线(glBegin/glEnd)绘制一个覆盖整个窗口的矩形,设置顶点和对应的纹理坐标。GPU 会自动根据当前绑定的纹理及过滤模式完成缩放和采样。释放资源与更新。

吃面不喝汤的博客 612

Go语言圣经 - 第2章 程序结构

第2章 程序结构 Go语言和其它语言一样,一个大的程序是由很多程序构件组成的。变量保存值,各种运算组成表达式,基础类型被聚合成为结构体、数组等更复杂的数据类型。然后使用for和if来组织和控制表达式的执行流程,然后多个语句被组织到一个函数中,以便代码的隔离和复用。函数以源文件和包的方式被组织 2.1 命名 Go语言中变量名、函数名、常量名、类型名、语句标号和包名等所有命名都遵循两条基本的准则: 以字母或者下划线开头,大写意味着可以导出,也就是说可以被外部包访问 区分大小写 Go语言中有25个关键字,关键字只

weixin_51487151的博客 320

声网PC端SDK,用于音视频服务

声网PC端SDK,用于音视频服务

某盾blackBox逆向——纯算

某盾blackBox逆向——纯算

Lzyydsb的博客 5891

Go+ 组织变更,并发布 weekly release: v0.7.1

这周时间虽然短,但是对 Go+ 来说发生了很多事情。最重要的一件事情当然是组织变更:我们有了 goplus 这个 github 组织啦。从代码管理的角度,这个影响不小,所以我们将版本号升...

QBox/Golang/CERL/StdExt/TPL/WinxGui - 许式伟的专栏 453

FPGA新手必看:XC7Z020最小系统搭建全攻略(附原理图解析)

本文为FPGA新手提供了XC7Z020最小系统搭建的完整实战指南。文章详细解析了电源架构、时钟复位、配置调试等关键硬件电路设计,并重点剖析了外围电路与端口引脚的连接要点及常见陷阱,帮助读者从零开始构建稳定可靠的开发平台。

weixin_29189377的博客 359

Qt之美(三)隐式数据共享

<br /><br />为了最大化资源使用,和最小化数据拷贝,Qt在很多类中用到了隐式数据共享,以便数据仅在被写入时才被拷贝。该机制也被称为flyweight模式。<br />让我们以QByteArray为例,看看其是如何实现的。其内部使用一个名为Data的私有结构体来追踪共享的数据:<br /> struct Data {    QBasicAtomicInt ref; // 引用计数器,对其的操作是原子的    int alloc; // 已分配的空间大小    int size; // 数据的实际大小

Seanyxie Blog 4908

别再到处找模板了!手把手教你用Vue3+Element Plus搭建一个‘能跑起来’的管理后台原型

本文详细介绍了如何利用Vue3和Element Plus在72小时内快速搭建一个功能完整的管理后台原型。从环境准备到项目获取、依赖安装、原型定制、数据模拟以及构建优化,手把手教你打造一个可直接演示的后台系统,特别适合创业者快速验证产品逻辑。

weixin_26854555的博客 182

一个响应ping包延迟偏大的问题

前段时间客户反馈一个使用pc上的命令行ping无线设备(WiFi4)时,设备平均响应时间(测试时长2小时,1秒Ping一次)相比其它机型偏慢的问题,并上传上具体的时间的Log.从log中看到,大多数包的响应还是比较快的,只是个别响应时间较长,且响应时间长的包时间几乎都是一样的100多ms.如下是部分响应时间截图. 整个2小时的测试时间里,总是时不时的出这么上102,104ms的响应时间,这直接导致了平均响应时间被拉大.于是本地测试,也发现了类似的延迟.本地测试时,现象略有...

流风回雪的博客 1万+

QSharedPointer<QByteArray> 入门

线程安全的引用计数智能指针自动管理所指向对象的内存释放当最后一个引用被销毁时自动删除目标对象cpp// 使用自定义删除器qDebug() << "删除数据";delete obj;});优先使用::create()而非new直接构造多线程传递时保持数据不可变大型数据优先使用共享指针而非传值及时释放不再需要的引用配合信号槽实现线程安全通信。

深之JohnChen的专栏 819

FPGA控制DDR读写(AXI4总线接口)

FPGA控制DDR读写

qq_42712308的博客 1万+

详解QString与QByteArray使用对比

是Qt库中两种不同的字符串/字节序列容器,各自服务于特定的应用场景。本篇文章将详细解析它们的异同,帮助您在实际编程中准确选择和有效地使用这两种类型。更适合存储二进制数据和简单文本,或是需要轻量级字符串处理的场合。

简介 2899

双屏DPI缩放问题全解析:从原理到实战解决窗口大小突变

DPI(每英寸点数)缩放是现代操作系统在多显示器环境下实现界面适配的核心技术。其原理是通过虚拟分辨率映射,在不同物理像素密度的屏幕上维持一致的视觉尺寸。这项技术的价值在于解决了高分辨率屏幕显示内容过小的问题,保障了跨屏工作流的视觉连续性。然而,在混合DPI环境(如4K屏与1080p屏组合)中,显卡驱动逻辑与应用程序DPI感知的协同问题,常导致窗口跨屏时出现剧烈缩放、尺寸错乱。这直接影响多屏办公、设计协作等需要频繁拖拽窗口的应用场景。本文聚焦于**显卡驱动**配置与**应用程序兼容性**设置,提供一套从系统底

weixin_34364071的博客 386

【SpringBoot 3.x 第51节】带你玩转 GraalVM Native Image:启动快到飞起,内存省到心疼,你还不试试?

🏆本文收录于《滚雪球学SpringBoot 3》,专门攻坚指数提升,本年度国内最系统+最专业+最详细(永久更新)。    本专栏致力打造最硬核 SpringBoot3 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。 如果想快速定位学习,可以看这篇【SpringBoot3教程导航帖】,你想学习的都被收集在内,快速投入学习!!两不误。    若还想学习更多,可直接前往《滚雪球学SpringBoot(全版本合集)》,涵盖SpringBoot所有版本教学文章

**My Coding Family** 1304

云通讯服务商有哪些?

网易云信于2015年成立,为网易集团下属的内资公司,总部位于杭州。音视频通信 RTC(Real-Time Communication)是阿里云覆盖全球的实时音视频开发平台,依托核心音视频编解码、信道传输、网络调度技术,提供高可用、高品质、超低延时的音视频通信服务,让用户快速搭建多端实时应用,适用于在线教育、视频会议、互动娱乐、音视频社交等场景。从个人收集的资料来看,国内云通讯服务商有ZOGO即构科技、融云、声网Agora、容联云、网易云信、腾讯云、阿里云、极光AURORA、梦网科技、菊风云等厂商。

weixin_57727992的博客 1118

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

FFmpeg4入门09:软解并使用QOpenGL播放视频(YUV420P)

我在开发过程中Qt用的比较大,所以本系列主要界面由Qt开发。而Qt主要的特性是跨平台,在嵌入式平台中,主要使用QML进行界面开发,如果使用QML开发视频的话,就需要用到OpenGL了。本篇主要介绍常用的桌面版的QOpenGL的视频显示,桌面版解码的YUV数据格式为YUV420P,下一篇介绍QML版的视频显示,也是YUV420P格式的。解码流程图为:解码函数调用流程图为:显示流程为:解码显示流程:共分为三个部分。这个就比较简单,前面几篇文章一直在说这个,FFmpeg默认软解输出格式为YUV420P。这里需要注

qq_26056015的博客 1810

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

利用 QWidget 进行声网SDK渲染

场景:qt quick 布局很灵活,想叠加窗口也很简单,但是想渲染视频 必须借助opengl 。可是部分音视频厂商提供的sdk 在自渲染层面 性能会有所下降,这就不得不选择传统的 QWidget 进行sdk渲染。 一,效果 二,QWidget 布局分为两种。 (1),运用layout ,自动布局 (2),利用setGeometry(x,y,width,height); 手动计算位置,利用lower() 或者raise() 来调整层级 三,单窗口代码 参考:https://b...

qthello 594

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

QML用openGL渲染NV12

前言 把之前写在windows的播放器移植到了我的安卓手机,就碰到了QOpenGLWidget需要替换的问题。 知乎、优快云一搜就有渲染YUV420P的代码,git也有。没打招呼不放链接了。 用的是fbo的那个。主要记录下参数和问题。 一、QOpenGLTexture改动 NV12的UV数据是在一起的,所以texture只要创建两个,一个Y,一个UV。 格式套用RGB,RED放Y,GREEN和BLUE放UV: ... mTexY->setFormat(QOpenGLTexture::R8_UN

qq_20408527的博客 1671

Qt QPointer 和 QSharedPointer 使用实例

    在网上查到很多关于Qt 中 QPointer 和 QSharedPointer, 大部分是官方的例子,这里给个实际在对象中使用。 首先介绍一下 QPointer 和 QSharedPointer的简单用法。 QPointer QPointer can only point to QObject instances. It will be automatically set to ...

luodanzhi的专栏 2万+

声网的视频直播与屏幕共享

视频直播 以下是操作步骤 1. 获取当前设备的 麦克风 摄像头 扬声器 等数据 2. 创建直播实例 3. 给当前实例绑定事件 4. 初始化频道 5. 加入频道 6. 创建本地流 7. 初始化本地流 8. 发布本地流 重中之中 引入声网的SDK,会用到jquery 也先引入 import AgoraRTC from 'agora-rtc-sdk' import $ from 'jquery' 获取当前设备的 麦克风 摄像头 扬声器 等数据 --(AgoraRTC.getDevices) Agor

kangluon的博客 2988

qt 使用opengl显示yuv实时视频流

我使用的是ffmpeg拉取实时流,并解码为yuv420p。然后用opengl转成rgb32渲染到qt中显示。用ffmpeg解码实时流和网上一样。参考:https://blog.youkuaiyun.com/su_vast/article/details/52214642进行修改和增加。 av_register_all(); avformat_network_init(); AV...

wanghualin033的博客 2万+

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

通过shader完成显示yuv

weixin_42427696的博客 2943
上一篇: C++面试题整理
下一篇: Qt 集成miniblink浏览器库之1编译使用
很菜但努力
博客等级 码龄17年 113粉丝 50原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值