]> git.vomp.tv Git - vompclient.git/commitdiff
Rewrite of ImageOMX to fix the PNG problem
authorChris Tallon <chris@vomp.tv>
Fri, 5 Nov 2021 18:37:09 +0000 (18:37 +0000)
committerChris Tallon <chris@vomp.tv>
Fri, 5 Nov 2021 18:37:09 +0000 (18:37 +0000)
18 files changed:
GNUmakefile
eglpicturecreator.h [new file with mode: 0644]
imageomx.cc
imageomx.h
imageomx2.cc [new file with mode: 0644]
imageomx2.h [new file with mode: 0644]
objects.mk
omx/omx.cc [new file with mode: 0644]
omx/omx.h [new file with mode: 0644]
omx/omxeglrender.cc [new file with mode: 0644]
omx/omxeglrender.h [new file with mode: 0644]
omx/omximagedecode.cc [new file with mode: 0644]
omx/omximagedecode.h [new file with mode: 0644]
osdopenvg.cc
osdopenvg.h
osdvector.cc
osdvector.h
vchannellist.cc

index 37c7ae2258ef7234aaafaabf2bacdf92d42040c2..2d556938c87cfa02a10dc5350e6bfae745bd4e00 100644 (file)
@@ -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 (file)
index 0000000..ffe0556
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef EGLPICTURECREATOR_H
+#define EGLPICTURECREATOR_H
+
+class EGLPictureCreator
+{
+  public:
+    virtual bool getEGLPicture(struct OsdVector::PictureInfo& pictureInfo, EGLDisplay* display)=0;
+};
+
+#endif
index e5bb105e5230dad80d389be5eeaf5a7c23526b70..a511c40652412d29ede769fb0b7ce97e60f52cfd 100644 (file)
@@ -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");
index 96442ba8b749275dbb4f355e61cbf73ac8757641..e2df77f288f7cb916cd407e0326a8781b5abfa00 100644 (file)
 #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 (file)
index 0000000..c9daf90
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+*/
+
+#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<char*>(buffer), length, &rawData, &rawDataSize, &rawDataWidth, &rawDataHeight, &rawDataStride);
+
+    log->debug(TAG, "decoded {}, size = {}, w = {}, h = {}, s = {}", static_cast<void*>(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(&currentDecode, 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<char*> chunks;
+  std::vector<int> 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<char*>(malloc(totalSize));
+    log->debug(TAG, "gluedTogether {}", static_cast<void*>(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<EGLPictureCreator*>(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 (file)
index 0000000..8f8fb63
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+*/
+
+/*
+
+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
index fb4bbf1c60bc6d97e859f1bc1d7be3bf0206418c..b6370b9909dbb182a05c5703e79b7439cce35e1f 100644 (file)
@@ -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 (file)
index 0000000..68225ac
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+*/
+
+#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<void*>(handle_image_decode), static_cast<void*>(omx_image_decode), static_cast<void*>(handle_egl_render), static_cast<void*>(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<void*>(handle_image_decode), static_cast<void*>(omx_image_decode), static_cast<void*>(handle_egl_render), static_cast<void*>(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<void*>(handle_image_decode), static_cast<void*>(omx_image_decode), static_cast<void*>(handle_egl_render), static_cast<void*>(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<void*>(handle), event_type, data1, data2, static_cast<void*>(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<std::mutex> 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<void*>(event), static_cast<void*>(eventWaiter));
+
+
+        if (eventWaiter->waiting)
+        {
+          // FIXME look at whether eventWaiter could miss this signal
+          log->debug(TAG, "Notifying eventWaiter: {} on cond {}", static_cast<void*>(eventWaiter), static_cast<void*>(&eventWaiter->cond));
+          eventWaiter->cond.notify_one();
+        }
+        else
+        {
+          log->debug(TAG, "EventWaiter not waiting. Deleting {}", static_cast<void*>(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<std::mutex> 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<void*>(&eventWaiter->cond));
+  eventWaiter->cond.wait(ul);  // sleep this thread
+  log->debug(TAG, "en/disablePort: Back from wait on cond {}", static_cast<void*>(&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, &currentState);
+  log->debug(TAG, "Current state: {}", currentState);
+  if (currentState == reqState)
+  {
+    log->debug(TAG, "changeState return immediately, already in reqState");
+    return;
+  }
+
+  std::unique_lock<std::mutex> 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<void*>(&eventWaiter->cond));
+  eventWaiter->cond.wait(ul);  // sleep this thread
+  log->debug(TAG, "changeState: Back from wait on cond {}", static_cast<void*>(&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<std::mutex> 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<void*>(&eventWaiter->cond));
+  eventWaiter->cond.wait(ul);  // sleep this thread
+  log->debug(TAG, "flushCommands: Back from wait on cond {}", static_cast<void*>(&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 (file)
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 <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef OMX_H
+#define OMX_H
+
+#include <deque>
+#include <mutex>
+#include <thread>
+#include <condition_variable>
+#include <exception>
+
+#include <bcm_host.h>
+
+#define OMX_SKIP64BIT
+#include <IL/OMX_Core.h>
+#include <IL/OMX_Types.h>
+#include <IL/OMX_Component.h>
+#include <IL/OMX_Broadcom.h>
+
+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<Event*>;
+using EventWaiters = std::deque<EventWaiter*>;
+
+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 (file)
index 0000000..3533863
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+*/
+
+#include <VG/openvg.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#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<void*>(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<void*>(data));
+  OMX_ERRORTYPE error = OMX_UseBuffer(componentHandle, &inBuffer1, inputPort, static_cast<OMX_PTR>(0), inputPortBufferSize, reinterpret_cast<OMX_U8*>(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<void*>(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<void*>(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<OMX_PTR>(0);
+  inBuffer1->nFlags |= OMX_BUFFERFLAG_EOS;
+  inBuffer1->pBuffer = reinterpret_cast<OMX_U8*>(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<OMX_PTR>(bufferWithOutputPort);
+
+    log->debug(TAG, "render: egl buffer header:");
+    log->debug(TAG, "{}", static_cast<void*>(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<void*>(eglRenderOutputBufferHeader->pAppPrivate));
+
+
+    std::unique_lock<std::mutex> ul(bufferWithOutputPort->mutex);
+
+    log->debug(TAG, "calling fillthisbuffer. bufhead: {}, BufferWithOutput: {}", static_cast<void*>(eglRenderOutputBufferHeader), static_cast<void*>(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<void*>(eglRenderOutputBufferHeader), static_cast<void*>(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<void*>(portSettings.format.video.cMIMEType));
+  log->debug(TAG, "  pNativeRender: {}", static_cast<void*>(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<void*>(hcomp), static_cast<void*>(appdata), static_cast<void*>(buffer));
+
+  struct BufferWithOutputPort* bufferWithOutputPort = static_cast<struct BufferWithOutputPort*>(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 (file)
index 0000000..9e106f9
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef OMXEGLRENDER_H
+#define OMXEGLRENDER_H
+
+#include <VG/openvg.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#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 (file)
index 0000000..06a4932
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+*/
+
+#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<void*>(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<void*>(indTest.format.image.cMIMEType));
+    log->debug(TAG, "pNativeRender: {}", static_cast<void*>(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<OMX_PTR>(0), dataSize, reinterpret_cast<OMX_U8*>(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<OMX_PTR>(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<OMX_PTR>(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<OMX_PTR>(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<std::mutex> 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<void*>(outputPortSettings.format.image.cMIMEType));
+  log->debug(TAG, "pNativeRender: {}", static_cast<void*>(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<OMX_U8*>(outputBufferMem);
+
+    struct BufferWithOutputPort* bufferWithOutputPort = new BufferWithOutputPort();
+    bufferWithOutputPort->bufhead = outputBufferHeader;
+    outputBufferHeader->pAppPrivate = static_cast<OMX_PTR>(bufferWithOutputPort);
+
+    std::unique_lock<std::mutex> ul(bufferWithOutputPort->mutex);
+
+    log->debug(TAG, "calling fillthisbuffer. bufhead: {}, BufferWithOutput: {}", static_cast<void*>(outputBufferHeader), static_cast<void*>(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<char*>(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<void*>(hcomp), static_cast<void*>(appdata), static_cast<void*>(buffer));
+
+  struct BufferWithOutputPort* bufferWithOutputPort = static_cast<struct BufferWithOutputPort*>(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 (file)
index 0000000..34e5c7d
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+*/
+
+#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
index 7f2e4915fc4d89e80d901b21c831ae9dcc9f7d9e..6c8ddd9ec2d6f14d68c80bc432c66a7b3e2b84b9 100644 (file)
@@ -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
 
index da9acb28c710f62b5717dc5df71dff47f7826f6b..4ccb079f69e829ddf15ed94cc51013e7f0dbce54 100644 (file)
@@ -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 <ft2build.h>
@@ -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
 };
 
index 90d918469c2e095706f53888c562adbcc277508d..97af0920382558422376a8d0b9e0053ac730f946 100644 (file)
@@ -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)
index f712ed452bf071e7e222c4e4fa3abd222814e5bd..cdf4fe54fc9cc45546b486c95cb3a3408717d16f 100644 (file)
@@ -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
index ff504f48f62ceba5ba3bb78efe62ea8466bf63cf..f13368b7e309d6365ce8d99466e0575f720a211c 100644 (file)
@@ -134,7 +134,6 @@ void VChannelList::highlightChannel(Channel* chan)
 void VChannelList::draw()
 {
   TBBoxx::draw();
-  sl.draw();
 
   // Put the status stuff at the bottom