Memprogram lapisan video dalam Raspberry Pi menggunakan API DispmanX


Pada artikel ini, saya ingin menunjukkan penggunaan API DispmanX dari Raspberry Single Board Computers. API DispmanX menyediakan kemampuan untuk membuat lapisan yang baru dirender di desktop Raspberry. Lapisan menggantung di atas lapisan. Mereka dapat dibuat secara dinamis, dihapus, dipindahkan, mereka dapat diskalakan. Pada saat yang sama, pengontrol video itu sendiri akan menggabungkan mereka dan menampilkannya di layar monitor. Menariknya, layer-layer tersebut dapat memiliki saluran alpha, dan kemudian, gambar dari semua layer akan bercampur dengan sendirinya. Selain itu, selain 32 lapisan ARGB dua bit, Anda dapat membuat, misalnya, lapisan YUV420, atau lapisan jenis lain. Raspberry sudah memiliki dua lapisan secara default. Yang terendah berisi gambar desktop. Semua output melalui X masuk ke lapisan ini. Dan ada lapisan kedua paling atas di mana gambar kursor mouse hidup.

Saya akan menunjukkan cara membuat layer baru, menulis gambar ke dalamnya dan bagaimana memindahkannya di sekitar layar. Sebenarnya, video demo di atas menunjukkan pengoperasian program semacam itu. Empat layer ARGB 32-bit baru dibuat di sini. Di setiap lapisan saya menulis piksel dari bitmap yang sudah disiapkan sebelumnya. Bitmap saya adalah gambar awan, matahari dan balon. Lapisan bergerak di sekitar layar dengan kecepatan yang berbeda, tergantung di atas lapisan X terendah.
Jadi, API DispmanX. Ini adalah hal tingkat rendah yang khusus untuk Raspberry dan pengontrol videonya. Dan ini adalah hal yang "tidak standar". Secara umum, kernel Linux dan subsistem grafis Linux memiliki tempat untuk memprogram lapisan video melalui DRM, Direct Rendering Manager, tetapi untuk beberapa alasan pencipta Raspberry memutuskan untuk membuat sepeda mereka sendiri. Meskipun, di sisi lain, ini bukan sepeda yang rumit, yang sangat mungkin untuk dikendarai. DispmanX beroperasi pada Pi-Zero, pada Raspberry Pi / Pi2 / Pi3 dan Pi4. Secara umum, pada semua raspberry. Meskipun, sebenarnya, Pi4 sudah memiliki OpenGLESv3 normal. Di sini rebana seperti itu mungkin tidak lagi dibutuhkan. Tetapi di sisi lain, DispmanX jauh lebih sederhana (walaupun lebih sedikit fitur) daripada OpenGLES.

Untuk menulis program menggunakan API DispmanX ini, Anda harus menyertakan file header /opt/vc/include/bcm_host.h. Selain itu, Anda juga perlu menautkan program ke pustaka libbcm_host.so, yang terletak di folder / opt / vc / lib.

Semua fungsi API yang perlu kita mulai dengan vc_dispmanx_ * ...

Hal pertama yang harus dilakukan untuk membuat layer baru adalah mengakses layar menggunakan pasangan fungsi berikut:

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

Sekarang Anda dapat membuat "sumber daya" yang akan berisi gambar lapisan:

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

Di sini, jenis lapisan adalah 32-bit dengan saluran alpha. Tapi mungkin ada jenis lain, seperti yang sudah saya tulis, bahkan YUV. Menggunakan lapisan YUV masuk akal untuk lapisan dinamis, seperti saat memutar video. Kemudian jumlah data yang ditulis ke layar berkurang secara signifikan, dan Anda tidak perlu transcode YUV ke RGB, yang menghemat siklus jam prosesor yang berharga.

Setelah membuat sumber daya baru, pengendali tampilan yang diperoleh sebelumnya dapat digunakan untuk menambahkan lapisan elemen baru ke tampilan:

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

Ada parameter pembaruan lain yang sangat penting. Setiap kali Anda perlu memodifikasi konten tampilan, menambah atau menghapus layer, atau memindahkannya, atau menulis gambar baru ke layer, Anda harus menandai awal dari perubahan dan akhir dari perubahan.

Untuk mulai mengubah elemen pada tampilan yang perlu Anda lakukan:

DISPMANX_UPDATE_HANDLE_T update =  vc_dispmanx_update_start( Priority );

Kemudian ubah semua yang Anda butuhkan, buat layer atau pindahkan, tulis piksel baru ke layer dan kemudian tutup perubahan menggunakan fungsi:

vc_dispmanx_update_submit_sync( update );

Seperti yang dapat Anda pahami dari nama fungsi, perubahan akan berlaku setelah denyut bingkai berikutnya dari pemindaian video.

Menulis piksel ke layer dilakukan oleh fungsi

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

Sayangnya, meskipun ada parameter rect, tidak mungkin untuk memperbarui fragmen sewenang-wenang di dalam gambar. Hanya "strip" yang dapat diperbarui, yaitu, bagian atas dan bawah dari persegi panjang dapat ditentukan, tetapi kiri akan selalu 0 dan kanan akan selalu menjadi lebar gambar.

Anehnya, ini mungkin seluruh pengetahuan minimum yang diperlukan untuk memanipulasi lapisan.

Dalam program saya, saya menulis pembungkus kecil di atas API DispmanX. Ini memungkinkan saya untuk menggambarkan setiap lapisan dalam struktur terpisah:

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

Nah, banyak fungsi pembungkus saya mengambil pointer ke struktur seperti itu sebagai 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 );

Dengan cara ini saya dapat membuat beberapa layer dan dengan mudah memanipulasi mereka.

Saya juga menulis fungsi untuk membaca bitmap. Selain itu, fungsinya rumit - jika piksel hijau murni 0x00FF00 ditemukan dalam bitmap, maka saya menganggapnya sebagai piksel transparan di gambar saya dan karenanya mengatur byte Alpha dari piksel ini ke nol.

Dalam cat, saya melukis tiga gambar.

Awan:



Matahari:



Balon:



Program ini menciptakan empat lapisan. Dua lapisan pertama dibuat dari bitmap cloud yang sama, tetapi saya membuat ukuran layer berbeda, saya menggunakan penskalaan, awan kedua lebih besar dari yang pertama. Lapisan ketiga adalah balon dan di lapisan keempat saya memuat matahari:

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

Dalam siklus abadi, saya memindahkan lapisan di sekitar layar dengan kecepatan yang berbeda:


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

Itu saja.

Semua kode program saya dapat diambil di github .

Kompilasi program di raspberry - dengan perintah make. Kemudian jalankan dari baris perintah: ./demo dan Anda mendapatkan apa yang Anda lihat di atas dalam demonstrasi video.

Omong-omong, pada Raspberry Anda dapat melihat daftar semua lapisan video dengan perintah vcgencmd dengan parameter dispmanx_list. Ini adalah output dari perintah ini pada Pi4 sebelum meluncurkan demo saya:



Seperti yang saya tulis, sudah ada dua layer: layer untuk Xorg dan layer untuk mouse.

Dan inilah daftar lapisan setelah memulai demo saya:



Dapat dilihat bahwa empat layer baru ditambahkan. Saya mengambil tangkapan layar di tim scrot. Sangat menarik bahwa ia hanya menangkap lapisan-x yang lebih rendah, oleh karena itu, dalam tangkapan layar, awan maupun balon yang berada di lapisan lain tidak terlihat.

Saya tahu bahwa mikrokomputer Raspberry kadang-kadang digunakan untuk membuat berbagai kios. Dan untuk kios, kadang-kadang Anda perlu melakukan OSD, On Screen Display - yaitu, overlay satu gambar di atas yang lain. Menurut saya, API DispmanX sangat cocok untuk aplikasi ini. Mungkin seseorang akan menyukai solusi ini.

All Articles