Programmation d'une couche vidéo dans un Raspberry Pi à l'aide de l'API DispmanX


Dans cet article, je veux démontrer l'utilisation de l'API DispmanX des ordinateurs à carte unique Raspberry. L'API DispmanX offre la possibilité de créer de nouvelles couches rendues sur le bureau Raspberry. Les couches se superposent aux couches. Ils peuvent être créés dynamiquement, supprimés, déplacés, ils peuvent être redimensionnés. Dans le même temps, le contrôleur vidéo lui-même les combinera et les affichera sur l'écran du moniteur. Fait intéressant, les calques peuvent avoir un canal alpha, puis les images de tous les calques se mélangeront par elles-mêmes. De plus, en plus des 32 couches ARGB à deux bits, vous pouvez créer, par exemple, des couches YUV420 ou des couches d'autres types. La framboise a déjà deux couches par défaut. La plus basse contient une image de bureau. Toute sortie via le X va à cette couche. Et il y a une deuxième couche, la plus haute, dans laquelle vit l'image du curseur de la souris.

Je vais montrer comment créer un nouveau calque, y écrire une image et comment la déplacer sur l'écran. En fait, la vidéo de démonstration ci-dessus montre le fonctionnement d'un tel programme. Quatre nouvelles couches ARGB 32 bits sont créées ici. Dans chacune des couches, j'écris des pixels à partir de bitmaps pré-préparés. Mes bitmaps sont des images de nuages, de soleil et de ballons. Les calques se déplacent autour de l'écran à différentes vitesses, suspendus au-dessus du calque X le plus bas.
Donc, l'API DispmanX. C'est une chose plutôt basique spécifique à Raspberry et à ses contrôleurs vidéo. Et c'est une chose «non standard». En général, le noyau Linux et le sous-système graphique Linux ont une place pour la programmation des couches vidéo via DRM, Direct Rendering Manager, mais pour une raison quelconque, les créateurs de Raspberry ont décidé de créer leur propre vélo. Bien que, d'autre part, ce ne soit pas un vélo compliqué, ce qui est tout à fait possible de rouler. DispmanX fonctionne sur Pi-Zero, sur Raspberry Pi / Pi2 / Pi3 et Pi4. En général, sur toutes les framboises. Bien que, en vérité, Pi4 dispose déjà d'OpenGLESv3 normal. Ici, ces tambourins peuvent ne plus être nécessaires. Mais d'un autre côté, DispmanX est beaucoup plus simple (quoique moins de fonctionnalités) qu'OpenGLES.

Pour écrire des programmes à l'aide de cette API DispmanX, vous devez inclure le fichier d'en-tête /opt/vc/include/bcm_host.h. De plus, vous devrez également lier le programme à la bibliothèque libbcm_host.so, qui se trouve dans le dossier / opt / vc / lib.

Toutes les fonctions de l'API dont nous avons besoin commencent par vc_dispmanx_ * ...

La première chose à faire pour créer de nouvelles couches est d'accéder à l'affichage en utilisant la paire de fonctions suivante:

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

Vous pouvez maintenant créer une «ressource» qui contiendra l'image de la couche:

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 );

Ici, le type de calque est de 32 bits avec le canal alpha. Mais il peut y avoir d'autres types, comme je l'ai déjà écrit, même YUV. L'utilisation d'un calque YUV est logique pour les calques dynamiques, comme lors de la lecture d'une vidéo. Ensuite, la quantité de données écrites sur l'écran est considérablement réduite et vous n'avez pas besoin de transcoder YUV en RVB, ce qui économise de précieux cycles d'horloge du processeur.

Après avoir créé une nouvelle ressource, le gestionnaire d'affichage obtenu précédemment peut être utilisé pour ajouter une nouvelle couche d'élément à l'affichage:

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 );

Il existe un autre paramètre de mise à jour très important. Chaque fois que vous devez modifier le contenu de l'affichage, ajouter ou supprimer un calque, le déplacer ou écrire une nouvelle image sur un calque, vous devez marquer le début des modifications et la fin des modifications.

Pour commencer à changer les éléments sur l'affichage, vous devez faire:

DISPMANX_UPDATE_HANDLE_T update =  vc_dispmanx_update_start( Priority );

Modifiez ensuite tout ce dont vous avez besoin, créez un calque ou déplacez-le, écrivez de nouveaux pixels dans le calque, puis fermez les modifications à l'aide de la fonction:

vc_dispmanx_update_submit_sync( update );

Comme vous pouvez le comprendre d'après le nom de la fonction, les modifications prendront effet après la prochaine impulsion de trame du balayage vidéo.

L'écriture de pixels sur un calque est effectuée par la fonction:

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

Malheureusement, malgré la présence du paramètre rect, il est impossible de mettre à jour un fragment arbitraire à l'intérieur de l'image. Seule la «bande» peut être mise à jour, c'est-à-dire que le haut et le bas du rectangle peuvent être spécifiés, mais à gauche sera toujours 0 et à droite sera toujours la largeur de l'image.

Curieusement, c'est peut-être le minimum de connaissances nécessaires pour manipuler les couches.

Dans mon programme, j'ai écrit un petit wrapper sur l'API DispmanX. Cela me permet de décrire chaque couche dans une structure distincte:

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_;
};

Eh bien, beaucoup de mes fonctions d'encapsulation prennent un pointeur vers une telle structure comme paramètre:

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 );

De cette façon, je peux créer plusieurs calques et les manipuler facilement.

J'ai également écrit une fonction pour lire une bitmap. De plus, la fonction est délicate - si un pixel vert pur 0x00FF00 est trouvé dans le bitmap, alors je le considère comme un pixel transparent dans mon image et je mets donc à zéro l'octet Alpha de ce pixel.

En peinture, j'ai peint trois images.

Nuages:



Soleil:



Ballons:



Le programme crée quatre couches. Les deux premières couches sont créées à partir du même bitmap du nuage, mais je fais une taille de couche différente, j'utilise une mise à l'échelle, les seconds nuages ​​sont plus grands que le premier. La troisième couche est des ballons et dans la quatrième couche je charge le soleil:

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_ );
.......................

Dans le cycle perpétuel, je déplace les calques autour de l'écran à différentes vitesses:


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 );
	}

C'est tout.

Tout le code de mon programme peut être pris sur github .

Compilez le programme sur framboise - avec la commande make. Exécutez ensuite à partir de la ligne de commande: ./demo et vous obtenez ce que vous voyez ci-dessus dans la démonstration vidéo.

Par ailleurs, sur Raspberry, vous pouvez voir une liste de toutes les couches vidéo avec la commande vcgencmd avec le paramètre dispmanx_list. Voici la sortie de cette commande sur Pi4 avant de lancer ma démo:



Comme je l'ai écrit, il y a déjà deux couches: une couche pour Xorg et une couche pour une souris.

Et voici la liste des calques après avoir démarré ma démo:



On peut voir que quatre nouvelles couches ont été ajoutées. J'ai pris des captures d'écran de l'équipe scrot. Il est intéressant de noter qu'il capture uniquement la couche x inférieure, par conséquent, dans la capture d'écran, ni les nuages ​​ni les ballons qui se trouvent dans d'autres couches ne sont visibles.

Je sais que les micro-ordinateurs Raspberry sont parfois utilisés pour créer divers stands. Et pour les kiosques, vous devez parfois faire OSD, affichage à l'écran - c'est-à-dire superposer une image au-dessus d'une autre. Il me semble que l'API DispmanX est parfaite pour ces applications. Peut-être que quelqu'un aimera cette solution.

All Articles