Programando una capa de video en una Raspberry Pi usando la API DispmanX


En este artículo, quiero demostrar el uso de la API DispmanX de las computadoras de placa única Raspberry. La API DispmanX proporciona la capacidad de crear nuevas capas renderizadas en el escritorio Raspberry. Las capas cuelgan sobre capas. Se pueden crear, eliminar, mover dinámicamente, se pueden escalar. Al mismo tiempo, el controlador de video los combinará y mostrará en la pantalla del monitor. Curiosamente, las capas pueden tener un canal alfa, y luego, las imágenes de todas las capas se mezclarán por sí mismas. Además, además de 32 capas ARGB de dos bits, puede crear, por ejemplo, capas YUV420 o capas de otros tipos. Frambuesa ya tiene dos capas por defecto. El más bajo contiene una imagen de escritorio. Toda la salida a través de la X va a esta capa. Y hay una segunda capa superior en la que vive la imagen del cursor del mouse.

Mostraré cómo crear una nueva capa, escribir una imagen en ella y cómo moverla por la pantalla. En realidad, el video de demostración anterior muestra el funcionamiento de dicho programa. Aquí se crean cuatro nuevas capas ARGB de 32 bits. En cada una de las capas escribo píxeles de mapas de bits preparados previamente. Mis mapas de bits son imágenes de nubes, sol y globos. Las capas se mueven alrededor de la pantalla a diferentes velocidades, colgando sobre la capa X más baja.
Entonces, la API DispmanX. Esto es algo de bajo nivel específico para Raspberry y sus controladores de video. Y esto es una cosa "no estándar". En general, el kernel de Linux y el subsistema de gráficos de Linux tienen un lugar para programar capas de video a través de DRM, Direct Rendering Manager, pero por alguna razón los creadores de Raspberry decidieron crear su propia bicicleta. Aunque, por otro lado, esta no es una bicicleta complicada, que es bastante posible de manejar. DispmanX se ejecuta en Pi-Zero, en Raspberry Pi / Pi2 / Pi3 y Pi4. En general, en todas las frambuesas. Aunque, en verdad, Pi4 ya tiene OpenGLESv3 normal. Aquí tales panderetas ya no serán necesarias. Pero, por otro lado, DispmanX es mucho más simple (aunque con menos funciones) que OpenGLES.

Para escribir programas utilizando esta API de DispmanX, debe incluir el archivo de encabezado /opt/vc/include/bcm_host.h. Además, también deberá vincular el programa a la biblioteca libbcm_host.so, que se encuentra en la carpeta / opt / vc / lib.

Todas las funciones de la API que necesitamos comienzan con vc_dispmanx_ * ...

Lo primero que debe hacer para crear nuevas capas es acceder a la pantalla utilizando el siguiente par de funciones:

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

Ahora puede crear un "recurso" que contendrá la imagen de la capa:

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

Aquí, el tipo de capa es de 32 bits con el canal alfa. Pero puede haber otros tipos, como ya escribí, incluso YUV. El uso de una capa YUV tiene sentido para capas dinámicas, como cuando se reproduce un video. Luego, la cantidad de datos escritos en la pantalla se reduce significativamente, y no necesita transcodificar YUV a RGB, lo que ahorra valiosos ciclos de reloj del procesador.

Después de crear un nuevo recurso, el controlador de pantalla obtenido anteriormente se puede usar para agregar una nueva capa de elemento a la pantalla:

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

Hay otro parámetro de actualización muy importante. Cada vez que necesite modificar el contenido de la pantalla, agregar o eliminar una capa, o moverla, o escribir una nueva imagen en la capa, debe marcar el comienzo de los cambios y el final de los cambios.

Para comenzar a cambiar los elementos en la pantalla, debe hacer lo siguiente:

DISPMANX_UPDATE_HANDLE_T update =  vc_dispmanx_update_start( Priority );

Luego cambie todo lo que necesita, cree una capa o muévala, escriba nuevos píxeles en la capa y luego cierre los cambios con la función:

vc_dispmanx_update_submit_sync( update );

Como puede entender por el nombre de la función, los cambios surtirán efecto después del siguiente pulso de fotograma del escaneo de video.

La función de escribir píxeles en una capa se realiza:

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

Desafortunadamente, a pesar de la presencia del parámetro rect, es imposible actualizar un fragmento arbitrario dentro de la imagen. Solo se puede actualizar la "tira", es decir, se puede especificar la parte superior e inferior del rectángulo, pero la izquierda siempre será 0 y la derecha siempre será el ancho de la imagen.

Curiosamente, este es quizás el mínimo de conocimiento necesario para manipular capas.

En mi programa, escribí un pequeño contenedor sobre la API DispmanX. Esto me permite describir cada capa en una estructura separada:

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

Bueno, muchas de mis funciones de contenedor toman un puntero a una estructura como parámetro:

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 esta manera puedo crear varias capas y manipularlas fácilmente.

También escribí una función para leer un mapa de bits. Además, la función es complicada: si se encuentra un píxel verde puro 0x00FF00 en el mapa de bits, entonces considero que es un píxel transparente en mi imagen y, en consecuencia, establezco el byte alfa de este píxel en cero.

En pintura, pinté tres imágenes.

Nubes:



Sol:



Globos:



El programa crea cuatro capas. Las primeras dos capas se crean a partir del mismo mapa de bits de la nube, pero hago que el tamaño de la capa sea diferente, uso la escala, las segundas nubes son más grandes que la primera. La tercera capa es globos y en la cuarta capa cargo el sol:

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

En el ciclo perpetuo, muevo las capas alrededor de la pantalla a diferentes velocidades:


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

Eso es todo.

Todo el código de mi programa se puede tomar en github .

Compile el programa en frambuesa con el comando make. Luego, ejecute desde la línea de comando: ./demo y obtendrá lo que ve arriba en la demostración de video.

Por cierto, en Raspberry puedes ver una lista de todas las capas de video con el comando vcgencmd con el parámetro dispmanx_list. Aquí está el resultado de este comando en Pi4 antes de iniciar mi demostración:



como escribí, ya hay dos capas: una capa para Xorg y una capa para un mouse.

Y aquí está la lista de capas después de comenzar mi demostración:



Se puede ver que se agregaron cuatro nuevas capas. Tomé capturas de pantalla en el equipo de scrot. Es interesante que capture solo la capa x inferior, por lo tanto, en la captura de pantalla, no se ven ni las nubes ni los globos que están en otras capas.

Sé que las microcomputadoras Raspberry a veces se usan para crear varios puestos. Y para los quioscos, a veces necesitas hacer OSD, On Screen Display, es decir, superponer una imagen encima de otra. Me parece que la API DispmanX es perfecta para estas aplicaciones. Quizás a alguien le guste esta solución.

All Articles