summaryrefslogtreecommitdiff
path: root/gst-decoder.c
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 /gst-decoder.c
parent4f4801b2b1f1aa00914f0c79fd3ab5ae8db2d284 (diff)
downloadkmscube-961c85f6eb42e4445513044c9944c83a0d9cb324.tar.gz
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>
Diffstat (limited to 'gst-decoder.c')
-rw-r--r--gst-decoder.c340
1 files changed, 340 insertions, 0 deletions
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);
+}