您的位置:首页 > 移动开发 > Android开发

Android TV Input Framework(TIF)--3 显示Tv Input内容

2016-01-25 20:03 991 查看
上一篇文章我们分析了TvInputManagerService如何构建Tv Input list,这篇文章主要分析如何切换到Tv Input,并显示其内容。App一般通过TvView来跟TvInputService建立联系,通过调用TvView的方法来选择Tv Input,显示内容。我们看一下 Android开发文档对TvView的描述:

TvView类概述:

显示TV内容。TvView类为应用程序提供了一个高级接口,以显示实现TvInputService的各种TV信号源的节目。(注意系统上可用的TV Inputs列表可以通过调用
TvInputManager.getTvInputList()
来获取。

一旦应用程序提供了特定TV通道的URL给
tune(String, Uri)
方法,它负责下层的服务绑定(和解除绑定如果当前的TvView已经绑定到一个服务)并且根据需要自动的分配或者释放资源。除了一些控制内容如何呈现的基本方法,它也提供了一个给已经连接的TvInputService分发输入事件的办法,以允许为Tv Input客制化按键动作。

Tv Input的选择和显示是通过TvView的tune方法来做的,我们看一下tune的实现:

/**
* Tunes to a given channel.
*
* @param inputId The ID of TV input which will play the given channel.
* @param channelUri The URI of a channel.
* @param params Extra parameters which might be handled with the tune event.
* @hide
*/
@SystemApi
public void tune(String inputId, Uri channelUri, Bundle params) {
...
if (mSessionCallback != null && mSessionCallback.mInputId.equals(inputId)) {
if (mSession != null) {
mSession.tune(channelUri, params);
} else {
// Session is not created yet. Replace the channel which will be set once the
// session is made.
mSessionCallback.mChannelUri = channelUri;
mSessionCallback.mTuneParams = params;
}
} else {
resetInternal();
// When createSession() is called multiple times before the callback is called,
// only the callback of the last createSession() call will be actually called back.
// The previous callbacks will be ignored. For the logic, mSessionCallback
// is newly assigned for every createSession request and compared with
// MySessionCreateCallback.this.
mSessionCallback = new MySessionCallback(inputId, channelUri, params);
if (mTvInputManager != null) {
mTvInputManager.createSession(inputId, mSessionCallback, mHandler);
}
}
}


tune方法有三个参数,我们只关注第一个参数inputId,每个Tv Input都有一个唯一的inputId和TvInputInfo实例,inputId由
TvInputInfo.getId()
获取。第一次调用tune的时候,mSessionCallback为null,所以首先执行
mSessionCallback = new MySessionCallback(inputId, channelUri, params)
创建一个SessionCallback实例,然后通过
mTvInputManager.createSession(inputId, mSessionCallback, mHandler)
来跟TvInputService之间创建一个会话,当会话创建成功以后,mSessionCallback被回调回来,createSession的具体细节我们忽略掉,最终会调用到TvInputService的createSession,

public void createSession(InputChannel channel, ITvInputSessionCallback cb,
String inputId) {
if (channel == null) {
Log.w(TAG, "Creating session without input channel");
}
if (cb == null) {
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = channel;
args.arg2 = cb;
args.arg3 = inputId;
mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
}


接着ServiceHandler.handleMessage

public final void handleMessage(Message msg) {
switch (msg.what) {
case DO_CREATE_SESSION: {
SomeArgs args = (SomeArgs) msg.obj;
InputChannel channel = (InputChannel) args.arg1;
ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
String inputId = (String) args.arg3;
args.recycle();
Session sessionImpl = onCreateSession(inputId);
...
else {
SomeArgs someArgs = SomeArgs.obtain();
someArgs.arg1 = sessionImpl;
someArgs.arg2 = stub;
someArgs.arg3 = cb;
someArgs.arg4 = null;
mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED,
someArgs).sendToTarget();
}
return;
}
case DO_NOTIFY_SESSION_CREATED: {
SomeArgs args = (SomeArgs) msg.obj;
Session sessionImpl = (Session) args.arg1;
ITvInputSession stub = (ITvInputSession) args.arg2;
ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg3;
IBinder hardwareSessionToken = (IBinder) args.arg4;
try {
cb.onSessionCreated(stub, hardwareSessionToken);
} catch (RemoteException e) {
Log.e(TAG, "error in onSessionCreated");
}
if (sessionImpl != null) {
sessionImpl.initialize(cb);
}
args.recycle();
return;
}


先调用onCreateSession(inputId)创建一个Session,然后通知Session创建成功,即调用cb.onSessionCreated,最终会调到TvView.MySessionCallback.onSessionCreated,

public void onSessionCreated(Session session) {
...
mSession = session;
if (session != null) {
synchronized (sMainTvViewLock) {
if (hasWindowFocus() && TvView.this == sMainTvView.get()) {
mSession.setMain();          ---<1>
}
}
// mSurface may not be ready yet as soon as starting an application.
// In the case, we don't send Session.setSurface(null) unnecessarily.
// setSessionSurface will be called in surfaceCreated.
if (mSurface != null) {
setSessionSurface(mSurface);      ---<2>
if (mSurfaceChanged) {
dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
}
}
createSessionOverlayView();
if (mCaptionEnabled != CAPTION_DEFAULT) {
mSession.setCaptionEnabled(mCaptionEnabled == CAPTION_ENABLED);
}
mSession.tune(mChannelUri, mTuneParams);    ---<3>
if (mHasStreamVolume) {
mSession.setStreamVolume(mStreamVolume);
}
...
}


<1> 设置当前的Session为主Session,
setMain
最终会调到
TvInputService.Session.onSetMain
,如果TV Input Service是管理HDMI CEC逻辑设备,需要实现
onSetMain
这个方法来选择对应的HDMI CEC逻辑设备作为源设备,主Session对应的Tv Input为当前活动的源设备(Active Source)。

<2> 为当前的Input Session设置Surface,Tv Input会在这个surface上渲染视频,
setSessionSurface
会最终调到
TvInputService.Session.setSurface
,接着会调到
TvInputHardwareManager.setSurface
,最后调用到
TvInputHal.addOrUpdateStream
让HAL来做相关的配置。这一步是关键,Tv Input端口的选择以及视频通道的配置都是这一部完成的。

<3> 调谐到对应的频道,主要是指模拟电视或者数字电视。

下面用一个序列图来简单的展示一个整个过程:

Created with Raphaël 2.1.0Tv appTv appTvViewTvViewTvInputManagerTvInputManagerTvInputServiceTvInputServiceTvInputHardwareManagerTvInputHardwareManagerTvInputHalTvInputHaltunecreateSessioncreateSessiononCreateSessiononCreateSessionpostSessionCreatedonSessionCreatedsetMainsetMainonSetMainsetSessionSurfacesetSurfacesetSurfaceonSetSurfacesetSurfaceaddOrUpdateStreamtuneonTune
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: