鸿蒙开源全场景应用开发—视频渲染

开发
上一期内容中,我们对视频编解码模块的实现原理进行了解析。本期将继续为大家讲解视频渲染模块,并解析鸿蒙视频渲染相关类之间的关系。

[[425232]]

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

背景

上期内容提到过,已开发的家庭合影美颜相机应用是同时基于鸿蒙和安卓设备的,我们将对其4个功能模块即视频编解码、视频渲染、通讯协议和美颜滤镜进行拆分讲解。上一期内容中,我们对视频编解码模块的实现原理进行了解析。本期将继续为大家讲解视频渲染模块,并解析鸿蒙视频渲染相关类之间的关系。相关代码已经开源到Gitee(https://gitee.com/isrc_ohos/cameraharmony ),欢迎各位下载使用并提出宝贵意见!

家庭合影美颜相机应用效果回顾

先来带家一起回顾下上期内容讲解的家庭合影美颜相机应用。此应用能够将鸿蒙大屏拍摄的视频数据实时传输到安卓手机上;并在安卓端为其添加滤镜,再将处理后的视频数据传回到鸿蒙大屏进行渲染显示,从而实现鸿蒙大屏美颜拍照的功能,其流程可以参考图1,其数据流向图可以参考图2:

鸿蒙开源全场景应用开发——视频渲染-鸿蒙HarmonyOS技术社区

图1 家庭合影美颜相机应用的效果示意图

鸿蒙开源全场景应用开发——视频渲染-鸿蒙HarmonyOS技术社区

图2 美颜相机应用视频数据流向图

应用运行后的动态场景效果可以参考图3,图中下方竖屏显示的是安卓手机,上方横屏显示的是鸿蒙手机(由于实验环境缺少搭载鸿蒙系统的大屏设备,因此我们使用鸿蒙手机替代大屏设备模拟实验场景 ),其显示的是视频解码后渲染的效果。

鸿蒙开源全场景应用开发——视频渲染-鸿蒙HarmonyOS技术社区

图3 应用运行效果图

SurfaceProvider视频渲染解析

在鸿蒙中,SurfaceProvider是专门用于绘制图像视图的组件,作为基本组件之一,它通常被用于需要快速绘制图像的地方,如播放视频的情况。下面为大家讲解在完成视频编解码处理后,通过鸿蒙SurfaceProvider完成视频渲染显示的具体实现原理。共分为如下6个步骤:

步骤1. 声明SurfaceProvider类对象。

步骤2. 设置SurfaceProvider属性并添加在页面整体布局中。

步骤3. 解码类VDDecoder继承 SurfaceOps.Callback接口类。

步骤4. 获取SurfaceOps并设置回调。

步骤5. 重写SurfaceCreated()方法,获取当前Surface。

步骤6. 渲染视频数据。

(1)声明SurfaceProvider类对象

在进行视频渲染之前,需要声明用于渲染视频的SurfaceProvider类对象。

  1. private SurfaceProvider surfaceview;// SurfaceProvider用于显示解码后的视频 

(2)设置SurfaceProvider属性并添加在页面整体布局中

实例化SurfaceProvider类对象并设置相关属性。先使用setWidth()和setHeight()方法设置大小;pinToZTop()方法使surfaceview置于屏幕布局最顶层显示。由于可能会出现待渲染视频数据本身是横屏而屏幕为竖屏显示,或待渲染视频数据本身是竖屏而屏幕为横屏显示等不匹配的情况,因此需要使用setRotation()方法调整屏幕参数,使得屏幕显示方向与视频数据方向相符,其中,屏幕参数0-180度为横屏显示,90-270度为竖屏显示,本应用中原始视频数据是横屏的所以此处需要将屏幕参数设置为180度。接着最主要的是,需要通过getSurfaceOps().get().addCallback()方法设置回调,这样可以通过回调将SurfaceProvider和设备相机相关联。

  1. surfaceview1 = new SurfaceProvider(this);  // 实例化类对象 
  2. surfaceview1.setWidth(400);  // 设置 SurfaceProvider 大小 
  3.  
  4. surfaceview1.setHeight(300); 
  5. surfaceview1.getSurfaceOps().get().addCallback(callback);// 设置回调 
  6. surfaceview1.pinToZTop(true); 
  7. surfaceview1.setRotation(180);  // 设置屏幕旋转角度 

通过Layout的addComponent()方法将SurfaceProvider添加到整体布局中。

  1. myLayout.addComponent(surfaceview);   // 添加到布局中 

(3)解码类VDDecoder继承SurfaceOps.Callback类

SurfaceOps.Callback提供了SurfaceProvider被创建、销毁或者改变时的回调通知。由于进行视频渲染的阶段是在完成视频编解码处理之后,因此解码类VDDecoder需要继承SurfaceOps.Callback类,即为SurfaceOps提供一个回调接口。其中需要全局声明Surface和SurfaceOps类对象并重写SurfaceCreated()、SurfaceDestroyed()和SurfaceDestroyed()方法。

  1. public class VDDecoder implements SurfaceOps.Callback { 
  2.    private SurfaceOps holder;// 全局声明SurfaceOps和SurfaceOps类对象 
  3.    private Surface mSurface; 
  4.    ... 
  5.    @Override  // 重写 SurfaceProvider被创建时的回调 
  6.    public void surfaceCreated(SurfaceOps holder) { 
  7.       ... 
  8.    @Override  // 重写SurfaceProvider被改变时的回调 
  9.    public void surfaceChanged(SurfaceOps holder, int format, int width, int height) { 
  10.      ... 
  11.    @Override  // 重写SurfaceProvider被销毁时的回调 
  12.    public void surfaceDestroyed(SurfaceOps holder) { 
  13.      ... 
  14.     } 

(4)获取SurfaceOps并设置回调

在实例化解码类对象时,将用于渲染编解码后视频的surfaceview作为参数传入。

  1. vdDecoder = new VDDecoder(surfaceview);// 创建解码类对象,并使用surfaceview显示解码后的视频 

在解码类VDDecoder构造函数中设置SurfaceProvider,调用SurfaceProvider类的getSurfaceOps().get()方法获取surfaceview的SurfaceOps;通过SurfaceOps类对象holder调用addCallback()方法设置回调;再调用setKeepScreenOn()方法,将参数设为true,来实现使屏幕一直显示不会自动关闭的效果。

  1. public VDDecoder(SurfaceProvider playerView) { 
  2.     // 设置 SurfaceProvider,即使用 surfaceview播放解码后的视频 
  3.     this.holder = surfaceview.getSurfaceOps().get(); 
  4.     holder.addCallback(this);// 设置回调 
  5.     // 设置该组件让屏幕不会自动关闭 
  6.     holder.setKeepScreenOn(true); 
  7.     ... 

(5)重写SurfaceCreated()方法,获取当前Surface

surfaceCreated()和surfaceDestroyed()是渲染处理的边界,分别代表SurfaceProvider的创建和销毁,正式的渲染操作必须在SurfaceProvider被创建后才能进行。重写surfaceCreated()方法创建SurfaceProvider,将创建状态isSurfaceCreated变量设置为true,表示已创建;通过SurfaceOps类对象holder调用getSurface()方法获得当前Surface到类对象mSurface中,以便后续将视频数据通过mSurface渲染到界面上。

  1. @Override  // 重写 SurfaceProvider被创建时的回调 
  2. public void surfaceCreated(SurfaceOps holder) { 
  3.     isSurfaceCreated = true;  // 设置创建状态为已创建 
  4.     mSurface = holder.getSurface();  // 获得当前Surface 
  5.     ... 

(6)渲染视频数据

在编解码类的监听事件decoderlistener中,获取编解码后的数据准备渲染。由于得到的相机图像数据是逆时针旋转90度的,此时如果直接进行渲染,显示的也会是逆时针旋转的效果,因此为了得到正常的显示画面,需要对图像参数进行调整,调用rotateNV21()方法对视频画面进行顺时针旋转90度,并将旋转后的数据存放在byte数组rotate_bytes中。

通过Surface类对象mSurface调用showRawImage()方法对旋转后的视频数据进行渲染。此方法第一个参数表示待渲染数据的byte数组;第二个表示待渲染数据的格式,由于此Demo中编解码的是摄像头直接获取的数据,所以格式是NV21即YUV420_SP;第三和第四个参数分别表示渲染画面的宽和高。

  1. private Codec.ICodecListener decoderlistener = new Codec.ICodecListener() { 
  2.     // 用于监听解码器,获取解码完成后的数据 
  3.     @Override 
  4.     public void onReadBuffer(ByteBuffer byteBuffer, BufferInfo bufferInfo, int i) { 
  5.         ... 
  6.         // 将解码后的 NV21(YUV420SP) 数据 bytes 顺时针旋转 90 度,并通过 Surface 显示 
  7.         rotateNV21(bytes, rotate_bytes, 640, 480, 90);// 旋转后的数据用 rotate_bytes 存放 
  8.         // 渲染旋转后的数据 rotate_bytes 通过mSurface显示出来,第二个参数是待渲染的数据格式即YUV420SP 
  9.         mSurface.showRawImage(rotate_bytes, Surface.PixelFormat.PIXEL_FORMAT_YCRCB_420_SP,640, 480); 
  10.     } 
  11.     ... 
  12. }; 

之后运行并点击“开始编解码”按钮即可得到上述图1中展示的将编解码后的视频数据渲染在surfaceview中的效果。

Surface、SurfaceOps与SurfaceProvider的关系

经过上述讲解,相信大家已经能够在鸿蒙中正确使用SurfaceProvider来进行视频渲染了。熟悉安卓的读者可能已经发现,鸿蒙SurfaceProvider用法和安卓Surface用法有异曲同工之妙。为了方便理解,可以将鸿蒙中的SurfaceProvider、Surface和SurfaceOps分别与安卓中的SurfaceView、Surface、和SurfaceHolder对照查看,其原理类似。下面将为大家解析在鸿蒙中这三个视频渲染类之间的关系。

鸿蒙开源全场景应用开发——视频渲染-鸿蒙HarmonyOS技术社区

图4 SurfaceProvider、Surface、SurfaceOps关系示意图

1.Surface与SurfaceProvider关系

Surface与SurfaceProvider之间的关系如图2所示。在鸿蒙中,每个窗口会对应一个SurfaceProvider,每个Surface会对应一块屏幕缓冲区,而SurfaceProvider的作用是处理屏幕缓冲区中的视频数据,并使用该数据在屏幕上绘图。也就是说,Surfac负责对视频数据进行管理;eSurfaceProvider负责对视频数据进行展示,Surface需要通过SurfaceProvider才能展示其中的内容并控制视图的位置和尺寸。

2.SurfaceOps与SurfaceProvider关系

SurfaceOps是一个接口,其作用类似于一个关于Surface的监听器,能够访问SurfaceProvider对应的Surface并调用Surface中的相关方法。并通过三个回调方法,及时捕捉Surface的状态如创建、销毁或者改变。

获取SurfaceOps的方式是:调用SurfaceProvider类中getSurfaceOps()方法,得到元素类型为SurfaceOps的Optional容器,再通过get()方法从容器中取出SurfaceOps类对象并返回。在成功调用并得到返回值之后,就可以通过返回的SurfaceOps类对象调用addCallback()方法为Surface设置回调:

  1. void addCallback(SurfaceOps.Callback var1);// 设置SurfaceOps回调 

图2中显示,在Surface与SurfaceProvider之间还存在一个SurfaceOps.Callback类,SurfaceOps的回调就是通过内部子接口SurfaceOps.Callback实现的,其有三个回调方法:

  • surfaceCreated():当SurfaceProvider发生结构性的变化如格式或大小改变时,调用此方法。
  • surfaceChanged():当SurfaceProvide被创建时,调用此方法。
  • surfaceDestroyed():当SurfaceProvider在要被销毁时,立即调用此方法。
  1. public interface Callback {  // 内部子接口CallBack 
  2.     void surfaceCreated(SurfaceOps var1);// SurfaceProvider被创建时 
  3.     void surfaceChanged(SurfaceOps var1, int var2, int var3, int var4);// SurfaceProvider被改变时 
  4.     void surfaceDestroyed(SurfaceOps var1);// SurfaceProvider被销毁时 

 上面提到过SurfaceOps是一个接口,因此在实际使用之前,需要先重写上述三个回调方法,才能正常感知到SurfaceProvider的创建、改变或销毁。

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐
点赞
收藏

51CTO技术栈公众号