Videorendering mit OpenGL und Third-party-libraries



  • Hallo zusammen,

    ich schreibe eine Anwendung bei der ein ankommendes Videosignal (Kamerabild mit einer Capturecard als Schnittstelle) in einer GUI gerendert werden soll, so dass dort ein Vorschaubild der Kamera angezeigt wird. Für die Aufnahme benutze ich die DeckLink API von Blackmagic. Rendern tue ich mit OpenGL. Das Fenster erstelle ich mit SDL.
    Nun stehe ich vor einem Problem bei dem ich nicht weiterkomme, denn wenn ich das Programm ausführe bleibt das Fenster schwarz.
    Ich erstelle mit SDL ein Fenster und in diesem Fenster rufe ich die Funktion glTexSubImage2D(). Handelt es sich hierbei um die falsche Methode? Ich finde allerdings auch keine andere passende.

    Grüße

    /**
    * Ctor
    */
    VideoScreen::VideoScreen(CComPtr<IDeckLink> deckLink)
    	: m_deckLink(deckLink), m_deckLinkInput(deckLink), m_sdlContext(nullptr), m_window(nullptr),
    	m_frameHeight(FRAME_HEIGHT), m_frameWidth(FRAME_WIDTH), m_windowHeight(WINDOW_HEIGHT),
    	m_windowWidth(WINDOW_WIDTH), m_referenceCounter(1) {
    	
    }
    
    /**
    * Dtor - delete the render context
    */
    VideoScreen::~VideoScreen() {
    
    	SDL_GL_DeleteContext(m_sdlContext);
    	SDL_DestroyWindow(m_window);
    	SDL_Quit();
    }
    
    /**
    * Create a window with SDL library
    */
    bool VideoScreen::InitializeScreen() {
    
    	VideoScreen sdlCallback; //create a object of class VideoScreen to call the calculation of the frame resolution
    
    	if (m_deckLinkInput->SetCallback(&sdlCallback) != S_OK) {
    		std::cout << "VideoScreen: SetCallback did not work.";
    		return 0;
    	}
    	else {
    		std::cout << "VideoScreen: SetCallback works.";
    	}
    
    	//Initializing SDL library
    	if (SDL_Init(SDL_INIT_VIDEO) < 0) {
    		std::cout << "Initializing SDL failed." << std::endl;
    		SDL_DestroyWindow(m_window);
    		SDL_Quit();
    		return 0;
    	}
    	else {
    		std::cout << "VideoScreen: SDL Init works.";
    	}
    
    	//it's optional to name this attributes
    	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
    	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
    	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
    	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
    	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
    	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
    	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    
    	//create a SDL window, not sure if this is nessacery
    	m_window = SDL_CreateWindow("Videometer", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, m_windowWidth, m_windowHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
    
    	//create an SDL context
    	SDL_GL_CreateContext(m_window);
    
    	//Initializing GLEW
    	if (glewInit() != S_OK) { std::cout << "Initializing GLEW does not work." << std::endl; }
    
    	glViewport(0, 0, m_windowWidth, m_windowHeight);
    
    	bool quit = false;
    	while (!quit) {
    
    		SDL_Event event;
    
    		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_frameWidth / 2, m_frameHeight, GL_BGRA,
    			GL_UNSIGNED_INT, (void*)sdlCallback.CalculateFrameResolution());
    
    		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    
    		SDL_GL_SwapWindow(m_window);
    
    		//using this while loop not renders only a single frame
    		while (SDL_PollEvent(&event)) {
    			if (event.type == SDL_QUIT) {
    				quit = true;
    			}
    		}
    	}
    }
    
    /**
    * Function that calculates the resolution of a frame that is drawn to the GUI. 
    * Called in InitializeScreen.
    */
    int VideoScreen::CalculateFrameResolution() {
    
    	return m_frameWidth * m_frameHeight;
    }
    
    /**
    * Function is called when a frame arrives
    */
    HRESULT VideoScreen::VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame, IDeckLinkAudioInputPacket* audioPacket) {
    
    	void* frameData;
    	VideoScreen sdlFrame;
    
    	if (videoFrame != nullptr) {
    		videoFrame->GetBytes(&frameData);
    		SDL_memcpy((void*)sdlFrame.CalculateFrameResolution(), frameData, sizeof(sdlFrame.CalculateFrameResolution()));
    	}
    	return S_OK;
    }
    


  • @makopo sagte in Videorendering mit OpenGL und Third-party-libraries:

    (void*)sdlCallback.CalculateFrameResolution()

    SDL_memcpy((void*)sdlFrame.CalculateFrameResolution(), frameData, sizeof(sdlFrame.CalculateFrameResolution()));

    Beide Codezeilen ergeben überhaupt keinen Sinn (sie kompilieren zwar, sind aber logisch unsinnig).

    Laut glTexSubImage2D ist der letzte Parameter ein Zeiger zu den Bilddaten.
    Wenn du diese in der Funktion VideoInputFrameArrived mittels videoFrame->GetBytes(&frameData); erhältst, so solltest du frameData als Klassenmember anlegen (und in Zeile 102 als lokale Variable löschen) und dann benutzen:

    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_frameWidth / 2, m_frameHeight, GL_BGRA, GL_UNSIGNED_INT, frameData);
    

    (ich kann dir aber nicht sagen, ob daß jetzt die passende GL-Funktion ist).

    Was du jedoch mit der zweiten Zeile SDL_memcpy(...) bezwecken willst, ist mir überhaupt nicht klar.



  • Hi,

    danke erstmal für die Rückmeldung. Mit SDL_memcpy kopiere ich die Daten jedes Mal in den Speicher wenn ein Frame vom System erkannt wird. In Zeile 73 hole ich die Daten dann aus dem Speicher und übergebe die der OpenGL Funktion, welche Sie dann als Bild rendern müsste.

    Oder was verstehe ich falsch?



  • Hast du denn mal meinen Vorschlag ausprobiert?

    Und was soll denn die CalculateFrameResolution für einen Sinn bei SDL_memcpy machen? Keinen!



  • Tja, ich hatte mich da an Beispielcode orientiert, bei dem anstelle von CalculateFrameDimension die Berechnung über Präprozessorbefehle gelöst wurde.

    Das sieht dann so aus:

    #define VIDEO_WIDTH 1280
    #define VIDEO_HEIGHT 720
    Uint16 YUVData[VIDEO_WIDTH * VIDEO_HEIGHT];
    SDL_memcpy(YUVData, frameData, sizeof(YUVData));
    
    glTexSubImage2D(
        GL_TEXTURE_2D,
        0,
        0,
        0,
        VIDEO_WIDTH / 2,
        VIDEO_HEIGHT,
        GL_BGRA,
        GL_UNSIGNED_INT_8_8_8_8_REV,
        deckLinkInputCallback.YUVData
    );
    

    CalculateFrameResolution gibt die Auflösung zurück, genauso wie YUVData die Auflösung mit den defines berechnet. Das dass so ist, habe ich erstmal als gegeben hingenommen. Da es kaum Material zum Videorendering mit OpenGL gibt, hangel ich mich da gerade irgendwie durch.

    Deinen Vorschlag hatte ich ausprobiert. Das Fenster bleibt aber schwarz.



  • Das ist ja auch korrekt so, denn in diesem Code wird ein kompletter Speicherbereich benutzt.
    Dein umgeschriebener Code gibt aber nur einen Zahlenwert bei CalculateFrameResolution() zurück (und keinen reservierten Speicher).

    Funktioniert denn die Anzeige, wenn du den statischen Speicher aus dem Beispielprogramm benutzt?



  • @Th69
    Ok danke, das mit dem Speicherbereich war mir so nicht klar. Das Bild bleibt aber auch schwarz wenn ich den statischen Speicher verwende.
    Ich hatte allerdings noch herausgefunden das Texturen notwendig sind um ein Bild zu rendern.

    Was meinst du denn mit, es wird ein "kompletter Speicherbereich" benutzt. Die Allocation durch YUV[ ]? Mir war das so nicht klar das da evtl der Stack allokiert wird.



  • Du solltest ersteinmal das Programm mit dem statischen Speicher zum Laufen (d.h. zur Anzeige) kriegen (damit du weißt, welche Funktionen und welcher Ablauf nötig sind).

    Und bzgl:

    Uint16 YUVData[VIDEO_WIDTH * VIDEO_HEIGHT];
    

    Dies ist eine normale Deklaration eines Arrays, d.h. es wird ein kompletter Speicherbereich allokiert (in diesem Fall also 1280*720 = 921600 Uint16-Werte, d.h. 1843200 Bytes).

    Warum jedoch die Framedaten dann erst noch in dieses Array umkopiert werden, kann ich dir nicht sagen.
    M.E. könnte man bei glTexSubImage2D auch direkt frameData benutzen (so wie ich es bei meinem ersten Beitrag geschrieben hatte).

    (Jetzt versteh ich daher zumindestens, warum du SDL_memcpy - wenn auch komplett falsch - benutzt hast.)



  • @Th69
    Danke für deine Erklärung. Ich dachte erst mit "kompletter Speicherbereich" meinst du die Festlesung der zwei #defines für VIDEO_WIDTH und VIDEO_HEIGHT.
    Das YUVData ein Array ist hatte ich schon verstanden, mich hatte nur die Multiplikation darin irritiert.
    Beschäftige mich noch nicht so lange mit C++...



  • @makopo sagte in Videorendering mit OpenGL und Third-party-libraries:

    Mit SDL kann ich leider nicht so viel dienen ....

    Aber generell:
    Videos im OpenContext rendern, da willst du nicht das einzelbild im Renderthread auf die Graka schieben ....
    Einige wenige Dinge wo OpenGL nicht an einen Thread gebunden ist, ist das lesen und schreiben von daten auf ein PBO.

    "Normal" bekommst aus einem video strom immer das komplette frame ....
    -> Ich würd für jedes video einen einen seperaten PBO anlegen, und die Daten komplett rüberschieben.
    In der Szene dann ein explizietes Viereck für jedes video anlegen und den PBO als texture zuweisen.
    glTexSubImage2D sollte man da nicht brauchen

    Uint16 YUVData[VIDEO_WIDTH * VIDEO_HEIGHT];
    

    Wie ist das gepackt ?
    also wo und wie liegt Y U und V verteilt ?

    Wenn glück hasst, kann die OpenGL implementierung das format .... (ycbcr formate)

    Wenn nicht ... auch nicht sooo schlimm
    Schieb den Farbcode als bytes in den Fragment schader, und rechne da zu rgb(a) um
    Wenn du die bilder einigermassen flott laufen sehen willst, würd ich nicht auf der CPU konvertieren, und auch würde ich bibs mit GPU support nicht nehmen (video in -> cpu -> gpu (dekoder) -> cpu (OpenGL PBO write) -> gpu (Texture) )


Anmelden zum Antworten