برمجة طبقة فيديو في Raspberry Pi باستخدام DispmanX API


في هذه المقالة ، أود أن أوضح استخدام API DispmanX لأجهزة كمبيوتر Raspberry Single Board. توفر واجهة برمجة تطبيقات DispmanX القدرة على إنشاء طبقات جديدة مقدمة على سطح المكتب Raspberry. الطبقات معلقة فوق الطبقات. يمكن إنشاؤها أو حذفها أو نقلها ديناميكيًا ، ويمكن تغيير حجمها. في الوقت نفسه ، ستقوم وحدة تحكم الفيديو نفسها بدمجها وعرضها على شاشة العرض. من المثير للاهتمام ، يمكن أن تحتوي الطبقات على قناة ألفا ، وبعد ذلك ، سيتم خلط صور جميع الطبقات من تلقاء نفسها. أيضًا ، بالإضافة إلى 32 طبقة ARGB ثنائية البت ، يمكنك إنشاء ، على سبيل المثال ، طبقات YUV420 ، أو طبقات من أنواع أخرى. يحتوي Raspberry بالفعل على طبقتين بشكل افتراضي. يحتوي الأقل على صورة سطح مكتب. كل المخرجات من خلال X تنتقل إلى هذه الطبقة. وهناك طبقة ثانية في الأعلى تعيش فيها صورة مؤشر الماوس.

سأوضح كيفية إنشاء طبقة جديدة ، وكتابة صورة فيها وكيفية تحريكها حول الشاشة. في الواقع ، يظهر الفيديو التوضيحي أعلاه تشغيل مثل هذا البرنامج. يتم إنشاء أربع طبقات ARGB جديدة 32 بت هنا. في كل طبقة ، أكتب البكسلات من صور نقطية معدة مسبقًا. الصور النقطية الخاصة بي هي صور للسحب والشمس والبالونات. تتحرك الطبقات حول الشاشة بسرعات مختلفة ، وتتدلى فوق أدنى طبقة X.
لذا ، فإن DispmanX API. هذا شيء منخفض المستوى خاص بـ Raspberry ووحدات تحكم الفيديو الخاصة به. وهذا شيء "غير قياسي". بشكل عام ، لدى Linux kernel والنظام الفرعي لرسومات Linux مكانًا لبرمجة طبقات الفيديو عبر DRM ، مدير التقديم المباشر ، ولكن لسبب ما قرر مبدعو Raspberry إنشاء دراجتهم الخاصة. على الرغم من ذلك ، من ناحية أخرى ، هذه ليست دراجة معقدة ، والتي يمكن ركوبها. يعمل DispmanX على Pi-Zero ، على Raspberry Pi / Pi2 / Pi3 و Pi4. بشكل عام ، على جميع التوت. على الرغم من أن Pi4 يحتوي بالفعل على OpenGLESv3 عادي بالفعل. هنا قد لا تكون هناك حاجة إلى مثل الدف. ولكن من ناحية أخرى ، فإن DispmanX أبسط بكثير (وإن كان أقل ميزات) من OpenGLES.

لكتابة البرامج باستخدام واجهة برمجة تطبيقات DispmanX ، تحتاج إلى تضمين ملف الرأس /opt/vc/include/bcm_host.h. بالإضافة إلى ذلك ، ستحتاج أيضًا إلى ربط البرنامج بمكتبة libbcm_host.so ، الموجودة في المجلد / opt / vc / lib.

تبدأ جميع وظائف واجهة برمجة التطبيقات التي نحتاجها بـ vc_dispmanx_ * ...

أول شيء يجب القيام به لإنشاء طبقات جديدة هو الوصول إلى الشاشة باستخدام الزوج التالي من الوظائف:

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

الآن يمكنك إنشاء "مورد" يحتوي على صورة الطبقة:

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

هنا ، يكون نوع الطبقة 32 بت مع قناة ألفا. ولكن قد تكون هناك أنواع أخرى ، كما كتبت بالفعل ، حتى YUV. يعد استخدام طبقة YUV منطقيًا للطبقات الديناميكية ، مثل عند تشغيل مقطع فيديو. ثم يتم تقليل كمية البيانات المكتوبة على الشاشة بشكل كبير ، ولا تحتاج إلى تحويل YUV إلى RGB ، مما يوفر دورات ساعة قيمة للمعالج.

بعد إنشاء مورد جديد ، يمكن استخدام معالج العرض الذي تم الحصول عليه سابقًا لإضافة طبقة عنصر جديدة للعرض:

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

هناك معلمة تحديث مهمة أخرى. في كل مرة تحتاج إلى تعديل محتويات العرض ، أو إضافة طبقة أو إزالتها ، أو نقلها ، أو كتابة صورة جديدة إلى الطبقة ، تحتاج إلى وضع علامة على بداية التغييرات ونهاية التغييرات.

لبدء تغيير العناصر على الشاشة ، عليك القيام بما يلي:

DISPMANX_UPDATE_HANDLE_T update =  vc_dispmanx_update_start( Priority );

ثم قم بتغيير كل ما تحتاجه ، وقم بإنشاء طبقة أو انقلها ، واكتب وحدات بكسل جديدة إلى الطبقة ثم أغلق التغييرات باستخدام الوظيفة:

vc_dispmanx_update_submit_sync( update );

كما يمكنك أن تفهم من اسم الوظيفة ، ستسري التغييرات بعد نبض الإطار التالي لمسح الفيديو.

تتم كتابة وحدات البكسل إلى طبقة بواسطة الوظيفة

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

لسوء الحظ ، على الرغم من وجود المعلمة المستقيمة ، فإنه من المستحيل تحديث جزء تعسفي داخل الصورة. يمكن تحديث "الشريط" فقط ، أي أن الجزء العلوي والسفلي من المستطيل يمكن تحديده ، ولكن يسارًا سيكون دائمًا 0 واليمين دائمًا هو عرض الصورة.

ومن الغريب أنه ربما يكون هذا هو الحد الأدنى من المعرفة اللازمة للتعامل مع الطبقات.

في برنامجي ، كتبت غلافًا صغيرًا فوق واجهة برمجة تطبيقات DispmanX. هذا يسمح لي بوصف كل طبقة في هيكل منفصل:

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

حسنًا ، تأخذ العديد من وظائف المجمع مؤشرًا إلى مثل هذه البنية كمعلمة:

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

بهذه الطريقة يمكنني إنشاء عدة طبقات ومعالجتها بسهولة.

كتبت أيضًا وظيفة لقراءة صورة نقطية. علاوة على ذلك ، فإن الوظيفة صعبة - إذا تم العثور على بكسل أخضر خالص 0x00FF00 في الصورة النقطية ، فأنا أعتبرها بكسلًا شفافًا في صورتي ، وبالتالي قم بتعيين ألفا بايت لهذه البكسل إلى صفر.

في الطلاء ، رسمت ثلاث صور.

الغيوم:



الشمس:



البالونات:



ينشئ البرنامج أربع طبقات. يتم إنشاء الطبقتين الأوليين من نفس الصورة النقطية للسحابة ، لكني أجعل حجم الطبقة مختلفًا ، أستخدم القياس ، السحب الثانية أكبر من الأولى. الطبقة الثالثة هي بالونات وفي الطبقة الرابعة أحمل الشمس:

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

في الدورة الدائمة ، أقوم بتحريك الطبقات حول الشاشة بسرعات مختلفة:


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

هذا كل شئ.

يمكن أن تؤخذ جميع التعليمات البرمجية لبرنامجي على جيثب .

قم بتجميع البرنامج على التوت - باستخدام الأمر make. ثم قم بتشغيل من سطر الأوامر:. / demo وستحصل على ما تراه أعلاه في عرض الفيديو.

بالمناسبة ، على Raspberry يمكنك رؤية قائمة بكل طبقات الفيديو باستخدام الأمر vcgencmd مع المعلمة dispmanx_list. فيما يلي إخراج هذا الأمر على Pi4 قبل إطلاق العرض التوضيحي الخاص بي:



كما كتبت ، هناك بالفعل طبقتان: طبقة لـ Xorg وطبقة للماوس.

وإليك قائمة الطبقات بعد بدء العرض التوضيحي الخاص بي:



يمكن ملاحظة أنه تمت إضافة أربع طبقات جديدة. أخذت لقطات للشاشة في فريق scrot. من المثير للاهتمام أنه يلتقط فقط الطبقة السفلية السفلية ، وبالتالي ، في لقطة الشاشة ، لا يمكن رؤية الغيوم أو البالونات الموجودة في طبقات أخرى.

أعلم أن الحواسيب الصغيرة Raspberry تستخدم أحيانًا لإنشاء أكشاك مختلفة. وبالنسبة للأكشاك ، تحتاج أحيانًا إلى تشغيل OSD ، على الشاشة ، أي تراكب صورة فوق أخرى. يبدو لي أن واجهة برمجة تطبيقات DispmanX مثالية لهذه التطبيقات. ربما شخص ما سيحب هذا الحل.

All Articles