Programando uma camada de vídeo em um Raspberry Pi usando a API DispmanX


Neste artigo, quero demonstrar o uso da API DispmanX dos computadores de placa única Raspberry. A API DispmanX oferece a capacidade de criar novas camadas renderizadas na área de trabalho do Raspberry. Camadas pairam sobre as camadas. Eles podem ser criados, excluídos, movidos dinamicamente, podem ser dimensionados. Ao mesmo tempo, o próprio controlador de vídeo os combinará e exibirá na tela do monitor. Curiosamente, as camadas podem ter um canal alfa e, em seguida, as imagens de todas as camadas se misturam sozinhas. Além disso, além de 32 camadas ARGB de dois bits, você pode criar, por exemplo, camadas YUV420 ou camadas de outros tipos. Framboesa já tem duas camadas por padrão. O menor contém uma imagem da área de trabalho. Toda a saída através do X vai para essa camada. E existe uma segunda camada superior na qual vive a imagem do cursor do mouse.

Vou mostrar como criar uma nova camada, escrever uma imagem nela e como movê-la pela tela. Na verdade, o vídeo de demonstração acima mostra a operação desse programa. Quatro novas camadas ARGB de 32 bits são criadas aqui. Em cada uma das camadas, escrevo pixels de bitmaps pré-preparados. Meus bitmaps são imagens de nuvens, sol e balões. As camadas se movem pela tela em velocidades diferentes, pairando sobre a camada X mais baixa.
Então, a API DispmanX. Isso é bastante específico para o Raspberry e seus controladores de vídeo. E isso é uma coisa "não-padrão". Em geral, o kernel do Linux e o subsistema de gráficos do Linux têm um lugar para a programação de camadas de vídeo via DRM, Direct Rendering Manager, mas por alguma razão os criadores do Raspberry decidiram criar sua própria bicicleta. Embora, por outro lado, essa bicicleta não seja complicada, é bem possível pedalar. DispmanX é executado no Pi-Zero, no Raspberry Pi / Pi2 / Pi3 e Pi4. Em geral, em todas as framboesas. Embora, na verdade, o Pi4 já tenha o OpenGLESv3 normal. Aqui, esses pandeiros podem não ser mais necessários. Mas, por outro lado, o DispmanX é muito mais simples (embora com menos recursos) do que o OpenGLES.

Para gravar programas usando esta API DispmanX, você precisa incluir o arquivo de cabeçalho /opt/vc/include/bcm_host.h. Além disso, você também precisará vincular o programa à biblioteca libbcm_host.so, localizada na pasta / opt / vc / lib.

Todas as funções da API que precisamos começam com vc_dispmanx_ * ...

A primeira coisa a fazer para criar novas camadas é acessar a exibição usando o seguinte par de funções:

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

Agora você pode criar um "recurso" que conterá a imagem da camada:

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

Aqui, o tipo de camada é de 32 bits com o canal alfa. Mas pode haver outros tipos, como eu já escrevi, até YUV. O uso de uma camada YUV faz sentido para camadas dinâmicas, como ao reproduzir um vídeo. Em seguida, a quantidade de dados gravados na tela é significativamente reduzida e você não precisa transcodificar o YUV para RGB, o que economiza valiosos ciclos de clock do processador.

Após criar um novo recurso, o manipulador de exibição obtido anteriormente pode ser usado para adicionar uma nova camada de elemento à exibição:

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

Há outro parâmetro de atualização muito importante. Cada vez que você precisa modificar o conteúdo da tela, adicionar ou remover uma camada, movê-la ou gravar uma nova imagem na camada, é necessário marcar o início das alterações e o final das alterações.

Para começar a alterar os elementos na tela, você precisa:

DISPMANX_UPDATE_HANDLE_T update =  vc_dispmanx_update_start( Priority );

Em seguida, altere tudo o que você precisa, crie uma camada ou mova-a, escreva novos pixels na camada e feche as alterações usando a função:

vc_dispmanx_update_submit_sync( update );

Como você pode entender pelo nome da função, as alterações entrarão em vigor após o próximo pulso de quadro da varredura de vídeo.

A gravação de pixels em uma camada é realizada pela função:

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

Infelizmente, apesar da presença do parâmetro rect, é impossível atualizar um fragmento arbitrário dentro da imagem. Somente a “faixa” pode ser atualizada, ou seja, a parte superior e inferior do retângulo podem ser especificadas, mas a esquerda sempre será 0 e a direita sempre será a largura da imagem.

Curiosamente, esse talvez seja o mínimo de conhecimento necessário para manipular as camadas.

No meu programa, escrevi um pequeno invólucro sobre a API DispmanX. Isso me permite descrever cada camada em uma estrutura 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_;
};

Bem, muitas das minhas funções de wrapper levam um ponteiro para uma estrutura 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 );

Dessa forma, eu posso criar várias camadas e manipulá-las facilmente.

Também escrevi uma função para ler um bitmap. Além disso, a função é complicada - se um pixel verde puro 0x00FF00 for encontrado no bitmap, considero que ele é um pixel transparente na minha imagem e, portanto, defina o byte Alpha desse pixel como zero.

Na pintura, pintei três imagens.

Nuvens:



Sol:



Balões:



O programa cria quatro camadas. As duas primeiras camadas são criadas a partir do mesmo bitmap da nuvem, mas diferencio o tamanho da camada, uso a escala, as segundas nuvens são maiores que a primeira. A terceira camada é de balões e na quarta camada carrego o 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_ );
.......................

No ciclo perpétuo, movo as camadas pela tela em velocidades diferentes:


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

Isso é tudo.

Todo o código do meu programa pode ser obtido no github .

Compile o programa em raspberry - com o comando make. Em seguida, execute a partir da linha de comando: ./demo e você obtém o que vê acima na demonstração em vídeo.

A propósito, no Raspberry, você pode ver uma lista de todas as camadas de vídeo com o comando vcgencmd com o parâmetro dispmanx_list. Aqui está a saída deste comando no Pi4 antes de iniciar minha demonstração:



Como eu escrevi, já existem duas camadas: uma camada para o Xorg e uma camada para o mouse.

E aqui está a lista de camadas depois de iniciar minha demonstração:



Pode-se ver que quatro novas camadas foram adicionadas. Tirei screenshots na equipe de escritores. É interessante que ele captura apenas a camada x inferior, portanto, na captura de tela, nem nuvens nem balões que estão em outras camadas são visíveis.

Sei que os microcomputadores Raspberry às vezes são usados ​​para criar várias barracas. E para quiosques, às vezes você precisa executar o OSD, On Screen Display - ou seja, sobrepor uma imagem sobre a outra. Parece-me que a API DispmanX é perfeita para essas aplicações. Talvez alguém goste desta solução.

All Articles