使用DispmanX API在Raspberry Pi中编程视频层


在本文中,我想演示Raspberry单板计算机的DispmanX API的用法。 DispmanX API提供了在Raspberry桌面上创建新渲染层的功能。层悬挂在层上。它们可以动态创建,删除,移动,也可以缩放。同时,视频控制器本身将合并它们并在监视器屏幕上显示它们。有趣的是,这些图层可以具有一个Alpha通道,然后,所有图层的图像将自己混合。此外,除了32个2位ARGB图层外,您还可以创建YUV420图层或其他类型的图层。默认情况下,Raspberry已经有两层。最低的包含桌面图像。通过X的所有输出都进入该层。第二层是最顶层,鼠标光标的图像位于其中。

我将展示如何创建一个新层,如何在其中写入图像以及如何在屏幕上移动图像。实际上,上面的演示视频显示了该程序的操作。在此创建四个新的32位ARGB层。在每一层中,我都根据预先准备好的位图写入像素。我的位图是云,太阳和气球的图像。图层以不同的速度在屏幕上移动,悬在最低的X图层上。
因此,DispmanX API。这是Raspberry及其视频控制器特有的低级内容。这是“非标准”的事情。通常,Linux内核和Linux图形子系统具有通过DRM,Direct Rendering Manager进行视频层编程的地方,但是由于某些原因,Raspberry的创建者决定创建自己的自行车。尽管从另一方面来说,这并不是一辆复杂的自行车,但很可能会骑行。 DispmanX可在Pi-Zero,Raspberry Pi / Pi2 / Pi3和Pi4上运行。一般来说,在所有树莓上。尽管实际上,Pi4已经具有普通的OpenGLESv3。此处可能不再需要手鼓。但另一方面,DispmanX比OpenGLES简单得多(尽管功能更少)。

要使用此DispmanX API编写程序,您需要包含头文件/opt/vc/include/bcm_host.h。另外,您还需要将程序链接到位于/ opt / vc / lib文件夹中的libbcm_host.so库。

我们需要的所有API功能都以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 );

在这里,图层类型是带有alpha通道的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 );

不幸的是,尽管存在rect参数,但无法更新图像内部的任意片段。只能更新“ strip”,即可以指定矩形的顶部和底部,但是left始终为0,right始终为图像的宽度。

奇怪的是,这也许是操纵图层所需的全部知识的最低限度。

在我的程序中,我在DispmanX API上写了一个小的包装。这使我可以用单独的结构描述每一层:

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,那么我认为它是图像中的透明像素,因此将该像素的Alpha字节设置为零。

在绘画中,我画了三个图像。

乌云:



太阳:



气球:



该程序创建四层。前两层是从云的同一位图创建的,但是我使层大小不同,使用缩放,第二层比第一层大。第三层是气球,在第四层中,我要加载太阳:

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

就这样。

我程序的所有代码都可以在github上获取

在树莓派上编译程序-使用make命令。然后从命令行运行:./demo,您将在视频演示中看到上面的内容。

顺便说一句,在Raspberry上,您可以使用带有dispmanx_list参数的vcgencmd命令查看所有视频层的列表。在启动演示之前,这是Pi4上此命令的输出:



正如我所写的,已经有两层:Xorg层和鼠标层。

这是开始演示后的图层列表:



可以看出,增加了四个新层。我在scrot团队拍摄了屏幕截图。有趣的是,它仅捕获较低的x层,因此,在屏幕截图中,看不见其他层中的云或气球。

我知道Raspberry微型计算机有时会用来创建各种档位。对于信息亭,有时您需要在屏幕上显示OSD,即在一个图像上叠加另一个图像。在我看来,DispmanX API非常适合这些应用程序。也许有人会喜欢这种解决方案。

All Articles