/** * @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; }