【问题标题】:Render OpenGL scene in Qt and stream it to HTML5 interface在 Qt 中渲染 OpenGL 场景并将其流式传输到 HTML5 界面
【发布时间】:2014-07-18 17:31:04
【问题描述】:

我想知道是否可以在 Qt 中渲染 OpenGL 场景并将其实时流式传输到 HTML5 界面(我的意思是现场生成场景)。

我一直在努力寻找有关这方面的信息以及如何做到这一点,但我没有成功...

如果存在,是否有任何现有机制来压缩图像并优化带宽使用。我正在考虑像 Citrix 这样的解决方案,但要使用 HTML5 客户端。

【问题讨论】:

标签: html qt opengl


【解决方案1】:

此答案解释了如何使用 OpenGL、Qt 和 GStreamer 完成此任务。但在我开始之前,有两个问题需要立即解决:

  • Streaming video to HTML5 is still problematic。我建议使用 Ogg 进行编码,因为它比 h264 更受现代浏览器的支持;
  • 对视频进行编码并将其流式传输到 HTTP 在没有第三方库来帮助您的情况下是一个相当大的挑战。好好看看GStreamer(一个用于处理多媒体文件的跨平台库)。我在这里使用它来编码和流式传输来自 OpenGL 的帧缓冲区的帧;

实现这样的事情的路线图是什么样的?

首先从帧缓冲区捕获帧。有different methods 可用于this purpose,谷歌搜索opengl 屏幕外渲染 将返回几个有趣的帖子和文档。由于该主题已被广泛介绍,因此我不会深入讨论技术细节,但出于教育目的,我将分享以下代码以演示如何检索帧并将其保存为磁盘上的 jpg

// GLWidget is a class based on QGLWidget.
void GLWidget::paintGL()
{
    /* Setup FBO and RBO */

    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fb);

    glGenFramebuffersEXT(1, &_fb);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fb);

    glGenRenderbuffersEXT(1, &_color_rb);
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, _color_rb);

    GLint viewport[4];
    glGetIntegerv(GL_VIEWPORT, viewport);
    glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_BGRA, viewport[2], viewport[3]);
    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, _color_rb);

    /* Draw the scene (with transparency) */

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glLoadIdentity();
    glTranslatef(-2.0f, 0.0f, -7.0f);
    glRotatef(45, 1.0f, 1.0f, 0.0f);
    _draw_cube();

    glLoadIdentity();
    glTranslatef(2.0f, 0.0f, -7.0f);
    glRotatef(30, 0.5f, 1.0f, 0.5f);
    _draw_cube();

    glFlush();

    /* Retrieve pixels from the framebuffer */

    int imgsize = viewport[2] * viewport[3];
    std::cout << "* Viewport size: " << viewport[2] << "x" << viewport[3] << std::endl;

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glReadBuffer(GL_COLOR_ATTACHMENT0);

    unsigned char* pixels = new unsigned char[sizeof(unsigned char) * imgsize * 4];
    glReadPixels(0, 0, viewport[2], viewport[3], GL_BGRA, GL_UNSIGNED_BYTE, pixels);

    // Use fwrite to dump data:
    FILE* fp = fopen("dumped.bin","w");
    fwrite(pixels, sizeof(unsigned char) * imgsize * 4, 1, fp);
    fclose(fp);

    // or use QImage to encode the raw data to jpg:
    QImage image((const unsigned char*)pixels, viewport[2], viewport[3], QImage::Format_RGB32);
    QImage flipped = image.mirrored();
    flipped.save("output2.jpg");

    // Disable FBO and RBO
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

    // Delete resources
    glDeleteRenderbuffersEXT(1, &_color_rb);
    glDeleteFramebuffersEXT(1, &_fb);
    delete[] pixels;
}

QImage 用于将原始 GL_BGRA 帧转换为 jpg 文件。 draw_scene() 方法只是简单地绘制一个具有透明度的彩色立方体:

下一步是对帧进行编码并通过 HTTP 进行流式传输。但是,您可能不想在能够流式传输之前将帧缓冲区中的每一帧都保存到磁盘。你是对的,你不必这样做! GStreamer 提供了一个C API,您可以在应用程序中使用它来执行gst-launch(下面介绍)完成的操作。这个库甚至还有一个名为 QtGstreamer 的 Qt 包装器,让事情变得更容易。

GStreamer 1.0 提供了一个名为gst-launch-1.0 的命令行应用程序,可用于在开始编码之前测试其功能。开发人员通常使用它来组装一条指令管道,在开始编码之前让魔法发生。

以下命令显示了如何使用它来解码 jpg、将其编码为 Ogg theora 并将单个图像以 HTML5 页面可以播放的方式流式传输到 HTTP:

gst-launch-1.0.exe -v filesrc location=output.jpg ! decodebin ! imagefreeze ! clockoverlay shaded-background=true font-desc="Sans 38" ! theoraenc ! oggmux ! tcpserversink host=127.0.0.1 port=8080

第三步也是最后一步是打开一个用于显示流的 HTML5 页面。此步骤必须在 gst-launch 运行时执行,因此将下面的代码复制并粘贴到文件中,然后在浏览器中打开该页面(我在 Chrome 上测试过)。该页面连接到本地主机,端口 8080 并开始接收流。您可能已经注意到gst-launch 管道在原始图像上覆盖了一个时钟:

<html>
    <title>A simple HTML5 video test</title>
</html>
<body> 
    <video autoplay controls width=320 height=240>    
    <source src="http://localhost:8080" type="video/ogg">
       You browser doesn't support element <code>video</code>.
    </video>
</body>

I'm just trying to figure out exactly how GStreamer can convert a raw BGRA frame to jpg (or other formats) 在流式传输之前。

更新:

问题解决了!可以encode a raw BGRA frame to jpg*Ogg 直接流式传输,而无需在磁盘上创建中间文件。我冒昧地将 FPS 限制设置为 15,并将 theoraenc 的标准质量降低了 50%:

gst-launch-1.0.exe -v filesrc location=dumped.bin blocksize=1920000 ! video/x-raw,format=BGRA,width=800,height=600,framerate=1/1 ! videoconvert ! video/x-raw,format=RGB,framerate=1/1 ! videoflip method=vertical-flip ! imagefreeze ! videorate ! video/x-raw,format=RGB,framerate=30/2 ! videoconvert ! clockoverlay shaded-background=true font-desc="Sans 38" ! theoraenc quality=24 ! oggmux ! queue ! tcpserversink host=127.0.0.1 port=8080 sync-method=2

此管道上有一些您并不真正需要的操作。不过,您可以采取一些措施来优化带宽,例如将帧缩放到更小的尺寸 (400x300)、为 FPS 设置下限、降低编码帧的质量等等:

gst-launch-1.0.exe -v filesrc location=dumped.bin blocksize=1920000 ! video/x-raw,format=BGRA,width=800,height=600,framerate=1/1 ! videoconvert ! video/x-raw,format=RGB,framerate=1/1 ! videoflip method=vertical-flip ! videoscale ! video/x-raw,width=400,height=300! imagefreeze ! videorate ! video/x-raw,format=RGB,framerate=30/2 ! videoconvert ! clockoverlay shaded-background=true font-desc="Sans 38" ! theoraenc quality=24 ! oggmux ! tcpserversink host=127.0.0.1 port=8080 sync-method=2

【讨论】:

  • 这种“保存到磁盘,从命令行为每个图像调用 gstreamer”的方法究竟是如何实现低延迟的呢?您应该链接 gstreamer 并使用其内部 API 直接编码和流式传输。
  • 我完全不建议这样做,答案清楚地说明了这一点。我刚刚更新了它以显示一个 gst-launch-1.0 管道,它显示了如何克服这个问题。
【解决方案2】:

这是完全可以实现的,但取决于您愿意扩展“实时”概念的程度。但 Qt 帮不了多少。

  • 1 - 从 GPU 内存中获取图像。这几乎是 Qt 可能提供帮助的唯一地方。它提供了两种“开箱即用”的方法,可以对您有所帮助。第一个是如果您已将 OpenGL 渲染合并到 QQuickView 或派生类中的元素中,那么您可以使用 grabWindow() 从帧缓冲区中获取 QImage。第二种是使用QScreen 类,它提供了类似的方法,但它可能比第一种方法更慢。在我的系统(相当高端)上,对于 720p 的分辨率,从 GPU 内存中获取原始图像大约需要 30 毫秒,对于较低的分辨率,它会以二次速率更快。如果您精通 OpenGL,则可能需要研究供应商特定的扩展,这些扩展在将每个渲染帧从 GPU 复制到 CPU 内存时可能会提供更少的开销,这就是 Sony 或 nVidia 等公司能够实现更好的图形流的方式。

  • 2 - 使用 FFmpeg 将传入的 QImage 数据编码为视频(最好是 H264)以最小化带宽。您可能想查看这个wrapper,它应该可以与 Qt 一起使用。 FFmpeg 还可以帮助处理实际的streaming,无需为此使用额外的库,尽管我不确定在不使用“中继”服务器重新流式传输的情况下,该流是否可以在 HTML 播放器中使用。

但你不应该期待奇迹。图形流在供应商自己的设备上使用他们的专有技术和快速的本地网络已经足够糟糕了。在现实世界的场景中,为延迟为半秒或更高的“实时”做好准备。当然,最近一直在为这种毫无意义的努力付出一些努力,但与许多其他人一样,它只是为了做这件事而做,而不是因为这样做有实际的好处。如果您有一个 10 gbit 网络并且具有可以直接使用它的特殊 GPU 硬件,则流式图形可能是可行的解决方案,但是该解决方案将是昂贵且低效的,看看今天消耗 2-3 瓦功率的 10 美元芯片如何能够渲染 OpenGL,这将永远是最可取的解决方案。既然你提到了 HTML5 浏览器,你很有可能会选择一个 WebGL 解决方案,IMO 将优于流式传输图形,就像 WebGL 在这一点上一样糟糕。更好的是,Qt 已经支持大量的平台,您可以轻松实现自己的渲染应用程序,并获得比 WebGL 更好的性能,以及可能更多的渲染功能。

【讨论】:

  • 非常感谢您的回答。我不得不将赏金奖励给@karlphillip,因为我认为他投入了最多的工作,但我希望我可以拆分它,因为你提出了一些优秀的观点。
  • @static_rtti IMO 渲染琐碎场景并将其保存到磁盘的代码并没有多大帮助,但它是您的代表,如果您认为它对您有帮助,那是个好消息;)同时,您可以就像view.grabWindow().save(path) 没有那堵代码墙一样容易,而且不管场景的复杂性如何,我只能假设他通过了hello gl 教程,因为他真的很想得到赏金,我的意图只是提供指导,不要模拟 12 小时的回答工作;)
【解决方案3】:

好吧,OTOY has done a similar thing...

我记得一个更简单但有效的开源项目,但我找不到链接。在这个项目中,视频捕获(或在您的情况下是窗口缓冲区)被编码为 MPEG 并通过 WebSocket 连接发送到浏览器。然后客户端 Javascript 解码这个 MPEG 流并显示它。这可能会为您提供有关此主题的更多信息...

Here it is...

【讨论】:

  • 谢谢!这看起来确实很有希望,但乍一看,它似乎还不可用。此外,我也很想知道如何使流式提要可用于 Web 应用程序。我在这个领域的知识为零,所以答案可能很明显(在这种情况下,您能否提供一个链接来描述它是如何完成的?)
【解决方案4】:

也许您可以使用带有出色Socket.IO 层的 WebSockets,以获得最大的跨浏览器兼容性,以便在您的 Qt 应用程序和 HTML5 客户端之间传输您的数据。

您必须在 Qt 应用程序中以某种方式对渲染的图片进行编码,然后通过套接字将其发送到 HTML5 客户端,该客户端将对其进行解码和渲染。

Socket.IO 在他们的网站上有一个很好的演示。 检查它here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-25
    • 1970-01-01
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-06
    • 1970-01-01
    相关资源
    最近更新 更多