[Mesa-dev] [PATCH kmscube 2/3] add video cube

Rob Clark robdclark at gmail.com
Sat Mar 25 20:11:45 UTC 2017


Uses gstreamer for a simple decoder.  If decoder can give us dma-buf's
directly, we'll directly use that as a texture (zero copy), otherwise
memcpy into a buffer from gbm.  This should work with both hw and sw
decoders.

Probably room for improvement.  And the interface between gl and the
decoder is pretty simple so I suppose other decoders would be possible.
(But hopefully they could already be supported via gstreamer.)

Signed-off-by: Rob Clark <robdclark at gmail.com>
---
 Makefile.am   |   6 +
 common.h      |  20 +++
 configure.ac  |   9 ++
 cube-tex.c    |   5 +-
 cube-video.c  | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gst-decoder.c | 340 +++++++++++++++++++++++++++++++++++++++++++++++++++
 kmscube.c     |  20 ++-
 7 files changed, 782 insertions(+), 4 deletions(-)
 create mode 100644 cube-video.c
 create mode 100644 gst-decoder.c

diff --git a/Makefile.am b/Makefile.am
index 270b760..a36087d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -53,3 +53,9 @@ kmscube_SOURCES = \
 	frame-512x512-NV12.c \
 	frame-512x512-RGBA.c \
 	kmscube.c
+
+if ENABLE_GST
+kmscube_LDADD += $(GST_LIBS)
+kmscube_CFLAGS += $(GST_CFLAGS)
+kmscube_SOURCES += cube-video.c gst-decoder.c
+endif
diff --git a/common.h b/common.h
index df07b61..ba7bcd1 100644
--- a/common.h
+++ b/common.h
@@ -24,6 +24,10 @@
 #ifndef _COMMON_H
 #define _COMMON_H
 
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 #include <EGL/egl.h>
@@ -85,9 +89,25 @@ enum mode {
 	RGBA,          /* single-plane RGBA */
 	NV12_2IMG,     /* NV12, handled as two textures and converted to RGB in shader */
 	NV12_1IMG,     /* NV12, imported as planar YUV eglimg */
+	VIDEO,         /* video textured cube */
 };
 
 const struct egl * init_cube_smooth(const struct gbm *gbm);
 const struct egl * init_cube_tex(const struct gbm *gbm, enum mode mode);
+#ifdef HAVE_GST
+struct decoder;
+struct decoder * video_init(const struct egl *egl, const struct gbm *gbm, const char *filename);
+EGLImage video_frame(struct decoder *dec);
+void video_deinit(struct decoder *dec);
+const struct egl * init_cube_video(const struct gbm *gbm, const char *video);
+#else
+static inline const struct egl *
+init_cube_video(const struct gbm *gbm, const char *video)
+{
+	(void)gbm; (void)video;
+	printf("no GStreamer support!\n");
+	return NULL;
+}
+#endif
 
 #endif /* _COMMON_H */
diff --git a/configure.ac b/configure.ac
index 4df788c..242164f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -40,5 +40,14 @@ PKG_CHECK_MODULES(GBM, gbm)
 PKG_CHECK_MODULES(EGL, egl)
 PKG_CHECK_MODULES(GLES2, glesv2)
 
+# Check for gst and enable cube-video conditionally:
+PKG_CHECK_MODULES(GST, gstreamer-1.0 gstreamer-plugins-base-1.0 gstreamer-app-1.0 gstreamer-allocators-1.0 gstreamer-video-1.0 glib-2.0,
+		 [HAVE_GST=yes], [HAVE_GST=no])
+if test "x$HAVE_GST" = "xyes"; then
+	AC_DEFINE(HAVE_GST, 1, [Have GStreamer support])
+	AC_MSG_NOTICE([Building cube-video support])
+fi
+AM_CONDITIONAL(ENABLE_GST, [test "x$HAVE_GST" = "xyes"])
+
 AC_CONFIG_FILES([Makefile])
 AC_OUTPUT
diff --git a/cube-tex.c b/cube-tex.c
index 0caeaea..0c3c8e3 100644
--- a/cube-tex.c
+++ b/cube-tex.c
@@ -46,7 +46,7 @@ struct {
 	GLuint tex[2];
 } gl;
 
-const struct egl *egl = &gl.egl;
+static const struct egl *egl = &gl.egl;
 
 static const GLfloat vVertices[] = {
 		// front
@@ -81,7 +81,7 @@ static const GLfloat vVertices[] = {
 		+1.0f, -1.0f, +1.0f,
 };
 
-GLfloat vTexCoords[] = {
+static GLfloat vTexCoords[] = {
 		//front
 		1.0f, 1.0f,
 		0.0f, 1.0f,
@@ -443,6 +443,7 @@ static int init_tex(enum mode mode)
 	case NV12_1IMG:
 		return init_tex_nv12_1img();
 	case SMOOTH:
+	case VIDEO:
 		assert(!"unreachable");
 		return -1;
 	}
diff --git a/cube-video.c b/cube-video.c
new file mode 100644
index 0000000..cbea8ec
--- /dev/null
+++ b/cube-video.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (c) 2017 Rob Clark <rclark at redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sub license,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#define _GNU_SOURCE
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+#include "esUtil.h"
+
+struct {
+	struct egl egl;
+
+	GLfloat aspect;
+	const struct gbm *gbm;
+
+	GLuint program, blit_program;
+	/* uniform handles: */
+	GLint modelviewmatrix, modelviewprojectionmatrix, normalmatrix;
+	GLint texture, blit_texture;
+	GLuint vbo;
+	GLuint positionsoffset, texcoordsoffset, normalsoffset;
+	GLuint tex;
+
+	/* video decoder: */
+	struct decoder *decoder;
+	int filenames_count, idx;
+	const char *filenames[32];
+} gl;
+
+static const struct egl *egl = &gl.egl;
+
+static const GLfloat vVertices[] = {
+		// front
+		-1.0f, -1.0f, +1.0f,
+		+1.0f, -1.0f, +1.0f,
+		-1.0f, +1.0f, +1.0f,
+		+1.0f, +1.0f, +1.0f,
+		// back
+		+1.0f, -1.0f, -1.0f,
+		-1.0f, -1.0f, -1.0f,
+		+1.0f, +1.0f, -1.0f,
+		-1.0f, +1.0f, -1.0f,
+		// right
+		+1.0f, -1.0f, +1.0f,
+		+1.0f, -1.0f, -1.0f,
+		+1.0f, +1.0f, +1.0f,
+		+1.0f, +1.0f, -1.0f,
+		// left
+		-1.0f, -1.0f, -1.0f,
+		-1.0f, -1.0f, +1.0f,
+		-1.0f, +1.0f, -1.0f,
+		-1.0f, +1.0f, +1.0f,
+		// top
+		-1.0f, +1.0f, +1.0f,
+		+1.0f, +1.0f, +1.0f,
+		-1.0f, +1.0f, -1.0f,
+		+1.0f, +1.0f, -1.0f,
+		// bottom
+		-1.0f, -1.0f, -1.0f,
+		+1.0f, -1.0f, -1.0f,
+		-1.0f, -1.0f, +1.0f,
+		+1.0f, -1.0f, +1.0f,
+};
+
+static GLfloat vTexCoords[] = {
+		//front
+		0.0f, 1.0f,
+		1.0f, 1.0f,
+		0.0f, 0.0f,
+		1.0f, 0.0f,
+		//back
+		0.0f, 1.0f,
+		1.0f, 1.0f,
+		0.0f, 0.0f,
+		1.0f, 0.0f,
+		//right
+		0.0f, 1.0f,
+		1.0f, 1.0f,
+		0.0f, 0.0f,
+		1.0f, 0.0f,
+		//left
+		0.0f, 1.0f,
+		1.0f, 1.0f,
+		0.0f, 0.0f,
+		1.0f, 0.0f,
+		//top
+		0.0f, 1.0f,
+		1.0f, 1.0f,
+		0.0f, 0.0f,
+		1.0f, 0.0f,
+		//bottom
+		0.0f, 1.0f,
+		1.0f, 1.0f,
+		0.0f, 0.0f,
+		1.0f, 0.0f,
+};
+
+static const GLfloat vNormals[] = {
+		// front
+		+0.0f, +0.0f, +1.0f, // forward
+		+0.0f, +0.0f, +1.0f, // forward
+		+0.0f, +0.0f, +1.0f, // forward
+		+0.0f, +0.0f, +1.0f, // forward
+		// back
+		+0.0f, +0.0f, -1.0f, // backward
+		+0.0f, +0.0f, -1.0f, // backward
+		+0.0f, +0.0f, -1.0f, // backward
+		+0.0f, +0.0f, -1.0f, // backward
+		// right
+		+1.0f, +0.0f, +0.0f, // right
+		+1.0f, +0.0f, +0.0f, // right
+		+1.0f, +0.0f, +0.0f, // right
+		+1.0f, +0.0f, +0.0f, // right
+		// left
+		-1.0f, +0.0f, +0.0f, // left
+		-1.0f, +0.0f, +0.0f, // left
+		-1.0f, +0.0f, +0.0f, // left
+		-1.0f, +0.0f, +0.0f, // left
+		// top
+		+0.0f, +1.0f, +0.0f, // up
+		+0.0f, +1.0f, +0.0f, // up
+		+0.0f, +1.0f, +0.0f, // up
+		+0.0f, +1.0f, +0.0f, // up
+		// bottom
+		+0.0f, -1.0f, +0.0f, // down
+		+0.0f, -1.0f, +0.0f, // down
+		+0.0f, -1.0f, +0.0f, // down
+		+0.0f, -1.0f, +0.0f  // down
+};
+
+static const char *blit_vs =
+		"attribute vec4 in_position;        \n"
+		"attribute vec2 in_TexCoord;        \n"
+		"                                   \n"
+		"varying vec2 vTexCoord;            \n"
+		"                                   \n"
+		"void main()                        \n"
+		"{                                  \n"
+		"    gl_Position = in_position;     \n"
+		"    vTexCoord = in_TexCoord;       \n"
+		"}                                  \n";
+
+static const char *blit_fs =
+		"#extension GL_OES_EGL_image_external : enable\n"
+		"precision mediump float;           \n"
+		"                                   \n"
+		"uniform samplerExternalOES uTex;   \n"
+		"                                   \n"
+		"varying vec2 vTexCoord;            \n"
+		"                                   \n"
+		"void main()                        \n"
+		"{                                  \n"
+		"    gl_FragColor = texture2D(uTex, vTexCoord);\n"
+		"}                                  \n";
+
+static const char *vertex_shader_source =
+		"uniform mat4 modelviewMatrix;      \n"
+		"uniform mat4 modelviewprojectionMatrix;\n"
+		"uniform mat3 normalMatrix;         \n"
+		"                                   \n"
+		"attribute vec4 in_position;        \n"
+		"attribute vec2 in_TexCoord;        \n"
+		"attribute vec3 in_normal;          \n"
+		"                                   \n"
+		"vec4 lightSource = vec4(2.0, 2.0, 20.0, 0.0);\n"
+		"                                   \n"
+		"varying vec4 vVaryingColor;        \n"
+		"varying vec2 vTexCoord;            \n"
+		"                                   \n"
+		"void main()                        \n"
+		"{                                  \n"
+		"    gl_Position = modelviewprojectionMatrix * in_position;\n"
+		"    vec3 vEyeNormal = normalMatrix * in_normal;\n"
+		"    vec4 vPosition4 = modelviewMatrix * in_position;\n"
+		"    vec3 vPosition3 = vPosition4.xyz / vPosition4.w;\n"
+		"    vec3 vLightDir = normalize(lightSource.xyz - vPosition3);\n"
+		"    float diff = max(0.0, dot(vEyeNormal, vLightDir));\n"
+		"    vVaryingColor = vec4(diff * vec3(1.0, 1.0, 1.0), 1.0);\n"
+		"    vTexCoord = in_TexCoord; \n"
+		"}                            \n";
+
+static const char *fragment_shader_source =
+		"#extension GL_OES_EGL_image_external : enable\n"
+		"precision mediump float;           \n"
+		"                                   \n"
+		"uniform samplerExternalOES uTex;   \n"
+		"                                   \n"
+		"varying vec4 vVaryingColor;        \n"
+		"varying vec2 vTexCoord;            \n"
+		"                                   \n"
+		"void main()                        \n"
+		"{                                  \n"
+		"    gl_FragColor = vVaryingColor * texture2D(uTex, vTexCoord);\n"
+		"}                                  \n";
+
+
+static void draw_cube_video(unsigned i)
+{
+	ESMatrix modelview;
+	EGLImage frame;
+
+	frame = video_frame(gl.decoder);
+	if (!frame) {
+		/* end of stream */
+		glDeleteTextures(1, &gl.tex);
+		glGenTextures(1, &gl.tex);
+		video_deinit(gl.decoder);
+		gl.idx = (gl.idx + 1) % gl.filenames_count;
+		gl.decoder = video_init(&gl.egl, gl.gbm, gl.filenames[gl.idx]);
+	}
+
+	glUseProgram(gl.blit_program);
+
+	glActiveTexture(GL_TEXTURE0);
+	glBindTexture(GL_TEXTURE_EXTERNAL_OES, gl.tex);
+	glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+	glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	egl->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, frame);
+
+	/* clear the color buffer */
+	glClearColor(0.5, 0.5, 0.5, 1.0);
+	glClear(GL_COLOR_BUFFER_BIT);
+
+	glUseProgram(gl.blit_program);
+	glUniform1i(gl.blit_texture, 0); /* '0' refers to texture unit 0. */
+	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+	glUseProgram(gl.program);
+
+	esMatrixLoadIdentity(&modelview);
+	esTranslate(&modelview, 0.0f, 0.0f, -8.0f);
+	esRotate(&modelview, 45.0f + (0.25f * i), 1.0f, 0.0f, 0.0f);
+	esRotate(&modelview, 45.0f - (0.5f * i), 0.0f, 1.0f, 0.0f);
+	esRotate(&modelview, 10.0f + (0.15f * i), 0.0f, 0.0f, 1.0f);
+
+	ESMatrix projection;
+	esMatrixLoadIdentity(&projection);
+	esFrustum(&projection, -2.1f, +2.1f, -2.1f * gl.aspect, +2.1f * gl.aspect, 6.0f, 10.0f);
+
+	ESMatrix modelviewprojection;
+	esMatrixLoadIdentity(&modelviewprojection);
+	esMatrixMultiply(&modelviewprojection, &modelview, &projection);
+
+	float normal[9];
+	normal[0] = modelview.m[0][0];
+	normal[1] = modelview.m[0][1];
+	normal[2] = modelview.m[0][2];
+	normal[3] = modelview.m[1][0];
+	normal[4] = modelview.m[1][1];
+	normal[5] = modelview.m[1][2];
+	normal[6] = modelview.m[2][0];
+	normal[7] = modelview.m[2][1];
+	normal[8] = modelview.m[2][2];
+
+	glUniformMatrix4fv(gl.modelviewmatrix, 1, GL_FALSE, &modelview.m[0][0]);
+	glUniformMatrix4fv(gl.modelviewprojectionmatrix, 1, GL_FALSE, &modelviewprojection.m[0][0]);
+	glUniformMatrix3fv(gl.normalmatrix, 1, GL_FALSE, normal);
+	glUniform1i(gl.texture, 0); /* '0' refers to texture unit 0. */
+
+	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+	glDrawArrays(GL_TRIANGLE_STRIP, 4, 4);
+	glDrawArrays(GL_TRIANGLE_STRIP, 8, 4);
+	glDrawArrays(GL_TRIANGLE_STRIP, 12, 4);
+	glDrawArrays(GL_TRIANGLE_STRIP, 16, 4);
+	glDrawArrays(GL_TRIANGLE_STRIP, 20, 4);
+}
+
+const struct egl * init_cube_video(const struct gbm *gbm, const char *filenames)
+{
+	char *fnames, *s;
+	int ret, i = 0;
+
+	ret = init_egl(&gl.egl, gbm);
+	if (ret)
+		return NULL;
+
+	if (!gl.egl.eglCreateImageKHR) {
+		printf("no eglCreateImageKHR\n");
+		return NULL;
+	}
+
+	fnames = strdup(filenames);
+	while ((s = strstr(fnames, ","))) {
+		gl.filenames[i] = fnames;
+		s[0] = '\0';
+		fnames = &s[1];
+		i++;
+	}
+	gl.filenames[i] = fnames;
+	gl.filenames_count = ++i;
+
+	gl.decoder = video_init(&gl.egl, gbm, gl.filenames[gl.idx]);
+	if (!gl.decoder) {
+		printf("cannot create video decoder\n");
+		return NULL;
+	}
+
+	gl.aspect = (GLfloat)(gbm->height) / (GLfloat)(gbm->width);
+	gl.gbm = gbm;
+
+	ret = create_program(blit_vs, blit_fs);
+	if (ret < 0)
+		return NULL;
+
+	gl.blit_program = ret;
+
+	glBindAttribLocation(gl.blit_program, 0, "in_position");
+	glBindAttribLocation(gl.blit_program, 1, "in_TexCoord");
+
+	ret = link_program(gl.blit_program);
+	if (ret)
+		return NULL;
+
+	gl.blit_texture = glGetUniformLocation(gl.blit_program, "uTex");
+
+	ret = create_program(vertex_shader_source, fragment_shader_source);
+	if (ret < 0)
+		return NULL;
+
+	gl.program = ret;
+
+	glBindAttribLocation(gl.program, 0, "in_position");
+	glBindAttribLocation(gl.program, 1, "in_TexCoord");
+	glBindAttribLocation(gl.program, 2, "in_normal");
+
+	ret = link_program(gl.program);
+	if (ret)
+		return NULL;
+
+	gl.modelviewmatrix = glGetUniformLocation(gl.program, "modelviewMatrix");
+	gl.modelviewprojectionmatrix = glGetUniformLocation(gl.program, "modelviewprojectionMatrix");
+	gl.normalmatrix = glGetUniformLocation(gl.program, "normalMatrix");
+	gl.texture   = glGetUniformLocation(gl.program, "uTex");
+
+	glViewport(0, 0, gbm->width, gbm->height);
+	glEnable(GL_CULL_FACE);
+
+	gl.positionsoffset = 0;
+	gl.texcoordsoffset = sizeof(vVertices);
+	gl.normalsoffset = sizeof(vVertices) + sizeof(vTexCoords);
+
+	glGenBuffers(1, &gl.vbo);
+	glBindBuffer(GL_ARRAY_BUFFER, gl.vbo);
+	glBufferData(GL_ARRAY_BUFFER, sizeof(vVertices) + sizeof(vTexCoords) + sizeof(vNormals), 0, GL_STATIC_DRAW);
+	glBufferSubData(GL_ARRAY_BUFFER, gl.positionsoffset, sizeof(vVertices), &vVertices[0]);
+	glBufferSubData(GL_ARRAY_BUFFER, gl.texcoordsoffset, sizeof(vTexCoords), &vTexCoords[0]);
+	glBufferSubData(GL_ARRAY_BUFFER, gl.normalsoffset, sizeof(vNormals), &vNormals[0]);
+	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)(intptr_t)gl.positionsoffset);
+	glEnableVertexAttribArray(0);
+	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)(intptr_t)gl.texcoordsoffset);
+	glEnableVertexAttribArray(1);
+	glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)(intptr_t)gl.normalsoffset);
+	glEnableVertexAttribArray(2);
+
+	glGenTextures(1, &gl.tex);
+
+	gl.egl.draw = draw_cube_video;
+
+	return &gl.egl;
+}
diff --git a/gst-decoder.c b/gst-decoder.c
new file mode 100644
index 0000000..1140213
--- /dev/null
+++ b/gst-decoder.c
@@ -0,0 +1,340 @@
+/*
+ * Copyright (c) 2017 Rob Clark <rclark at redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sub license,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <assert.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+
+#include <drm_fourcc.h>
+
+#include <gst/gst.h>
+#include <gst/gstmemory.h>
+#include <gst/gstpad.h>
+#include <gst/allocators/gstdmabuf.h>
+#include <gst/app/gstappsink.h>
+#include <gst/video/gstvideometa.h>
+
+struct decoder {
+	GMainLoop          *loop;
+	GstElement         *pipeline;
+	GstElement         *sink;
+	pthread_t           gst_thread;
+
+	uint32_t            format;
+	GstVideoInfo        info;
+
+	const struct gbm   *gbm;
+	const struct egl   *egl;
+	unsigned            frame;
+
+	EGLImage            last_frame;
+	GstSample          *last_samp;
+};
+
+static GstPadProbeReturn
+pad_probe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
+{
+	struct decoder *dec = user_data;
+	GstQuery *query = GST_PAD_PROBE_INFO_QUERY(info);
+	gboolean need_pool;
+	GstCaps *caps;
+
+	(void)pad;
+
+	if (GST_QUERY_TYPE(query) != GST_QUERY_ALLOCATION)
+		return GST_PAD_PROBE_OK;
+
+	gst_query_parse_allocation(query, &caps, &need_pool);
+
+	if (!caps) {
+		GST_ERROR("allocation query without caps");
+		return GST_PAD_PROBE_OK;
+	}
+
+	if (!gst_video_info_from_caps(&dec->info, caps)) {
+		GST_ERROR("allocation query with invalid caps");
+		return GST_PAD_PROBE_OK;
+	}
+
+	switch (dec->info.finfo->format) {
+	case GST_VIDEO_FORMAT_I420:
+		dec->format = DRM_FORMAT_YUV420;
+		break;
+	case GST_VIDEO_FORMAT_NV12:
+		dec->format = DRM_FORMAT_NV12;
+		break;
+	default:
+		GST_ERROR("unknown format\n");
+		return GST_PAD_PROBE_OK;
+	}
+
+	GST_DEBUG("got: %ux%u@%4.4s\n", dec->info.width, dec->info.height,
+			(char *)&dec->format);
+
+	return GST_PAD_PROBE_OK;
+}
+
+static void *
+gst_thread_func(void *args)
+{
+	struct decoder *dec = args;
+	g_main_loop_run(dec->loop);
+	return NULL;
+}
+
+static void
+element_added_cb(GstBin *bin, GstElement *element, gpointer user_data)
+{
+	(void)user_data;
+	(void)bin;
+
+	printf("added: %s\n", GST_OBJECT_NAME(element));
+
+	// XXX is there a better way to do this, like match class name?
+	if (strstr(GST_OBJECT_NAME(element), "v4l2video0dec") == GST_OBJECT_NAME(element)) {
+		/* yes, "capture" rather than "output" because v4l2 is bonkers */
+		gst_util_set_object_arg(G_OBJECT(element), "capture-io-mode", "dmabuf");
+	}
+}
+
+struct decoder *
+video_init(const struct egl *egl, const struct gbm *gbm, const char *filename)
+{
+	struct decoder *dec;
+	GstElement *src, *decodebin;
+
+	dec = calloc(1, sizeof(*dec));
+	dec->loop = g_main_loop_new(NULL, FALSE);
+	dec->gbm = gbm;
+	dec->egl = egl;
+
+	/* Setup pipeline: */
+	static const char *pipeline =
+		"filesrc name=\"src\" ! decodebin name=\"decode\" ! video/x-raw ! appsink sync=false name=\"sink\"";
+	dec->pipeline = gst_parse_launch(pipeline, NULL);
+
+	dec->sink = gst_bin_get_by_name(GST_BIN(dec->pipeline), "sink");
+
+	src = gst_bin_get_by_name(GST_BIN(dec->pipeline), "src");
+	g_object_set(G_OBJECT(src), "location", filename, NULL);
+	gst_object_unref(src);
+
+	/* if we don't limit max-buffers then we can let the decoder outrun
+	 * vsync and quickly chew up 100's of MB of buffers:
+	 */
+	g_object_set(G_OBJECT(dec->sink), "max-buffers", 2, NULL);
+
+	gst_pad_add_probe(gst_element_get_static_pad(dec->sink, "sink"),
+			GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM,
+			pad_probe, dec, NULL);
+
+	/* hack to make sure we get dmabuf's from v4l2video0dec.. */
+	decodebin = gst_bin_get_by_name(GST_BIN(dec->pipeline), "decode");
+	g_signal_connect(decodebin, "element-added", G_CALLBACK(element_added_cb), dec);
+
+	/* let 'er rip! */
+	gst_element_set_state(dec->pipeline, GST_STATE_PLAYING);
+
+	pthread_create(&dec->gst_thread, NULL, gst_thread_func, dec);
+
+	return dec;
+}
+
+static void
+set_last_frame(struct decoder *dec, EGLImage frame, GstSample *samp)
+{
+	if (dec->last_frame)
+		dec->egl->eglDestroyImageKHR(dec->egl->display, dec->last_frame);
+	dec->last_frame = frame;
+	if (dec->last_samp)
+		gst_sample_unref(dec->last_samp);
+	dec->last_samp = samp;
+}
+
+// TODO this could probably be a helper re-used by cube-tex:
+static int
+buf_to_fd(const struct gbm *gbm, int size, void *ptr)
+{
+	struct gbm_bo *bo;
+	void *map, *map_data = NULL;
+	uint32_t stride;
+	int fd;
+
+	/* NOTE: do not actually use GBM_BO_USE_WRITE since that gets us a dumb buffer: */
+	bo = gbm_bo_create(gbm->dev, size, 1, GBM_FORMAT_R8, GBM_BO_USE_LINEAR);
+
+	map = gbm_bo_map(bo, 0, 0, size, 1, GBM_BO_TRANSFER_WRITE, &stride, &map_data);
+
+	memcpy(map, ptr, size);
+
+	gbm_bo_unmap(bo, map_data);
+
+	fd = gbm_bo_get_fd(bo);
+
+	/* we have the fd now, no longer need the bo: */
+	gbm_bo_destroy(bo);
+
+	return fd;
+}
+
+static EGLImage
+buffer_to_image(struct decoder *dec, GstBuffer *buf)
+{
+	struct { int fd, offset, stride; } planes[3];
+	GstVideoMeta *meta = gst_buffer_get_video_meta(buf);
+	EGLImage image;
+	unsigned nmems = gst_buffer_n_memory(buf);
+	unsigned nplanes = (dec->format == DRM_FORMAT_YUV420) ? 3 : 2;
+	unsigned i;
+
+	if (nmems == nplanes) {
+		// XXX TODO..
+	} else if (nmems == 1) {
+		GstMemory *mem = gst_buffer_peek_memory(buf, 0);
+		int fd;
+
+		if (dec->frame == 0) {
+			printf("%s zero-copy\n", gst_is_dmabuf_memory(mem) ? "using" : "not");
+		}
+
+		if (gst_is_dmabuf_memory(mem)) {
+			fd = dup(gst_dmabuf_memory_get_fd(mem));
+		} else {
+			GstMapInfo info;
+			gst_memory_map(mem, &info, GST_MAP_READ);
+			fd = buf_to_fd(dec->gbm, info.size, info.data);
+			gst_memory_unmap(mem, &info);
+		}
+
+		// XXX why don't we get meta??
+		if (meta) {
+			for (i = 0; i < nplanes; i++) {
+				planes[i].fd = fd;
+				planes[i].offset = meta->offset[i];
+				planes[i].stride = meta->stride[i];
+			}
+		} else {
+			int offset = 0, stride = dec->info.width, height = dec->info.height;
+
+			for (i = 0; i < nplanes; i++) {
+
+				if (i == 1) {
+					height /= 2;
+					if (nplanes == 3)
+						stride /= 2;
+				}
+
+				planes[i].fd = fd;
+				planes[i].offset = offset;
+				planes[i].stride = stride;
+
+				offset += stride * height;
+			}
+		}
+	}
+
+	if (dec->format == DRM_FORMAT_NV12) {
+		const EGLint attr[] = {
+			EGL_WIDTH, dec->info.width,
+			EGL_HEIGHT, dec->info.height,
+			EGL_LINUX_DRM_FOURCC_EXT, dec->format,
+			EGL_DMA_BUF_PLANE0_FD_EXT, planes[0].fd,
+			EGL_DMA_BUF_PLANE0_OFFSET_EXT, planes[0].offset,
+			EGL_DMA_BUF_PLANE0_PITCH_EXT, planes[0].stride,
+			EGL_DMA_BUF_PLANE1_FD_EXT, planes[1].fd,
+			EGL_DMA_BUF_PLANE1_OFFSET_EXT, planes[1].offset,
+			EGL_DMA_BUF_PLANE1_PITCH_EXT, planes[1].stride,
+			EGL_NONE
+		};
+
+		image = dec->egl->eglCreateImageKHR(dec->egl->display, EGL_NO_CONTEXT,
+				EGL_LINUX_DMA_BUF_EXT, NULL, attr);
+	} else {
+		const EGLint attr[] = {
+			EGL_WIDTH, dec->info.width,
+			EGL_HEIGHT, dec->info.height,
+			EGL_LINUX_DRM_FOURCC_EXT, dec->format,
+			EGL_DMA_BUF_PLANE0_FD_EXT, planes[0].fd,
+			EGL_DMA_BUF_PLANE0_OFFSET_EXT, planes[0].offset,
+			EGL_DMA_BUF_PLANE0_PITCH_EXT, planes[0].stride,
+			EGL_DMA_BUF_PLANE1_FD_EXT, planes[1].fd,
+			EGL_DMA_BUF_PLANE1_OFFSET_EXT, planes[1].offset,
+			EGL_DMA_BUF_PLANE1_PITCH_EXT, planes[1].stride,
+			EGL_DMA_BUF_PLANE2_FD_EXT, planes[2].fd,
+			EGL_DMA_BUF_PLANE2_OFFSET_EXT, planes[2].offset,
+			EGL_DMA_BUF_PLANE2_PITCH_EXT, planes[2].stride,
+			EGL_NONE
+		};
+
+		image = dec->egl->eglCreateImageKHR(dec->egl->display, EGL_NO_CONTEXT,
+				EGL_LINUX_DMA_BUF_EXT, NULL, attr);
+	}
+
+	for (unsigned i = 0; i < nmems; i++)
+		close(planes[i].fd);
+
+	return image;
+}
+
+EGLImage
+video_frame(struct decoder *dec)
+{
+	GstSample *samp;
+	GstBuffer *buf;
+	EGLImage   frame = NULL;
+
+	samp = gst_app_sink_pull_sample(GST_APP_SINK(dec->sink));
+	if (!samp)
+		return NULL;
+
+	buf = gst_sample_get_buffer(samp);
+
+	// TODO inline buffer_to_image??
+	frame = buffer_to_image(dec, buf);
+
+	// TODO in the zero-copy dmabuf case it would be nice to associate
+	// the eglimg w/ the buffer to avoid recreating it every frame..
+
+	set_last_frame(dec, frame, samp);
+
+	dec->frame++;
+
+	return frame;
+}
+
+void video_deinit(struct decoder *dec)
+{
+	set_last_frame(dec, NULL, NULL);
+	gst_element_set_state(dec->pipeline, GST_STATE_NULL);
+	gst_object_unref(dec->sink);
+	gst_object_unref(dec->pipeline);
+	g_main_loop_quit(dec->loop);
+	g_main_loop_unref(dec->loop);
+	pthread_join(dec->gst_thread, 0);
+	free(dec);
+}
diff --git a/kmscube.c b/kmscube.c
index fcfd902..3140574 100644
--- a/kmscube.c
+++ b/kmscube.c
@@ -31,6 +31,9 @@
 #include "common.h"
 #include "drm-common.h"
 
+#ifdef HAVE_GST
+#  include <gst/gst.h>
+#endif
 
 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
 
@@ -38,12 +41,13 @@ static const struct egl *egl;
 static const struct gbm *gbm;
 static const struct drm *drm;
 
-static const char *shortopts = "AD:M:";
+static const char *shortopts = "AD:M:V:";
 
 static const struct option longopts[] = {
 	{"atomic", no_argument,       0, 'M'},
 	{"device", required_argument, 0, 'D'},
 	{"mode",   required_argument, 0, 'M'},
+	{"video",  required_argument, 0, 'V'},
 	{0, 0, 0, 0}
 };
 
@@ -58,17 +62,23 @@ static void usage(const char *name)
 			"        smooth    -  smooth shaded cube (default)\n"
 			"        rgba      -  rgba textured cube\n"
 			"        nv12-2img -  yuv textured (color conversion in shader)\n"
-			"        nv12-1img -  yuv textured (single nv12 texture)\n",
+			"        nv12-1img -  yuv textured (single nv12 texture)\n"
+			"    -V, --video=FILE         video textured cube\n",
 			name);
 }
 
 int main(int argc, char *argv[])
 {
 	const char *device = "/dev/dri/card0";
+	const char *video = NULL;
 	enum mode mode = SMOOTH;
 	int atomic = 0;
 	int opt;
 
+#ifdef HAVE_GST
+	gst_init(&argc, &argv);
+#endif
+
 	while ((opt = getopt_long_only(argc, argv, shortopts, longopts, NULL)) != -1) {
 		switch (opt) {
 		case 'A':
@@ -92,6 +102,10 @@ int main(int argc, char *argv[])
 				return -1;
 			}
 			break;
+		case 'V':
+			mode = VIDEO;
+			video = optarg;
+			break;
 		default:
 			usage(argv[0]);
 			return -1;
@@ -115,6 +129,8 @@ int main(int argc, char *argv[])
 
 	if (mode == SMOOTH)
 		egl = init_cube_smooth(gbm);
+	else if (mode == VIDEO)
+		egl = init_cube_video(gbm, video);
 	else
 		egl = init_cube_tex(gbm, mode);
 
-- 
2.9.3



More information about the mesa-dev mailing list