当前位置: 首页 > 科技观察

Harmonyos开源全场景应用开发——通信协议

时间:2023-03-12 12:55:15 科技观察

更多内容请访问:与华为官方合作建立的Harmonyos技术社区https://harmonyos.51cto.com前言如前所述,开发家族合影美颜相机应用基于鸿蒙和安卓设备。我们将对它的四个功能模块进行拆分讲解,分别是视频编解码、视频渲染、通信协议和美颜滤镜。上期我们分析了视频编解码和视频渲染模块的实现原理。本期将继续讲解通信协议,简单介绍一下Android美颜滤镜的实现原理。背景技术RTP是Internet上流媒体传输的一种基本协议。它工作在一对一或一对多传输的情况下,其目的是提供时间信息,实现流同步。它可以建立在底层面向连接和无连接的传输协议之上,一般使用UDP协议进行传输。从同步源发送的一系列RTP数据包称为流,一个RTP会话可能包含多个RTP流。应用效果展示1、家庭合影美颜相机应用效果回顾。本应用可将鸿蒙大屏拍摄的视频数据实时传输至安卓手机;并在Android端为其添加滤镜,然后将处理后的视频数据传回鸿蒙大屏进行渲染显示,从而实现鸿蒙大屏美颜拍照功能,可参考图1为应用运行后的动态场景效果。图片下方竖屏为安卓手机,上方横屏为鸿蒙手机(由于实验环境缺少搭载鸿蒙系统的大屏设备,我们使用鸿蒙手机代替)大屏设备模拟实验场景),展示了视频解码后的渲染效果。图1家庭美颜相机应用运行效果图2.RTP传输Demo效果为了更清楚的说明通信协议,我们将家庭美颜相机应用的数据传输部分拆分成一个RTP传输demo,以及为了对功能进行整理和优化,将原来的视频传输改为图传。视频由多帧图像组成。传输数据类型的改变不会影响RTP传输的原理和步骤。图2为RTP传输Demo的运行效果图。上图是发送端的效果,下图是接收端的效果。成功安装并打开APP后,点击发送端蓝色按钮,发送开发者选择的特定区域的图片数据;点击接收端的粉色按钮,接收发送端刚刚发送的图像数据,并显示在按钮下方。图2RTP传输Demo运行效果图(上端发送端,下端接收端)RTP传输原理及步骤分析下面重点分析RTP传输的实现原理和步骤。RTP传输Demo的原理流程见图3。在鸿蒙发送端(服务器端),设置要传输的图像数据,使用RTP协议和Socket点对点数据通信,通过无线网络发送给鸿蒙接收端(客户端)。在鸿蒙的接收端(客户端),接收到发送端发送的图像数据后,进行图像绘制。接下来分析RTP传输Demo的实现步骤。图3RTP传输原理流程图服务器端发送数据,将要发送的图片放在resources->base->media文件夹中,如图4所示。然后将图片数据的格式转换为发送。通过无线网络,使用RTP协议和Socket点对点数据通信方式,将图像数据传输到鸿蒙接收端。图4图片在项目结构中的位置服务器发送数据的过程包括以下三个步骤:步骤1.通过资源ID获取位图对象;Step2.转换位图指定区域像素的格式;第三步,数据传输;(1)通过资源ID获取位图对象通过getResource()方法,以资源IDdrawableID对象作为入参,获取资源输入流drawableInputStream;实例化图片设置类ImageSource.SourceOptions对象,设置图片源格式为png;创建图片Source,参数为资源输入流和图片源ImageSource类对象;实例化图像参数类DecodingOptions对象,初始化图像大小、区域并为其设置位图格式;根据图片参数类对象decodingOptions,通过图片源ImageSource类对象创建Bitmap对象;返回一个位图对象。//通过资源ID获取位图对象privatePixelMapgetPixelMap(intdrawableId){InputStreamdrawableInputStream=null;try{//以资源ID为入参获取资源输入流drawableInputStream=this.getResourceManager().getResource(drawableId);//实例化图片源ImageSource类对象ImageSource.SourceOptionssourceOptions=newImageSource.SourceOptions();sourceOptions.formatHint="image/png";//设置图片源格式//创建图片源,参数为资源输入流和图片源ImageSource类对象ImageSourceimageSource=ImageSource.create(drawableInputStream,sourceOptions);//实例化图片源解码操作DecodingOptions类对象ImageSource.DecodingOptionsdecodingOptions=newImageSource.DecodingOptions();decodingOptions.desiredSize=newSize(0,0);//设置图片大小decodingOptions.desiredRegion=newRect(0,0,0,0);decodingOptions.desiredPixelFormat=PixelFormat.ARGB_8888;//设置位图格式PixelMappixelMap=imageSource.createPixelmap(decodingOptions);//根据解码操作类对象创建位图returnpixelMap;//返回Bitmap}...}(2)转换位图指定区域像素的格式.获取位图对象后,实例化一个矩形Rect对象,用于为开发者选择特定的图片区域(该区域不能大于resources->base->media路径下图片的大小);通过位图对象pixelMap调用readPixels()方法将指定区域的像素点转换为int[]类型数据;调用intToBytes()方法,然后将int[]类型的数据格式转换为byte类型的数据//读取指定区域的像素Rectregion=newRect(0,0,30,30);//实例化holding类对象,指定指定区域pixelMap.readPixels(pixelArray,0,30,region);//将指定区域的像素转换为int[]类型数据pic=intToBytes(pixelArray);//将int[]类型数据变为Bitbytetypedata(3)数据传输实例化RTP发送类对象RtpSenderWrapper,IP地址设置为接收端手机IP地址,端口号设置为5005;调用sendAvcPacket()方法发送图片数据。由于RTP传输的数据类型被简化了,图像RTP传输会相对容易一些,但是如果在原来的应用中是视频RTP传输,就需要将视频数据的格式逐帧转换,转换成从摄像头获取的YUV类型的原始视频数据,压缩成h264类型的视频数据,方便Socket传输。mRtpSenderWrapper=newRtpSenderWrapper("192.168.31.12",5005,false);mRtpSenderWrapper.sendAvcPacket(pic,0,pic.length,0);//发送数据sender通过RTP协议发送成功后,client收到数据,然后receives终端就可以开始正常接收了。发送端接收数据的过程主要分为以下五个步骤:第一步,创建数据接收线程;Step2.接收数据;Step3.线程间传递数据;Step4.处理位图数据得到pixelMapHolder;第5步。绘制图像。(1)创建数据接收线程创建子线程作为数据接收线程。newThread(newRunnable())//新开一个数据接收线程(2)在子线程接收线程中接收数据,实例化数据包DatagramPacket;通过Socket类对象调用receive()方法,将发送端的数据接收到数据包DatagramPacket中;通过数据包DatagramPacket调用getData()方法获取数据包中的RTP数据。datagramPacket=newDatagramPacket(data,data.length);//实例化数据包socket.receive(datagramPacket);//接收数据放入数据包rtpData=datagramPacket.getData();获取数据包中的RTP数据(3)线程间数据传输子线程获取到要发送的RTP数据后,需要将RTP数据从子线程传输到主线程。这就涉及到线程之间的数据传输。在本应用中,我们使用Java类的SynchronousQueue并发队列来实现子线程与主线程之间的数据传递。首先实例化一个byte[]类型的并发队列SynchronousQueue类对象;将h264类型数据放入并发队列;然后从队列中获取数据。SynchronousQueuequeue=newSynchronousQueue();//实例化byte[]类型并发队列queue.put(h264Data);//将h264类型数据放入并发队列rgbData=queue.take();//从队列中获取数据(4)对解码后的位图数据进行处理,得到PixelMapHolder。主线程从队列中拿到图像RGB数据后,就可以绘制图像了。PixelMap是接收到的位图数据。PixelMapHolder使用PixelMap生成后端渲染所需的数据,并提供该数据作为Canvas中方法的入参。因此,为了后面能够渲染位图,需要在图像数据从子线程传递到主线程后,即实例化pixelMapHolder时,将图像数据pixelmap转为pixelMapHolder类对象类对象,像素图位图数据用作传递给实例化方法的输入参数。publicvoidputPixelMap(PixelMappixelMap){if(pixelMap!=null){//判断接收到的位图数据是否为空rectSrc=newRectFloat(0,0,pixelMap.getImageInfo().size.width,pixelMap.getImageInfo().size.height);pixelMapHolder=newPixelMapHolder(pixelMap);//实例化PixelMapHolder类对象}else{pixelMapHolder=null;//如果接收到的位图为空,则全部设置为nullsetPixelMap(null);}}(5)绘制图像并实例化一个矩形Rect类对象,设置图片信息,指定指定区域的宽高等;添加一个同步绘制任务,首先判断pixelMapHolder是否为空,如果为空则直接返回,如果不为空则开始绘制任务;在绘图任务中,调用drawPixelMapHolderRoundRectShape()方法将PixelMapHolder类对象绘制成实例化的矩形Rect类对象,并设置为圆角效果;它的位置由rectDst指定;绘制完成后,释放pixelMapHolder,设置为Leaveblank。privatevoidonDraw(){this.addDrawTask((view,canvas)->{//添加绘图任务if(pixelMapHolder==null){//判断pixelMapHolder是否为空return;}synchronized(pixelMapHolder){//同步任务Draw图片canvas.drawPixelMapHolderRoundRectShape(pixelMapHolder,rectSrc,rectDst,radius,radius);//将图片绘制成圆角效果pixelMapHolder=null;//绘制完成后释放pixelMapHolder}});}Android侧边美颜滤镜效果美颜滤镜部分,我们参考了GitHub上的开源项目(https://github.com/google/grafika,https://github.com/cats-oss/android-gpuimage,https://github.com/wuhaoyu1990/MagicCamera),使用GPUshader实现添加滤镜和切换滤镜的效果。由于不涉及鸿蒙的能力,这部分不是重点,只是简单总结一下它的实现过程,大致分为以下五个步骤:(1)设置不同的滤镜使用shader语言,设置各种代码必需的。图5美颜相机使用的滤镜(二)opengl绘图;importandroid.opengl.GLES20;...//addthevertexshadertoprogramGLES20.glAttachShader(mProgram,vertexShader);//addthefragmentshadertoprogramGLES20.glAttachShader(mProgram,fragmentShader);//createsOpenGLESprogramexecutablesGLES20.glLinkProgram(mProgram);(3)添加过滤器;privateListfilters=newArrayList<>();...filters.add(FilterFactory.FilterType.Original);filters.add(FilterFactory.FilterType.Sunrise);...(4)开启或关闭美颜滤镜;mCameraView.enableBeauty(true);(5)设置美颜等级;mCameraView.setBeautyLevel(0.5f);(6)设置切换滤镜和切换镜头,然后设置相机拍摄和拍摄完成后的回调即可。mCameraView.updateFilter(filters.get(pos));//切花滤镜mCameraView.switchCamera();//切换相机更多信息请访问:与华为官方共建的鸿蒙技术社区https://harmonyos.51cto。com