Fehler: Auf Speicher kann nicht zugegriffen werden
-
Hallo,
ich möchte aus Videodaten, auf die ich mittles API zugreife, ein
QImage
erstellen und dieses in einemQWidget
darstellen. Dafür überschreibe ich Qt`spaintEvent()
. Auf die Videodaten greife ich in der KlasseInputVideo
zu und sende die per Signal an einen Slot in der KlasseVideoMonitor
.
Im Slot konstruiere ich dann auch dasQImage
und das klappt auch, nur wenn ich das Image inpaintEvent
verwende möchte ist es dort NULL. Wenn ich mirm_srcImage
inpaintEvent()
mit dem Debugger anschaue steht dort "Der Speicher kann nicht gelesen werden". Ich vermute mal ein Memory Leak? Aber wie kann ich das beseitigen?
Ein Teil des Code://InputVideo.cpp InputVideo::InputVideo(QObject *parent) : m_videoMonitor(new VideoMonitor) { QObject::connect(this, &InputVideo::sendFrameData, m_videoMonitor, &VideoMonitor::receiveFrameData, Qt::DirectConnection); } bool InputVideo::getFrameData(DtMxData* data) { DtMxFrame* mxFrame; mxFrame = data->m_Rows[0].m_CurFrame; if (mxFrame->m_Status != DTAPI_FRAME_STATUS_OK) { return false; } else { if (true) { uchar* pData = mxFrame->m_Video->m_Planes->m_pBuf; int bytesPerRow = mxFrame->m_Video->m_Planes->m_Stride; int frameWidth = mxFrame->m_Video->m_Width; int frameHeight = mxFrame->m_Video->m_Height; emit sendFrameData(pData, frameWidth, frameHeight, bytesPerRow); } return true; } //VideoMonitor.h class VideoMonitor : public QOpenGLWidget { Q_OBJECT public: VideoMonitor(QWidget* parent = nullptr); private: QImage m_srcImage; protected: void paintEvent(QPaintEvent*); public slots: void receiveFrameData(uchar* pData, int width, int height, int bytesPerRow); }; //Videomonitor.cpp VideoMonitor::VideoMonitor(QWidget* parent) : QOpenGLWidget(parent), m_srcImage(nullptr) { } void VideoMonitor::receiveFrameData(uchar* pData, int width, int height, int bytesPerRow) { m_srcImage = QImage(pData, width, height, bytesPerRow, QImage::Format_Indexed8); qDebug() << "Slot is called." << m_srcImage; //QImage is constructed QWidget::update(); } void VideoMonitor::paintEvent(QPaintEvent*) { if (!m_srcImage.isNull()) { //m_srcImage ist null, Debugger: Auf Speicher kann nicht zugegriffen werden. QPainter painter(this); painter.drawImage(this->rect(), m_srcImage); painter.end(); qDebug() << "Draw image."; } else { qDebug() << "QImage is null"; //Programm springt hier hin } }
Hat jemand einen Tipp? Ich habe auch versucht für m_srcImage dynamisch Speicher zu reservieren. Das klappt auch nicht.
EDIT: Code ergänzt (13:45 Uhr)
-
Ich würde stark davon ausgehen, dass die Daten vom letzten Frame nicht mehr gültig sind, nachdem das nächste Frame geladen wurde. QImage kopiert die Daten nicht und verweist auf ungültige Daten.
Dann hätte ich allerdings nicht erwartet, dass isNull true zurückgibt, sondern dass das einfach auf ungültigen Speicher verweist.
-
QImage.IsNull gibt true zurück, wenn alle parameter 0 sind und keine Daten allokiert wurden.
Das bedeutet, dass irgendwann die API dir einen leeren Frame liefert, wo data und die größe null/0 sind (Wenn es eine Video datei ist, dann ist vermutlich das ende erreicht worden).
-
Ich habe vergessen zu erwähnen, das ich einen Button im Programm habe der beim anklicken die Daten an den Slot sendet. Beim Programmstart kann
m_srcImage
nur 0 zurückgeben, weil daspaintEvent
als erstes aufgerufen wird. Aber wenn die Daten den Slot erreichen gibt es kein Update vonm_srcImage
(so erscheint es mir zumindest).Bzgl. ungültiger Daten oder einem leeren Frame: Ich frage im Code den Status vom Frame ab. Wenn der nicht OK ist dann wird nicht eingelesen. Ich hatte das Einlesen mal auf 25 Frames reduziert und da wurden im Slot 25 QImages erstellt. Mit den Daten die darin gespeichert waren hat das eig. gepasst. Da war kein QImage mit Null dazwischen.
Achso und es ist keine Videodatei, sondern ein Videostream von einer Kamera.
-
Wird
receiveFrameData
aus einem anderen Thread (also nicht dem UI-Thread) heraus aufgerufen? Dann hast du eine Race Condition.Edit: s.a. Multithreading with Qt
-
Ja, InputVideo und VideoMonitor laufen im gleichen Thread, aber in einem anderem als die Qt-Main-Klasse. Das es sich um eine Race Condition handelt hätte ich jetzt nicht gedacht. Tritt die auf weil ich von der VideoMonitor Klasse zwei Instanzen erzeuge (einmal in InputVideo für
QObject::connect()
und dann in der Main-Class um den Process per Button zu starten)?
-
Eher direkt die Variable
m_srcImage
, da diese inreceiveFrameData
zugewiesen wird, während inpaintEvent
diese gelesen wird.
DaspaintEvent
wird ja nicht nur durch ein explizitesQWidget::update()
ausgelöst, sondern kann auch bei anderen Window-Operationen (z.B. Aktivierung des Anwenders oder Verschieben des Fensters) auftreten.Ich bin mir daher auch nicht sicher, ob du überhaupt
QWidget::update()
direkt aus einem Non-UI-Thread aufrufen darfst (denn dadurch wird nicht synchron daspaintEvent
aufgerufen, sondern erst eineWM_PAINT
-Nachricht in die MessageLoop des UI-Threads hinzugefügt (und dies sollte nur aus einem UI-Thread heraus erfolgen).
Ich denke, du mußt dann "cross-thread signals and slot" benutzen, d.h. aus demreceiveFrameData
heraus ein Signal senden und das UI-Control müßte dann im Slot dasUpdate
durchführen.Edit: Die Qt-Widget-Klasse
VideoMonitor
darf natürlich auch nur von dem UI-Thread heraus erzeugt und benutzt werden!!!
Und wieso erzeugst du zwei Instanzen davon?Edit2: Ich meine, ich hätte vor einiger Zeit schon mal ein ähnliches Thema bzgl. Qt und Multithreading (mit einem anderen User?) hier behandelt.
Edit3: Nein, das warst ja sogar du: Slot-Funktion in QObject::connect wird nicht aufgerufen
-
Genau, das war ich^^^. Damals lief die die Draw Funktion auch in einem anderen Thread als die Main-Klasse, aber da konnte ich das Problem letztlich nur mit
Qt:QueuedConnection
undQWidget::update()
lösen.
EDIT: und das Wichtigste: Damals funktionierte die Verbindung mitconnect()
nicht. Diesmal gibt es da kein Problem. Wie gesagt, die Daten werden versendet, erreichen den Slot und daraus konstruiere ich dann ein Frame.Eine Instanz von
VideoMonitor
benutze ich um die Daten per Signal und Slot vonInputVideo
aus zu versenden (siehe Snippet oben).
Die zweite Instanz in der UI Klasse um daspaintEvent
zu einem Widget hinzuzufügen.
-
Sobald du Variablen aus verschiedenen Thread benutzt (lesen/schreiben), mußt du dies synchronisieren - darum kommst du nicht umher.
Aber ich sehe nicht, wo du eine
VideoMonitor
-Instanz inInputVideo
benutzt (und warum du sie überhaupt benötigst). Du mußt dieselbe Instanz für die UI als auch denconnect
benutzen.Edit: Ok, habe deinen geänderten Code jetzt angeschaut.
So sendest du ja dassendFrameData
-Signal nur an den mittelsnew VideoMonitor
erzeugten Memberm_videoMonitor
(und dann wundert es mich, daß du überhaupt Bilder siehst).
Entferne diesen Member und führe denconnect
von der übergeordneten Programmlogik (immain
oderMainWindow
) aus, so daß das richtige von der UI aus instanziierteVideoMonitor
-Objekt benutzt wird.Und wenn du dann als letzten Parameter
Qt::QueuedConnection
benutzt, sollte auch automatisch die Thread-Synchronisation funktionieren.
-
Es funktioniert jetzt insoweit das
m_srcImage
inpaintEvent()
nicht mehr 0 ist. Eine Implementierung von QThread war dafür allerdings nicht notwendig.Der Aufruf von
QObject::connect(m_inConfig, &InputConfig::sendFrameData, m_videoMonitor, &VideoMonitor::receiveFrameData, Qt::QueuedConnection)
in der UI - Klasse und nicht inInputVideo
hat das Problem gelöst. Danke @Th69 für deine Hinweise!