From 63cba67892b2d7c29a5ffdca4b34c24a0bbc0caf Mon Sep 17 00:00:00 2001 From: Chris Tallon Date: Fri, 5 Nov 2021 18:37:09 +0000 Subject: [PATCH] Rewrite of ImageOMX to fix the PNG problem --- GNUmakefile | 4 +- eglpicturecreator.h | 29 +++ imageomx.cc | 2 + imageomx.h | 6 +- imageomx2.cc | 339 +++++++++++++++++++++++++++++ imageomx2.h | 90 ++++++++ objects.mk | 3 +- omx/omx.cc | 489 ++++++++++++++++++++++++++++++++++++++++++ omx/omx.h | 171 +++++++++++++++ omx/omxeglrender.cc | 336 +++++++++++++++++++++++++++++ omx/omxeglrender.h | 80 +++++++ omx/omximagedecode.cc | 353 ++++++++++++++++++++++++++++++ omx/omximagedecode.h | 81 +++++++ osdopenvg.cc | 15 +- osdopenvg.h | 11 +- osdvector.cc | 4 + osdvector.h | 4 +- vchannellist.cc | 1 - 18 files changed, 2000 insertions(+), 18 deletions(-) create mode 100644 eglpicturecreator.h create mode 100644 imageomx2.cc create mode 100644 imageomx2.h create mode 100644 omx/omx.cc create mode 100644 omx/omx.h create mode 100644 omx/omxeglrender.cc create mode 100644 omx/omxeglrender.h create mode 100644 omx/omximagedecode.cc create mode 100644 omx/omximagedecode.h diff --git a/GNUmakefile b/GNUmakefile index 37c7ae2..2d55693 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -109,7 +109,7 @@ else CXXFLAGS_DEV = $(DEFINES) -DDEV -g -O0 -Wall -Wextra -Wshadow -Werror=return-type -Wmissing-format-attribute -Wdisabled-optimization -Wmissing-declarations -Wmissing-noreturn -Winit-self -Woverloaded-virtual -Wold-style-cast -Wconversion -std=c++14 $(CXXFLAGS_EXTRA) $(INCLUDES) endif -CXXFLAGS_REL = $(DEFINES) -O3 -Wall -Werror -std=c++14 $(CXXFLAGS_EXTRA) $(INCLUDES) +CXXFLAGS_REL = $(DEFINES) -O3 -std=c++14 $(CXXFLAGS_EXTRA) $(INCLUDES) .PHONY: clean fresh all install strip @@ -133,7 +133,7 @@ dev: CXXFLAGS := $(CXXFLAGS_DEV) dev: vompclient release: CXXFLAGS := $(CXXFLAGS_REL) -release: clean vompclient strip +release: vompclient strip clean: rm -f *.o deps vompclient *~ fonts/*.o fonts/*~ teletxt/*.o diff --git a/eglpicturecreator.h b/eglpicturecreator.h new file mode 100644 index 0000000..ffe0556 --- /dev/null +++ b/eglpicturecreator.h @@ -0,0 +1,29 @@ +/* + Copyright 2021 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP. If not, see . +*/ + +#ifndef EGLPICTURECREATOR_H +#define EGLPICTURECREATOR_H + +class EGLPictureCreator +{ + public: + virtual bool getEGLPicture(struct OsdVector::PictureInfo& pictureInfo, EGLDisplay* display)=0; +}; + +#endif diff --git a/imageomx.cc b/imageomx.cc index e5bb105..a511c40 100644 --- a/imageomx.cc +++ b/imageomx.cc @@ -602,6 +602,8 @@ bool ImageOMX::intDecodePicture(LoadIndex index, unsigned char* /* buffer */, un return false; } + // usleep(100000); -- This delay triggers the race bug + // LogNT::getInstance()->debug(TAG, // "decodePicture 9"); diff --git a/imageomx.h b/imageomx.h index 96442ba..e2df77f 100644 --- a/imageomx.h +++ b/imageomx.h @@ -33,11 +33,7 @@ #include "osdvector.h" #include "videoomx.h" - -class EGLPictureCreator { -public: - virtual bool getEGLPicture(struct OsdVector::PictureInfo & info, EGLDisplay * display ) = 0; -}; +#include "eglpicturecreator.h" diff --git a/imageomx2.cc b/imageomx2.cc new file mode 100644 index 0000000..c9daf90 --- /dev/null +++ b/imageomx2.cc @@ -0,0 +1,339 @@ +/* + Copyright 2021 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP. If not, see . +*/ + +#include "osdvector.h" + +#include "omx/omximagedecode.h" +#include "omx/omxeglrender.h" + +#include "imageomx2.h" + +#include "eglpicturecreator.h" + +const static char* TAG = "ImageOMX2"; + +ImageOMX2::ImageOMX2(/*EGLDisplay t_egl_display*/) +: OsdVector::PictureDecoder(NULL) +{ + log = LogNT::getInstance(); + //egl_display = t_egl_display; +} + +void ImageOMX2::init() +{ + omx_imagedecode = new OMX_Image_Decode(); + if (!omx_imagedecode->init()) + { + delete omx_imagedecode; + omx_imagedecode = NULL; + log->crit(TAG, "omx_imagedecode failed to init"); + } + + omx_eglrender = new OMX_EGL_Render(); + if (!omx_eglrender->init()) + { + delete omx_imagedecode; + delete omx_eglrender; + omx_imagedecode = NULL; + omx_eglrender = NULL; + log->crit(TAG, "omx_eglrender failed to init"); + } +} + +void ImageOMX2::shutdown() +{ + if (omx_imagedecode) omx_imagedecode->shutdown(); + if (omx_eglrender) omx_eglrender->shutdown(); + delete omx_imagedecode; + delete omx_eglrender; + omx_imagedecode = NULL; + omx_eglrender = NULL; +} + +void ImageOMX2::reinit() +{ + // Only used from main exception handler in decodePicture. *Should* reset everything back to normal + shutdown(); + init(); +} + +unsigned char* ImageOMX2::decodePicture(LoadIndex index, unsigned char* buffer, unsigned int length, bool freemem) noexcept +{ + // This function needs to return NULL if successful, buffer if not. + + char* rawData{}; + + try + { + if (!omx_imagedecode) throw 1; + if (!omx_eglrender) throw 2; + if (currentDecodeValid) throw 3; + if (length < 3) throw 4; + if ((buffer[0] != 0x89) || (buffer[1] != 0x50) || (buffer[2] != 0x4e)) throw 5; + + currentDecodeValid = true; + + currentDecode.lindex = index; + currentDecode.decoder = this; + currentDecode.type = OsdVector::PictureInfo::RGBAMemBlock; + //currentDecode.image/handle = // filled during render + //currentDecode.reference = // filled during render + + int rawDataSize{}; + int rawDataWidth{}; + int rawDataHeight{}; + int rawDataStride{}; + + decode(reinterpret_cast(buffer), length, &rawData, &rawDataSize, &rawDataWidth, &rawDataHeight, &rawDataStride); + + log->debug(TAG, "decoded {}, size = {}, w = {}, h = {}, s = {}", static_cast(rawData), rawDataSize, rawDataWidth, rawDataHeight, rawDataStride); + + currentDecode.width = rawDataWidth; + currentDecode.height = rawDataHeight; + + render(rawData, rawDataSize, rawDataWidth, rawDataHeight, rawDataStride); + free(rawData); + + // By the interface, if freemem = true, free buffer before returning + if (freemem) free(buffer); + return NULL; + } + catch (int e) + { + switch(e) + { + case 1: log->error(TAG, "omx_imagedecode not initted"); break; + case 2: log->error(TAG, "omx_eglrender not initted"); break; + case 3: log->error(TAG, "Program error, currentDecodeValid = true on call to decodePicture"); break; + case 4: log->error(TAG, "Data length too short"); break; + case 5: log->error(TAG, "Input data not in PNG format"); break; + case 101: log->error(TAG, "Received all image from image_decode but error with nFlags and EOS"); break; + case 201: log->error(TAG, "Could not get egl picture creator"); break; + case 202: log->error(TAG, "Create egl picture failed"); break; + } + + if (rawData) free(rawData); + reinit(); + return buffer; + } + catch (OMX_Exception& e) + { + log->error(TAG, "OMX Exception"); + log->error(TAG, "{:#x} - {}", e.errorCode(), e.what()); + + if (rawData) free(rawData); + reinit(); + return buffer; + } + catch (...) + { + log->error(TAG, "Other exception caught in decodePicture?!"); + + if (rawData) free(rawData); + reinit(); + return buffer; + } +} + +bool ImageOMX2::getDecodedPicture(struct OsdVector::PictureInfo& pict_inf) +{ + if (!currentDecodeValid) return false; + pict_inf = currentDecode; + currentDecodeValid = false; + memset(¤tDecode, 0, sizeof(currentDecode)); + return true; +} + +// Internal functions + +void ImageOMX2::decode(char* inputData, int inputDataSize, char** outRawData, int* outRawDataSize, int* outWidth, int* outHeight, int* outStride) +{ + std::vector chunks; + std::vector sizes; + + try + { + // Talk to image_decode + + log->debug(TAG, "image_decode: disable input"); + omx_imagedecode->disableInput(); + log->debug(TAG, "image_decode: disable output"); + omx_imagedecode->disableOutput(); + log->debug(TAG, "image_decode: change state to idle"); + omx_imagedecode->changeState(OMX_StateIdle); + log->debug(TAG, "image_decode: set format"); + omx_imagedecode->setFormat(); + log->debug(TAG, "image_decode: prepare input buffers"); + omx_imagedecode->prepareInputBuffers(inputDataSize); + log->debug(TAG, "image_decode: enable input"); + omx_imagedecode->enableInput(false); // Don't wait for this, it depends on OMX_UseBuffer being called next + log->debug(TAG, "image_decode: allocate input buffers"); + omx_imagedecode->allocateInputBuffers(inputData); + + log->debug(TAG, "image_decode: change state to executing"); + omx_imagedecode->changeState(OMX_StateExecuting); + + log->debug(TAG, "image_decode: sendtoinput"); + omx_imagedecode->sendToInput(); + log->debug(TAG, "image_decode: wait for port settings change"); + omx_imagedecode->waitForOutputPortSettingsChange(); + log->debug(TAG, "image_decode: fix slice height"); + omx_imagedecode->setSliceHeight(16); // FIXME allow full height if imageHeight % 16 = 0 ?? + log->debug(TAG, "image_decode: enable output"); + omx_imagedecode->enableOutput(false); // Don't wait for this, I hope it depends on OMX_UseBuffer being called next + log->debug(TAG, "image_decode: allocate output buffer"); + omx_imagedecode->allocateOutputBuffer(); + + int xwidth{}; + int xheight{}; + int xstride{}; + int xsliceHeight{}; + + omx_imagedecode->getImageInfo(&xwidth, &xheight, &xstride, &xsliceHeight); + log->debug(TAG, "ImageInfo: {} {} {} {}", xwidth, xheight, xstride, xsliceHeight); + + int linesGot = 0; + int linesToGet; + while(1) + { + char* data; + int nFlags; + log->debug(TAG, "image_decode: receiveFromOutput"); + omx_imagedecode->receiveFromOutput(&data, &nFlags); + log->debug(TAG, "image_decode: receiveFromOutput {:#x}", nFlags); + + + linesToGet = xheight - linesGot; + if (linesToGet > xsliceHeight) linesToGet = xsliceHeight; + + chunks.push_back(data); + sizes.push_back(linesToGet * xstride); + + linesGot += linesToGet; + + if (linesGot == xheight) + { + if ((nFlags & OMX_BUFFERFLAG_EOS) == 0) throw 101; + break; + } + } + + log->debug(TAG, "image_decode: change state to idle"); + omx_imagedecode->changeState(OMX_StateIdle); + log->debug(TAG, "image_decode: disable input"); + omx_imagedecode->disableInput(false); + log->debug(TAG, "image_decode: disable output"); + omx_imagedecode->disableOutput(false); + log->debug(TAG, "image_decode: deallocate input buffers"); + omx_imagedecode->deallocateInputBuffers(); + log->debug(TAG, "image_decode: deallocate output buffer"); + omx_imagedecode->deallocateOutputBuffer(); + log->debug(TAG, "image_decode: change state to loaded"); + omx_imagedecode->changeState(OMX_StateLoaded); + + int totalSize{}; + for (int chunkSize : sizes) totalSize += chunkSize; + log->debug(TAG, "total decoded mem chunks size: {}", totalSize); + char* gluedTogether = static_cast(malloc(totalSize)); + log->debug(TAG, "gluedTogether {}", static_cast(gluedTogether)); + char* dest = gluedTogether; + for (unsigned int i = 0 ; i < chunks.size(); i++) + { + memcpy(dest, chunks[i], sizes[i]); + dest += sizes[i]; + } + + for(char* chunk : chunks) free(chunk); + + *outRawDataSize = totalSize; + *outRawData = gluedTogether; + *outWidth = xwidth; + *outHeight = xheight; + *outStride = xstride; + } + catch (...) + { + for(char* chunk : chunks) free(chunk); + throw; + } +} + +void ImageOMX2::render(char* inputData, int inputDataSize, int imageWidth, int imageHeight, int imageStride) +{ + log->debug(TAG, "render: inputdataSize = {}", inputDataSize); + + EGLPictureCreator* pictcreat = dynamic_cast(Osd::getInstance()); + if (!pictcreat) throw 201; + EGLDisplay egl_display2; + if (!pictcreat->getEGLPicture(currentDecode, &egl_display2)) throw 202; + + // pictcreat must read width, height from currentDecode + // The VGImage handle returned from vgCreateImage is stored in currentDecode.handle + // The EGLImageKHR returned from eglCreateImageKHR is stored in currentDecode.reference + + log->debug(TAG, "egl_render: disable input"); + omx_eglrender->disableInput(); + log->debug(TAG, "egl_render: disable output"); + omx_eglrender->disableOutput(); + log->debug(TAG, "egl_render: prep"); + omx_eglrender->prepareOutputPort(egl_display2); + log->debug(TAG, "egl_render: change state to idle"); + omx_eglrender->changeState(OMX_StateIdle); + log->debug(TAG, "egl_render: prepareInputPort"); + omx_eglrender->prepareInputPort(imageWidth, imageHeight, imageStride); + log->debug(TAG, "egl_render: enable input"); + omx_eglrender->enableInput(false); + log->debug(TAG, "egl_render: allocate input buffers"); + omx_eglrender->allocateInputBuffers(inputData); + log->debug(TAG, "Print port settings"); + omx_eglrender->printPortSettings(false); + log->debug(TAG, "egl_render: enable output"); + omx_eglrender->enableOutput(false); + log->debug(TAG, "egl_render: allocate output buffer"); + omx_eglrender->allocateEGLImageKHR(currentDecode.reference); + log->debug(TAG, "egl_render: change state to executing"); + omx_eglrender->changeState(OMX_StateExecuting); + log->debug(TAG, "egl_render: sendtoinput"); + omx_eglrender->sendToInput(inputData, inputDataSize); + + omx_eglrender->printPortSettings(true); + + log->debug(TAG, "egl_render: render"); + omx_eglrender->render(); + log->debug(TAG, "egl_render: render done"); + + + log->debug(TAG, "egl_render: change state to idle"); + omx_eglrender->changeState(OMX_StateIdle); + + log->debug(TAG, "egl_render: flush input commands"); + omx_eglrender->flushInputCommands(); + log->debug(TAG, "egl_render: flush output commands"); + omx_eglrender->flushOutputCommands(); + log->debug(TAG, "egl_render: disable input"); + omx_eglrender->disableInput(false); + log->debug(TAG, "egl_render: disable output"); + omx_eglrender->disableOutput(false); + log->debug(TAG, "egl_render: deallocate output buffer header"); + omx_eglrender->deallocateEGLImageKHR(); + log->debug(TAG, "egl_render: deallocate input buffer header"); + omx_eglrender->deallocateInputBuffers(); + log->debug(TAG, "egl_render: change state to loaded"); + omx_eglrender->changeState(OMX_StateLoaded); +} diff --git a/imageomx2.h b/imageomx2.h new file mode 100644 index 0000000..8f8fb63 --- /dev/null +++ b/imageomx2.h @@ -0,0 +1,90 @@ +/* + Copyright 2021 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP. If not, see . +*/ + +/* + +TL;DR: + +The OMX image_decode "hardware" is buggy when given an image with height not divisible +by 16. Extensive testing shows there is no way to use tunneled communication between +image_decode and egl_render when the image height is not divisible by 16 and guarantee +correct image display. + +ImageOMX1 mitigated this by cropping the image height to a value divisible by 16 +but this introduced a new race condition bug which corrupted some images. +ImageOMX1 works most of the time because it happens to pull down the tunnel before +it has finished. If a 0.1s delay is inserted after the + if (!video->ChangeComponentState(omx_egl_render,OMX_StateExecuting)) { +if block, it will break every image with height not divisible by 16. + +The solution is to not use tunneled communication between image_decode and +egl_render. If the input image height is not divisible by 16, set the slice height +to 16. Then feed egl_render separately. + +This ImageOMX2 is a rework of my clean-implementation test programs used to debug +the problem. Future work: Fix ImageOMX1 or swap over to ImageOMX2 if it works +well enough? (Is it even possible to run image_decode/egl_render without integration +with VideoOMX?) + +*/ + +#ifndef IMAGEOMX2_H +#define IMAGEOMX2_H + +#include "osdvector.h" +#include "osdopenvg.h" + +class LogNT; +class OMX_Image_Decode; +class OMX_EGL_Render; + +class ImageOMX2 : public OsdVector::PictureDecoder +{ +// PictureDecoder Interface + public: + ImageOMX2(/*EGLDisplay egl_display*/); + virtual ~ImageOMX2() {}; + + void init(); + void shutdown(); + + unsigned char* decodePicture(LoadIndex index, unsigned char* buffer, unsigned int length, bool freemem) noexcept; + bool getDecodedPicture(struct OsdVector::PictureInfo& pict_inf); + void freeReference(void*) {}; + +// Internal stuff + + private: + LogNT* log{}; + //EGLDisplay egl_display; + OMX_Image_Decode* omx_imagedecode{}; + OMX_EGL_Render* omx_eglrender{}; + + // State data for the decode + struct OsdVector::PictureInfo currentDecode{}; // There's only one user so no locking required + bool currentDecodeValid{}; + + // Internal functions + + void reinit(); + void decode(char* inputData, int inputDataSize, char** outRawData, int* outRawDataSize, int* outWidth, int* outHeight, int* outStride); // throws + void render(char* inputData, int inputDataSize, int imageWidth, int imageHeight, int imageStride); // throws +}; + +#endif diff --git a/objects.mk b/objects.mk index fb4bbf1..b6370b9 100644 --- a/objects.mk +++ b/objects.mk @@ -23,7 +23,8 @@ OBJ_COMMON = util.o control.o thread.o timers.o i18n.o udp4.o udp6.o vdpc.o tcp. OBJ_RASPBERRY = main.o threadp.o osdopenvg.o \ ledraspberry.o videoomx.o audioomx.o imageomx.o \ - wjpegsimple.o inputlinux.o inputcec.o + wjpegsimple.o inputlinux.o inputcec.o \ + omx/omx.o omx/omximagedecode.o omx/omxeglrender.o imageomx2.o OBJ_WINDOWS = winmain.o threadwin.o inputwin.o ledwin.o videowin.o \ audiowin.o windowsosd.o dsallocator.o dssourcefilter.o dssourcepin.o \ diff --git a/omx/omx.cc b/omx/omx.cc new file mode 100644 index 0000000..68225ac --- /dev/null +++ b/omx/omx.cc @@ -0,0 +1,489 @@ +/* + Copyright 2021 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP. If not, see . +*/ + +#include "../log.h" +#include "omximagedecode.h" +#include "omxeglrender.h" + +#include "omx.h" + +static const char* TAG = "OMX"; + + +// Static variable storage +OMX_Image_Decode* OMX::omx_image_decode{}; +OMX_EGL_Render* OMX::omx_egl_render{}; + +OMX_HANDLETYPE OMX::handle_image_decode{}; +OMX_HANDLETYPE OMX::handle_egl_render{}; + +std::thread OMX::eventsProcessorThread; +std::mutex OMX::eventsProcessorMutex; +std::condition_variable OMX::eventsProcessorCond; +std::mutex OMX::eventsMutex; +Events OMX::events; +bool OMX::eventsProcessorWake{}; +bool OMX::eventsProcessorShutdownNow{}; +int OMX::eventsProcessorUsageCount{}; +EventWaiters OMX::eventWaiters; +std::mutex OMX::eventWaitersMutex; + + +// Main static callbacks + +OMX_ERRORTYPE OMX::scb_EventHandler( + OMX_IN OMX_HANDLETYPE handle, OMX_IN OMX_PTR appdata, OMX_IN OMX_EVENTTYPE event_type, + OMX_IN OMX_U32 data1, OMX_IN OMX_U32 data2, OMX_IN OMX_PTR event_data) +{ + LogNT::getInstance()->debug("OMXSCB", "EventHandler {} {} {} {}", static_cast(handle_image_decode), static_cast(omx_image_decode), static_cast(handle_egl_render), static_cast(omx_egl_render)); + if (handle == handle_image_decode) + return omx_image_decode->cb_EventHandler(handle, appdata, event_type, data1, data2, event_data); + else if (handle == handle_egl_render) + return omx_egl_render->cb_EventHandler(handle, appdata, event_type, data1, data2, event_data); + + return OMX_ErrorNone; +} + +OMX_ERRORTYPE OMX::scb_EmptyBufferDone(OMX_IN OMX_HANDLETYPE handle, OMX_IN OMX_PTR appdata, OMX_IN OMX_BUFFERHEADERTYPE* buffer) +{ + LogNT::getInstance()->debug("OMXSCB", "EmptyBufferDone {} {} {} {}", static_cast(handle_image_decode), static_cast(omx_image_decode), static_cast(handle_egl_render), static_cast(omx_egl_render)); + if (handle == handle_image_decode) + return omx_image_decode->cb_EmptyBufferDone(handle, appdata, buffer); + else if (handle == handle_egl_render) + return omx_egl_render->cb_EmptyBufferDone(handle, appdata, buffer); + + return OMX_ErrorNone; +} + +OMX_ERRORTYPE OMX::scb_FillBufferDone(OMX_IN OMX_HANDLETYPE handle, OMX_IN OMX_PTR appdata, OMX_IN OMX_BUFFERHEADERTYPE* buffer) +{ + LogNT::getInstance()->debug("OMXSCB", "FillBufferDone {} {} {} {}", static_cast(handle_image_decode), static_cast(omx_image_decode), static_cast(handle_egl_render), static_cast(omx_egl_render)); + if (handle == handle_image_decode) + return omx_image_decode->cb_FillBufferDone(handle, appdata, buffer); + else if (handle == handle_egl_render) + return omx_egl_render->cb_FillBufferDone(handle, appdata, buffer); + return OMX_ErrorNone; +} + +// --- + +OMX::OMX() +{ + log = LogNT::getInstance(); +} + +OMX::~OMX() +{ +} + +// bool OMX::OMX_Master_Init() +// { +// OMX_ERRORTYPE error; +// error = OMX_Init(); +// if (error != OMX_ErrorNone) return false; +// return true; +// } + +OMX_ERRORTYPE OMX::cb_EventHandler(OMX_IN OMX_HANDLETYPE handle, OMX_IN OMX_PTR appdata, OMX_IN OMX_EVENTTYPE event_type, OMX_IN OMX_U32 data1, OMX_IN OMX_U32 data2, OMX_IN OMX_PTR event_data) +{ + //Log* l = Log::getInstance(); + log->debug(TAG, "CB: eventHandler {} {} {:#x} {:#x} {}", static_cast(handle), event_type, data1, data2, static_cast(event_data)); + + eventsMutex.lock(); + Event* incomingEvent = new Event{.appdata = appdata, .eventType = event_type, .data1 = data1, .data2 = data2, .event_data = event_data}; + events.push_back(incomingEvent); + eventsMutex.unlock(); + + eventsProcessorMutex.lock(); + eventsProcessorWake = true; + eventsProcessorMutex.unlock(); + eventsProcessorCond.notify_one(); + + log->debug(TAG, "eventHandler end"); + return OMX_ErrorNone; +} + +void OMX::initEventsProcessing() +{ + eventsProcessorMutex.lock(); + + if (eventsProcessorUsageCount == 0) + { + eventsProcessorUsageCount = 1; + + eventsProcessorThread = std::thread( [&] + { + eventsProcessorMutex.lock(); + eventsProcessorMutex.unlock(); + eventsProcessorLoop(); + }); + } + else + { + ++eventsProcessorUsageCount; + } + + eventsProcessorMutex.unlock(); +} + +void OMX::stopEventsProcessing() +{ + eventsProcessorMutex.lock(); + + --eventsProcessorUsageCount; + + if (eventsProcessorUsageCount == 0) + { + // stop event loop + eventsProcessorWake = true; + eventsProcessorShutdownNow = true; + eventsProcessorCond.notify_one(); + eventsProcessorMutex.unlock(); + eventsProcessorThread.join(); + } + else + { + eventsProcessorMutex.unlock(); + } +} + +void OMX::eventsProcessorLoop() +{ + LogNT* log = LogNT::getInstance(); + std::unique_lock ul(eventsProcessorMutex); // locks + + while(1) + { + // locked + if (eventsProcessorWake) + { + eventsProcessorWake = false; + ul.unlock(); + + if (eventsProcessorShutdownNow) return; + + eventsMutex.lock(); + for (Events::iterator eventIterator = events.begin(); eventIterator != events.end(); ) // for each event received from OMX + { + Event* event = *eventIterator; + + if (event->isNew) + { + event->isNew = false; + + log->debug(TAG, "Event Processor Loop - event:"); + + if (event->eventType == 0) + log->debug(TAG, "* eventType: OMX_EventCmdComplete"); + else if (event->eventType == 1) + log->debug(TAG, "* eventType: OMX_EventError"); + else if (event->eventType == 2) + log->debug(TAG, "* eventType: OMX_EventMark"); + else if (event->eventType == 3) + log->debug(TAG, "* eventType: OMX_EventPortSettingsChanged"); + else if (event->eventType == 4) + log->debug(TAG, "* eventType: OMX_EventBufferFlag"); + else + log->debug(TAG, "* eventType: {:#x}", event->eventType); + + if (event->eventType == OMX_EventCmdComplete) + { + if (event->data1 == OMX_CommandStateSet) + log->debug(TAG, "* OMX_COMMANDTYPE: OMX_CommandStateSet"); + if (event->data1 == OMX_CommandFlush) + log->debug(TAG, "* OMX_COMMANDTYPE: OMX_CommandFlush"); + if (event->data1 == OMX_CommandPortDisable) + log->debug(TAG, "* OMX_COMMANDTYPE: OMX_CommandPortDisable"); + if (event->data1 == OMX_CommandPortEnable) + log->debug(TAG, "* OMX_COMMANDTYPE: OMX_CommandPortEnable"); + if (event->data1 == OMX_CommandMarkBuffer) + log->debug(TAG, "* OMX_COMMANDTYPE: OMX_CommandMarkBuffer"); + + if (event->data1 == OMX_CommandStateSet) + { + log->debug(TAG, "* new state: {}", event->data2); + } + + if ( (event->data1 == OMX_CommandPortEnable) || (event->data1 == OMX_CommandPortDisable) ) + { + log->debug(TAG, "* port en/disabled: {}", event->data2); + } + } + else if (event->eventType == OMX_EventBufferFlag) + { + log->debug(TAG, "* Port: {:#x} {}", event->data1, event->data1); + } + else if (event->eventType == OMX_EventError) + { + log->debug(TAG, "* Error code: {:#x}", event->data1); + + if (event->data1 == OMX_ErrorPortUnpopulated) + log->debug(TAG, "* = : OMX_ErrorPortUnpopulated"); + if (event->data1 == OMX_ErrorInsufficientResources) + log->debug(TAG, "* =: OMX_ErrorInsufficientResources"); + } + } // end if event is new (then print it out) + + + // Find matching EventWaiter + + eventWaitersMutex.lock(); + + // Now have EventsMutex and eventWaitersMutex + + EventWaiters::iterator eventWaiterIterator; + EventWaiter* eventWaiter{}; + + for (eventWaiterIterator = eventWaiters.begin(); eventWaiterIterator < eventWaiters.end(); eventWaiterIterator++) + { + // structure here not great yet + // not looking at eventWaiter->eventType?? + + eventWaiter = *eventWaiterIterator; + + if (event->eventType == OMX_EventCmdComplete) + { + if ( ((event->data1 == OMX_CommandPortEnable) && (eventWaiter->command == OMX_CommandPortEnable)) + || ((event->data1 == OMX_CommandPortDisable) && (eventWaiter->command == OMX_CommandPortDisable)) ) + { + if (event->data2 == eventWaiter->port) + { + // Found! + eventWaiter->receivedEvent = event; + break; + } + } + else if ((event->data1 == OMX_CommandStateSet) && (eventWaiter->command == OMX_CommandStateSet)) + { + if (event->data2 == eventWaiter->newState) + { + // Found! + eventWaiter->receivedEvent = event; + break; + } + } + else if ((event->data1 == OMX_CommandFlush) && (eventWaiter->command == OMX_CommandFlush)) + { + if (event->data2 == eventWaiter->flushPort) + { + // Found! + eventWaiter->receivedEvent = event; + break; + } + } + } + else if (event->eventType == OMX_EventPortSettingsChanged) + { + if ( (eventWaiter->eventType == OMX_EventPortSettingsChanged) + && (event->data1 == eventWaiter->port) ) + { + // Found! + eventWaiter->receivedEvent = event; + break; + } + } + } // end of each eventWaiter loop + + + // Now obv we have an event, but do we have an eventWaiter? If not, advance and continue + if (!eventWaiter || (eventWaiterIterator == eventWaiters.end())) // A) none in the loop at all, B) none matched + { + eventWaitersMutex.unlock(); + + eventIterator++; + continue; + } + + // Now we have an event (event, eventIterator) and a found eventWaiter (eventWaiter, eventWaiterIterator) + + // Remove the eventWaiter from the deque + eventWaiters.erase(eventWaiterIterator); + eventWaitersMutex.unlock(); + + // Remove the event from the deque + eventIterator = events.erase(eventIterator); + + // Iterators are now dead. + // Now we have an event and an eventWaiter + // Still have the EventsMutex but we have released the eventWaitersMutex. + + log->debug(TAG, "This event {} found an event waiter for it {}", static_cast(event), static_cast(eventWaiter)); + + + if (eventWaiter->waiting) + { + // FIXME look at whether eventWaiter could miss this signal + log->debug(TAG, "Notifying eventWaiter: {} on cond {}", static_cast(eventWaiter), static_cast(&eventWaiter->cond)); + eventWaiter->cond.notify_one(); + } + else + { + log->debug(TAG, "EventWaiter not waiting. Deleting {}", static_cast(eventWaiter)); + delete event; + delete eventWaiter; + } + + log->debug(TAG, "Done processing eventWaiter, goaround events loop"); + + + } // end for each received event + + // FIXME If there are events still in events that have not been matched up and sent + // to an eventWaiter, or that were not marked as waiting so we deleted it here, then this is a bug + eventsMutex.unlock(); + + // Reacquire eventsProcessor lock + ul.lock(); + } // end if event processor wake + + // locked + + eventsProcessorCond.wait(ul, [&] { return eventsProcessorWake; }); + + // relocked, loop + } // while(1) +} + +void OMX::enablePort(OMX_U32 port, bool enable, bool wait) +{ + OMX_ERRORTYPE error; + + OMX_PARAM_PORTDEFINITIONTYPE pdt; + memset(&pdt, 0, sizeof(pdt)); + pdt.nSize = sizeof(pdt); + pdt.nVersion.nVersion = OMX_VERSION; + pdt.nPortIndex = port; + error = OMX_GetParameter(componentHandle, OMX_IndexParamPortDefinition, &pdt); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_GetParameter in OMX::enablePort()", error); + + if (pdt.bEnabled == enable) return; // already in requested state + + std::unique_lock ul(eventWaitersMutex); + + OMX_COMMANDTYPE command = enable ? OMX_CommandPortEnable : OMX_CommandPortDisable; + + log->debug(TAG, "en/disablePort: port: {:#x}, command: {:#x}", port, command); + error = OMX_SendCommand(componentHandle, command, port, 0); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_SendCommand in OMX::enablePort()", error); + log->debug(TAG, "en/disablePort: port: {:#x}, command: {:#x} done", port, command); + + EventWaiter* eventWaiter = new EventWaiter(); + eventWaiter->waiting = wait; + eventWaiter->command = command; + eventWaiter->port = port; + + eventWaiters.push_back(eventWaiter); + + if (!wait) return; // && unlock. eventWaiter becomes owned by eventProcessor thread + + log->debug(TAG, "en/disablePort: Going to wait on cond {}", static_cast(&eventWaiter->cond)); + eventWaiter->cond.wait(ul); // sleep this thread + log->debug(TAG, "en/disablePort: Back from wait on cond {}", static_cast(&eventWaiter->cond)); + ul.unlock(); + + + if (eventWaiter->receivedEvent) + { + // The event processor thread received an event and saved it here + log->debug(TAG, "received event!"); + delete eventWaiter->receivedEvent; + delete eventWaiter; + } + else + { + delete eventWaiter; // This is ours, at least we can clean this up + throw OMX_Exception("Failed to receive event!", 0); + } +} + +void OMX::changeState(OMX_STATETYPE reqState, bool wait) +{ + OMX_STATETYPE currentState; + OMX_GetState(componentHandle, ¤tState); + log->debug(TAG, "Current state: {}", currentState); + if (currentState == reqState) + { + log->debug(TAG, "changeState return immediately, already in reqState"); + return; + } + + std::unique_lock ul(eventWaitersMutex); + OMX_ERRORTYPE error = OMX_SendCommand(componentHandle, OMX_CommandStateSet, reqState, 0); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_SendCommand in OMX::changeState()", error); + + EventWaiter* eventWaiter = new EventWaiter(); + eventWaiter->waiting = wait; + eventWaiter->command = OMX_CommandStateSet; + eventWaiter->newState = reqState; + + eventWaiters.push_back(eventWaiter); + + if (!wait) return; // && unlock. eventWaiter becomes owned by eventProcessor thread + + log->debug(TAG, "changeState: Going to wait on cond {}", static_cast(&eventWaiter->cond)); + eventWaiter->cond.wait(ul); // sleep this thread + log->debug(TAG, "changeState: Back from wait on cond {}", static_cast(&eventWaiter->cond)); + ul.unlock(); + + if (eventWaiter->receivedEvent) + { + // The event processor thread received an event and saved it here + log->debug(TAG, "received event!"); + delete eventWaiter->receivedEvent; + delete eventWaiter; + } + else + { + delete eventWaiter; // This is ours, at least we can clean this up + throw OMX_Exception("Failed to receive event!", 0); + } +} + +void OMX::flushCommands(OMX_U32 port, bool wait) +{ + std::unique_lock ul(eventWaitersMutex); + + OMX_ERRORTYPE error = OMX_SendCommand(componentHandle, OMX_CommandFlush, port, NULL); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_SendCommand in OMX::flushCommands()", error); + + EventWaiter* eventWaiter = new EventWaiter(); + eventWaiter->waiting = wait; + eventWaiter->command = OMX_CommandFlush; + eventWaiter->flushPort = port; + + eventWaiters.push_back(eventWaiter); + + if (!wait) return; // && unlock. eventWaiter becomes owned by eventProcessor thread + + log->debug(TAG, "flushCommands: Going to wait on cond {}", static_cast(&eventWaiter->cond)); + eventWaiter->cond.wait(ul); // sleep this thread + log->debug(TAG, "flushCommands: Back from wait on cond {}", static_cast(&eventWaiter->cond)); + ul.unlock(); + + if (!eventWaiter->receivedEvent) + { + delete eventWaiter; + throw OMX_Exception("Error waiting for event in flushCommands", 0); + } + + // The event processor thread received an event and saved it here + log->debug(TAG, "received event!"); + delete eventWaiter->receivedEvent; + delete eventWaiter; +} diff --git a/omx/omx.h b/omx/omx.h new file mode 100644 index 0000000..17a5669 --- /dev/null +++ b/omx/omx.h @@ -0,0 +1,171 @@ +/* + Copyright 2021 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP. If not, see . +*/ + +#ifndef OMX_H +#define OMX_H + +#include +#include +#include +#include +#include + +#include + +#define OMX_SKIP64BIT +#include +#include +#include +#include + +struct Event +{ + OMX_PTR appdata; + OMX_EVENTTYPE eventType; + OMX_U32 data1; + OMX_U32 data2; + OMX_PTR event_data; + + bool isNew{true}; +}; + +struct EventWaiter +{ + bool waiting; + OMX_EVENTTYPE eventType; + OMX_COMMANDTYPE command; + OMX_U32 port; + OMX_STATETYPE newState; + OMX_U32 flushPort; + std::condition_variable cond; + Event* receivedEvent{}; +}; + +struct BufferWithOutputPort +{ + OMX_BUFFERHEADERTYPE* bufhead; + std::condition_variable cond; + std::mutex mutex; + bool done{}; +}; + +using Events = std::deque; +using EventWaiters = std::deque; + +class OMX_Exception : public std::exception +{ + public: + OMX_Exception(const char* tdesc, uint32_t terrCode) : desc(tdesc), errCode(terrCode) {} + const char* what() const noexcept { return desc; }; + uint32_t errorCode() { return errCode; } + private: + const char* desc{}; + uint32_t errCode{}; +}; + + +class LogNT; +class OMX_Image_Decode; +class OMX_EGL_Render; + +class OMX +{ + public: + OMX(); + ~OMX(); + + //static bool OMX_Master_Init(); + + virtual bool init()=0; + virtual void shutdown()=0; + + void enablePort(OMX_U32 port, bool enable, bool wait); // throws + void changeState(OMX_STATETYPE type, bool wait = true); // throws + + // Static event callbacks + + static OMX_ERRORTYPE scb_EventHandler( + OMX_IN OMX_HANDLETYPE handle, OMX_IN OMX_PTR appdata, OMX_IN OMX_EVENTTYPE event_type, + OMX_IN OMX_U32 data1, OMX_IN OMX_U32 data2, OMX_IN OMX_PTR event_data); + + static OMX_ERRORTYPE scb_FillBufferDone( + OMX_IN OMX_HANDLETYPE handle, OMX_IN OMX_PTR appdata, OMX_IN OMX_BUFFERHEADERTYPE* buffer); + + static OMX_ERRORTYPE scb_EmptyBufferDone( + OMX_IN OMX_HANDLETYPE handle, OMX_IN OMX_PTR appdata, OMX_IN OMX_BUFFERHEADERTYPE* buffer); + + static constexpr const char* componentName_image_decode = "OMX.broadcom.image_decode"; + static constexpr const char* componentName_egl_render = "OMX.broadcom.egl_render"; + + protected: + LogNT* log; + + void flushCommands(OMX_U32 port, bool wait = true); // throws + + // static singleton object pointers + static OMX_Image_Decode* omx_image_decode; + static OMX_EGL_Render* omx_egl_render; + + // static component handles provided by OMX + static OMX_HANDLETYPE handle_image_decode; + static OMX_HANDLETYPE handle_egl_render; + + // A non-static copy of the above + OMX_HANDLETYPE componentHandle; + + // In-object callbacks + + virtual OMX_ERRORTYPE cb_EventHandler( + OMX_IN OMX_HANDLETYPE handle, OMX_IN OMX_PTR appdata, OMX_IN OMX_EVENTTYPE event_type, + OMX_IN OMX_U32 data1, OMX_IN OMX_U32 data2, OMX_IN OMX_PTR event_data) final; + + virtual OMX_ERRORTYPE cb_FillBufferDone( + OMX_IN OMX_HANDLETYPE handle, OMX_IN OMX_PTR appdata, OMX_IN OMX_BUFFERHEADERTYPE* buffer)=0; + + virtual OMX_ERRORTYPE cb_EmptyBufferDone( + OMX_IN OMX_HANDLETYPE handle, OMX_IN OMX_PTR appdata, OMX_IN OMX_BUFFERHEADERTYPE* buffer)=0; + + /* Incoming events processing stuff + * + * Events are handled entirely by the base class which runs a thread to match incoming + * events with a class and a particular sleeping calling thread. It will wake the correct + * call, if wait = true in the original call. If not it will just delete the incoming event. + * + * FillBufferDone and EmptyBufferDone will call the derived object's functions. + */ + + static void initEventsProcessing(); + static void stopEventsProcessing(); + + static Events events; + static std::mutex eventsMutex; + static std::mutex eventsProcessorMutex; + static std::thread eventsProcessorThread; + static std::condition_variable eventsProcessorCond; + static bool eventsProcessorWake; + static bool eventsProcessorShutdownNow; + static int eventsProcessorUsageCount; + static void eventsProcessorLoop(); + + static EventWaiters eventWaiters; + static std::mutex eventWaitersMutex; + +}; + +#endif diff --git a/omx/omxeglrender.cc b/omx/omxeglrender.cc new file mode 100644 index 0000000..3533863 --- /dev/null +++ b/omx/omxeglrender.cc @@ -0,0 +1,336 @@ +/* + Copyright 2021 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP. If not, see . +*/ + +#include +#include +#include + +#include "../log.h" + +#include "omxeglrender.h" + +static const char* TAG = "OMX_EGL_Render"; + + +OMX_EGL_Render::OMX_EGL_Render() +{ + omx_egl_render = this; +} + +OMX_EGL_Render::~OMX_EGL_Render() +{ + omx_image_decode = NULL; + handle_egl_render = 0; +} + +bool OMX_EGL_Render::init() +{ + initEventsProcessing(); + + OMX_CALLBACKTYPE callbacks = {&scb_EventHandler, &scb_EmptyBufferDone, &scb_FillBufferDone}; + + char* componentName; + asprintf(&componentName, "%s", componentName_egl_render); + OMX_ERRORTYPE error = OMX_GetHandle(&componentHandle, componentName, NULL, &callbacks); + free(componentName); + log->debug(TAG, "HANDLE ALLOC: OMX_EGL_Render: {} error: {:#x}", static_cast(componentHandle), error); + if (error != OMX_ErrorNone) return false; + + handle_egl_render = componentHandle; + + OMX_PORT_PARAM_TYPE p_param; + memset(&p_param, 0, sizeof(p_param)); + p_param.nSize = sizeof(p_param); + p_param.nVersion.nVersion = OMX_VERSION; + + error = OMX_GetParameter(componentHandle, OMX_IndexParamVideoInit, &p_param); + if (error != OMX_ErrorNone) return false; + + inputPort = p_param.nStartPortNumber; + outputPort = p_param.nStartPortNumber + 1; + + log->debug(TAG, "IN: {}, OUT: {}", inputPort, outputPort); + return true; +} + +void OMX_EGL_Render::shutdown() +{ + stopEventsProcessing(); + OMX_ERRORTYPE error = OMX_FreeHandle(componentHandle); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_FreeHandle in OMX_EGL_Render::shutdown()", error); +} + +void OMX_EGL_Render::prepareInputPort(OMX_U32 frameWidth, OMX_U32 frameHeight, OMX_U32 stride) +{ + log->debug(TAG, "prepareInputPort"); + + // Calculate a slice height. It must be divisible by 16 and >= frameHeight + // Temporarily use sliceHeight for remainder + int sliceHeight = frameHeight % 16; + if (sliceHeight == 0) + sliceHeight = frameHeight; + else + sliceHeight = frameHeight + 16 - sliceHeight; + + // Buffer size IS THE SLICE HEIGHT multiplied by the stride + inputPortBufferSize = sliceHeight * stride; + + + OMX_PARAM_PORTDEFINITIONTYPE portConfig; + memset(&portConfig, 0, sizeof(portConfig)); + portConfig.nSize = sizeof(portConfig); + portConfig.nVersion.nVersion = OMX_VERSION; + portConfig.nPortIndex = inputPort; + + OMX_ERRORTYPE error = OMX_GetParameter(componentHandle, OMX_IndexParamPortDefinition, &portConfig); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_GetParameter in OMX_EGL_Render::prepareInputPort()", error); + + log->debug(TAG, "Recv from get:"); + log->debug(TAG, "nPortIndex: {}", portConfig.nPortIndex); + log->debug(TAG, "eDir: {}", portConfig.eDir); + log->debug(TAG, "buffer count actual: {}", portConfig.nBufferCountActual); + log->debug(TAG, "buffer count min: {}", portConfig.nBufferCountMin); + log->debug(TAG, "nBufferSize: {}", portConfig.nBufferSize); + log->debug(TAG, "bEnabled: {}", portConfig.bEnabled); + log->debug(TAG, "bPopulated: {}", portConfig.bPopulated); + log->debug(TAG, "eDomain: {}", portConfig.eDomain); + + portConfig.nBufferCountActual = 1; + portConfig.nBufferSize = inputPortBufferSize; + portConfig.format.video.eCompressionFormat = OMX_VIDEO_CodingUnused; + portConfig.format.video.eColorFormat = OMX_COLOR_Format32bitABGR8888; + portConfig.format.video.nFrameWidth = frameWidth; + portConfig.format.video.nStride = stride; + portConfig.format.video.nFrameHeight = frameHeight; + portConfig.format.video.nSliceHeight = sliceHeight; + + log->debug(TAG, "Setting buf count actual: {}, buf size: {}", portConfig.nBufferCountActual, portConfig.nBufferSize); + log->debug(TAG, "calling setParameter: w {} h {} str {} sli {}", portConfig.format.video.nFrameWidth, portConfig.format.video.nFrameHeight, + portConfig.format.video.nStride, portConfig.format.video.nSliceHeight); + + error = OMX_SetParameter(componentHandle, OMX_IndexParamPortDefinition, &portConfig); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_SetParameter in OMX_EGL_Render::prepareInputPort()", error); + + printPortSettings(false); +} + +void OMX_EGL_Render::prepareOutputPort(EGLDisplay egldisplay) +{ + OMX_PARAM_PORTDEFINITIONTYPE port_def_type; + memset(&port_def_type, 0, sizeof(port_def_type)); + port_def_type.nSize = sizeof(port_def_type); + port_def_type.nVersion.nVersion = OMX_VERSION; + port_def_type.nPortIndex = outputPort; + + OMX_ERRORTYPE error = OMX_GetParameter(componentHandle, OMX_IndexParamPortDefinition, &port_def_type); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_GetParameter in OMX_EGL_Render::prep()", error); + + port_def_type.nBufferCountActual = 1; + port_def_type.format.video.pNativeWindow = egldisplay; + + error = OMX_SetParameter(componentHandle, OMX_IndexParamPortDefinition, &port_def_type); // set port def: buffer count actual = 1 + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_SetParameter in OMX_EGL_Render::prep()", error); +} + +void OMX_EGL_Render::allocateInputBuffers(char* data) +{ + inBuffer1 = NULL; + + log->debug(TAG, "Calling OMX_UseBuffer with data = {}", static_cast(data)); + OMX_ERRORTYPE error = OMX_UseBuffer(componentHandle, &inBuffer1, inputPort, static_cast(0), inputPortBufferSize, reinterpret_cast(data)); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_UseBuffer in OMX_EGL_Render::allocateInputBuffers()", error); + + log->debug(TAG, "OMX_UseBuffer:"); + log->debug(TAG, " nSize = {}", inBuffer1->nSize); + log->debug(TAG, " pBuffer = {}", static_cast(inBuffer1->pBuffer)); + log->debug(TAG, " nAllocLen = {}", inBuffer1->nAllocLen); + log->debug(TAG, " nFilledLen = {}", inBuffer1->nFilledLen); + log->debug(TAG, " nOffset = {}", inBuffer1->nOffset); + log->debug(TAG, " nInputPortIndex = {}", inBuffer1->nInputPortIndex); + log->debug(TAG, " nOutputPortIndex = {}", inBuffer1->nOutputPortIndex); + log->debug(TAG, " nFlags = {:#x}", inBuffer1->nFlags); + + printPortSettings(false); +} + +void OMX_EGL_Render::allocateEGLImageKHR(EGLImageKHR eglimagekhr) +{ + OMX_ERRORTYPE error = OMX_UseEGLImage(componentHandle, &eglRenderOutputBufferHeader, outputPort, this, static_cast(eglimagekhr)); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_UseEGLImage in OMX_EGL_Render::allocateEGLImageKHR()", error); + + printPortSettings(true); +} + +void OMX_EGL_Render::deallocateInputBuffers() +{ + OMX_ERRORTYPE error = OMX_FreeBuffer(componentHandle, inputPort, inBuffer1); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_FreeBuffer in OMX_EGL_Render::deallocateInputBuffers()", error); + + inBuffer1 = NULL; +} + +void OMX_EGL_Render::deallocateEGLImageKHR() +{ + OMX_ERRORTYPE error = OMX_FreeBuffer(componentHandle, outputPort, eglRenderOutputBufferHeader); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_FreeBuffer in OMX_EGL_Render::deallocateEGLImageKHR()", error); + + eglRenderOutputBufferHeader = NULL; +} + +void OMX_EGL_Render::sendToInput(char* data, int dataSize) +{ + log->debug(TAG, "Starting sendToInput {}", dataSize); + + inBuffer1->nFilledLen = dataSize; + inBuffer1->nOffset = 0; + inBuffer1->nTimeStamp = { 0, 0 }; + inBuffer1->pAppPrivate = static_cast(0); + inBuffer1->nFlags |= OMX_BUFFERFLAG_EOS; + inBuffer1->pBuffer = reinterpret_cast(data); + + log->debug(TAG, "calling emptythisbuffer"); + OMX_ERRORTYPE error = OMX_EmptyThisBuffer(componentHandle, inBuffer1); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_EmptyThisBuffer in OMX_EGL_Render::sendToInput()", error); + log->debug(TAG, "sendToInput done"); +} + +void OMX_EGL_Render::render() +{ + struct BufferWithOutputPort* bufferWithOutputPort{}; + + try + { + bufferWithOutputPort = new BufferWithOutputPort(); + bufferWithOutputPort->bufhead = eglRenderOutputBufferHeader; + eglRenderOutputBufferHeader->pAppPrivate = static_cast(bufferWithOutputPort); + + log->debug(TAG, "render: egl buffer header:"); + log->debug(TAG, "{}", static_cast(eglRenderOutputBufferHeader->pBuffer)); + log->debug(TAG, "{}", eglRenderOutputBufferHeader->nAllocLen); + log->debug(TAG, "{}", eglRenderOutputBufferHeader->nFilledLen); + log->debug(TAG, "{}", eglRenderOutputBufferHeader->nOffset); + log->debug(TAG, "{:#x}", eglRenderOutputBufferHeader->nFlags); + log->debug(TAG, "{}", eglRenderOutputBufferHeader->nInputPortIndex); + log->debug(TAG, "{}", eglRenderOutputBufferHeader->nOutputPortIndex); + log->debug(TAG, "{}", static_cast(eglRenderOutputBufferHeader->pAppPrivate)); + + + std::unique_lock ul(bufferWithOutputPort->mutex); + + log->debug(TAG, "calling fillthisbuffer. bufhead: {}, BufferWithOutput: {}", static_cast(eglRenderOutputBufferHeader), static_cast(bufferWithOutputPort)); + + OMX_ERRORTYPE error = OMX_FillThisBuffer(componentHandle, eglRenderOutputBufferHeader); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_FillThisBuffer in OMX_EGL_Render::render()", error); + + log->debug(TAG, "done fillthisbuffer. bufhead: {}, BufferWithOutput: {}", static_cast(eglRenderOutputBufferHeader), static_cast(bufferWithOutputPort)); + + bufferWithOutputPort->cond.wait(ul, [bufferWithOutputPort] { return bufferWithOutputPort->done; }); + ul.unlock(); + + log->debug(TAG, "receiveFromOutputPort signalled"); + + log->debug(TAG, "receiveFromOutput:"); + log->debug(TAG, " nSize = {}", eglRenderOutputBufferHeader->nSize); + log->debug(TAG, " nAllocLen = {}", eglRenderOutputBufferHeader->nAllocLen); + log->debug(TAG, " nFilledLen = {}", eglRenderOutputBufferHeader->nFilledLen); + log->debug(TAG, " nOffset = {}", eglRenderOutputBufferHeader->nOffset); + log->debug(TAG, " nInputPortIndex = {}", eglRenderOutputBufferHeader->nInputPortIndex); + log->debug(TAG, " nOutputPortIndex = {}", eglRenderOutputBufferHeader->nOutputPortIndex); + log->debug(TAG, " nFlags = {:#x}", eglRenderOutputBufferHeader->nFlags); + + delete bufferWithOutputPort; + bufferWithOutputPort = NULL; + } + catch(...) + { + if (bufferWithOutputPort) + { + free(bufferWithOutputPort); + bufferWithOutputPort = NULL; + } + throw; + } +} + +void OMX_EGL_Render::printPortSettings(bool which) +{ + OMX_PARAM_PORTDEFINITIONTYPE portSettings; + + memset(&portSettings, 0, sizeof(portSettings)); + portSettings.nSize = sizeof(portSettings); + portSettings.nVersion.nVersion = OMX_VERSION; + + if (which) + { + log->debug(TAG, "------ Port settings for OUTPUT:"); + portSettings.nPortIndex = outputPort; + } + else + { + log->debug(TAG, "------ Port settings for INPUT:"); + portSettings.nPortIndex = inputPort; + } + + OMX_ERRORTYPE error = OMX_GetParameter(componentHandle, OMX_IndexParamPortDefinition, &portSettings); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_GetParameter in OMX_EGL_Render::printPortSettings()", error); + + log->debug(TAG, " nPortIndex: {}", portSettings.nPortIndex); + log->debug(TAG, " buffer count min, actual, size: {} {} {}", portSettings.nBufferCountMin, portSettings.nBufferCountActual, portSettings.nBufferSize); + log->debug(TAG, " eDir: {}", portSettings.eDir); + log->debug(TAG, " bEnabled: {}", portSettings.bEnabled); + log->debug(TAG, " bPopulated: {}", portSettings.bPopulated); + log->debug(TAG, " eDomain: {}", portSettings.eDomain); + + log->debug(TAG, " cMIMEType: {}", static_cast(portSettings.format.video.cMIMEType)); + log->debug(TAG, " pNativeRender: {}", static_cast(portSettings.format.video.pNativeRender)); + log->debug(TAG, " nFrameWidth: {}", portSettings.format.video.nFrameWidth); + log->debug(TAG, " nFrameHeight: {}", portSettings.format.video.nFrameHeight); + log->debug(TAG, " nStride: {}", portSettings.format.video.nStride); + log->debug(TAG, " nSliceHeight: {}", portSettings.format.video.nSliceHeight); + log->debug(TAG, " nBitrate: {}", portSettings.format.video.nBitrate); + log->debug(TAG, " xFramerate: {}", portSettings.format.video.xFramerate); + log->debug(TAG, " eCompressionFormat: {:#x}", portSettings.format.video.eCompressionFormat); + log->debug(TAG, " eColorFormat: {:#x}", portSettings.format.video.eColorFormat); +} + +OMX_ERRORTYPE OMX_EGL_Render::cb_EmptyBufferDone(OMX_IN OMX_HANDLETYPE /*hcomp*/, OMX_IN OMX_PTR /*appdata*/, OMX_IN OMX_BUFFERHEADERTYPE* buffer) +{ + log->debug(TAG, "CB: EmptyBufferDone"); + log->debug(TAG, " nSize = {}", buffer->nSize); + log->debug(TAG, " nAllocLen = {}", buffer->nAllocLen); + log->debug(TAG, " nFilledLen = {}", buffer->nFilledLen); + log->debug(TAG, " nOffset = {}", buffer->nOffset); + log->debug(TAG, " nInputPortIndex = {}", buffer->nInputPortIndex); + log->debug(TAG, " nOutputPortIndex = {}", buffer->nOutputPortIndex); + log->debug(TAG, " nFlags = {:#x}", buffer->nFlags); + return OMX_ErrorNone; +} + +OMX_ERRORTYPE OMX_EGL_Render::cb_FillBufferDone(OMX_IN OMX_HANDLETYPE hcomp, OMX_IN OMX_PTR appdata, OMX_IN OMX_BUFFERHEADERTYPE* buffer) +{ + log->debug(TAG, "CB: FillBufferDone, handle: {}, appdata: {}, buffer: {}", static_cast(hcomp), static_cast(appdata), static_cast(buffer)); + + struct BufferWithOutputPort* bufferWithOutputPort = static_cast(buffer->pAppPrivate); + bufferWithOutputPort->mutex.lock(); + bufferWithOutputPort->done = true; + bufferWithOutputPort->mutex.unlock(); + bufferWithOutputPort->cond.notify_one(); + + return OMX_ErrorNone; +} diff --git a/omx/omxeglrender.h b/omx/omxeglrender.h new file mode 100644 index 0000000..9e106f9 --- /dev/null +++ b/omx/omxeglrender.h @@ -0,0 +1,80 @@ +/* + Copyright 2021 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP. If not, see . +*/ + +#ifndef OMXEGLRENDER_H +#define OMXEGLRENDER_H + +#include +#include +#include + +#include "omx.h" + +class OMX_EGL_Render : public OMX +{ + friend class OMX; + + public: + OMX_EGL_Render(); + virtual ~OMX_EGL_Render(); + + bool init(); + void shutdown(); + + void prepareInputPort(OMX_U32 frameWidth, OMX_U32 frameHeight, OMX_U32 stride); // throws + void prepareOutputPort(EGLDisplay egldisplay); // throws + + void allocateInputBuffers(char* buffer); // throws + void allocateEGLImageKHR(EGLImageKHR eglimagekhr); // throws + + void deallocateInputBuffers(); // throws + void deallocateEGLImageKHR(); // throws + + void sendToInput(char* data, int dataSize); // throws + void render(); // throws + + void printPortSettings(bool which); // throws + + // Buffers callback overrides + + OMX_ERRORTYPE cb_FillBufferDone( + OMX_IN OMX_HANDLETYPE handle, OMX_IN OMX_PTR appdata, OMX_IN OMX_BUFFERHEADERTYPE* buffer) override; + + OMX_ERRORTYPE cb_EmptyBufferDone( + OMX_IN OMX_HANDLETYPE handle, OMX_IN OMX_PTR appdata, OMX_IN OMX_BUFFERHEADERTYPE* buffer) override; + + void enableInput(bool wait = true) { enablePort(inputPort, true, wait); } // throws + void disableInput(bool wait = true) { enablePort(inputPort, false, wait); } // throws + void enableOutput(bool wait = true) { enablePort(outputPort, true, wait); } // throws + void disableOutput(bool wait = true) { enablePort(outputPort, false, wait); } // throws + void flushInputCommands(bool wait = true) { flushCommands(inputPort, wait); } // throws + void flushOutputCommands(bool wait = true) { flushCommands(outputPort, wait); } // throws + + protected: + + OMX_U32 inputPort; + OMX_U32 outputPort; + + OMX_BUFFERHEADERTYPE* inBuffer1 = NULL; + OMX_BUFFERHEADERTYPE* eglRenderOutputBufferHeader; + + int inputPortBufferSize{}; +}; + +#endif diff --git a/omx/omximagedecode.cc b/omx/omximagedecode.cc new file mode 100644 index 0000000..06a4932 --- /dev/null +++ b/omx/omximagedecode.cc @@ -0,0 +1,353 @@ +/* + Copyright 2021 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP. If not, see . +*/ + +#include "../log.h" + +#include "omximagedecode.h" + +static const char* TAG = "OMX_Image_Decode"; + +OMX_Image_Decode::OMX_Image_Decode() +{ + omx_image_decode = this; +} + +OMX_Image_Decode::~OMX_Image_Decode() +{ + omx_image_decode = NULL; + handle_image_decode = 0; +} + +bool OMX_Image_Decode::init() +{ + initEventsProcessing(); + + OMX_CALLBACKTYPE callbacks = {&scb_EventHandler, &scb_EmptyBufferDone, &scb_FillBufferDone}; + + char* componentName; + asprintf(&componentName, "%s", componentName_image_decode); + OMX_ERRORTYPE error = OMX_GetHandle(&componentHandle, componentName, NULL, &callbacks); + free(componentName); + log->debug(TAG, "HANDLE ALLOC: omx_image_decode: {}", static_cast(componentHandle)); + if (error != OMX_ErrorNone) return false; + + handle_image_decode = componentHandle; + + OMX_PORT_PARAM_TYPE p_param; + memset(&p_param, 0, sizeof(p_param)); + p_param.nSize = sizeof(p_param); + p_param.nVersion.nVersion = OMX_VERSION; + + error = OMX_GetParameter(componentHandle, OMX_IndexParamImageInit, &p_param); + if (error != OMX_ErrorNone) return false; + + inputPort = p_param.nStartPortNumber; + outputPort = p_param.nStartPortNumber + 1; + + log->debug(TAG, "IN: {}, OUT: {}", inputPort, outputPort); + return true; +} + +void OMX_Image_Decode::shutdown() +{ + stopEventsProcessing(); + OMX_ERRORTYPE error = OMX_FreeHandle(componentHandle); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_FreeHandle in OMX_Image_Decode::shutdown()", error); +} + +void OMX_Image_Decode::prepareInputBuffers(int tdataSize) +{ + dataSize = tdataSize; + + OMX_PARAM_PORTDEFINITIONTYPE port_def_type; + memset(&port_def_type, 0, sizeof(port_def_type)); + port_def_type.nSize = sizeof(port_def_type); + port_def_type.nVersion.nVersion = OMX_VERSION; + port_def_type.nPortIndex = inputPort; + + OMX_ERRORTYPE error = OMX_GetParameter(componentHandle, OMX_IndexParamPortDefinition, &port_def_type); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_GetParameter in OMX_Image_Decode::prepareInputBuffers()", error); + + log->debug(TAG, "Defaults: bufferCountActual: {}, bufferCountMin: {}, nBufferSize: {}", + port_def_type.nBufferCountActual, port_def_type.nBufferCountMin, port_def_type.nBufferSize); + + port_def_type.nBufferCountActual = port_def_type.nBufferCountMin; + if (dataSize > port_def_type.nBufferSize) port_def_type.nBufferSize = dataSize; + + log->debug(TAG, "Setting buf count actual: {}, buf size: {}", + port_def_type.nBufferCountActual, port_def_type.nBufferSize); + + error = OMX_SetParameter(componentHandle, OMX_IndexParamPortDefinition, &port_def_type); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_SetParameter in OMX_Image_Decode::prepareInputBuffers()", error); +} + +void OMX_Image_Decode::setFormat() +{ + OMX_IMAGE_PARAM_PORTFORMATTYPE ft_type; + memset(&ft_type, 0, sizeof(ft_type)); + ft_type.nSize = sizeof(ft_type); + ft_type.nVersion.nVersion = OMX_VERSION; + ft_type.eCompressionFormat = OMX_IMAGE_CodingPNG; + ft_type.nPortIndex = inputPort; + + // Would use this function to select between PNG and JPEG but image_decode + // only outputs JPEG decoded images in YUV420, because of course it does. + // ft_type.eCompressionFormat = OMX_IMAGE_CodingJPEG; + + OMX_ERRORTYPE error = OMX_SetParameter(componentHandle, OMX_IndexParamImagePortFormat, &ft_type); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_SetParameter in OMX_Image_Decode::setFormat()", error); + log->debug(TAG, "setFormat OK"); +} + +void OMX_Image_Decode::setSliceHeight(int newSliceHeight) +{ + outputPortSettings.format.image.nSliceHeight = newSliceHeight; + log->debug(TAG, "nSliceHeight: {}", outputPortSettings.format.image.nSliceHeight); + + OMX_ERRORTYPE error = OMX_SetParameter(componentHandle, OMX_IndexParamPortDefinition, &outputPortSettings); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_SetParameter in OMX_Image_Decode::setSliceHeight()", error); + + { + // Dump new independant output settings + OMX_PARAM_PORTDEFINITIONTYPE indTest; + memset(&indTest, 0, sizeof(indTest)); + indTest.nSize = sizeof(indTest); + indTest.nVersion.nVersion = OMX_VERSION; + indTest.nPortIndex = outputPort; + + error = OMX_GetParameter(componentHandle, OMX_IndexParamPortDefinition, &indTest); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_GetParameter in OMX_Image_Decode::setSliceHeight()", error); + + log->debug(TAG, "Port def type from outputPort"); + log->debug(TAG, "buffer count actual: {}", indTest.nBufferCountActual); + log->debug(TAG, "eDir: {}", indTest.eDir); + log->debug(TAG, "nBufferSize: {}", indTest.nBufferSize); + log->debug(TAG, "bEnabled: {}", indTest.bEnabled); + log->debug(TAG, "bPopulated: {}", indTest.bPopulated); + log->debug(TAG, "eDomain: {}", indTest.eDomain); + + log->debug(TAG, "cMIMEType: {}", static_cast(indTest.format.image.cMIMEType)); + log->debug(TAG, "pNativeRender: {}", static_cast(indTest.format.image.pNativeRender)); + log->debug(TAG, "nFrameWidth: {}", indTest.format.image.nFrameWidth); + log->debug(TAG, "nFrameHeight: {}", indTest.format.image.nFrameHeight); + log->debug(TAG, "nStride: {}", indTest.format.image.nStride); + log->debug(TAG, "nSliceHeight: {}", indTest.format.image.nSliceHeight); + log->debug(TAG, "eColorFormat: {:#x}", indTest.format.image.eColorFormat); + } +} + +void OMX_Image_Decode::allocateInputBuffers(char* data) +{ + inBuffer1 = NULL; + inBuffer2 = NULL; + + log->debug(TAG, "Calling OMX_UseBuffer"); + OMX_ERRORTYPE error = OMX_UseBuffer(componentHandle, &inBuffer1, inputPort, static_cast(0), dataSize, reinterpret_cast(data)); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_UseBuffer 1 in OMX_Image_Decode::allocateInputBuffers()", error); + + // Hardcoded assumption - image_decode has min 2 input buffers + error = OMX_UseBuffer(componentHandle, &inBuffer2, inputPort, static_cast(0), 0, NULL); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_UseBuffer 2 in OMX_Image_Decode::allocateInputBuffers()", error); +} + +void OMX_Image_Decode::allocateOutputBuffer() +{ + OMX_ERRORTYPE error = OMX_UseBuffer(componentHandle, &outputBufferHeader, outputPort, static_cast(0), outputPortSettings.nBufferSize, (OMX_U8*)NULL); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_UseBuffer in OMX_Image_Decode::allocateOutputBuffer()", error); +} + +void OMX_Image_Decode::deallocateInputBuffers() +{ + OMX_ERRORTYPE error = OMX_FreeBuffer(componentHandle, inputPort, inBuffer1); + inBuffer1 = NULL; + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_FreeBuffer 1 in OMX_Image_Decode::deallocateInputBuffers()", error); + + error = OMX_FreeBuffer(componentHandle, inputPort, inBuffer2); + inBuffer2 = NULL; + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_FreeBuffer 2 in OMX_Image_Decode::deallocateInputBuffers()", error); +} + +void OMX_Image_Decode::deallocateOutputBuffer() +{ + OMX_ERRORTYPE error = OMX_FreeBuffer(componentHandle, outputPort, outputBufferHeader); + outputBufferHeader = NULL; + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_FreeBuffer in OMX_Image_Decode::deallocateOutputBuffer()", error); +} + +void OMX_Image_Decode::sendToInput() +{ + log->debug(TAG, "Starting sendToInput"); + inBuffer1->nFilledLen = dataSize; + inBuffer1->nOffset = 0; + inBuffer1->nTimeStamp = { 0, 0 }; + inBuffer1->pAppPrivate = static_cast(0); + inBuffer1->nFlags |= OMX_BUFFERFLAG_EOS; + + log->debug(TAG, "calling emptythisbuffer"); + OMX_ERRORTYPE error = OMX_EmptyThisBuffer(componentHandle, inBuffer1); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_EmptyThisBuffer in OMX_Image_Decode::sendToInput()", error); + log->debug(TAG, "sendToInput done"); +} + +void OMX_Image_Decode::waitForOutputPortSettingsChange() +{ + eventsProcessorMutex.lock(); + std::unique_lock ul(eventWaitersMutex); + + EventWaiter* eventWaiter = new EventWaiter(); + eventWaiter->waiting = true; + eventWaiter->eventType = OMX_EventPortSettingsChanged; + eventWaiter->port = outputPort; + eventWaiters.push_back(eventWaiter); + + eventsProcessorWake = true; + eventsProcessorMutex.unlock(); + eventsProcessorCond.notify_one(); + + eventWaiter->cond.wait(ul); // sleep this thread + ul.unlock(); + + if (!eventWaiter->receivedEvent) + { + delete eventWaiter; + throw OMX_Exception("Error waiting for event in waitForOutputPortSettingsChange", 0); + } + + // The event processor thread received an event and saved it here + log->debug(TAG, "received outputportsettingschanged event!"); + delete eventWaiter->receivedEvent; + delete eventWaiter; + + memset(&outputPortSettings, 0, sizeof(outputPortSettings)); + outputPortSettings.nSize = sizeof(outputPortSettings); + outputPortSettings.nVersion.nVersion = OMX_VERSION; + outputPortSettings.nPortIndex = outputPort; + + OMX_ERRORTYPE error = OMX_GetParameter(componentHandle, OMX_IndexParamPortDefinition, &outputPortSettings); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_GetParameter in OMX_Image_Decode::waitForOutputPortSettingsChange()", error); + + log->debug(TAG, "Port def type from outputPort"); + log->debug(TAG, "buffer count actual: {}", outputPortSettings.nBufferCountActual); + log->debug(TAG, "eDir: {}", outputPortSettings.eDir); + log->debug(TAG, "nBufferSize: {}", outputPortSettings.nBufferSize); + log->debug(TAG, "bEnabled: {}", outputPortSettings.bEnabled); + log->debug(TAG, "bPopulated: {}", outputPortSettings.bPopulated); + log->debug(TAG, "eDomain: {}", outputPortSettings.eDomain); + + log->debug(TAG, "cMIMEType: {}", static_cast(outputPortSettings.format.image.cMIMEType)); + log->debug(TAG, "pNativeRender: {}", static_cast(outputPortSettings.format.image.pNativeRender)); + log->debug(TAG, "nFrameWidth: {}", outputPortSettings.format.image.nFrameWidth); + log->debug(TAG, "nFrameHeight: {}", outputPortSettings.format.image.nFrameHeight); + log->debug(TAG, "nStride: {}", outputPortSettings.format.image.nStride); + log->debug(TAG, "nSliceHeight: {}", outputPortSettings.format.image.nSliceHeight); +} + +void OMX_Image_Decode::receiveFromOutput(char** data, int* nFlags) +{ + void* outputBufferMem{}; + + try + { + outputBufferMem = malloc(outputPortSettings.nBufferSize); + memset(outputBufferMem, 0, outputPortSettings.nBufferSize); + outputBufferHeader->pBuffer = static_cast(outputBufferMem); + + struct BufferWithOutputPort* bufferWithOutputPort = new BufferWithOutputPort(); + bufferWithOutputPort->bufhead = outputBufferHeader; + outputBufferHeader->pAppPrivate = static_cast(bufferWithOutputPort); + + std::unique_lock ul(bufferWithOutputPort->mutex); + + log->debug(TAG, "calling fillthisbuffer. bufhead: {}, BufferWithOutput: {}", static_cast(outputBufferHeader), static_cast(bufferWithOutputPort)); + + OMX_ERRORTYPE error; + + error = OMX_FillThisBuffer(componentHandle, outputBufferHeader); + if (error != OMX_ErrorNone) throw OMX_Exception("OMX_FillThisBuffer in OMX_Image_Decode::receiveFromOutput()", error); + + log->debug(TAG, "called fillthisbuffer. bufsize = {}", outputPortSettings.nBufferSize); + + bufferWithOutputPort->cond.wait(ul, [bufferWithOutputPort] { return bufferWithOutputPort->done; }); + ul.unlock(); + + log->debug(TAG, "receiveFromOutputPort signalled"); + + *data = static_cast(outputBufferMem); // caller takes ownership + *nFlags = outputBufferHeader->nFlags; + + outputBufferMem = NULL; + + // The slice height is all important - can't trust nFilledLen + + log->debug(TAG, "receiveFromOutput:"); + log->debug(TAG, " nSize = {}", outputBufferHeader->nSize); + log->debug(TAG, " nAllocLen = {}", outputBufferHeader->nAllocLen); + log->debug(TAG, " nFilledLen = {}", outputBufferHeader->nFilledLen); + log->debug(TAG, " nOffset = {}", outputBufferHeader->nOffset); + log->debug(TAG, " nInputPortIndex = {}", outputBufferHeader->nInputPortIndex); + log->debug(TAG, " nOutputPortIndex = {}", outputBufferHeader->nOutputPortIndex); + log->debug(TAG, " nFlags = {:#x}", outputBufferHeader->nFlags); + + delete bufferWithOutputPort; + } + catch(...) + { + if (outputBufferMem) + { + free(outputBufferMem); + outputBufferMem = NULL; + } + throw; + } +} + +void OMX_Image_Decode::getImageInfo(int* width, int* height, int* stride, int* sliceHeight) +{ + *width = outputPortSettings.format.image.nFrameWidth; + *height = outputPortSettings.format.image.nFrameHeight; + *stride = outputPortSettings.format.image.nStride; + *sliceHeight = outputPortSettings.format.image.nSliceHeight; +} + +OMX_ERRORTYPE OMX_Image_Decode::cb_EmptyBufferDone(OMX_IN OMX_HANDLETYPE /*hcomp*/, OMX_IN OMX_PTR /*appdata*/, OMX_IN OMX_BUFFERHEADERTYPE* buffer) +{ + log->debug(TAG, "CB: EmptyBufferDone"); + log->debug(TAG, " nSize = {}", buffer->nSize); + log->debug(TAG, " nAllocLen = {}", buffer->nAllocLen); + log->debug(TAG, " nFilledLen = {}", buffer->nFilledLen); + log->debug(TAG, " nOffset = {}", buffer->nOffset); + log->debug(TAG, " nInputPortIndex = {}", buffer->nInputPortIndex); + log->debug(TAG, " nOutputPortIndex = {}", buffer->nOutputPortIndex); + log->debug(TAG, " nFlags = {:#x}", buffer->nFlags); + + return OMX_ErrorNone; +} + +OMX_ERRORTYPE OMX_Image_Decode::cb_FillBufferDone(OMX_IN OMX_HANDLETYPE hcomp, OMX_IN OMX_PTR appdata, OMX_IN OMX_BUFFERHEADERTYPE* buffer) +{ + log->debug(TAG, "CB: FillBufferDone, handle: {}, appdata: {}, buffer: {}", static_cast(hcomp), static_cast(appdata), static_cast(buffer)); + + struct BufferWithOutputPort* bufferWithOutputPort = static_cast(buffer->pAppPrivate); + bufferWithOutputPort->mutex.lock(); + bufferWithOutputPort->done = true; + bufferWithOutputPort->mutex.unlock(); + bufferWithOutputPort->cond.notify_one(); + + return OMX_ErrorNone; +} diff --git a/omx/omximagedecode.h b/omx/omximagedecode.h new file mode 100644 index 0000000..34e5c7d --- /dev/null +++ b/omx/omximagedecode.h @@ -0,0 +1,81 @@ +/* + Copyright 2021 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP. If not, see . +*/ + +#ifndef OMXIMAGEDECODE_H +#define OMXIMAGEDECODE_H + +#include "omx.h" + +class OMX_Image_Decode : public OMX +{ + friend class OMX; + + public: + OMX_Image_Decode(); + virtual ~OMX_Image_Decode(); + + bool init(); + void shutdown(); + + void prepareInputBuffers(int dataSize); // throws + void setFormat(); // throws + void setSliceHeight(int sliceHeight); // throws + + void allocateInputBuffers(char* data); // throws + void allocateOutputBuffer(); + + void deallocateInputBuffers(); // throws + void deallocateOutputBuffer(); // throws + + void sendToInput(); // throws + + void waitForOutputPortSettingsChange(); // throws + void receiveFromOutput(char** data, int* nFlags); // throws // caller frees data + void getImageInfo(int* width, int* height, int* stride, int* sliceHeight); + + void enableInput(bool wait = true) { enablePort(inputPort, true, wait); } // throws + void disableInput(bool wait = true) { enablePort(inputPort, false, wait); } // throws + void enableOutput(bool wait = true) { enablePort(outputPort, true, wait); } // throws + void disableOutput(bool wait = true) { enablePort(outputPort, false, wait); } // throws + void flushInputCommands(bool wait = true) { flushCommands(inputPort, wait); } + void flushOutputCommands(bool wait = true) { flushCommands(outputPort, wait); } + + // Buffers callback overrides + + OMX_ERRORTYPE cb_FillBufferDone( + OMX_IN OMX_HANDLETYPE handle, OMX_IN OMX_PTR appdata, OMX_IN OMX_BUFFERHEADERTYPE* buffer) override; + + OMX_ERRORTYPE cb_EmptyBufferDone( + OMX_IN OMX_HANDLETYPE handle, OMX_IN OMX_PTR appdata, OMX_IN OMX_BUFFERHEADERTYPE* buffer) override; + + protected: + + OMX_U32 inputPort; + OMX_U32 outputPort; + OMX_BUFFERHEADERTYPE* inBuffer1{}; + OMX_BUFFERHEADERTYPE* inBuffer2{}; + + unsigned int dataSize{}; + + OMX_PARAM_PORTDEFINITIONTYPE outputPortSettings; + + OMX_BUFFERHEADERTYPE* outputBufferHeader{}; +}; + +#endif diff --git a/osdopenvg.cc b/osdopenvg.cc index 7f2e491..6c8ddd9 100644 --- a/osdopenvg.cc +++ b/osdopenvg.cc @@ -315,8 +315,11 @@ int OsdOpenVG::init() vgmutex.unlock(); #ifdef PICTURE_DECODER_OMX - imageomx = new ImageOMX(&reader); - reader.addDecoder(imageomx); + // imageomx = new ImageOMX(&reader); + // reader.addDecoder(imageomx); + + imageomx2 = new ImageOMX2(/*egl_display*/); + reader.addDecoder(imageomx2); #endif return 1; @@ -345,8 +348,10 @@ int OsdOpenVG::shutdown() reader.shutdown(); #ifdef PICTURE_DECODER_OMX - if (imageomx) reader.removeDecoder(imageomx); - imageomx = NULL; +// if (imageomx) reader.removeDecoder(imageomx); +// imageomx = NULL; + if (imageomx2) reader.removeDecoder(imageomx2); + imageomx2 = NULL; #endif if (!initted) return 0; @@ -518,7 +523,7 @@ void OsdOpenVG::renderLoop() return; } - if (renderThreadReq == 0) abort(); // OSDOVG-ROD-EXPERIMENT - check this never happens + if (renderThreadReq == 0) abort(); // OSDOVG-ROD-EXPERIMENT - check this never happens - confirmed // Either timeout or renderThreadReq == 1 - go around diff --git a/osdopenvg.h b/osdopenvg.h index da9acb2..4ccb079 100644 --- a/osdopenvg.h +++ b/osdopenvg.h @@ -36,7 +36,8 @@ #include "videoomx.h" #include "staticartwork.h" #ifdef PICTURE_DECODER_OMX - #include "imageomx.h" + //#include "imageomx.h" + #include "imageomx2.h" #endif #include @@ -74,6 +75,11 @@ struct OpenVGCommand struct OpenVGResponse* response{}; // If !NULL, a thread is waiting }; +class ImageOMX2; + +#include "eglpicturecreator.h" + + class OsdOpenVG : public OsdVector #ifdef PICTURE_DECODER_OMX , public EGLPictureCreator @@ -184,7 +190,8 @@ class OsdOpenVG : public OsdVector uint8_t* static_artwork_end[sa_MAX]; #ifdef PICTURE_DECODER_OMX - ImageOMX* imageomx; + //ImageOMX* imageomx; + ImageOMX2* imageomx2; #endif }; diff --git a/osdvector.cc b/osdvector.cc index 90d9184..97af092 100644 --- a/osdvector.cc +++ b/osdvector.cc @@ -818,6 +818,10 @@ void OsdVector::informPicture(LoadIndex index, VectorHandleImage handle) } surfaces_mutex.unlock(); + + // OSDOVG-ROD-EXPERIMENT + logger->trace(TAG, "EXPERIMENT - call doRender"); + doRender(); } void OsdVector::processMessage(Message* m) diff --git a/osdvector.h b/osdvector.h index f712ed4..cdf4fe5 100644 --- a/osdvector.h +++ b/osdvector.h @@ -286,7 +286,7 @@ class OsdVector : public Osd, public MessageReceiver class PictureDecoder { public: - PictureDecoder(PictureReader* treader) {reader = treader;}; + PictureDecoder(PictureReader* treader) {/* reader = treader; */}; virtual ~PictureDecoder() {}; // its is always guaranted, that after getDecodedPicture a call to decodePicture follows, if the return value was true; @@ -299,7 +299,7 @@ class OsdVector : public Osd, public MessageReceiver virtual void shutdown() {}; protected: - PictureReader* reader; + //PictureReader* reader; }; class PictureReader diff --git a/vchannellist.cc b/vchannellist.cc index ff504f4..f13368b 100644 --- a/vchannellist.cc +++ b/vchannellist.cc @@ -134,7 +134,6 @@ void VChannelList::highlightChannel(Channel* chan) void VChannelList::draw() { TBBoxx::draw(); - sl.draw(); // Put the status stuff at the bottom -- 2.39.5