From ae1f6ea602a5b206f61e678b5249c01abe7cb7df Mon Sep 17 00:00:00 2001 From: Jonathan Sieber Date: Thu, 6 Apr 2017 18:36:58 +0200 Subject: 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 --- modules/omx/README | 17 +++ modules/omx/module.c | 133 ++++++++++++++++++++ modules/omx/module.mk | 23 ++++ modules/omx/omx.c | 326 ++++++++++++++++++++++++++++++++++++++++++++++++++ modules/omx/omx.h | 49 ++++++++ 5 files changed, 548 insertions(+) create mode 100644 modules/omx/README create mode 100644 modules/omx/module.c create mode 100644 modules/omx/module.mk create mode 100644 modules/omx/omx.c create mode 100644 modules/omx/omx.h (limited to 'modules/omx') 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 + +#include +#include +#include + +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 +#include +#include + +#include +#include +#include + +/* Avoids a VideoCore header warning about clock_gettime() */ +#include +#include + +/** + * @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 +#include +#include +#else +#include +#include +#include + +#define OMX_VERSION 0x01010101 +#define OMX_ERROR_NONE 0 +#endif + +#include +#include +#include + +/* Needed for usleep to appear */ +#define _BSD_SOURCE +#include + +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); -- cgit v1.2.3