Fehler: Auf Speicher kann nicht zugegriffen werden
-
Hallo,
ich möchte aus Videodaten, auf die ich mittles API zugreife, ein
QImageerstellen und dieses in einemQWidgetdarstellen. Dafür überschreibe ich Qt`spaintEvent(). Auf die Videodaten greife ich in der KlasseInputVideozu und sende die per Signal an einen Slot in der KlasseVideoMonitor.
Im Slot konstruiere ich dann auch dasQImageund das klappt auch, nur wenn ich das Image inpaintEventverwende möchte ist es dort NULL. Wenn ich mirm_srcImageinpaintEvent()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_srcImagenur 0 zurückgeben, weil daspaintEventals 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
receiveFrameDataaus 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 inreceiveFrameDatazugewiesen wird, während inpaintEventdiese gelesen wird.
DaspaintEventwird 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 daspaintEventaufgerufen, 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 demreceiveFrameDataheraus ein Signal senden und das UI-Control müßte dann im Slot dasUpdatedurchführen.Edit: Die Qt-Widget-Klasse
VideoMonitordarf 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:QueuedConnectionundQWidget::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
VideoMonitorbenutze ich um die Daten per Signal und Slot vonInputVideoaus zu versenden (siehe Snippet oben).
Die zweite Instanz in der UI Klasse um daspaintEventzu 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 inInputVideobenutzt (und warum du sie überhaupt benötigst). Du mußt dieselbe Instanz für die UI als auch denconnectbenutzen.Edit: Ok, habe deinen geänderten Code jetzt angeschaut.
So sendest du ja dassendFrameData-Signal nur an den mittelsnew VideoMonitorerzeugten Memberm_videoMonitor(und dann wundert es mich, daß du überhaupt Bilder siehst).
Entferne diesen Member und führe denconnectvon der übergeordneten Programmlogik (immainoderMainWindow) aus, so daß das richtige von der UI aus instanziierteVideoMonitor-Objekt benutzt wird.Und wenn du dann als letzten Parameter
Qt::QueuedConnectionbenutzt, sollte auch automatisch die Thread-Synchronisation funktionieren.
-
Es funktioniert jetzt insoweit das
m_srcImageinpaintEvent()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 inInputVideohat das Problem gelöst. Danke @Th69 für deine Hinweise!