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 einem QWidget darstellen. Dafür überschreibe ich Qt`s paintEvent(). Auf die Videodaten greife ich in der Klasse InputVideo zu und sende die per Signal an einen Slot in der Klasse VideoMonitor.
    Im Slot konstruiere ich dann auch das QImage und das klappt auch, nur wenn ich das Image in paintEvent verwende möchte ist es dort NULL. Wenn ich mir m_srcImage in paintEvent() 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 das paintEvent als erstes aufgerufen wird. Aber wenn die Daten den Slot erreichen gibt es kein Update von m_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



  • @Th69

    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 in receiveFrameData zugewiesen wird, während in paintEvent diese gelesen wird.
    Das paintEvent wird ja nicht nur durch ein explizites QWidget::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 das paintEventaufgerufen, sondern erst eine WM_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 dem receiveFrameData heraus ein Signal senden und das UI-Control müßte dann im Slot das Update 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



  • @Th69

    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 und QWidget::update() lösen.
    EDIT: und das Wichtigste: Damals funktionierte die Verbindung mit connect() 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 von InputVideo aus zu versenden (siehe Snippet oben).
    Die zweite Instanz in der UI Klasse um das paintEvent 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 in InputVideo benutzt (und warum du sie überhaupt benötigst). Du mußt dieselbe Instanz für die UI als auch den connect benutzen.

    Edit: Ok, habe deinen geänderten Code jetzt angeschaut.
    So sendest du ja das sendFrameData-Signal nur an den mittels new VideoMonitor erzeugten Member m_videoMonitor (und dann wundert es mich, daß du überhaupt Bilder siehst).
    Entferne diesen Member und führe den connect von der übergeordneten Programmlogik (im main oder MainWindow) aus, so daß das richtige von der UI aus instanziierte VideoMonitor-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 in paintEvent() 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 in InputVideo hat das Problem gelöst. Danke @Th69 für deine Hinweise!


Anmelden zum Antworten