From 157d92ef7e6d1e9e29b2cf5fc180ece81c77a72c Mon Sep 17 00:00:00 2001 From: m7913d Date: Mon, 21 Apr 2025 18:13:30 +0200 Subject: [PATCH 1/3] Prevent image buffer exhaustion on Android using Qt This commit fixes the following exception: java.lang.IllegalStateException: maxImages (10) has already been acquired, call #close before acquiring more. This exception is thrown when the device cannot process the images fast enough. Note that Qt captures the images in a separate (Java) thread (on Android) and sends them asynchronously to the main (GUI) thread. This fix moves the processing to a separate thread and discards all new images while another image (frame) is still being processed. This allows Qt to close the Android resources before the buffer is full. In recent versions of Qt, Qt catches this exception itself (see https:// bugreports.qt.io/browse/QTBUG-116526) by restarting the camera, but frequent restarts may degrade performance. On non-Android platforms, this fix has the advantage that the GUI remains responsive even if the barcode processing is slow, and that always the most recently captured frame is processed. Related to #743 --- example/ZXingQtReader.h | 72 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/example/ZXingQtReader.h b/example/ZXingQtReader.h index 629f44423b..0719d9a2e8 100644 --- a/example/ZXingQtReader.h +++ b/example/ZXingQtReader.h @@ -16,8 +16,11 @@ #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include #else +#include +#include #include #include +#include #endif #include #endif @@ -307,6 +310,47 @@ inline Barcode ReadBarcode(const QVideoFrame& frame, const ReaderOptions& opts = return !res.isEmpty() ? res.takeFirst() : Barcode(); } +class BarcodeReader; + +class BarcodeProcessor : public QThread +{ + BarcodeReader& _reader; + QVideoFrame _nextFrame; + QMutex _mutex; + QWaitCondition _wait; +public: + BarcodeProcessor(BarcodeReader& reader) : _reader(reader) {} + + void start() {QThread::start();} + void stop() + { + requestInterruption(); + _wait.wakeOne(); + wait(); + } + + void setNextFrame(const QVideoFrame& frame) + { + QMutexLocker l(&_mutex); + _nextFrame = frame; + _wait.wakeOne(); + } + +private: + virtual void run() override; + + QVideoFrame takeNextFrame () + { + QMutexLocker l(&_mutex); + if (!_nextFrame.isValid()) + _wait.wait(&_mutex); + + QVideoFrame f = _nextFrame; + _nextFrame = QVideoFrame(); + return f; + } +}; + #define ZQ_PROPERTY(Type, name, setter) \ public: \ Q_PROPERTY(Type name READ name WRITE setter NOTIFY name##Changed) \ @@ -333,7 +377,15 @@ class BarcodeReader : public QObject, private ReaderOptions #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) BarcodeReader(QObject* parent = nullptr) : QAbstractVideoFilter(parent) {} #else - BarcodeReader(QObject* parent = nullptr) : QObject(parent) {} + BarcodeReader(QObject* parent = nullptr) : QObject(parent), _processor(*this) + { + _processor.start(); + } + ~BarcodeReader() + { + _processor.stop(); + } + #endif // TODO: find out how to properly expose QFlags to QML @@ -373,10 +425,11 @@ class BarcodeReader : public QObject, private ReaderOptions ZQ_PROPERTY(bool, isPure, setIsPure) // For debugging/development - int runTime = 0; + QAtomicInt runTime = 0; Q_PROPERTY(int runTime MEMBER runTime) public slots: + // Function should be thread safe, as it may be called from a separate thread. ZXingQt::Barcode process(const QVideoFrame& image) { QElapsedTimer t; @@ -403,6 +456,7 @@ public slots: #else private: QVideoSink *_sink = nullptr; + BarcodeProcessor _processor; public: void setVideoSink(QVideoSink* sink) { @@ -410,10 +464,10 @@ public slots: return; if (_sink) - disconnect(_sink, nullptr, this, nullptr); + disconnect(_sink, nullptr, &_processor, nullptr); _sink = sink; - connect(_sink, &QVideoSink::videoFrameChanged, this, &BarcodeReader::process); + connect(_sink, &QVideoSink::videoFrameChanged, &_processor, &BarcodeProcessor::setNextFrame, Qt::DirectConnection); } Q_PROPERTY(QVideoSink* videoSink MEMBER _sink WRITE setVideoSink) #endif @@ -441,6 +495,16 @@ inline QVideoFilterRunnable* BarcodeReader::createFilterRunnable() { return new VideoFilterRunnable(this); } +#else +inline void BarcodeProcessor::run() +{ + while(!isInterruptionRequested()) + { + QVideoFrame frame = takeNextFrame(); + if (frame.isValid()) + _reader.process(frame); + } +} #endif #endif // QT_MULTIMEDIA_LIB From efb08a0cbbe8cc0ccd2e944f4b260d91d7ceee38 Mon Sep 17 00:00:00 2001 From: m7913d Date: Sat, 26 Apr 2025 22:54:51 +0200 Subject: [PATCH 2/3] Support multiple threads to process video frames in Qt --- example/ZXingQtReader.h | 87 +++++++++++++---------------------------- 1 file changed, 27 insertions(+), 60 deletions(-) diff --git a/example/ZXingQtReader.h b/example/ZXingQtReader.h index 0719d9a2e8..14659fb4fa 100644 --- a/example/ZXingQtReader.h +++ b/example/ZXingQtReader.h @@ -16,11 +16,9 @@ #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include #else -#include -#include +#include #include #include -#include #endif #include #endif @@ -310,47 +308,6 @@ inline Barcode ReadBarcode(const QVideoFrame& frame, const ReaderOptions& opts = return !res.isEmpty() ? res.takeFirst() : Barcode(); } -class BarcodeReader; - -class BarcodeProcessor : public QThread -{ - BarcodeReader& _reader; - QVideoFrame _nextFrame; - QMutex _mutex; - QWaitCondition _wait; -public: - BarcodeProcessor(BarcodeReader& reader) : _reader(reader) {} - - void start() {QThread::start();} - void stop() - { - requestInterruption(); - _wait.wakeOne(); - wait(); - } - - void setNextFrame(const QVideoFrame& frame) - { - QMutexLocker l(&_mutex); - _nextFrame = frame; - _wait.wakeOne(); - } - -private: - virtual void run() override; - - QVideoFrame takeNextFrame () - { - QMutexLocker l(&_mutex); - if (!_nextFrame.isValid()) - _wait.wait(&_mutex); - - QVideoFrame f = _nextFrame; - _nextFrame = QVideoFrame(); - return f; - } -}; - #define ZQ_PROPERTY(Type, name, setter) \ public: \ Q_PROPERTY(Type name READ name WRITE setter NOTIFY name##Changed) \ @@ -377,13 +334,13 @@ class BarcodeReader : public QObject, private ReaderOptions #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) BarcodeReader(QObject* parent = nullptr) : QAbstractVideoFilter(parent) {} #else - BarcodeReader(QObject* parent = nullptr) : QObject(parent), _processor(*this) + BarcodeReader(QObject* parent = nullptr) : QObject(parent) { - _processor.start(); + _pool.setMaxThreadCount(1); } ~BarcodeReader() { - _processor.stop(); + _pool.waitForDone(-1); } #endif @@ -456,7 +413,7 @@ public slots: #else private: QVideoSink *_sink = nullptr; - BarcodeProcessor _processor; + QThreadPool _pool; public: void setVideoSink(QVideoSink* sink) { @@ -464,12 +421,32 @@ public slots: return; if (_sink) - disconnect(_sink, nullptr, &_processor, nullptr); + disconnect(_sink, nullptr, this, nullptr); _sink = sink; - connect(_sink, &QVideoSink::videoFrameChanged, &_processor, &BarcodeProcessor::setNextFrame, Qt::DirectConnection); + connect(_sink, &QVideoSink::videoFrameChanged, this, &BarcodeReader::onVideoFrameChanged, Qt::DirectConnection); + } + void onVideoFrameChanged(const QVideoFrame& frame) + { + if (_pool.activeThreadCount() >= _pool.maxThreadCount()) + return; // we are busy => skip the frame + + _pool.start([this, frame](){process(frame);}); } Q_PROPERTY(QVideoSink* videoSink MEMBER _sink WRITE setVideoSink) + Q_PROPERTY(int maxThreadCount READ maxThreadCount WRITE setMaxThreadCount) + int maxThreadCount () const + { + return _pool.maxThreadCount(); + } + void setMaxThreadCount (int maxThreadCount) + { + if (_pool.maxThreadCount() != maxThreadCount) { + _pool.setMaxThreadCount(maxThreadCount); + emit maxThreadCountChanged(); + } + } + Q_SIGNAL void maxThreadCountChanged(); #endif }; @@ -495,16 +472,6 @@ inline QVideoFilterRunnable* BarcodeReader::createFilterRunnable() { return new VideoFilterRunnable(this); } -#else -inline void BarcodeProcessor::run() -{ - while(!isInterruptionRequested()) - { - QVideoFrame frame = takeNextFrame(); - if (frame.isValid()) - _reader.process(frame); - } -} #endif #endif // QT_MULTIMEDIA_LIB From 02a7b6976bde6b6366a83c57e1cbe95284672875 Mon Sep 17 00:00:00 2001 From: m7913d Date: Sun, 27 Apr 2025 22:50:28 +0200 Subject: [PATCH 3/3] Do not start processing new frames during shutdown. --- example/ZXingQtReader.h | 1 + 1 file changed, 1 insertion(+) diff --git a/example/ZXingQtReader.h b/example/ZXingQtReader.h index 14659fb4fa..b32e37fe43 100644 --- a/example/ZXingQtReader.h +++ b/example/ZXingQtReader.h @@ -340,6 +340,7 @@ class BarcodeReader : public QObject, private ReaderOptions } ~BarcodeReader() { + _pool.setMaxThreadCount(0); _pool.waitForDone(-1); } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy