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

Harmonyos开源全场景应用开发-视频渲染

时间:2023-03-14 20:01:07 科技观察

更多内容请访问:Harmonyos技术社区https://harmonyos.51cto.com与华为官方共建背景如前一期所述,开发的家族照片美颜相机应用基于鸿蒙和Android设备。我们将对它的四个功能模块进行拆分讲解,分别是视频编解码、视频渲染、通信协议和美颜滤镜。上一期我们分析了视频编解码模块的实现原理。本期继续讲解视频渲染模块,分析鸿蒙视频渲染相关类的关系。相关代码已经开源到Gitee(https://gitee.com/isrc_ohos/cameraharmony),欢迎大家下载使用并提出宝贵意见!全家福美颜相机应用效果回顾下面我们就带一家人一起来回顾一下上期美颜相机app讲解的全家福吧。本应用可将鸿蒙大屏拍摄的视频数据实时传输至安卓手机;并在Android端为其添加滤镜,然后将处理后的视频数据传回鸿蒙大屏进行渲染显示,从而实现鸿蒙大屏美颜相机功能,其流程可以参考图1、其数据流图可以参考图2:图1全家福美颜相机应用效果示意图图2美颜相机应用视频数据流图应用运行后的动态场景效果可以参考图3.图下方竖屏为安卓手机,上方横屏为鸿蒙手机(由于实验环境缺少搭载鸿蒙系统的大屏设备,我们使用鸿蒙手机代替大屏设备模拟实验场景),展示了视频解码后的渲染效果。图3应用运行效果图SurfaceProvider视频渲染分析在鸿蒙中,SurfaceProvider是一个专门用来绘制图像视图的组件。作为基础组件之一,通常用在需要快速绘制图像的地方,比如视频播放的场景。下面讲解视频编解码过程完成后,通过鸿蒙SurfaceProvider进行视频渲染显示的具体实现原理。分为以下六个步骤:步骤1.声明SurfaceProvider类对象。步骤2.设置SurfaceProvider属性并将其添加到页面的整体布局中。Step3.解码类VDDecoder继承SurfaceOps.Callback接口类。步骤4.获取SurfaceOps并设置回调。第五步,覆盖SurfaceCreated()方法获取当前Surface。步骤6.渲染视频数据。(1)声明SurfaceProvider类对象在进行视频渲染之前,需要先声明用于渲染视频的SurfaceProvider类对象。privateSurfaceProvidersurfaceview;//SurfaceProvider用于显示解码后的视频(2)设置SurfaceProvider属性并添加它在页面整体布局中实例化SurfaceProvider类对象并设置相关属性。首先使用setWidth()和setHeight()方法设置尺寸;pinToZTop()方法使surfaceview显示在屏幕布局的顶部。由于要渲染的视频数据是横屏而屏幕是竖屏,或者是要渲染的视频数据是竖屏而屏是横屏,可能会出现不匹配的情况,所以需要使用setRotation()方法来调整屏幕参数,使屏幕显示方向与视频数据方向一致。其中0-180度屏幕参数为横屏显示,90-270度为竖屏显示。本应用中的原始视频数据为横屏,因此这里需要将屏幕参数设置为180度。然后最重要的是需要通过getSurfaceOps().get().addCallback()方法设置回调,这样SurfaceProvider就可以通过回调关联到设备摄像头了。surfaceview1=newSurfaceProvider(this);//实例化类对象surfaceview1.setWidth(400);//设置SurfaceProvider大小surfaceview1.setHeight(300);surfaceview1.getSurfaceOps().get().addCallback(callback);//设置调用背面view1.pinToZTop(true);surfaceview1.setRotation(180);//设置屏幕旋转角度通过Layout的addComponent()方法将SurfaceProvider添加到整体布局中。myLayout.addComponent(surfaceview);//添加到布局中(3)解码类VDDecoder继承SurfaceOps.Callback类SurfaceOps.Callback提供SurfaceProvider创建、销毁或改变时的回调通知。由于视频渲染阶段是在视频编解码过程完成之后,解码类VDDecoder需要继承SurfaceOps.Callback类,为SurfaceOps提供回调接口。其中,需要全局声明Surface和SurfaceOps类对象,并重写SurfaceCreated()、SurfaceDestroyed()、SurfaceDestroyed()方法。publicclassVDDecoderimplementsSurfaceOps.Callback{privateSurfaceOpsholder;//全局声明SurfaceOps和SurfaceOps类对象privateSurfacemSurface;...@Override//重写SurfaceProvider创建时的回调publicvoidsurfaceCreated(SurfaceOpsholder){...}@Override//重写SurfaceProvider被改变时的回调publicvoidsurfaceChanged(SurfaceOpsholder,intformat,intwidth,intheight){...}@Override//重写SurfaceProvider销毁时的回调publicvoidsurfaceDestroyed(SurfaceOpsholder){...}}(4)获取SurfaceOps并在instance在转换解码类对象时,将用于渲染编解码视频的surfaceview作为参数传入。vdDecoder=newVDDecoder(surfaceview);//创建解码??类对象,并使用surfaceview显示解码后的视频在解码类VDDecoder构造函数中设置SurfaceProvider,调用SurfaceProvider类的getSurfaceOps().get()方法获取SurfaceOps表面视图;通过SurfaceOps类对象持有者调用addCallback()方法设置回调;然后调用setKeepScreenOn()方法,将参数设置为true,达到一直不会自动熄屏的效果。publicVDDecoder(SurfaceProviderplayerView){//设置SurfaceProvider,即使用surfaceview播放解码后的视频this.holder=surfaceview.getSurfaceOps().get();holder.addCallback(this);//设置回调//设置组件让屏幕不会自动关闭holder.setKeepScreenOn(true);...}(5)重写SurfaceCreated()方法获取当前SurfaceSurfaceCreated()和surfaceDestroyed()是渲染处理的边界,分别代表SurfaceProvider的创建和销毁,形式上的渲染操作必须在SurfaceProvider创建之后进行。重写surfaceCreated()方法创建一个SurfaceProvider,设置创建状态isSurfaceCreated变量为true,表示已经创建;通过SurfaceOps类对象holder调用getSurface()方法获取当前Surface到类对象mSurface中,这样视频数据就可以渲染到mSurface后面的接口上了。@Override//重写SurfaceProvider创建时的回调publicvoidsurfaceCreated(SurfaceOpsholder){isSurfaceCreated=true;//设置创建状态为createdmSurface=holder.getSurface();//获取当前Surface...}(6)rendering视频数据在codec类的监听事件decoderListener中,获取编码后的数据进行渲染。由于获取的摄像头图像数据是逆时针旋转了90度,如果此时直接进行渲染,显示器也会显示出逆时针旋转的效果。因此,为了获得正常的显示画面,需要对图像参数进行调整。调用rotateNV21()方法将视频图像顺时针旋转90度,并将旋转后的数据存入字节数组rotate_bytes中。通过Surface类对象mSurface调用showRawImage()方法渲染旋转后的视频数据。该方法的第一个参数表示要渲染的数据的字节数组;第二个表示要呈现的数据的格式。由于本demo中的codec为摄像头直接获取的数据,格式为NV21,即YUV420_SP;第三个和第四个参数分别代表渲染图像的宽度和高度。privateCodec.ICodecListenerdecoderlistener=newCodec.ICodecListener(){//用于监听解码器,获取解码后的数据@OverridepublicvoidonReadBuffer(ByteBufferbyteBuffer,BufferInfobufferInfo,inti){...//对解码后的NV21(YUV420SP)数据字节进行排序时针旋转90度,通过Surface显示rotateNV21(bytes,rotate_bytes,640,480,90);//旋转后的数据存放在rotate_bytes//渲染旋转后的数据rotate_bytes通过mSurface显示,第二个参数是要渲染的数据格式为YUV420SPmSurface.showRawImage(rotate_bytes,Surface.PixelFormat.PIXEL_FORMAT_YCRCB_420_SP,640,480);}...};运行后点击“StartCodec”按钮,即可得到如上图1所示的编码视频数据在surfaceview中渲染的效果。Surface、SurfaceOps、SurfaceProvider的关系经过上面的讲解,相信大家可以在鸿蒙中正确使用SurfaceProvider进行视频渲染了。熟悉Android的读者可能已经发现,鸿蒙SurfaceProvider的用法与AndroidSurface的用法类似。为了便于理解,可以将鸿蒙中的SurfaceProvider、Surface、SurfaceOps与Android中的SurfaceView、Surface、SurfaceHolder进行对比,原理类似。下面将分析这三个视频渲染类在鸿蒙中的关系。图4SurfaceProvider、Surface、SurfaceOps的关系示意图1.Surface与SurfaceProvider的关系Surface与SurfaceProvider的关系如图2所示,在鸿蒙中,每个窗口对应一个SurfaceProvider,每个Surface对应一个屏幕缓冲区。SurfaceProvider的作用是处理屏幕缓冲区中的视频数据,并使用数据在屏幕上进行绘制。也就是说,Surface负责管理视频数据;eSurfaceProvider负责显示视频数据,而Surface需要SurfaceProvider来显示内容和控制view的位置和大小。2、SurfaceOps与SurfaceProvider的关系SurfaceOps是一个接口,其功能类似于Surface上的监听器,可以访问SurfaceProvider对应的Surface,并调用Surface中的相关方法。并通过三个回调方法,及时捕捉Surface的创建、销毁或变化等状态。获取SurfaceOps的方法是:调用SurfaceProvider类中的getSurfaceOps()方法获取元素类型为SurfaceOps的Optional容器,然后使用get()方法从容器中取出SurfaceOps类对象并返回。调用成功并得到返回值后,可以通过返回的SurfaceOps类对象调用addCallback()方法为Surface设置回调:voidaddCallback(SurfaceOps.Callbackvar1);//设置SurfaceOps回调如图2,Surface和SurfaceProvider之间还有一个SurfaceOps.Callback类。SurfaceOps回调是通过内部子接口SurfaceOps.Callback实现的,它有3个回调方法:surfaceCreated():当SurfaceProvider发生格式或尺寸变化等结构变化时调用该方法。surfaceChanged():创建SurfaceProvide时调用此方法。surfaceDestroyed():当SurfaceProvider即将被销毁时立即调用该方法。publicinterfaceCallback{//内部子接口CallBackvoidsurfaceCreated(SurfaceOpsvar1);//SurfaceProvider创建时voidsurfaceChanged(SurfaceOpsvar1,intvar2,intvar3,intvar4);//SurfaceProvider改变时voidsurfaceDestroyed(SurfaceOpsvar1);//SurfaceProvider销毁时}上面提到的SurfaceOps是一个接口,所以在实际使用前,需要重写以上三个回调方法,才能正常感知SurfaceProvider的创建、改变或销毁。更多信息请访问:Harmonyos.51cto.com,与华为官方合作打造的鸿蒙技术社区