在Qt中渲染OpenGL场景并将其传输到HTML5界面

ixM*_*ixM 6 opengl html5 qt

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

我一直试图找到有关它的信息以及如何做到但我没有成功......

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

dte*_*ech 7

这是完全可以实现的,但取决于你愿意扩展"实时"的概念.但是Qt将无法提供很多帮助.

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

  • 2 - 使用FFmpeg将输入QImage数据编码为视频(最好是H264),以便最大限度地减少带宽.您可能想要查看这个包装器,该包装器应该与Qt开箱即用.FFmpeg也可以帮助实际流式传输,无需为此使用额外的库,虽然我不确定该流是否可以在HTML播放器中使用而不使用"中继"服务器来重新流式传输.

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

  • @static_rtti IMO代码渲染一个简单的场景并将其保存到磁盘没有多大帮助,但它是你的代表,如果你认为它可以帮助你这是个好消息;)同时,你可以很容易`view.grabWindow( ).save(路径)`没有那段代码,无论场景的复杂程度如何,我只能假设他去了'你好gl`教程,因为他真的很想得到赏金,我的意图只是提供指导,不要模拟12小时的回答工作;) (2认同)

kar*_*lip 7

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

  • 将视频流式传输到HTML5仍然存在问题.我建议使用 Ogg进行编码,因为它比现代浏览器更好地支持h264;
  • 如果没有第三方库帮助您,对视频进行编码并将其流式传输到HTTP是一项相当大的挑战.仔细看看GStreamer(一个用于处理多媒体文件的跨平台库).这就是我在这里用来编码和流式传输来自OpenGL的帧缓冲区的帧;

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

首先从帧缓冲区捕获帧.有不同的方法可以用于此目的,并为谷歌搜索的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;
}
Run Code Online (Sandbox Code Playgroud)

A QImage用于将原始GL_BGRA帧转换为jpg文件.该draw_scene()方法只是绘制一个透明的彩色立方体:

下一步是对帧进行编码并通过HTTP对其进行流式处理.但是,您可能不希望必须将每个帧从帧缓冲区保存到磁盘才能进行流式传输.而你是对的,你不必!GStreamer提供了一个C API,您可以在应用程序中使用它来执行gst-launch(由下面介绍)完成的操作.甚至还有一个名为QtGstreamer的Qt包装器,使事情变得更加容易.

GStreamer 1.0提供了一个cmd-line应用程序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
Run Code Online (Sandbox Code Playgroud)

第三步也是最后一步是打开一个精心设计的HTML5页面来显示流.必须在gst-launch运行时执行此步骤,因此请将下面的代码复制并粘贴到文件中,然后在浏览器中打开该页面(我在Chrome上对此进行了测试).该页面连接到localhost,端口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>
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

我只想弄清楚GStreamer在流式传输之前如何将原始BGRA帧转换为jpg(或其他格式).

更新:

问题解决了!可以将原始BGRA帧编码为jpg*Ogg并直接对其进行流式传输,而无需在磁盘上创建中间文件.我冒昧地将FPS限制设置为15,并将标准质量降低了theoraenc50%:

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
Run Code Online (Sandbox Code Playgroud)

这个管道上有一些你真的不需要的操作.不过,您可以采取的一些优化带宽的方法是将帧缩放到较小的尺寸(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
Run Code Online (Sandbox Code Playgroud)


Mal*_*glu 4

嗯,OTOY也做了类似的事情......

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

这里是...