Programmieren einer Videoebene in einem Raspberry Pi mithilfe der DispmanX-API


In diesem Artikel möchte ich die Verwendung der DispmanX-API von Raspberry Single Board-Computern demonstrieren. Die DispmanX-API bietet die Möglichkeit, neue gerenderte Ebenen auf dem Raspberry-Desktop zu erstellen. Ebenen hängen über Ebenen. Sie können dynamisch erstellt, gelöscht, verschoben und skaliert werden. Gleichzeitig kombiniert der Videocontroller sie und zeigt sie auf dem Monitorbildschirm an. Interessanterweise können die Ebenen einen Alphakanal haben, und dann werden die Bilder aller Ebenen von selbst gemischt. Zusätzlich zu 32 Zwei-Bit-ARGB-Layern können Sie beispielsweise YUV420-Layer oder Layer anderer Typen erstellen. Himbeere hat standardmäßig bereits zwei Schichten. Das unterste enthält ein Desktop-Image. Alle Ausgaben über das X gehen an diese Ebene. Und es gibt eine zweite, oberste Ebene, in der das Bild des Mauszeigers lebt.

Ich werde zeigen, wie man eine neue Ebene erstellt, ein Bild hineinschreibt und wie man es auf dem Bildschirm bewegt. Das obige Demo-Video zeigt den Betrieb eines solchen Programms. Hier werden vier neue 32-Bit-ARGB-Ebenen erstellt. In jede der Ebenen schreibe ich Pixel aus vorbereiteten Bitmaps. Meine Bitmaps sind Bilder von Wolken, Sonne und Luftballons. Ebenen bewegen sich mit unterschiedlichen Geschwindigkeiten über den Bildschirm und hängen über der untersten X-Ebene.
Also die DispmanX-API. Dies ist eine eher niedrige Sache, die für Raspberry und seine Videocontroller spezifisch ist. Und das ist eine "nicht standardmäßige" Sache. Im Allgemeinen haben der Linux-Kernel und das Linux-Grafiksubsystem einen Platz zum Programmieren von Videoebenen über DRM, Direct Rendering Manager, aber aus irgendeinem Grund haben die Entwickler von Raspberry beschlossen, ihr eigenes Fahrrad zu erstellen. Auf der anderen Seite ist dies kein kompliziertes Fahrrad, das durchaus zu fahren ist. DispmanX läuft auf Pi-Zero, auf Raspberry Pi / Pi2 / Pi3 und Pi4. Im Allgemeinen auf allen Himbeeren. Obwohl Pi4 in Wahrheit bereits normales OpenGLESv3 hat. Hier werden solche Tamburine möglicherweise nicht mehr benötigt. Andererseits ist DispmanX viel einfacher (wenn auch weniger Funktionen) als OpenGLES.

Um Programme mit dieser DispmanX-API zu schreiben, müssen Sie die Header-Datei /opt/vc/include/bcm_host.h einfügen. Darüber hinaus müssen Sie das Programm mit der Bibliothek libbcm_host.so verknüpfen, die sich im Ordner / opt / vc / lib befindet.

Alle Funktionen der API, die wir benötigen, beginnen mit vc_dispmanx_ * ... Um

neue Ebenen zu erstellen, müssen Sie zunächst mit den folgenden Funktionen auf die Anzeige zugreifen:

bcm_host_init();
DISPMANX_DISPLAY_HANDLE_T display = vc_dispmanx_display_open( 0 );

Jetzt können Sie eine „Ressource“ erstellen, die das Bild der Ebene enthält:

VC_IMAGE_TYPE_T type = VC_IMAGE_ARGB8888;
uint32_t UnusedImagePtr;
int SrcImageWidth = 512; //image must be 32 bytes aligned size
int SrcImageWidth = 196;
DISPMANX_RESOURCE_HANDLE_T resource = vc_dispmanx_resource_create( type, SrcImageWidth, SrcImageHeight,  &UnusedImagePtr );

Hier ist der Layertyp 32-Bit mit dem Alpha-Kanal. Aber es kann andere Typen geben, wie ich bereits schrieb, sogar YUV. Die Verwendung einer YUV-Ebene ist für dynamische Ebenen sinnvoll, z. B. beim Abspielen eines Videos. Dann wird die auf den Bildschirm geschriebene Datenmenge erheblich reduziert, und Sie müssen YUV nicht in RGB umcodieren, wodurch wertvolle Prozessortaktzyklen eingespart werden.

Nach dem Erstellen einer neuen Ressource kann der zuvor erhaltene Anzeigehandler verwendet werden, um der Anzeige eine neue Elementebene hinzuzufügen:

VC_DISPMANX_ALPHA_T alpha;
	alpha.flags =
		(DISPMANX_FLAGS_ALPHA_T)(DISPMANX_FLAGS_ALPHA_FROM_SOURCE | DISPMANX_FLAGS_ALPHA_MIX);
	alpha.opacity = 255;
	alpha.mask = 0;
int OutLayer = 200;
DISPMANX_ELEMENT_HANDLE_T vc_element = vc_dispmanx_element_add(
		update,
		display,
		OutLayer,
		&dst_rect, resource, &src_rect, DISPMANX_PROTECTION_NONE, &alpha, NULL, DISPMANX_NO_ROTATE );

Es gibt einen weiteren sehr wichtigen Aktualisierungsparameter. Jedes Mal, wenn Sie den Inhalt der Anzeige ändern, eine Ebene hinzufügen oder entfernen oder verschieben oder ein neues Bild in eine Ebene schreiben müssen, müssen Sie den Beginn der Änderungen und das Ende der Änderungen markieren.

Um die Elemente auf dem Display zu ändern, müssen Sie Folgendes tun:

DISPMANX_UPDATE_HANDLE_T update =  vc_dispmanx_update_start( Priority );

Ändern Sie dann alles, was Sie benötigen, erstellen Sie eine Ebene oder verschieben Sie sie, schreiben Sie neue Pixel in die Ebene und schließen Sie die Änderungen mit der folgenden Funktion:

vc_dispmanx_update_submit_sync( update );

Wie Sie dem Namen der Funktion entnehmen können, werden die Änderungen nach dem nächsten Bildimpuls des Videoscans wirksam.

Das Schreiben von Pixeln in eine Ebene wird durch die folgende Funktion ausgeführt:

vc_dispmanx_resource_write_data(
		resource,
		type,
		pitch,
		Pixels, //pointer to ARGB pixels
		&rect );

Leider ist es trotz des Vorhandenseins des Parameters rect nicht möglich, ein beliebiges Fragment im Bild zu aktualisieren. Es kann nur der „Streifen“ aktualisiert werden, dh der obere und untere Rand des Rechtecks ​​können angegeben werden, aber links ist immer 0 und rechts ist immer die Breite des Bildes.

Seltsamerweise ist dies möglicherweise das gesamte Minimum an Wissen, das zum Manipulieren von Ebenen erforderlich ist.

In meinem Programm habe ich einen kleinen Wrapper über die DispmanX-API geschrieben. Dadurch kann ich jede Schicht in einer separaten Struktur beschreiben:

struct DISPMANX_ELEMENT {
    DISPMANX_RESOURCE_HANDLE_T  res_;
    DISPMANX_ELEMENT_HANDLE_T   vc_element_;
    VC_IMAGE_TYPE_T type_;
    uint32_t src_width_;
    uint32_t src_height_;
    uint32_t dst_layer_;
    uint32_t dst_width_;
    uint32_t dst_height_;
    int32_t  dst_x_;
    int32_t  dst_y_;
};

Nun, viele meiner Wrapper-Funktionen nehmen einen Zeiger auf eine solche Struktur als Parameter:

void dispmanx_init();
void dispmanx_element_init(struct DISPMANX_ELEMENT* Element);
struct DISPMANX_ELEMENT dispmanx_element_create( VC_IMAGE_TYPE_T type, int SrcW, int SrcH, int OutX, int OutY, int OutW, int OutH, int OutLayer );
void dispmanx_element_delete(struct DISPMANX_ELEMENT* Element);
void dispmanx_element_write(struct DISPMANX_ELEMENT* Element, char* Pixels);
void dispmanx_element_move( DISPMANX_UPDATE_HANDLE_T update, struct DISPMANX_ELEMENT* Element, int32_t NewX, int32_t NewY );
DISPMANX_UPDATE_HANDLE_T dispmanx_start_update( int Priority );
void dispmanx_sync( DISPMANX_UPDATE_HANDLE_T Update );

Auf diese Weise kann ich mehrere Ebenen erstellen und diese einfach bearbeiten.

Ich habe auch eine Funktion zum Lesen einer Bitmap geschrieben. Darüber hinaus ist die Funktion schwierig - wenn in der Bitmap ein reines grünes Pixel 0x00FF00 gefunden wird, betrachte ich es als transparentes Pixel in meinem Bild und setze das Alpha-Byte dieses Pixels entsprechend auf Null.

In Farbe habe ich drei Bilder gemalt.

Wolken:



Sonne:



Luftballons:



Das Programm erstellt vier Ebenen. Die ersten beiden Ebenen werden aus derselben Bitmap der Wolke erstellt, aber ich mache die Ebenengröße unterschiedlich, ich verwende Skalierung, die zweiten Wolken sind größer als die erste. Die dritte Schicht sind Luftballons und in der vierten Schicht lade ich die Sonne:

typedef struct OBJ{
	int width_;
	int height_;
	int x_;
	int y_;
	int layer_;
	int speed_;
	char* pixels_;
} OBJ_;

int main(int argc , char *argv[])
{
	cout << "Hello Raspberry DispmanX API!\n";
	dispmanx_init();

	OBJ_ cloud1;
	cloud1.pixels_ = LoadBitmap( (char*)"clouds.bmp", &cloud1.width_, &cloud1.height_ );
	cloud1.layer_ = 100;
	cloud1.x_ = 100;
	cloud1.y_ = 120;
	cloud1.speed_ = 3;

	struct DISPMANX_ELEMENT cloud1_element = dispmanx_element_create(
		VC_IMAGE_ARGB8888, cloud1.width_, cloud1.height_, cloud1.x_, cloud1.y_, cloud1.width_, cloud1.height_, cloud1.layer_ );
	dispmanx_element_write( &cloud1_element, cloud1.pixels_ );

	OBJ_ cloud2;
	cloud2.pixels_ = LoadBitmap( (char*)"clouds.bmp", &cloud2.width_, &cloud2.height_ );
	cloud2.layer_ = 101;
	cloud2.x_ = 10;
	cloud2.y_ = 230;
	cloud2.speed_ = 2;

	struct DISPMANX_ELEMENT cloud2_element = dispmanx_element_create(
		VC_IMAGE_ARGB8888, cloud2.width_, cloud2.height_, cloud2.x_, cloud2.y_, cloud2.width_*1.3, cloud2.height_*1.4, cloud2.layer_ );
	dispmanx_element_write( &cloud2_element, cloud2.pixels_ );

	OBJ_ balls;
	balls.pixels_ = LoadBitmap( (char*)"balls.bmp", &balls.width_, &balls.height_ );
	balls.layer_ = 102;
	balls.x_ = -100;
	balls.y_ = 351;
	balls.speed_ = 5;

	struct DISPMANX_ELEMENT balls_element = dispmanx_element_create(
		VC_IMAGE_ARGB8888, balls.width_, balls.height_, balls.x_, balls.y_, balls.width_, balls.height_, balls.layer_ );
	dispmanx_element_write( &balls_element, balls.pixels_ );

	OBJ_ sun;
	sun.pixels_ = LoadBitmap( (char*)"sun.bmp", &sun.width_, &sun.height_ );
	sun.layer_ = 99;
	sun.x_ = -250;
	sun.y_ = 10;
	sun.speed_ = 1;

	struct DISPMANX_ELEMENT sun_element = dispmanx_element_create(
		VC_IMAGE_ARGB8888, sun.width_, sun.height_, sun.x_, sun.y_, sun.width_, sun.height_, sun.layer_ );
	dispmanx_element_write( &sun_element, sun.pixels_ );
.......................

Im ewigen Zyklus bewege ich die Ebenen mit unterschiedlichen Geschwindigkeiten über den Bildschirm:


while(1)
	{
		this_thread::sleep_for( chrono::milliseconds(20) );
		cloud1.x_ += cloud1.speed_;
		if( cloud1.x_>= 1920 )
			cloud1.x_ = 10 - cloud1.width_;

		cloud2.x_ += cloud2.speed_;
		if( cloud2.x_>= 1920 )
			cloud2.x_ = 133 - cloud2.width_;

		balls.x_ += balls.speed_;
		if( balls.x_>= 1920 )
			balls.x_ = 200 - balls.width_;

		sun.x_ += sun.speed_;
		if( sun.x_>= 1920 )
			sun.x_ = 250 - sun.width_;

		DISPMANX_UPDATE_HANDLE_T update = dispmanx_start_update(10);
		dispmanx_element_move( update, &cloud1_element, cloud1.x_, cloud1.y_ );
		dispmanx_element_move( update, &cloud2_element, cloud2.x_, cloud2.y_ );
		dispmanx_element_move( update, &balls_element, balls.x_, balls.y_ );
		dispmanx_element_move( update, &sun_element,   sun.x_,   sun.y_ );
		dispmanx_sync( update );
	}

Das ist alles.

Der gesamte Code meines Programms kann auf Github übernommen werden .

Kompilieren Sie das Programm auf Himbeere - mit dem Befehl make. Führen Sie dann die Befehlszeile aus: ./demo und Sie erhalten das, was Sie oben in der Videodemonstration sehen.

Auf Raspberry sehen Sie übrigens eine Liste aller Videoebenen mit dem Befehl vcgencmd mit dem Parameter dispmanx_list. Hier ist die Ausgabe dieses Befehls auf Pi4, bevor ich meine Demo starte:



Wie ich geschrieben habe, gibt es bereits zwei Ebenen: eine Ebene für Xorg und eine Ebene für eine Maus.

Und hier ist die Liste der Ebenen nach dem Start meiner Demo:



Es ist ersichtlich, dass vier neue Ebenen hinzugefügt wurden. Ich habe Screenshots im Scrot-Team gemacht. Es ist interessant, dass nur die untere x-Ebene erfasst wird. Im Screenshot sind daher weder Wolken noch Luftballons in anderen Ebenen sichtbar.

Ich weiß, dass Himbeer-Mikrocomputer manchmal verwendet werden, um verschiedene Stände zu erstellen. Und für Kioske müssen Sie manchmal OSD (On Screen Display) ausführen, dh ein Bild über ein anderes legen. Mir scheint, dass die DispmanX-API perfekt für diese Anwendungen ist. Vielleicht wird jemand diese Lösung mögen.

All Articles