summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Sieber <jonathan_sieber@yahoo.de>2017-04-06 18:36:58 +0200
committerAlfred E. Heggestad <alfred.heggestad@gmail.com>2017-04-06 18:36:58 +0200
commitae1f6ea602a5b206f61e678b5249c01abe7cb7df (patch)
tree37e5ccc2647ae745fa3d8adcc12fec93efcdc60a
parent6a9ccd80ef607eb3600c98ef5692e552d1b7302f (diff)
Video Display Support for Raspberry Pi (#228)
* Adding a new video display module for the Raspberry Pi... * Made it work with libomxil-bellagio - Fixed framing woes with vidconv() :) * Some Attention to the build system, Automatically use libomx-bellagio or RPi OMX API Introduces USE_OMX_RPI and USE_OMX_BELLAGIO in mk/modules.mk use sys_usleep() Removed pthread mutexes, they are not needed anymore
-rw-r--r--mk/modules.mk17
-rw-r--r--modules/omx/README17
-rw-r--r--modules/omx/module.c133
-rw-r--r--modules/omx/module.mk23
-rw-r--r--modules/omx/omx.c326
-rw-r--r--modules/omx/omx.h49
6 files changed, 565 insertions, 0 deletions
diff --git a/mk/modules.mk b/mk/modules.mk
index 1aa2166..439c214 100644
--- a/mk/modules.mk
+++ b/mk/modules.mk
@@ -32,6 +32,9 @@
# USE_L16 L16 audio codec
# USE_MPA MPA audo codec
# USE_MPG123 Use mpg123
+# USE_OMX_RPI RaspberryPi VideoCore display driver
+# USE_OMX_BELLAGIO libomxil-bellagio xvideosink driver
+# USE_OPUS Opus audio codec
# USE_OPUS Opus audio codec
# USE_OSS OSS audio driver
# USE_PLC Packet Loss Concealment
@@ -201,6 +204,14 @@ USE_VPX := $(shell [ -f $(SYSROOT)/include/vpx/vp8.h ] \
|| [ -f $(SYSROOT)/local/include/vpx/vp8.h ] \
|| [ -f $(SYSROOT_ALT)/include/vpx/vp8.h ] \
&& echo "yes")
+USE_OMX_RPI := $(shell [ -f /opt/vc/include/bcm_host.h ] || \
+ [ -f $(SYSROOT)/include/bcm_host.h ] \
+ || [ -f $(SYSROOT_ALT)/include/bcm_host.h ] \
+ && echo "yes")
+USE_OMX_BELLAGIO := $(shell [ -f /usr/include/OMX_Core.h ] \
+ || [ -f $(SYSROOT)/include/OMX_Core.h ] \
+ || [ -f $(SYSROOT_ALT)/include/OMX_Core.h ] \
+ && echo "yes")
else
# Windows.
# Accounts for mingw with Windows SDK (formerly known as Platform SDK)
@@ -413,6 +424,12 @@ endif
ifneq ($(USE_V4L2),)
MODULES += v4l2 v4l2_codec
endif
+ifneq ($(USE_OMX_RPI),)
+MODULES += omx
+endif
+ifneq ($(USE_OMX_BELLAGIO),)
+MODULES += omx
+endif
ifneq ($(USE_VPX),)
MODULES += vp8
MODULES += $(shell pkg-config 'vpx >= 1.3.0' && echo "vp9")
diff --git a/modules/omx/README b/modules/omx/README
new file mode 100644
index 0000000..e24c274
--- /dev/null
+++ b/modules/omx/README
@@ -0,0 +1,17 @@
+README
+------
+
+This module implements support for the VideoCore4 of
+the Raspberry Pi A/B/2/3.
+Currently it only does video playback.
+
+EXAMPLE CONFIG
+--------------
+
+# Video
+video_display omx,nil
+
+# Video codec Modules (in order)
+module omx.so
+
+
diff --git a/modules/omx/module.c b/modules/omx/module.c
new file mode 100644
index 0000000..b9d04cf
--- /dev/null
+++ b/modules/omx/module.c
@@ -0,0 +1,133 @@
+/**
+ * @file omx/module.c Raspberry Pi VideoCoreIV OpenMAX interface
+ *
+ * Copyright (C) 2016 - 2017 Creytiv.com
+ * Copyright (C) 2016 - 2017 Jonathan Sieber
+ */
+
+
+#include "omx.h"
+
+#include <stdlib.h>
+
+#include <re/re.h>
+#include <rem/rem.h>
+#include <baresip.h>
+
+int omx_vidisp_alloc(struct vidisp_st **vp, const struct vidisp* vd,
+ struct vidisp_prm *prm, const char *dev, vidisp_resize_h *resizeh,
+ void *arg);
+int omx_vidisp_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame);
+
+struct vidisp_st {
+ const struct vidisp *vd; /* inheritance */
+ struct vidsz size;
+ struct omx_state* omx;
+};
+
+static struct vidisp* vid;
+
+static struct omx_state omx;
+
+static void destructor(void *arg)
+{
+ struct vidisp_st *st = arg;
+ omx_display_disable(st->omx);
+}
+
+int omx_vidisp_alloc(struct vidisp_st **vp, const struct vidisp* vd,
+ struct vidisp_prm *prm, const char *dev, vidisp_resize_h *resizeh,
+ void *arg)
+{
+ struct vidisp_st *st;
+
+ /* Not used by OMX */
+ (void) prm;
+ (void) dev;
+ (void) resizeh;
+ (void) arg;
+
+ info("omx: vidisp_alloc\n");
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->vd = vd;
+ *vp = st;
+
+ st->omx = &omx;
+
+ return 0;
+}
+
+
+int omx_vidisp_display(struct vidisp_st *st, const char *title,
+ const struct vidframe *frame)
+{
+ int err = 0;
+ void* buf;
+ uint32_t len;
+
+ struct vidframe omx_frame;
+
+ (void)title;
+
+ if (frame->fmt != VID_FMT_YUV420P) {
+ return EINVAL;
+ }
+
+ if (!vidsz_cmp(&st->size, &frame->size)) {
+ info("omx: new frame size: w=%d h=%d\n",
+ frame->size.w, frame->size.h);
+ info("omx: linesize[0]=%d\tlinesize[1]=%d\tlinesize[2]=%d\n",
+ frame->linesize[0], frame->linesize[1],
+ frame->linesize[2]);
+ err = omx_display_enable(st->omx,
+ frame->size.w, frame->size.h, frame->linesize[0]);
+ if (err) {
+ error("omx_display_enable failed");
+ return err;
+ }
+ st->size = frame->size;
+ }
+
+ /* Get Buffer Pointer */
+ omx_display_input_buffer(st->omx, &buf, &len);
+
+ vidframe_init_buf(&omx_frame, VID_FMT_YUV420P, &frame->size,
+ buf);
+
+ vidconv(&omx_frame, frame, 0);
+
+ omx_display_flush_buffer(st->omx);
+ return 0;
+}
+
+static int module_init(void)
+{
+ if (omx_init(&omx) != 0) {
+ error("Could not initialize OpenMAX");
+ return ENODEV;
+ }
+
+ return vidisp_register(&vid, "omx",
+ omx_vidisp_alloc, NULL, omx_vidisp_display, NULL);
+}
+
+static int module_close(void)
+{
+ /* HACK: not deinitializing OMX because of a hangup */
+ /* omx_deinit(&omx) */
+ vid = mem_deref(vid);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(omx) = {
+ "omx",
+ "vidisp",
+ module_init,
+ module_close
+};
diff --git a/modules/omx/module.mk b/modules/omx/module.mk
new file mode 100644
index 0000000..958a1a5
--- /dev/null
+++ b/modules/omx/module.mk
@@ -0,0 +1,23 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 - 2015 Creytiv.com
+#
+
+MOD := omx
+$(MOD)_SRCS += omx.c module.c
+
+ifneq ($(USE_OMX_RPI),)
+$(MOD)_CFLAGS := -DRASPBERRY_PI -DOMX_SKIP64BIT \
+ -I/usr/local/include/interface/vmcs_host/linux/ \
+ -I /usr/local/include/interface/vcos/pthreads/ \
+ -I /opt/vc/include -I /opt/vc/include/interface/vmcs_host/linux \
+ -I /opt/vc/include/interface/vcos/pthreads
+$(MOD)_LFLAGS += -lvcos -lbcm_host -lopenmaxil -L /opt/vc/lib
+endif
+
+ifneq ($(USE_OMX_BELLAGIO),)
+$(MOD)_LFLAGS += -lomxil-bellagio
+endif
+
+include mk/mod.mk
diff --git a/modules/omx/omx.c b/modules/omx/omx.c
new file mode 100644
index 0000000..8f1c069
--- /dev/null
+++ b/modules/omx/omx.c
@@ -0,0 +1,326 @@
+/**
+ * @file omx.c Raspberry Pi VideoCoreIV OpenMAX interface
+ *
+ * Copyright (C) 2016 - 2017 Creytiv.com
+ * Copyright (C) 2016 - 2017 Jonathan Sieber
+ */
+
+#include "omx.h"
+
+#include <re/re.h>
+#include <rem/rem.h>
+#include <baresip.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Avoids a VideoCore header warning about clock_gettime() */
+#include <time.h>
+#include <sys/time.h>
+
+/**
+ * @defgroup omx omx
+ *
+ * TODO:
+ * * Proper sync OMX events across threads, instead of busy waiting
+ */
+
+#ifdef RASPBERRY_PI
+static const int VIDEO_RENDER_PORT = 90;
+#else
+static const int VIDEO_RENDER_PORT = 0;
+#endif
+
+/*
+static void setHeader(OMX_PTR header, OMX_U32 size) {
+ OMX_VERSIONTYPE* ver = (OMX_VERSIONTYPE*)(header + sizeof(OMX_U32));
+ *((OMX_U32*)header) = size;
+
+ ver->s.nVersionMajor = VERSIONMAJOR;
+ ver->s.nVersionMinor = VERSIONMINOR;
+ ver->s.nRevision = VERSIONREVISION;
+ ver->s.nStep = VERSIONSTEP;
+}
+* */
+
+static OMX_ERRORTYPE EventHandler(OMX_HANDLETYPE hComponent, OMX_PTR pAppData,
+ OMX_EVENTTYPE eEvent, OMX_U32 nData1, OMX_U32 nData2,
+ OMX_PTR pEventData)
+{
+ (void) hComponent;
+ switch (eEvent) {
+ case OMX_EventCmdComplete:
+ debug("omx.EventHandler: Previous command completed\n"
+ "d1=%x\td2=%x\teventData=%p\tappdata=%p\n",
+ nData1, nData2, pEventData, pAppData);
+ /* TODO: Put these event into a multithreaded queue,
+ * properly wait for them in the issuing code */
+ break;
+ case OMX_EventError:
+ warning("omx.EventHandler: Error event type "
+ "data1=%x\tdata2=%x\n", nData1, nData2);
+ break;
+ default:
+ warning("omx.EventHandler: Unknown event type %d\t"
+ "data1=%x data2=%x\n", eEvent, nData1, nData2);
+ return -1;
+ break;
+ }
+ return 0;
+}
+
+static OMX_ERRORTYPE EmptyBufferDone(
+ OMX_HANDLETYPE hComponent,
+ OMX_PTR pAppData, OMX_BUFFERHEADERTYPE* pBuffer)
+{
+ (void) hComponent;
+ (void) pAppData;
+ (void) pBuffer;
+
+ /* TODO: Wrap every call that can generate an event,
+ * and panic if an unexpected event arrives */
+ return 0;
+}
+
+static OMX_ERRORTYPE FillBufferDone(OMX_HANDLETYPE hComponent,
+ OMX_PTR pAppData, OMX_BUFFERHEADERTYPE* pBuffer)
+{
+ (void) hComponent;
+ (void) pAppData;
+ (void) pBuffer;
+ debug("FillBufferDone\n");
+ return 0;
+}
+
+static struct OMX_CALLBACKTYPE callbacks = {
+ EventHandler,
+ EmptyBufferDone,
+ &FillBufferDone
+};
+
+int omx_init(struct omx_state* st)
+{
+ OMX_ERRORTYPE err;
+#ifdef RASPBERRY_PI
+ bcm_host_init();
+#endif
+
+ st->buffers = NULL;
+
+ err = OMX_Init();
+#ifdef RASPBERRY_PI
+ err |= OMX_GetHandle(&st->video_render,
+ "OMX.broadcom.video_render", 0, &callbacks);
+#else
+ err |= OMX_GetHandle(&st->video_render,
+ "OMX.st.video.xvideosink", 0, &callbacks);
+#endif
+
+ if (!st->video_render || err != OMX_ERROR_NONE) {
+ error("Failed to create OMX video_render component");
+ return ENOENT;
+ }
+ else {
+ info("created video_render component");
+ return 0;
+ }
+}
+
+
+/* Some busy loops to verify we're running in order */
+static void block_until_state_changed(OMX_HANDLETYPE hComponent,
+ OMX_STATETYPE wanted_eState)
+{
+ OMX_STATETYPE eState;
+ unsigned int i = 0;
+ while (i++ == 0 || eState != wanted_eState) {
+ OMX_GetState(hComponent, &eState);
+ if (eState != wanted_eState) {
+ sys_usleep(10000);
+ }
+ }
+}
+
+
+void omx_deinit(struct omx_state* st)
+{
+ info("omx_deinit");
+ OMX_SendCommand(st->video_render,
+ OMX_CommandStateSet, OMX_StateIdle, NULL);
+ block_until_state_changed(st->video_render, OMX_StateIdle);
+ OMX_SendCommand(st->video_render,
+ OMX_CommandStateSet, OMX_StateLoaded, NULL);
+ block_until_state_changed(st->video_render, OMX_StateLoaded);
+ OMX_FreeHandle(st->video_render);
+ OMX_Deinit();
+}
+
+void omx_display_disable(struct omx_state* st)
+{
+ (void)st;
+
+ #ifdef RASPBERRY_PI
+ OMX_ERRORTYPE err;
+ OMX_CONFIG_DISPLAYREGIONTYPE config;
+ memset(&config, 0, sizeof(OMX_CONFIG_DISPLAYREGIONTYPE));
+ config.nSize = sizeof(OMX_CONFIG_DISPLAYREGIONTYPE);
+ config.nVersion.nVersion = OMX_VERSION;
+ config.nPortIndex = VIDEO_RENDER_PORT;
+ config.fullscreen = 0;
+ config.set = OMX_DISPLAY_SET_FULLSCREEN;
+
+ err = OMX_SetParameter(st->video_render,
+ OMX_IndexConfigDisplayRegion, &config);
+
+ if (err != OMX_ERROR_NONE) {
+ warning("omx_display_disable command failed");
+ }
+
+ #endif
+}
+
+static void block_until_port_changed(OMX_HANDLETYPE hComponent,
+ OMX_U32 nPortIndex, OMX_BOOL bEnabled) {
+
+ OMX_ERRORTYPE r;
+ OMX_PARAM_PORTDEFINITIONTYPE portdef;
+ OMX_U32 i = 0;
+
+ memset(&portdef, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+ portdef.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
+ portdef.nVersion.nVersion = OMX_VERSION;
+ portdef.nPortIndex = nPortIndex;
+
+ while (i++ == 0 || portdef.bEnabled != bEnabled) {
+ r = OMX_GetParameter(hComponent,
+ OMX_IndexParamPortDefinition, &portdef);
+ if (r != OMX_ErrorNone) {
+ error("block_until_port_changed: OMX_GetParameter "
+ " failed with Result=%d\n", r);
+ }
+ if (portdef.bEnabled != bEnabled) {
+ sys_usleep(10000);
+ }
+ }
+}
+
+int omx_display_enable(struct omx_state* st,
+ int width, int height, int stride)
+{
+ unsigned int i;
+ OMX_PARAM_PORTDEFINITIONTYPE portdef;
+#ifdef RASPBERRY_PI
+ OMX_CONFIG_DISPLAYREGIONTYPE config;
+#endif
+ OMX_ERRORTYPE err = OMX_ERROR_NONE;
+
+ info("omx_update_size %d %d\n", width, height);
+
+ #ifdef RASPBERRY_PI
+ memset(&config, 0, sizeof(OMX_CONFIG_DISPLAYREGIONTYPE));
+ config.nSize = sizeof(OMX_CONFIG_DISPLAYREGIONTYPE);
+ config.nVersion.nVersion = OMX_VERSION;
+ config.nPortIndex = VIDEO_RENDER_PORT;
+ config.fullscreen = 1;
+ config.set = OMX_DISPLAY_SET_FULLSCREEN;
+
+ err |= OMX_SetParameter(st->video_render,
+ OMX_IndexConfigDisplayRegion, &config);
+
+ #endif
+
+ memset(&portdef, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
+ portdef.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
+ portdef.nVersion.nVersion = OMX_VERSION;
+ portdef.nPortIndex = VIDEO_RENDER_PORT;
+
+ /* specify buffer requirements */
+ err |= OMX_GetParameter(st->video_render,
+ OMX_IndexParamPortDefinition, &portdef);
+
+ portdef.format.video.nFrameWidth = width;
+ portdef.format.video.nFrameHeight = height;
+ portdef.format.video.nStride = stride;
+ portdef.format.video.nSliceHeight = height;
+ portdef.bEnabled = 1;
+
+ if (err != OMX_ERROR_NONE) {
+ error("omx_display_enable: failed to set up video port");
+ err = ENOMEM;
+ goto exit;
+ }
+
+
+ err |= OMX_SetParameter(st->video_render,
+ OMX_IndexParamPortDefinition, &portdef);
+ block_until_port_changed(st->video_render, VIDEO_RENDER_PORT, true);
+
+ err |= OMX_GetParameter(st->video_render,
+ OMX_IndexParamPortDefinition, &portdef);
+
+ if (err != OMX_ERROR_NONE || !portdef.bEnabled) {
+ error("omx_display_enable: failed to set up video port");
+ err = ENOMEM;
+ goto exit;
+ }
+
+ /* HACK: This state-change sometimes hangs for unknown reasons,
+ * so we just send the state command and wait 50 ms */
+ /* block_until_state_changed(st->video_render, OMX_StateIdle); */
+
+ OMX_SendCommand(st->video_render, OMX_CommandStateSet,
+ OMX_StateIdle, NULL);
+ sys_usleep(50000);
+
+ if (!st->buffers) {
+ st->buffers =
+ malloc(portdef.nBufferCountActual * sizeof(void*));
+ st->num_buffers = portdef.nBufferCountActual;
+ st->current_buffer = 0;
+
+ for (i = 0; i < portdef.nBufferCountActual; i++) {
+ err = OMX_AllocateBuffer(st->video_render,
+ &st->buffers[i], VIDEO_RENDER_PORT,
+ st, portdef.nBufferSize);
+ if (err) {
+ error("OMX_AllocateBuffer failed: %d\n", err);
+ err = ENOMEM;
+ goto exit;
+ }
+ }
+ }
+
+ debug("omx_update_size: send to execute state");
+ OMX_SendCommand(st->video_render, OMX_CommandStateSet,
+ OMX_StateExecuting, NULL);
+ block_until_state_changed(st->video_render, OMX_StateExecuting);
+
+exit:
+ return err;
+}
+
+
+int omx_display_input_buffer(struct omx_state* st,
+ void** pbuf, uint32_t* plen)
+{
+ if (!st->buffers) return EINVAL;
+
+ *pbuf = st->buffers[0]->pBuffer;
+ *plen = st->buffers[0]->nAllocLen;
+
+ st->buffers[0]->nFilledLen = *plen;
+ st->buffers[0]->nOffset = 0;
+
+ return 0;
+}
+
+int omx_display_flush_buffer(struct omx_state* st)
+{
+ if (OMX_EmptyThisBuffer(st->video_render, st->buffers[0])
+ != OMX_ErrorNone) {
+ error("OMX_EmptyThisBuffer error");
+ }
+
+ return 0;
+}
diff --git a/modules/omx/omx.h b/modules/omx/omx.h
new file mode 100644
index 0000000..5ca49bf
--- /dev/null
+++ b/modules/omx/omx.h
@@ -0,0 +1,49 @@
+/**
+ * @file omx.h Raspberry Pi VideoCoreIV OpenMAX interface
+ *
+ * Copyright (C) 2016 - 2017 Creytiv.com
+ * Copyright (C) 2016 - 2017 Jonathan Sieber
+ */
+
+#ifdef RASPBERRY_PI
+#include <IL/OMX_Core.h>
+#include <IL/OMX_Video.h>
+#include <IL/OMX_Broadcom.h>
+#else
+#include <OMX_Core.h>
+#include <OMX_Component.h>
+#include <OMX_Video.h>
+
+#define OMX_VERSION 0x01010101
+#define OMX_ERROR_NONE 0
+#endif
+
+#include <pthread.h>
+#include <stdint.h>
+#include <string.h>
+
+/* Needed for usleep to appear */
+#define _BSD_SOURCE
+#include <unistd.h>
+
+struct omx_state {
+ pthread_mutex_t omx_mutex;
+
+ OMX_HANDLETYPE video_render;
+ OMX_BUFFERHEADERTYPE** buffers;
+ int num_buffers;
+ int current_buffer;
+
+ int width, height;
+};
+
+int omx_init(struct omx_state* st);
+void omx_deinit(struct omx_state* st);
+
+int omx_display_input_buffer(struct omx_state* st,
+ void** pbuf, uint32_t* plen);
+int omx_display_flush_buffer(struct omx_state* st);
+
+int omx_display_enable(struct omx_state* st,
+ int width, int height, int stride);
+void omx_display_disable(struct omx_state* st);