summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Clark <robdclark@gmail.com>2017-03-15 09:54:04 -0400
committerRob Clark <robdclark@gmail.com>2017-03-27 11:19:16 -0400
commit961c85f6eb42e4445513044c9944c83a0d9cb324 (patch)
tree910f4ac4d44599c6e335e7e6625ddff8ca305a34
parent4f4801b2b1f1aa00914f0c79fd3ab5ae8db2d284 (diff)
add video cube
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@gmail.com> Reviewed-by: Emil Velikov <emil.l.velikov@gmail.com>
-rw-r--r--Makefile.am6
-rw-r--r--common.h20
-rw-r--r--configure.ac9
-rw-r--r--cube-tex.c5
-rw-r--r--cube-video.c386
-rw-r--r--gst-decoder.c340
-rw-r--r--kmscube.c20
7 files changed, 782 insertions, 4 deletions
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..2eceac7 100644
--- a/common.h
+++ b/common.h
@@ -85,9 +85,29 @@ 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 427da75..b11023f 100644
--- a/cube-tex.c
+++ b/cube-tex.c
@@ -46,7 +46,7 @@ struct {
GLuint tex[2];
} gl;
-static const struct egl *egl = &gl.egl;
+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,
};
-static const GLfloat vTexCoords[] = {
+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..6ce20da
--- /dev/null
+++ b/cube-video.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (c) 2017 Rob Clark <rclark@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 const 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@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);