当前位置: 首页 > news >正文

OpenHD改造实现廉价高清数字图传(树莓派+PC)—(六)OSD和视频画面整合显示

        这个OpenHD改造移植系列的最后一篇文章,这篇文章主要讲如何讲前面说到的全部内容串接起来,讲OSD画面显示和视频画面整合到一起,形成完整的图传地面显示,真正实现PC上直接接收显示图传视频和数据。

OpenHD改造实现廉价高清数字图传(树莓派+PC)—(一)概述_hoopertsau的博客-CSDN博客_openhd

OpenHD改造实现廉价高清数字图传(树莓派+PC)—(二)Wifibroadcast Wifi广播通信_hoopertsau的博客-CSDN博客

OpenHD改造实现廉价高清数字图传(树莓派+PC )—(三)OpenVG和libshapes在PC上的移植_hoopertsau的博客-CSDN博客

OpenHD改造实现廉价高清数字图传(树莓派+PC )—(四)OSD数据传输和画面显示_hoopertsau的博客-CSDN博客

OpenHD改造实现廉价高清数字图传(树莓派+PC)—(五)gstreamer视频采集、传输和显示_hoopertsau的博客-CSDN博客

一、OSD和视频整合显示改造

        整合OSD和视频显示核心思路:

        在X11window上创建两个窗口,一个用OpenVG来绘制OSD画面,一个用gstreamer来显示视频画面。将OSD画面的背景要设置为透明,并叠加显示在视频窗口上,这样就有浮在视频画面上的效果了。这里主要就在于怎么将OSD的背景设置为透明显示。

        这里主要修改的还是openvg目录下的oglinit.c文件,主要是创建窗口这里要修改。

        直接看一下代码,如何创建这样的两个窗口。

// 创建X11窗口
VGboolean windowCreate(STATE_T * state, const char* title,
                              const VGuint width,
                              const VGuint height)
{
    VGint screen, screenWidth, screenHeight;
    XSetWindowAttributes windowAttributes;
    XSizeHints windowSizeHints;

    XVisualInfo* visualInfo;
    MY_PFNGLXSWAPINTERVALSGIPROC sgiSwapInterval;
    MY_PFNGLXSWAPINTERVALEXTPROC extSwapInterval;
    // OpenGL surface configuration
	GLXFBConfig fbconfig;
    VGint fbConfigsCount = 0;
	
	static int glAttributes[] = {
		GLX_RENDER_TYPE, GLX_RGBA_BIT,
		GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
		GLX_DOUBLEBUFFER, True,
		GLX_RED_SIZE, 1,
		GLX_GREEN_SIZE, 1,
		GLX_BLUE_SIZE, 1,
		GLX_ALPHA_SIZE, 1,
		GLX_DEPTH_SIZE, 1,
		None
	};

    // open a display on the current root window
    display = XOpenDisplay(NULL);
    if (display == NULL) {
        fprintf(stderr, "Unable to open display.\n");
        return VG_FALSE;
    }

    // get the default screen associated with the previously opened display
    screen = DefaultScreen(display);
	// 根窗口
	rootWindow = RootWindow(display, screen);

    // get screen bitdepth
    screenDepth = DefaultDepth(display, screen);
    
    // run only on a 32bpp display
    if ((screenDepth != 24) && (screenDepth != 32)) {
        fprintf(stderr, "Cannot find 32bit pixel format on the current display.\n");
        XCloseDisplay(display);
        return VG_FALSE;
    }

    // get screen dimensions
    screenWidth = DisplayWidth(display, screen);
    screenHeight = DisplayHeight(display, screen);
	state->screen_width = screenWidth;
	state->screen_height = screenHeight;
	printf("screen width=%d height=%d\r\n",screenWidth,screenHeight);
	
	unsigned long white = WhitePixel(display,screen);
	unsigned long black = BlackPixel(display,screen);
	
				   
	// 再创建透明的OSD显示,在上层
	// 找到合适的配置
	GLXFBConfig* fbconfigs = glXChooseFBConfig(display, screen, glAttributes, &fbConfigsCount);
	int i = 0;
	static XRenderPictFormat *pictFormat;
    for(i = 0; i<fbConfigsCount; i++) {
        visualInfo = (XVisualInfo_CPP*) glXGetVisualFromFBConfig(display, fbconfigs[i]);
        if(!visualInfo)
            continue;

        pictFormat = XRenderFindVisualFormat(display, visualInfo->visual);
        if(!pictFormat)
            continue;

        if(pictFormat->direct.alphaMask > 0) {
            fbconfig = fbconfigs[i];
            break;
        }
    }

	// initialize window's attribute structure
    windowAttributes.colormap = XCreateColormap(display, rootWindow, visualInfo->visual, AllocNone);
	windowAttributes.border_pixel = 0;
	windowAttributes.background_pixmap = None;
	printf("None=%d\r\n",None);
	int attr_mask = 
        CWBackPixmap|
        CWColormap|
        CWBorderPixel;    /* What's in the attr data */
	
    // create the window centered on the screen
	state->window_width = width;
	state->window_height = height;
	
	// 先创建视频窗口,在底层
	video_window =  XCreateSimpleWindow(display,
									   rootWindow,
									   0, 0, // origin
									   width, height, // size
									   1, black, // border
									   white );  // backgd
				   
	// 创建OSD窗口
    window = XCreateWindow(display, rootWindow, 0,0, width, height, 1, visualInfo->depth, InputOutput, visualInfo->visual, 						attr_mask, &windowAttributes);
    //if ( window == None ) 
	if (window == None || video_window == None) 
	{
        fprintf(stderr, "Unable to create the window.\n");
        XCloseDisplay(display);
        return VG_FALSE;
    }
	
    // set the window's name
    XStoreName(display, window, title);
	XStoreName(display, video_window, "video window");
	
    // tell the server to report mouse and key-related events
    XSelectInput(display, window, KeyPressMask | KeyReleaseMask | ButtonPressMask | Button1MotionMask | Button2MotionMask | Button3MotionMask | StructureNotifyMask | ExposureMask);
	
    // initialize window's sizehint definition structure
    windowSizeHints.flags = PPosition | PMinSize | PMaxSize;
    windowSizeHints.x = 0;
    windowSizeHints.y = 0;
    windowSizeHints.min_width = 1;
    // clamp window dimensions according to the maximum surface dimension supported by the OpenVG backend
    windowSizeHints.max_width = vgPrivSurfaceMaxDimensionGetMZT();
    if (screenWidth < windowSizeHints.max_width) {
        windowSizeHints.max_width = screenWidth;
    }
    windowSizeHints.min_height = 1;
    windowSizeHints.max_height = windowSizeHints.max_width;
    if (screenHeight < windowSizeHints.max_height) {
        windowSizeHints.max_height = screenHeight;
    }
    // set the window's sizehint 不加这一句话,窗口的位置就不能正确设置
    XSetWMNormalHints(display, video_window, &windowSizeHints);
    // clear the window
    XClearWindow(display, video_window);
	
	// set the window's sizehint
    XSetWMNormalHints(display, window, &windowSizeHints);
    // clear the window
    XClearWindow(display, window);

    // create a GLX context for OpenGL rendering
    glContext = glXCreateNewContext(display, fbconfig, GLX_RGBA_TYPE, NULL, True);
    if (!glContext) {
        fprintf(stderr, "Unable to create the GLX context.\n");
		XDestroyWindow(display, video_window);
        XDestroyWindow(display, window);
        XCloseDisplay(display);
        return VG_FALSE;
    }
    // create a GLX window to associate the frame buffer configuration with the created X window
    glWindow = glXCreateWindow(display, fbconfig, window, NULL);
    // bind the GLX context to the Window
    glXMakeContextCurrent(display, glWindow, glWindow, glContext);
    // GLX_EXT_swap_control
    extSwapInterval = (MY_PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalEXT");
    if (extSwapInterval) {
        extSwapInterval(display, glWindow, 0);
    }
    else {
        // GLX_SGI_swap_control
        sgiSwapInterval = (MY_PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalSGI");
        if (sgiSwapInterval) {
            sgiSwapInterval(0);
        }
    }
	
    // put the window on top of the others
	XMapRaised(display, video_window);
    XMapRaised(display, window);
	
    // clear event queue
    XFlush(display);
    XFree(visualInfo);

/*
	// 无边框的效果
	Atom window_type = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
	long value = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DOCK", False);
	XChangeProperty(display, window, window_type, XA_ATOM, 32, PropModeReplace, (unsigned char *) &value,1 );
*/

	// 初始化视频显示
	gst_init (NULL,NULL);
	printf("gst inited\r\n");
	initGst(video_window);
	
    return VG_TRUE;
}

        分析一下这个窗口创建的代码,有几个关键的地方。

        1、找到合适的显示配置

	// 找到合适的配置
	GLXFBConfig* fbconfigs = glXChooseFBConfig(display, screen, glAttributes, &fbConfigsCount);
	int i = 0;
	static XRenderPictFormat *pictFormat;
    for(i = 0; i<fbConfigsCount; i++) {
        visualInfo = (XVisualInfo_CPP*) glXGetVisualFromFBConfig(display, fbconfigs[i]);
        if(!visualInfo)
            continue;

        pictFormat = XRenderFindVisualFormat(display, visualInfo->visual);
        if(!pictFormat)
            continue;

        if(pictFormat->direct.alphaMask > 0) {
            fbconfig = fbconfigs[i];
            break;
        }
    }

        2、窗口的属性设置

    // initialize window's attribute structure
    windowAttributes.colormap = XCreateColormap(display, rootWindow, visualInfo->visual, AllocNone);
	windowAttributes.border_pixel = 0;
	windowAttributes.background_pixmap = None;
    int attr_mask = 
        CWBackPixmap|
        CWColormap|
        CWBorderPixel;    /* What's in the attr data */

        要将背景设置为None,并且mask中要设置背景标志

        3、创建两个窗口

// 先创建视频窗口,在底层
	video_window =  XCreateSimpleWindow(display,
									   rootWindow,
									   0, 0, // origin
									   width, height, // size
									   1, black, // border
									   white );  // backgd
				   
	// 创建OSD窗口
    window = XCreateWindow(display, rootWindow, 0,0, width, height, 1, visualInfo->depth, InputOutput, visualInfo->visual,attr_mask, &windowAttributes);

        一个是用于显示视频的video_window ,一个是用于显示osd的

注意:

这两个的父窗口都是rootWindow,我尝试将osd的父窗口设置为视频,这样背景就不透明了。但是如果将视频窗口也设置为透明,gstreamer就显示不正常了,相互之间有影响。所以就干脆分开两个窗口,互不影响。

        4、设置窗口的位置

    // set the window's sizehint, 不加这一句话,窗口的位置就不能正确设置
    XSetWMNormalHints(display, video_window, &windowSizeHints);
    // clear the window
    XClearWindow(display, video_window);

        如果不设置的话,窗口的初始位置可能会有错位,导致两个窗口不能叠加在一起。

        5、初始化gstreamer并绑定GUI      

	// 初始化gstreamer库
	gst_init (NULL,NULL);
    // 创建视频处理的管道,并绑定到视频显示窗口video_window
	initGst(video_window);

          这个与上一篇文章的内容类似,就不再详细描述了,直接看之前的代码。

          看一下最终效果,叠在一起显示      

二、总结

        通过将OpenHD深度的剥离改造,可以实现在普通的PC上显示图传,大大降低我们的成本,不需要再买地面端的树莓派了。同时,OpenHD里面有大量“无用”的脚本和代码,过于复杂。

        其实用到的主要就是四个软件:

        1、wifibroadcast,提供底层的通信

        2、openvg,提供osd绘图的支撑

        3、gstreamer,osd调用并用来解析视频用的

        4、osd,接收解析并绘制视频和叠加显示信息

       

        当然OpenHD提供的是一揽子解决方案,直接烧写镜像即可开机使用,比较适合不会开发的航模玩家,可以直接上手使用

三、后续

        考虑到天空端的树莓派Zero也开始涨价了,当前价格大概在500块左右(2022年9月30日),为了进一步降低成本,我还考虑将天空端移植到国产的PI上。

        这里有几个可选的PI,后续再讲如何往这些国产化的PI上进行迁移,进一步降低成本

        1、OrangePi Zero

                全志的H3处理器,只要大概120块钱。但是没有CSI接口的摄像头,需要外接使用USB摄像头。

        

        2、OrangePi i96

        RDA8810PL处理器,非常便宜,开发板只要39块钱!摄像头32块。当然,性能不是太好,而且CPU也很老了,也很难找到技术支持,而且坑也比较多。但是架不住它便宜啊,哈哈。

        3、Banana pi-M2 Zero

                使用的也是全志H3处理器,价格140左右,但是摄像头贵了,要60块钱。

        

        

        

相关文章:

  • 从0开始学c语言-33-动态内存管理
  • java详解队列
  • springboot - 2.7.3版本 - (八)ELK整合Kafka
  • uniCloud开发公众号:一、接收、解析、组装xml消息
  • YOLO系列目标检测算法-YOLOv1
  • JavaScript高级,ES6 笔记 第三天
  • 【雷达图】R语言绘制雷达图(ggradar),NBA季后赛数据为例
  • 机器学习笔记 - 在QT/PyTorch/C++ 中加载 TORCHSCRIPT 模型
  • redis 技术分享
  • 怎么让面试官喜欢你?
  • 深度学习模型理解-CNN-手写数据字代码
  • C# ZBar解码测试(QRCode、一维码条码)并记录里面隐藏的坑
  • 【技术美术图形部分】图形渲染管线3.0-光栅化和像素处理阶段
  • css:一个容器(页面),里面有两个div左右摆放并且高度和容器高度一致,左div不会随着页面左右伸缩而变化,右div随页面左右伸缩宽度自适应(手写)
  • Kubernetes 1.25 集群搭建
  • php的引用
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • 30天自制操作系统-2
  • Git同步原始仓库到Fork仓库中
  • Java 11 发布计划来了,已确定 3个 新特性!!
  • Java到底能干嘛?
  • Node.js 新计划:使用 V8 snapshot 将启动速度提升 8 倍
  • NSTimer学习笔记
  • select2 取值 遍历 设置默认值
  • SpringBoot几种定时任务的实现方式
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • webpack4 一点通
  • 创建一个Struts2项目maven 方式
  • 记录:CentOS7.2配置LNMP环境记录
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 在weex里面使用chart图表
  • 正则与JS中的正则
  • 终端用户监控:真实用户监控还是模拟监控?
  • ​​​​​​​GitLab 之 GitLab-Runner 安装,配置与问题汇总
  • # Maven错误Error executing Maven
  • #1015 : KMP算法
  • #Linux(权限管理)
  • (12)Linux 常见的三种进程状态
  • (2)nginx 安装、启停
  • (2)STM32单片机上位机
  • (NO.00004)iOS实现打砖块游戏(九):游戏中小球与反弹棒的碰撞
  • (附源码)计算机毕业设计ssm-Java网名推荐系统
  • (考研湖科大教书匠计算机网络)第一章概述-第五节1:计算机网络体系结构之分层思想和举例
  • (数据结构)顺序表的定义
  • (一)SpringBoot3---尚硅谷总结
  • (转)http协议
  • (转)JVM内存分配 -Xms128m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=512m
  • (转)详解PHP处理密码的几种方式
  • **PyTorch月学习计划 - 第一周;第6-7天: 自动梯度(Autograd)**
  • .360、.halo勒索病毒的最新威胁:如何恢复您的数据?
  • .bat批处理(二):%0 %1——给批处理脚本传递参数
  • .java 指数平滑_转载:二次指数平滑法求预测值的Java代码