Multithreading-Problem unter Qt und Linux mit QImage beim Freimachen von Speicher



  • Hallo allerseits!

    Habe ein kleines Problem mit Qt 4.3.4 unter Linux.
    Und zwar habe ich ein kleines Programm geschrieben, weleches ein V4L2-Device öffnet, und das Bild (konstanter Strom an neuen Bildern) in einem QLabel dargestellt. Dazu verwende ich neben der GUI einen weiteren Thread.

    Erst mal etwas zum Aufbau:

    Die GUI hat einen Button (Open/Close) und ein QLabel namens imageLabel, in welchem das Bild dargstellt wird. Dazu wird der Slot setImage(QImage*) implementiert, der einen Zeiger auf ein Image erhält und dann mittels imageLabel.setPixmap(fromImage(img)) das Bild in das Label überträgt.

    Der zweite Thread (CamGrabber) initialisiert beim Click auf "Open/Close" in der GUI die V4L2-Verbindung und ruft dann auf sich selbst start() aus. In der run()-Methode werden dann die Buffer (Streaming I/O) "dequeued", das yuyv-Array nach rgb konvertiert und die Daten in das QImage übertragen. Dann wird "emit frameTaken(img)" aufgerufen, das mit dem setImage(QImage*)-Slot in der GUI verknüft ist.

    So weit so gut. Das klappt wirklich hervorragend. Allerdings geht das in die Hose, sobald ich zum Stoppen des Streams "Open/Close" anklicke. Dort wird unterschieden, ob der Stream läuft oder nicht. Da er nun läuft, soll er beendet werden. Auch das klappt. Allerdings gebe ich nun den Speicher frei, also auch das allozierte QImage, das ich beim emit an die GUI schicke. Das mag die GUI gar nicht und fliegt leider hin. Da die Operation jedoch durch einen Mutex geschützt ist, sollte nun gerade das nicht passieren.

    Woran mag das liegen? Laut meinem Verständnis wird bei QPixmap::fromImage() eine Umwandlung durchgeführt, also Daten (deep) kopiert. Warum kann ich das Bild anschließend nicht einfach löschen? Das Pixmap existiert ja dann weiter.

    Hier die relevanten Stellen noch mal im Pseudocode:

    GUI-Thread:

    class GUI {
    
    QButton open_close;
    QLabel imageLabel;
    CamGrabber grabber;
    
    SLOT:
     setImage(QImage *img) {
         imagelabe.setPixmap(QPixmap::fromImage(img));
     }
    
    }
    

    V4L2-Grabber:

    class CamGrabber {
    
      bool connected = false;
      QImage *img;
      QMutex  mutex;
    
      void openClose() {
         if (!connected) {
           v4l2_init();
           img = new QImage(width, height);
           start();
           connected = true;
           return; 
         }
    
         connected = false;
         mutex.lock();
         v4l_close();
         delete img;
         mutex.unlock(); 
      }
    
       run() {
        mutex.lock();
    
        while (connected) {
         daten_zeiger = v4l_hole_frame_zeiger();
         bild_zeiger = img->bits();
    
         konvertiere_format(daten_zeiger);   
    
         memcpy(bildzeiger,datenzeiger,width*height); 
    
         emit frameTaken(img); 
        }
    
         mutex.unlock();
       }
    }
    

    Connections:

    QButton open_close, signal clicked() -> grabber, slot openClose()

    und

    CamGrabber grabber, signal frameTaken(QImage*) -> GUI, slot setImage()

    Ich würde auch den echten Sourcecode pasten, allerdings ist der ziemlich unaufgeräumt, weil ich mich mit Qt und C++ erst vertraut mache. Ich glaube mit diesem Pseudocode ist das besser zu verstehen.

    Wäre schön, wenn da jemand eine Antwort weiß. Ich stehe da ziemlich auf dem Schlauch, im Moment. Oder gehe ich fälschlich davon aus, dass setImage im Kontext des aufrufenden Threads ausgeführt wird?

    Danke,

    Stefan



  • Ich sehe in der Doku keine explizite Angabe über "deep-copy".
    Wenn du die anderen Funktionen von QPixmap bzw. QImage, steht in manchen Funktionen explizit copy, bei fromImage nur "konvert".



  • nurf schrieb:

    Ich sehe in der Doku keine explizite Angabe über "deep-copy".
    Wenn du die anderen Funktionen von QPixmap bzw. QImage, steht in manchen Funktionen explizit copy, bei fromImage nur "konvert".

    Das stimmt. Das steht da nicht explizit. Deswegen habe ich mich noch mal vergewissert:

    Ein QImage* erstellt, ein QPixmap erstellt (fromImage).
    das QImage* anschließend gelöscht, dann QPixmap.toImage() aufgerufen. War kein Problem und lieferte mir ein neues QImage aus.

    Das Problem habe ich nun anders "gelöst".

    void CamWidget::bttnClicked() {
    
        if (grabber.isConnected()) {
            disconnect(&grabber,SIGNAL(frameTaken(QImage*)),this, SLOT(setImage(QImage*)));
        } else {
            connect(&grabber,SIGNAL(frameTaken(QImage*)),this, SLOT(setImage(QImage*)));
        }
    
        grabber.openCloseCamDevice(&defaultCamDev);
    }
    

    Habe explizit connect und disconnect, je nach Verbindungszustand im Slot gesetzt. Das scheint zu helfen. Irgendwas muss also mit der Mutex-Synchronisation faul sein, die ich da gebastelt habe. Die sollte das Problem nämlich von vornherein verhindern, dass der GUI-Thread das delete initiiert, während der Grabber noch fleißig Images emittiert.


Anmelden zum Antworten