此答案解释了如何使用 OpenGL、Qt 和 GStreamer 完成此任务。但在我开始之前,有两个问题需要立即解决:
实现这样的事情的路线图是什么样的?
首先从帧缓冲区捕获帧。有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