本文是在乐视做Phone开发时,预研视频通话功能的文档
简介
本文作为mtk的viLTE的预研文档,主要从从视频通话流程的角度阐述viLTE相关内容,为后续开发做充分的理论准备。从上层代码结构看,高通和MTK的代码调用和路径是相同的,没有太大的差异,但从实现方式上看,高通和MTK不同。高通方面,与volte通话一样,指令通过ImsSenderRxr.java发送给modem层,同样,上层也是通过
ImsSendRxr.java来获取modem上报的信息;
与ImsSendRxr.java交互的是ImsCallModification.java。MTK则有所不同,MTK将viLTE大部分业务逻辑放在了native层,如与modem交互的逻辑、与Camera交互的逻辑等。上层通过与native层交发送和接收消息。所以上层是相对封闭的,也较好维护。
视频通话主要有发起视频通话请求和接受视频通话请求。
发起视频请求有两种方式,从联系人中直接拨打视频通话;或在通话过程中发起通话升级请求,将当前通话升级为视频通话。同样接收视频通话请求也可以在语音通话过程和非语音通话中接收。上述这两种方式走的代码路径是一样的,我们就以语音通话过程中发起视频通话请求(send upgrade call request)和接收视频通话请求(receive upgrade request)为例,分析流程
整体流程示意:
上图中有两种情况,第一种为正常upgrade call的流程。MO向服务器发送send request请求,服务器处理后向MT发送指令,MT方modem会上报receive request。MT处理后(receive或reject)向服务器send response。MO的modem会上报receive response。第二种是超时后服务器给双发送消息。我们就以第一种情况来分析视频通话的功能流程。
viLTE功能流程
发起视频请求(send upgrade call request)
流程
当用户发起upgrade call请求后,InCallUI会向下发送sendSessionModifyRequest(),这个比较好理解,up/down grade都可以理解为modify call,向网络请求建立视频通话,当然是sendRequest。请求最后由ImsVTProvider传递给native层,由native层与modem交互。对方接听/拒接后,native层会回调ImsVTProvider的postEventFromNative()方法,将modem的信息传递上来。随后ImsVTProvider上报receiveSessionModifyResponse()消息。最终由CallList将消息传递给Presenter。流程如下图
关键log
做ROM开发时,系统Log是重要的调试流程通畅的关键依据,所以这里将关键log贴出
//发起升级请求
VideoCallImpl.sendSessionModifyRequest()
logv("[sendSessionModifyRequest]current: " + originalProfile + ", requesting: " + requestProfile);
//收到native层上报的消息
ImsVTProvider.postEventFromNative()
Log.d(TAG, "postEventFromNative : msg = SESSION_EVENT_RECV_SESSION_CONFIG_RSP");
//InCallUI收到消息
InCallVideoCallCallback.onSessionModifyResponseReceived()
Log.d(this, "onSessionModifyResponseReceived status=" + status + " requestedProfile=" + requestedProfile
+ " responseProfile=" + responseProfile);
InCallUI.Call.setSessionModificationState()
Log.d(this, "setSessionModificationState " + state + " mSessionModificationState=" + mSessionModificationState);
CallCardPresenter.onSessionModificationStateChange()
Log.d(this, "onSessionModificationStateChange : sessionModificationState = " + sessionModificationState);
接收视频通话请求(receive upgrade request)
流程
当MT收到modem上报的视频请求时,ImsVTProvider.java收到来自native层的recevieSessionModifyRequest()消息,一步步将消息更新至UI,代码路径与send request一致;用户接听后,向下发送sendSessionModifyResponse()消息。同样,通过ImsVTProvider.java将消息发送至native层,从而下发到modem。流程图如下:
另外,我们在请求或接受视频通话时,都会带有一个参数,那就是VideoProfile,类位于framework/base/telecom目录下,它封装了viLTE的业务数据,包括视频通话质量、摄像 头使用情况、viLTE状态等。我们可以使用该类获取或表示viLTE的当前状态来进行相应逻辑操作。例如根据视频质量来toast信息。由于它需要在进程间通信,所以它实现了Parcelable接口。
关键log
ImsVTProvider.postEventFromNative()
Log.d(TAG, "postEventFromNative : msg = SESSION_EVENT_RECV_SESSION_CONFIG_REQ");
InCallVideoCallCallback.onSessionModifyRequestReceived()
Log.d(this, " onSessionModifyRequestReceived videoProfile=" + videoProfile);
Log.d(this, "VideoState[pre:" + previousVideoState + ",new:" + newVideoState + ",preVC:"
+ wasVideoCall + ",curVC" + isVideoCall);
InCallVideoCallCallbackNotifier.upgradeToVideoRequest()
Log.d(this, "upgradeToVideoRequest call = " + call + " new video state = " + videoState);
VideoSessionController.onUpgradeToVideoRequest()
logd("onUpgradeToVideoRequest callId = " + getId(call) + " new video state = " + videoState);
InCallUI.Call.setRequestedVideoState()
Log.d(this, "setRequestedVideoState - video state= " + videoState);
Log.d(this, "setRequestedVideoState - mSessionModificationState=" + mSessionModificationState
+ " video state= " + videoState);
CallList.onUpgradeToVideo()
Log.d(this, "onUpgradeToVideo call=" + call);
视频通话界面初始化(video call UI Init)
当对方接受upgrade request后,modem上报的call信息就会变化,消息一层层传递到InCallUI,VideoCallPresenter.onStateChange()方法会监听到这种变化,将通话界面更新 为视频通话。也就是初始化video call UI。其中主要有两部分,打开Camera以及启动
VideoCallFragment
打开摄像头(setCamera(int CameraId))
流程
通过调用Camera API看开启摄像头,
默认开启前置摄像头,CameraId为1。转换摄像头的操作也是通过setCamera()实现,只不过是参数CameraId不同。流程如下:
与send reques操作的代码路径一样,调用Camera API的逻辑实现也是在native中。上层通过ImsVTProvider下发消息。但需要注意的是,ImsVTProvider的native回调方法postEventFromNative中调用的是ImsVideoCallProvider.handleCallSessionEvent()向上反馈信息,从方法名可以看出来,这是个通用方法,底层的好多消息都是通过该方法向上传递的。如下:
关键log
VideoCallImpl.setCamera()
logv("[setCamera]cameraId = " + cameraId);
ImsVTProvider.postEventFromNative()
Log.d(TAG, "postEventFromNative : msg = MSG_PEER_CAMERA_OPEN");
VideoCallFragment初始化
流程
从打开摄像头的流程图可以看出,VideoCallPresenter向VideoCallFragment发送消息showVideoViews()从而启动VideoCallFragment,那么
VideoCallFragment是如何展现Camera捕获到的本端媒体流以及网络上报的对端媒体流呢?VideoCallFragment的初始化就进行了准备工作。
首先,VideoImpl.java提供了方法
setPreviewSurface(Surface surface)及setDisplaySurface(Surface surface)来设置用于展现本端媒体流(Preview)及对端媒体流(Display)的Surface。Surface是一个控件,用于展示媒体流,它有很多子类,在VideoCallFragment中使用的是Surface的子类之一
TextureView来展现媒体流。在VideoCallFragment中,内部类VideoCallSurface封装了TextureView及获取媒体流的逻辑,我们通过调用创建VideoCallSurface对象及调用其暴露的方法便可以获取媒体流。
VideoCallSurface类实现了TextureView.SurfaceTextureListener接口,并实现onSurfaceTextureAvailable()、onSurfaceTextureDestroyed()等接口方法。首先调用TextureView.setSurfaceTextureListener(this),这样,在SurfaceTexture准备好后,会调用到onSurfaceTextureAvailable()方法,在该方法中上报了准备好的surfaceTexture。这时我们将获取到的surfaceTexture设置给TextureView,随后,调用VideoImpl.setPreviewSurface()或setDisplaySurface()将TextureView传递到VideoImpl中。这样,TextureView便能显示图像流了。流程如下:
上图中,setPreviewSurface()的代码路径与setCamera、send requset等一样,因此在流程图中简化了VideoCallImpl向下发送setPreviewSurface的流程。另外,在步骤3inflatVideoCallViews()在,构造了两个VideoCallSurface,在构造时传入了参数surfaceId用于标记该VideoCallSurface是用于Preview还是Display。这样,在步骤10 onSurfaceCreated中,可以用该Id来决定调用setPreviewSurface还是setDisplaySurface(),code如下:
inflatVideoCallViews()代码截图
setPreviewSurface()代码截图
关键log
//VideoCallPresenter收到通话信息的变化
VideoCallPresenter.onStateChange()
Log.d(this, "onStateChange oldState" + oldState + " newState=" + newState +" isVideoMode=" + isVideoMode());
VideoCallPresenter.checkForVideoCallChange()
Log.d(this, "checkForVideoCallChange: videoCall=" + videoCall + " mVideoCall=" + mVideoCall);
VideoCallPresenter.checkForVideoStateChange()
Log.d(this, "checkForVideoStateChange: isVideoCall= " + isVideoCall + " hasVideoStateChanged=" +
hasVideoStateChanged + " isVideoMode=" + isVideoMode() + " previousVideoState: " +
VideoProfile.videoStateToString(mCurrentVideoState) + " newVideoState: "+
VideoProfile.videoStateToString(call.getVideoState()));
//当视频通话成功建立,isVideoCall = true
VideoCallPresenter.enterVideoMode()
Log.d(this, "enterVideoMode videoCall= " + videoCall + " videoState: " + newVideoState);
//videoCallFragment初始化
VideoCallFragment.inflateVideoCallViews()
Log.d(this, "inflateVideoCallViews");
Log.d(this, "inflateVideoCallViews: sVideoSurfacesInUse=" + sVideoSurfacesInUse);
Log.d(this, " inflateVideoCallViews screenSize" + screenSize);
VideoCallSurface.VideoCallSurface()
Log.d(this, "VideoCallSurface: surfaceId=" + surfaceId + " width=" + width + " height=" + height);
Log.d(this, "recreateView: SavedSurfaceTexture=" + mSavedSurfaceTexture + "
areSameSurfaces=" + areSameSurfaces);
VideoCallSurface.onSurfaceTextureAvailable()
Log.d(this, " onSurfaceTextureAvailable mSurfaceId=" + mSurfaceId + " surfaceTexture=" +
surfaceTexture + " width=" + width+ " height=" + height + " mSavedSurfaceTexture=" +
mSavedSurfaceTexture);
VideoCallPresenter.onSurfaceCreated()
Log.d(this, "onSurfaceCreated surface=" + surface + " mVideoCall=" + mVideoCall);
Log.d(this, "onSurfaceCreated PreviewSurfaceState=" + mPreviewSurfaceState);
Log.d(this, "onSurfaceCreated presenter=" + this);
VideoCallImpl.setPreviewSurface()
logv("[setPreviewSurface]preview = " + surface);
VideoCallImpl.setDisplaySurface()
logv("[setDisplaySurface]display = " + surface);