Linux基础项目开发1:量产工具——显示系统(二)

前言:

前面我们已经对这个项目的基本框架有了一个初步的了解与认识,要实现显示管理器与输入管理器,有输入有输出基本就实现这个项目的大部分功能了,首先我们先来做显示系统,对于上层系统为了让程序更好扩展,我们得添加一个显示管理器,在下面有各种设备,就比如有Framebuflerweb输出。

一、数据结构抽象

        我们添加的一个显示管理器中有Framebuflerweb输出,对于俩个不同的设备我们需要抽象出同一个结构体类型。

1.使用场景

        在上图中我们可以将其分为两层,上层要获得下层某个结构体,通过这个结构体中的函数来操作、绘制、刷新上层的界面。

        所有我们需要定义一个统一的结构体DispOpr

 2.disp_manager.h

  1 #ifndef _DISP_MANAGER_H2 #define _DISP_MANAGER_H34 typedef struct Region {5     int iLeftUpX;                //左上角x坐标6     int iLeftUpY;                //左上角y坐标7     int iWidth;                  //宽度8     int iHeigh;                  //高度9 }Region, *PRegion;1011 typedef struct DispOpr {12     char *name;                  //显示模块的名字 13     char *GetBuffer(int *pXres, int *pYres, int *pBpp);//分辨率(长和宽)每个像素占据多少位14     int FlushRegion(PRegion ptRegion, char *buffer);//刷出某个区域15     struct DispOpr *ptNext;      //链表16 };1718 #endif19

第1~2行:防止头文件在.c文件中多次定义

第4~9行:定义刷新区域结构体

第11~16行:定义统一管理的结构体

二、Framebuffer编程

1.disp_manager.h

  1 #ifndef _DISP_MANAGER_H2 #define _DISP_MANAGER_H34 typedef struct Region {5     int iLeftUpX;6     int iLeftUpY;7     int iWidth;8     int iHeigh;9 }Region, *PRegion;1011 typedef struct DispOpr {12     char *name;13     int DeviceInit(void);14     int DeviceExit(void);15     char *GetBuffer(int *pXres, int *pYres, int *pBpp);16     int FlushRegion(PRegion ptRegion, char *buffer);17     struct DispOpr *ptNext;18 }DispOpr, *PDispOpr;1920 #endif

第13行:初始化函数定义

第14行:退出函数定义

2.framebuffer.c

  1 #include <sys/mman.h>2 #include <sys/types.h>3 #include <sys/stat.h>4 #include <unistd.h>5 #include <linux/fb.h>6 #include <fcntl.h>7 #include <stdio.h>8 #include <string.h>9 #include <sys/ioctl.h>1011 #include "disp_manager.h"1213 static int fd_fb;                       //framebuffer文件14 static struct fb_var_screeninfo var;    /* Current var */15 static int screen_size;                 //framebuffer长度16 static unsigned char *fb_base;          //framebuffer地址17 static unsigned int line_width;18 static unsigned int pixel_width;1920 static int DeviceInit(void)21 {22     fd_fb = open("/dev/fb0", O_RDWR);23     if (fd_fb < 0)24     {25         printf("can't open /dev/fb0\n");26         return -1;27     }28     if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))29     {30         printf("can't get var\n");31         return -1;32     }3334     line_width  = var.xres * var.bits_per_pixel / 8;35     pixel_width = var.bits_per_pixel / 8;36     screen_size = var.xres * var.yres * var.bits_per_pixel / 8;37     fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);38     if (fb_base == (unsigned char *)-1)39     {40         printf("can't mmap\n");41         return -1;42     }4344     return 0;45 }4647 static int DeviceExit(void)48 {49     munmap(fb_base, screen_size);50     close(fd_fb);51     return 0;52 }535455 /* 可以返回LCD的framebuffer, 以后上层APP可以直接操作LCD, 可以不用FbFlushRegion56  * 也可以malloc返回一块无关的buffer, 要使用FbFlushRegion刷到LCD上57  */58 static int FbGetBuffer(PDispBuff ptDispBuff);5960 {61     ptDispBuff->iXres = var.xres;62     ptDispBuff->iYres = var.yres;63     ptDispBuff->iBpp  = var.bits_per_pixel;64     ptDispBuff->buff  = (char *)fb_base;65     return 0;66 }6768 static int FbFlushRegion(PRegion ptRegion, PDispBuff ptDispBuff)69 {70     return 0;71 }727374 static DispOpr g_tFramebufferOpr = {75     .name        = "fb",76     .DeviceInit  = FbDeviceInit,77     .DeviceExit  = FbDeviceExit,78     .GetBuffer   = FbGetBuffer,79     .FlushRegion = FbFlushRegion,80 };818283 void FramebufferInit(void)84 {85     RegisterDisplay(&g_tFramebufferOpr);86 }87

 20 static int DeviceInit(void)

第20~45行:初始化函数

 47 static int DeviceExit(void)

第47~52行:退出函数

第58~66行的FbGetBuffer是已经封装好的,详细封装过程后续会讲

第61~62行:设置分辨率

第63行:设置bpp

74 static DispOpr g_tFramebufferOpr

第74~80行:上层代码可以根据这个结构体里的函数来初始化LCD来得到Buffer

        第78行:通过.GetBuffer   = FbGetBuffer来构建好图像和文字

        第79行:通过.FlushRegion = FbFlushRegion来刷新到LCD上

第83~86行:注册结构体g_tFramebufferOpr,详细介绍在三、显示管理

        

三、显示管理

        上层函数想要选择哪个设备进行显示,需要中间加一个函数进行选择,起到承上启下的作用,用来实现显示管理,是操作Framebuffer还是WEB设备,需要进行选择某个模块,好可以提供一些函数,描点等

1.disp_manager.h

  1 #ifndef _DISP_MANAGER_H2 #define _DISP_MANAGER_H34 #ifndef NULL5 #define NULL (void *)06 #endif78 typedef struct DispBuff {9     int iXres;     //x坐标分辨率10     int iYres;     //y坐标分辨率11     int iBpp;      //bpp12     char *buff;    缓冲区地址13 }DispBuff, *PDispBuff;141516 typedef struct Region {17     int iLeftUpX;18     int iLeftUpY;19     int iWidth;20     int iHeigh;21 }Region, *PRegion;2223 typedef struct DispOpr {24     char *name;25     int (*DeviceInit)(void);26     int (*DeviceExit)(void);27     int (*GetBuffer)(PDispBuff ptDispBuff);28     int (*FlushRegion)(PRegion ptRegion, PDispBuff ptDispBuff);29     struct DispOpr *ptNext;30 }DispOpr, *PDispOpr;3132 void RegisterDisplay(PDispOpr ptDispOpr);3334 void DisplayInit(void);35 int SelectDefaultDisplay(char *name);36 int InitDefaultDisplay(void);37 int PutPixel(int x, int y, unsigned int dwColor);38 int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff);39 PDispBuff GetDisplayBuffer(void);404142 #endif

第8~13行:进一步封装GetBuffer(int *pXres, int *pYres, int *pBpp)

第27行:将4~9行的PDispBuff保存到GetBuffer

第32行:定义注册函数RegisterDisplay

第34行:调用底层提供的模块

第35行:定义选择模块函数

第36行:选择默认的SelectDefaultDisplay后,我们还需要定义初始化函数

第37行:提供绘制图像函数

第38行:将绘制好的图像刷新到硬件上

第39行:返回取址 

2.disp_manager.c

  1 #include <disp_manager.h>2 #include <stdio.h>2 #include <string.h>23 /* 管理底层的LCD、WEB */4 static PDispOpr g_DispDevs = NULL;5 static PDispOpr g_DispDefault = NULL;6 static DispBuff g_tDispBuff;7 static int line_width;8 static int pixel_width;910 int PutPixel(int x, int y, unsigned int dwColor)11 {12     unsigned char *pen_8 = (*unsigned char*)(g_tDispBuff.buff+y*line_width+x*pixel_width);13     unsigned short *pen_16;14     unsigned int *pen_32;1516     unsigned int red, green, blue;1718     pen_16 = (unsigned short *)pen_8;19     pen_32 = (unsigned int *)pen_8;2021     switch (g_tDispBuff.iBpp)22     {23         case 8:24         {25             *pen_8 = dwColor;26             break;27         }28         case 16:29         {30             /* 565 */31             red   = (dwColor >> 16) & 0xff;32             green = (dwColor >> 8) & 0xff;33             blue  = (dwColor >> 0) & 0xff;34             dwColor = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);35             *pen_16 = dwColor;36             break;37         }38         case 32:39         {40             *pen_32 = dwColor;41             break;42         }43         default:44         {45             printf("can't surport %dbpp\n", g_tDispBuff.iBpp);45             return -1;46             break;47         }48     }48      return 0;49 }50515253 void RegisterDisplay(PDispOpr ptDispOpr)54 {55     ptDispOpr->ptNext = g_DispDevs;56     g_DispDevs = ptDispOpr;57 }585960 int SelectDefaultDisplay(char *name)61 {62     PDispOpr pTmp = g_DispDevs;63     while (pTmp)64     {65         if (strcmp(name, pTmp->name) == 0)66         {67             g_DispDefault = pTmp;68             return 0;69         }7071         pTmp = pTmp->ptNext;72     }7374     return -1;75 }7677 int InitDefaultDisplay(void)78 {79     int ret;8081     ret = g_DispDefault->DeviceInit();82     if (ret)83     {84         printf("DeviceInit err\n");85         return -1;86     }878889     ret = g_DispDefault->GetBuffer(&g_tDispBuff);90     if (ret)91     {92         printf("GetBuffer err\n");93         return -1;94     }9596     line_width  = g_tDispBuff.iXres * g_tDispBuff.iBpp/8;97     pixel_width = g_tDispBuff.iBpp/8;9899     return 0;
100 }
101
102
103 int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff)
104 {
105     return g_DispDefault->FlushRegion(ptRegion, ptDispBuff);
106 }
107
108
109 void DisplayInit(void)
110 {
111     void FramebufferInit(void);
112     FramebufferInit();
113 }
114
115 PDispBuff GetDisplayBuffer(void)
116 {
117    return &g_tDispBuff;
118 }

 第4行:将底层的LCD、WEB所实现的结构体放入到链表(指针)中,所以我们需要提供一个函数。void RegisterDisplay(PDispOpr ptDispOpr)

 第5行:存放找到名字的新链表

 第6行:定义一个全局变量 g_tDispBuff之后用来放第89行中GetBuffer中的(int* pxres, int * pYres, int * pBpp)

53 void RegisterDisplay(PDispOpr ptDispOpr)

  第53~57行:构造注册函数,将typedef struct DispOpr这个结构体注册到g_DispDevs链表中

        第55行:传入底层结构体指针ptDispOpr指向链表头g_DispDevs

        第56行:链表头g_DispDevs指向ptDispOpr

109 void DisplayInit(void)

第109~112行:调用底层提供的 FramebufferInit();目前我们只需要调用FramebufferInit()即可,还没有实现WEB的功能

        如果g_DispDevs链表中有好几个模块,那我们应该如何选择呢,现在我们就要设计一个模块选择函数进行模块的选择 

60 int SelectDefaultDisplay(char *name)

第60~75行:模块选择函数

        定义一个临时指针pTmp存放链表头

        while循环里根据名字name找到那一项

        找到的名字放到新链表g_DispDefault中,在第五行中有定义

        第71行,如果这个不对,这找寻下一个是否正确     

        因为上层需要通过中间的显示管理来绘制图像,坐标反馈给底层进行反应,所有需要在中间层加入绘制函数

10 int PutPixel(int x, int y, unsigned int dwColor)

第7~8行:每个像素占据多长多少像素可以先定义好,事先计算好,每一行多少字节,每个像素多少宽度,这个在InitDefaultDisplay()中已经计算好了

第10~49行:设置绘制图像函数

        函数内表示绘制的x、y坐标和绘制图像的颜色       

        第12行:g_tDispBuff.buff是写存的基地址

        第21行:g_tDispBuff.iBpp是每个像素的宽度

   

       想要在上层显示一个像素,首先需要得到一块内存,内存可以调用到底层提供的结构体,里面的g_tDispBuff这个函数,在上层得到一个新的buf,在这块buf里面绘制图像,但是g_tDispBuff不可以放到PutPixel,我们需要再定义一个函数: InitDefaultDisplay(void)

选择默认的SelectDefaultDisplay后,我们还需要初始化

 77 int InitDefaultDisplay(void)

第77~100行:

           第81行: 调用 g_DispDefault 中的DeviceInit()

           第89行: 调用 g_DispDefault  中的GetBuffer(&g_tDispBuff)

           这里我们需要定义一个新的GetBuffer结构体,把它的(int* pxres, int * pYres, int * pBpp)再次进行封装,封装结构体在disp_manager.h中的DispBuff

           第89行:将GetBuffer中的(int* pxres, int * pYres, int * pBpp)放到全局变量 g_tDispBuff

           第96行:line_width表示每一行占据多少个字节

           第97行:pixel_width表示每个像素的宽度

        当我们上层已经通过 PutPixel 已经绘制好图像,那我们就需要把它刷到硬件上,我们需要再提供一个函数FlushDisplayRegion

103 int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff)

        第105行:调用底层设备提供的FlushRegion函数

        返回取址 

114 PDispBuff GetDisplayBuffer(void)

献上韦东山老师对以上代码流程的梳理过程:

电子产品量产工具显示系统代码流程

四、测试单元

book@100ask:~/04_disp_unittest/display$ vi Makefile

1.通用Makefile

  12 CROSS_COMPILE ?=3 AS      = $(CROSS_COMPILE)as4 LD      = $(CROSS_COMPILE)ld5 CC      = $(CROSS_COMPILE)gcc6 CPP     = $(CC) -E7 AR      = $(CROSS_COMPILE)ar8 NM      = $(CROSS_COMPILE)nm910 STRIP       = $(CROSS_COMPILE)strip11 OBJCOPY     = $(CROSS_COMPILE)objcopy12 OBJDUMP     = $(CROSS_COMPILE)objdump1314 export AS LD CC CPP AR NM15 export STRIP OBJCOPY OBJDUMP1617 CFLAGS := -Wall -O2 -g18 CFLAGS += -I $(shell pwd)/include1920 LDFLAGS :=2122 export CFLAGS LDFLAGS2324 TOPDIR := $(shell pwd)25 export TOPDIR2627 TARGET := test282930 obj-y += display/31 obj-y += unittest/3233 all : start_recursive_build $(TARGET)34     @echo $(TARGET) has been built!3536 start_recursive_build:37     make -C ./ -f $(TOPDIR)/Makefile.build3839 $(TARGET) : built-in.o40     $(CC) -o $(TARGET) built-in.o $(LDFLAGS)4142 clean:43     rm -f $(shell find -name "*.o")44     rm -f $(TARGET)4546 distclean:47     rm -f $(shell find -name "*.o")48     rm -f $(shell find -name "*.d")49     rm -f $(TARGET)50

第27行:编译出test的应用程序

第30行:指定display下的目录

第31行:指定unittest下的目录 

 2.display下的Makefile

1 EXTRA_CFLAGS  :=2 CFLAGS_file.o :=34 obj-y += disp_manager.o5 obj-y += framebuffer.o

3. unittest下的Makefile

  1 EXTRA_CFLAGS  :=2 CFLAGS_file.o :=34 obj-y += disp_test.o

4.

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>#include <disp_manager.h>#define FONTDATAMAX 4096static const unsigned char fontdata_8x16[FONTDATAMAX] = {//字符编码,由于太多就不展示了.........
}void lcd_put_ascii(int x, int y, unsigned char c)
{unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];int i, b;unsigned char byte;for (i = 0; i < 16; i++){byte = dots[i];for (b = 7; b >= 0; b--){if (byte & (1<<b)){/* show */PutPixel(x+7-b, y+i, 0xffffff); /* 白 */}else{/* hide */PutPixel(x+7-b, y+i, 0); /* 黑 */}}}
}int main(int argc, char **argv)
{Region region;                 //定义刷新区域的大小PDispBuff ptBuffer;DisplayInit();                 //调用DisplayInit()SelectDefaultDisplay("fb");    //选择默认的设备,传入一个名字为fd的设备InitDefaultDisplay();          //初始化这个默认的设备lcd_put_ascii(100, 100, 'A');  //在屏幕100,100的位置显示一个字母Aregion.iLeftUpX = 100;region.iLeftUpY = 100;region.iWidth   = 8;region.iHeigh   = 16;ptBuffer = GetDisplayBuffer();FlushDisplayRegion(&region, ptBuffer);//将这块区域刷到这个硬件中return 0;	
}

五、上板测试:

将原有的qt程序停止

[root@100ask:~]# systemctl stop myir

最终效果:在开发板上打印出字符A

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/179286.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

策略模式实践

目录 前言 五个部分 名词解释 代码 controller层 HelloService接口 实现类 自定义注解 上下文 策略工厂 Java SPI配置 验证 前言 五个部分 接口、实现类、自定义注解、上下文、策略工厂 名词解释 自定义注解(方便后期增加实现类后灵活控制策略) 上下文(初始化…

【RESTful API】RESTful接口设计练习

参考: BV1Ps4y1J7Ve ---------------------------------------------------------------------------------------------------------- 一、RESTful框架 常见的有SpringMVC,jersey,play 二、API测试工具 Postman,Insomnia 三、RESTful接口设计练习 3.1 项目准备 构…

【项目实战】SpringBoot连接openGauss

一&#xff1a;Docker安装openGauss 1.下载openGauss 安装好Docker好以后&#xff0c;执行如下命令下载openGauss3.0镜像。docker pull enmotech/opengauss:3.0.0 2.运行openGauss 执行如下命令docker run -itd --name opengauss \ --restartalways \ --privilegedtrue \ …

eNSP防火墙USG6000V使用Web界面登入教程

文章目录 登入流程1、下载USG6000V的镜像包2、导入USG6000V的镜像包3、配置防火墙web页面4、修改本机vmnet1网卡的ipv4地址5、在eNSP上添加云6、配置防火墙管理地址&#xff0c;开启http服务7、关闭电脑防火墙8、访问web页面 登入流程 1、下载USG6000V的镜像包 链接&#xff…

MySQL表连接

文章目录 MySQL内外连接1.内连接2.外连接&#xff08;1&#xff09;左外连接&#xff08;2)右外连接 3.简单案例 MySQL内外连接 1.内连接 内连接的SQL如下&#xff1a; SELECT ... FROM t1 INNER JOIN t2 ON 连接条件 [INNER JOIN t3 ON 连接条件] ... AND 其他条件;说明一下…

pytest-pytest-html测试报告这样做,学完能涨薪3k

在 pytest 中提供了生成html格式测试报告的插件 pytest-html 安装 安装命令如下&#xff1a; pip install pytest-html使用 我们已经知道执行用例的两种方式&#xff0c;pytest.main()执行和命令行执行&#xff0c;而要使用pytest-html生成报告&#xff0c;只需要在执行时加…

分布式运用之ELK企业级日志分析系统

1.1 ELK的概念与组件 ELK平台是一套完整的日志集中处理解决方案&#xff0c;将 ElasticSearch、Logstash 和 Kiabana 三个开源工具配合使用&#xff0c; 完成更强大的用户对日志的查询、排序、统计需求。 ElasticSearch&#xff1a; 是基于Lucene&#xff08;一个全文检索引…

2023-简单点-机器学习中常用的特殊函数,激活函数[sigmoid tanh ]

机器学习中的特殊函数 Sigmoidsoftplus函数tanhReLu(x)Leaky-ReluELUSiLu/ SwishMish伽玛函数beta函数Ref Sigmoid 值域: 【0,1】 定义域&#xff1a;【负无穷,正无穷】 特殊点记忆&#xff1a; 经过 [0 , 0.5] 关键点[0,0.5]处的导数是 0.025 相关导数&#xff1a; softplu…

『VUE3 の 要点摘录』

✅v-model 用法 v-model 原生方法&#xff1a; v-model computed &#xff1a; 更改名字&#xff1a; 多个 v-model 绑定 处理 v-model 修饰符 ✅TS项目报错 1、TypeScript 错误 “Module ‘…index’ has no default export” // tsconfig.json {...."compilerOpt…

大金仓数据库:kingbase学习

kingbase学习 1.简介2. 安装3. 基础使用3.1 客户端连接3.2 表数据测试3.2.1 建表创建字段备注 3.2.2 数据写入测试3.2.2 json测试3.2.2.1 json查询测试3.2.2.2 json修改测试3.2.2.3 json数据迁移测试 4.springboot实战4.1 maven依赖4.2 连接配置4.3 mybatis-plus测试4.4 liquib…

【AI认证笔记】NO.2人工智能的发展

目录 一、人工智能的发展里程碑 二、当前人工智能的发展特点 1.人工智能进入高速发展阶段 2.人工智能元年 三、人工智能高速发展的三大引擎 1.算法突破 2.算力飞跃 3.数据井喷 四、AI的机遇 五、AI人才的缺口 六、行业AI 人工智能算法&#xff0c;万物互联&#xff…

qInstallMessageHandler的学习

背景&#xff1a;需要做一个日志系统。 把信息重定向到txt文件中。 参考&#xff1a; QT 调试信息如何输出到文件&#xff08;qDebug/qWarning/qCritical/qFatal&#xff09;-CSDN博客 Qt 之 qInstallMessageHandler&#xff08;重定向至文件&#xff09;-CSDN博客 demo…

Python小技巧:探索函数调用为何加速代码执行

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com Python 作为一种解释型语言&#xff0c;其执行速度相对于编译型语言可能会较慢。然而&#xff0c;在Python中&#xff0c;通常观察到代码在函数中运行得更快的现象。这个现象主要是由于函数调用的内部优化和解释…

神器!使用 patchworklib 库进行多图排版真棒啊

如果想把多个图合并放在一个图里&#xff0c;如图&#xff0c;该如何实现 好在R语言 和 Python 都有对应的解决方案&#xff0c; 分别是patchwork包和patchworklib库。 推介1 我们打造了《100个超强算法模型》&#xff0c;特点&#xff1a;从0到1轻松学习&#xff0c;原理、…

整车测试中的UDS诊断

UDS&#xff08;Unified Diagnostic Services&#xff0c;统一的诊断服务&#xff09;诊断协议是在汽车电子ECU环境下的一种诊断通信协议。这种通信协议被用在几乎所有由OEM一级供应商所制造的新ECU上面。这些ECU控制车辆的各种功能&#xff0c;包括电控燃油喷射系统&#xff0…

Week-T11-优化器对比试验

文章目录 一、准备环境二、准备数据三、搭建训练网络三、训练模型&#xff08;1&#xff09;VSCode训练情况&#xff1a;&#xff08;2&#xff09;jupyter notebook训练情况&#xff1a; 四、模型评估 & 模型预测1、绘制Accuracy-Loss图2、显示model2的预测效果 五、总结1…

C++类与对象(7)—友元、内部类、匿名对象、拷贝对象时编译器优化

目录 一、友元 1、定义 2、友元函数 3、友元类 二、内部类 1、定义 2、特性&#xff1a; 三、匿名对象 四、拷贝对象时的一些编译器优化 1、传值&传引用返回优化对比 2、匿名对象作为函数返回对象 3、接收返回值方式对比 总结&#xff1a; 一、友元 1、定义…

RPC之grpc重试策略

1、grpc重试策略 RPC 调用失败可以分为三种情况&#xff1a; 1、RPC 请求还没有离开客户端&#xff1b; 2、RPC 请求到达服务器&#xff0c;但是服务器的应用逻辑还没有处理该请求&#xff1b; 3、服务器应用逻辑开始处理请求&#xff0c;并且处理失败&#xff1b; 最后一种…

2020年3月2日 Go生态洞察:Go协议缓冲区的新API发布

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

如何轻松将 4K 转换为 1080p 高清视频

由于某些原因&#xff0c;你可能有一些 4K 视频&#xff0c;与1080p、1080i、720p、720i等高清视频相比&#xff0c;4K 视频具有更高的分辨率&#xff0c;可以给您带来更多的视觉和听觉享受。但是&#xff0c;播放4k 视频是不太容易的&#xff0c;因为超高清电视没有高清电视那…