[출처] : http://androidxpost.blogspot.kr/2015/08/develope-hdmi-tv-input-for-using.html
Develop a HDMI TV Input using Android TV Framework - Part 1
This basic article is for System Developer for Android TV, provides background on what and how to develop so that a TV application(which is based on Android TV framework) can render video from a HDMI input(like Nexus Player or HDMI Media Player etc.) plugged in TV.
We can further divide this task in 2 sub-task -
[1]Develop TvInputService for HDMI Input
[2]Implement TvInput HAL for HDMI Input
Also this development is done in a SOC vendor side, i.e.
- First sub-task will be part of System-Apps, it uses System APIs,
- and second HAL implementation sub-task is a typical Android HAL implementation which relies on lower layer HDMI layer.
Before this following Android Developer pages should be gone through
- For Android TV Architecture
- Developing a TV input service
- A sample tv input which streams some HLS/DASH streams and local file
And obviously one must have installed a TV application(like "Live Channels" by Google) which can detect and interact to our HDMI input implementation.
Now let's start 1st part,
Develop TvInputService for HDMI Input
Android TV framework base classes/APIs can be found in following path
/platform/frameworks/base/media/java/android/media/tv/
A TV input Service should be extended from TvInputService and hence implements all the abstract methods of it and it's inner class Session, also overrides some of methods.
Develop a HDMI TV Input using Android TV Framework - Part 2
Implement TvInput HAL for HDMI Input
This part deals with Android TV Input HAL implementation for HDMI. By end of this part a dummy input HAL will be implemented which can interact to any TVInput service like one of ours in part 1.We will be able to see the interaction in logs as well as our input/session and service in "dumpsys tv_input"
If one has further lower level HDMI kernel driver implementation, it can be called here as mentioned at various placeholders and will be able to play the videos from HDMI media player connected to Android TV, using an TV application.
As like other Android HALs, tv_input hal is located in /hardware/libhardware/modules/tv_inputand default lib will be built as "tv_input.default.so",
So a basic dummy tv_input hal can be implemented like below :
typedef struct tv_input_private {
tv_input_device_t device;
// Callback related data
const tv_input_callback_ops_t* callback;
void* callback_data;
} tv_input_private_t;
static int tv_input_device_open(const struct hw_module_t* module,
const char* name, struct hw_device_t** device);
static struct hw_module_methods_t tv_input_module_methods = {
open: tv_input_device_open
};
tv_input_module_t HAL_MODULE_INFO_SYM = {
common: {
tag: HARDWARE_MODULE_TAG,
version_major: 0,
version_minor: 1,
id: TV_INPUT_HARDWARE_MODULE_ID,
name: "Sample TV input module",
author: "The Android Open Source Project",
methods: &tv_input_module_methods,
}
};
#define HDMI_DEV_ID 1
#define HDMI_PORT_ID 1
void notify_hdmi_device_available()
{
tv_input_event_t event;
event.device_info.device_id =HDMI_DEV_ID;
event.device_info.type = TV_INPUT_TYPE_HDMI;
event.type = TV_INPUT_EVENT_DEVICE_AVAILABLE;
event.device_info.audio_type = AUDIO_DEVICE_NONE;
event.device_info.hdmi.port_id = HDMI_PORT_ID;
callback->notify(dev, &event, data);
}
tv_stream_config_t* get_stream_configs()
{
tv_stream_config_t* config = (tv_stream_config_t*)malloc(sizeof(config));
config->stream_id=0;
config->type =TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE ;
config->max_video_width = 1280;
config->max_video_height = 1080;
return config;
}
static int tv_input_initialize(struct tv_input_device* dev,
const tv_input_callback_ops_t* callback, void* data)
{
if (dev == NULL || callback == NULL) {
return -EINVAL;
}
tv_input_private_t* priv = (tv_input_private_t*)dev;
if (priv->callback != NULL) {
return -EEXIST;
}
priv->callback = callback;
priv->callback_data = data;
notify_hdmi_device_available();
return 0;
}
static int tv_input_get_stream_configurations(
const struct tv_input_device*, int, int*, const tv_stream_config_t**)
{
*num_configurations = 1;
*config = get_stream_configs();
return 0;
}
static int tv_input_open_stream(struct tv_input_device*, int, tv_stream_t*)
{
return 0;
}
static int tv_input_close_stream(struct tv_input_device*, int, int)
{
return 0;
}
static int tv_input_request_capture(
struct tv_input_device*, int, int, buffer_handle_t, uint32_t)
{
return 0;
}
static int tv_input_cancel_capture(struct tv_input_device*, int, int, uint32_t)
{
return 0;
}
static int tv_input_device_close(struct hw_device_t *dev)
{
tv_input_private_t* priv = (tv_input_private_t*)dev;
if (priv) {
free(priv);
}
return 0;
}
static int tv_input_device_open(const struct hw_module_t* module,
const char* name, struct hw_device_t** device)
{
int status = -EINVAL;
if (!strcmp(name, TV_INPUT_DEFAULT_DEVICE)) {
tv_input_private_t* dev = (tv_input_private_t*)malloc(sizeof(*dev));
/* initialize our state here */
memset(dev, 0, sizeof(*dev));
/* initialize the procs */
dev->device.common.tag = HARDWARE_DEVICE_TAG;
dev->device.common.version = TV_INPUT_DEVICE_API_VERSION_0_1;
dev->device.common.module = const_cast<hw_module_t*>(module);
dev->device.common.close = tv_input_device_close;
dev->device.initialize = tv_input_initialize;
dev->device.get_stream_configurations =
tv_input_get_stream_configurations;
dev->device.open_stream = tv_input_open_stream;
dev->device.close_stream = tv_input_close_stream;
dev->device.request_capture = tv_input_request_capture;
dev->device.cancel_capture = tv_input_cancel_capture;
*device = &dev->device.common;
status = 0;
}
return status;
}
This HAL will interact to "TvInputManagerService", a SystemService which will call tv_input_initialize at the time of system bootup.
TvInputManagerService uses TvInputHardwareManager/TvInputHal, further jni binding, source code location is as below.
/frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java
/frameworks/base/services/core/jni/com_android_server_tv_TvInputHal.cpp
Now if HDMI source is selected from TV application, it will interact to TvInputService(implemented in part1) and TvInputService will be able to further talk to our above tv_input hal.
Also $adb shell dumpsys tv_input shall now list our input in "inputmap" and if session is running, it will show sessionStateMap as well.
TvInputService Implementation : Creating TvInputInfo
As we know Android Tv Input Framework, a data source or Tv Input like IP-TV, Tuner, HDMI etc. is nothing else but the implementation of TvInputService.
TvInputManagerService provides the info of Hardware/HDMI devices available in the system, to all concrete classes of TvInputService by following callbcaks,
@SystemApi
public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) {
return null;
}
@SystemApi
public TvInputInfo onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
return null;
}
While implementing TvInputService for any Hardware or HDMI-CEC, we need to override these methods carefuly, so a create correct TvInputInfo which will be returned back to TvInputManagerService for storing it.
TvInputInfo.java
private final String mId; // a string representation(example at end)
private final String mParentId; //relevant in case of HDMI-CEC
private int mType = TYPE_TUNER;
private HdmiDeviceInfo mHdmiDeviceInfo; //relevant in case of HDMI-CEC
For creating TvInputInfo we will use one of the overloaded static methods createTvInputInfo::createTvInputInfo,
1. For Built-in Tuner, createTvInputInfo(Context context, ResolveInfo service)
2. For HDMI-CEC, createTvInputInfo(Context, ResolveInfo service, HdmiDeviceInfo, String parentId, label, iconUri)
3. For other hardware like HDMI-Input, createTvInputInfo(Context, ResolveInfo service, TvInputHardwareInfo, label, iconUri)
Here is an example of method implementation,
public class MyTvInputService extends TvInputService
{
private final Context context;
private final ResolveInfo service;
public void onCreate(
{
context = getApplicationContext();
service = context.getPackageManager().
resolveService(new Intent(TvInputService.SERVICE_INTERFACE), PackageManager.GET_INTENT_FILTERS | PackageManager.GET_META_DATA);
}
public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo)
{
TvInputInfo info;
try
{
String lebel = new StringBuilder("HDMI").append(hardwareInfo.getDeviceId()).toString();
info = TvInputInfo.createTvInputInfo(context, service, hardwareInfo, lebel, iconUri);
} catch (Exception ex) { }
return info;
}
public TvInputInfo onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo)
{
TvInputInfo info;
try
{
String lebel = deviceInfo.getDisplayName();
info = TvInputInfo.createTvInputInfo(context, service, deviceInfo, parentId, lebel, iconUri);
} catch (Exception ex) { }
return info;
}
}
In case of onHdmiDeviceAdded, parentId is ID of this TV input's parent input, this is the Id of previously registered TvInput's, on same port-id as this HdmiDeviceInfo's port.
In "adb shell dumpsys tv_input" we can see all the TvInputs available in system,
For example,
Id will be "com.xyz.android.tv/.MyTvInputService/HW1" for 1st HW of MyTvInputService in package com.xyz.android.tv
HDMI Events Call Flow to TvInputService
In previous post we understood how HdmiControlManager.DEVICE_EVENT_ADD_DEVICE event is generated in Hdmi framework, now we will check further how this event is reached in TvInputFramework and finaly to a TvInputService implementation.
There are 3 categories of events notified from HdmiControlService to TvInputFramework,
1.HotplugEvent and
2.DeviceEvent
3.SystemAudioModeChange
TvInputManagerService registers for these as EventListener in TvInputHardwareManager class.
TvInputHardwareManager manages the hardwares either of TvInputHal(tv_input_device_t) type implemented in tv_input.cpp or hdmi_cec_device Type implemented in HDMI-CEC hal.
public TvInputManagerService(){
mTvInputHardwareManager = new TvInputHardwareManager(context, new HardwareListener());
}
//TvInputHardwareManager.java
public void onBootPhase(int phase) {
mHdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener);
mHdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);
mHdmiControlService.addSystemAudioModeChangeListener(
mHdmiSystemAudioModeChangeListener);
mHdmiDeviceList.addAll(mHdmiControlService.getInputDevices());
}
HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
public void onReceived(HdmiHotplugEvent event) {
mHdmiStateMap.put(event.getPort(), event.isConnected());
mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
}
HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) {
switch (status) {
case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: {
mHdmiDeviceList.add(deviceInfo);
messageType = ListenerHandler.HDMI_DEVICE_ADDED;
}
case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: {
if (!mHdmiDeviceList.remove(originalDeviceInfo))
messageType = ListenerHandler.HDMI_DEVICE_REMOVED;
}
case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: {
if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
mHdmiDeviceList.add(deviceInfo);
messageType = ListenerHandler.HDMI_DEVICE_UPDATED;
}
Message msg = mHandler.obtainMessage(messageType, 0, 0, obj);
msg.sendToTarget();
}
This handler notifies to TvInputManagerService.
ListenerHandler extends Handler {
public final void handleMessage(Message msg) {
switch (msg.what) {
case STATE_CHANGED: {
mListener.onStateChanged(inputId, state);
break;
}
case HDMI_DEVICE_ADDED: {
mListener.onHdmiDeviceAdded(info);
break;
}
case HDMI_DEVICE_REMOVED: {
mListener.onHdmiDeviceRemoved(info);
break;
}
TvInputManagerService broadcasts this event to all services currently available in system.
public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
// Broadcast the event to all hardware inputs.
serviceState.service.notifyHdmiDeviceAdded(deviceInfo);
}
TvInputService gets the event and posts to its handler which will call the onHdmiDeviceAdded.
TvInputService.java
void notifyHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HDMI_TV_INPUT,
deviceInfo).sendToTarget();
}
ServiceHandler extends Handler {
case DO_ADD_HDMI_TV_INPUT: {
HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
TvInputInfo inputInfo = onHdmiDeviceAdded(deviceInfo);
if (inputInfo != null) {
broadcastAddHdmiTvInput(deviceInfo.getId(), inputInfo);
}
}
Note here that a custom TvInputService must override to modify default behavior of ignoring all HDMI logical input device i.e. build a TvInputInfo using this HdmiDeviceInfo and return it.
public TvInputInfo onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
return null;
}
(How to override this? link TvInputService Implementation : Creating TvInputInfo )
With this TvInputInfo, TvInputService broadcasts to TvInputManagerService so that it can add/update this new TvInputInfo in TvInputList.
broadcastAddHdmiTvInput(int id, TvInputInfo inputInfo) {
mCallbacks.getBroadcastItem(i).addHdmiTvInput(id, inputInfo);
}
In next post we will use this HdmiDeviceInfo/TvInputInfo for CEC communication implementation.
TvInputService Implementation : KeyEvents to a HDMI-CEC Device
As we know Android Tv Input Framework, a data source or Tv Input like IP-TV, Tuner, HDMI etc. is nothing else but the implementation of TvInputService. So if any of these datasource/device need KeyEvents, that should be implemented in TvInputService.
A TvInputService implementation can be found in this post.
For this following TvInputService$Session must be overridden in order to intercept key down events before they are processed by the application.
public boolean onKeyDown(int keyCode, KeyEvent event) {
return false;
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
return false;
}
If true is returned, the application will not process the event itself.
If false is returned, the normal application processing will occur as if the TV input had not seen the event at all.
We can overide these methods to control a connected HDMI-CEC device.
Following code should be added to our TvInputService implementation in order to work this.
mHdmiControlService = IHdmiControlService.Stub
.asInterface(ServiceManager.getService(Context.HDMI_CONTROL_SERVICE));
mHdmiControlService.deviceSelect(currentHdmiDeviceInfo.getDeviceId(), mHdmiControlCallbackImpl);
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(interestedToHandle(keyCode))
{
mHdmiControlService.sendKeyEvent(activeLocalDeviceType, keyCode, true);
return true;
}
return false;
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
if(interestedToHandle(keyCode))
{
mHdmiControlService.sendKeyEvent(activeLocalDeviceType, keyCode, false);
return true;
}
return false;
}
So now let's see how the Android system key event will flow through HdmiControlService and mapped as CEC message, passed to hdmi_cec HAL.
Here is the call flow,
HdmiControlService.java
sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
localDevice.sendKeyEvent(keyCode, isPressed);
}
HdmiCecLocalDeviceTv.java
sendKeyEvent(int keyCode, boolean isPressed) {
action.get(0).processKeyEvent(keyCode, isPressed);
}
SendKeyAction.java
processKeyEvent(int keycode, boolean isPressed) {
if (isPressed) {
sendKeyDown(keycode);
} else {
sendKeyUp();
}
sendKeyDown(int keycode) {
byte[] cecKeycodeAndParams = HdmiCecKeycode.androidKeyToCecKey(keycode);
sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(),
mTargetAddress, cecKeycodeAndParams));
}
sendKeyUp() {
sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
mTargetAddress));
}
HdmiCecMessageBuilder builds the HdmiCecMessage/Commands. HdmiCecKeycode class contains Android Key to CEC command mapping.
HdmiCecMessage contains source and destination address, command (or opcode) and optional params.
HdmiCecFeatureAction.java
sendCommand(HdmiCecMessage cmd) {
mService.sendCecCommand(cmd);
}
HdmiControlService.java
Transmit a CEC command to CEC bus.
sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
mCecController.sendCommand(command, callback);
}
HdmiCecController.java
sendCommand(final HdmiCecMessage cecMessage,
final HdmiControlService.SendMessageCallback callback) {
byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
errorCode = nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
cecMessage.getDestination(), body);
callback.onSendCompleted(finalError);
}
/jni/com_android_server_hdmi_HdmiCecController.cpp
nativeSendCecCommand(JNIEnv* env, jclass clazz, jlong controllerPtr,
jint srcAddr, jint dstAddr, jbyteArray body) {
cec_message_t message;
return controller->sendMessage(message);
}
HdmiCecController::sendMessage(const cec_message_t& message) {
return mDevice->send_message(mDevice, &message);
}
Finally the call in hdmi_cec HAL, and if following method implemented to interface hdmi-cec driver,
the device will respond to the keys pressed on remote of hosting device.
(*send_message)() //transmits HDMI-CEC message to other HDMI device.
TvInputService Implementation : Displaying Video from Connected HDMI Device
As in the post we implemented a TvInputService for HDMI input source. In order to get the video from device connected to HDMI input, we need to override following method from TvInputService correctly.
public boolean onSetSurface(Surface surface) { }
Let's see the call flow from application and implement this method.
An application uses TvView and calls it's setSurface() method to show content from selected TvInput source.
TvView.java
surfaceCreated(SurfaceHolder holder) {
mSurface = holder.getSurface();
setSessionSurface(mSurface);
}
private void setSessionSurface(Surface surface) {
mSession.setSurface(surface);
}
TvInputManager.java
setSurface(Surface surface) {
mService.setSurface(mToken, surface, mUserId);
}
TvInputManagerService.java
setSurface(IBinder sessionToken, Surface surface, int userId) {
getSessionLocked(sessionState.hardwareSessionToken,
Process.SYSTEM_UID, resolvedUserId).setSurface(surface);
}
TvInputService.java
setSurface(Surface surface) {
onSetSurface(surface);
}
Now we this need to be overriden. For HDMI-Input, or any other Hardware device, it can be done like below.
private final TvInputManager.HardwareCallback mHardwareCallback = new TvInputManager.HardwareCallback() {
@Override
public void onReleased()
{
}
@Override
public void onStreamConfigChanged(TvStreamConfig[] configs)
{
mTvStreamConfig = configs;
}
};
mHardware = mTvInputManager.acquireTvInputHardware(mDeviceId, mHardwareCallback, mTvInputInfo);
onSetSurface(Surface surface)
{
mHardware.setSurface(mSurface, mTvStreamConfig[0]);
return true;
}
Now this call will be directed to
TvInputManager.java
setSurface(Surface surface, TvStreamConfig config) {
return mInterface.setSurface(surface, config);
}
TvInputHardwareManager.java
A TvInputHardwareImpl object holds only one active session. Therefore, if a client
attempts to call setSurface with different TvStreamConfig objects, the last call will prevail.
setSurface(Surface surface, TvStreamConfig config)
if (surface == null) {
result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
mActiveConfig = null;
} else {
result = mHal.addStream(mInfo.getDeviceId(), surface, config);
}
TvInputHal.java
addStream(int deviceId, Surface surface, TvStreamConfig streamConfig) {
if (nativeAddStream(mPtr, deviceId, streamConfig.getStreamId(), surface) == 0) {
return SUCCESS;
}
jni/com_android_server_tv_TvInputHal.cpp
JTvInputHal::addStream(int deviceId, int streamId, const sp<Surface>& surface) {
if (mDevice->get_stream_configurations(
mDevice, deviceId, &numConfigs, &configs) != 0) {
return UNKNOWN_ERROR;
}
if (mDevice->open_stream(mDevice, deviceId, &stream) != 0) {
ALOGE("Couldn't add stream");
return UNKNOWN_ERROR;
}
}
And finally in tv_input HAL implementation, here put your code for HDMI kernel driver calls to get the video stream.
tv_input_open_stream(struct tv_input_device*, int device id , tv_stream_t* stream)
{
//This is the place, the HDMI driver functions call related to opening stream should be made.
return 0;
}
Similarly if surface is null, then close_stream will be called,
tv_input_close_stream(struct tv_input_device*, int device id , int)
{
//This is the place, the HDMI driver functions call related to closing stream should be made.
return 0;
}
For a dummy tv_input HAL implementation, refer to post.
'Programming > Android' 카테고리의 다른 글
empty activity 만들기 (0) | 2017.04.27 |
---|---|
android studio에서 NDK 빌드하기 (0) | 2016.08.11 |
안드로이드 jack 빌드 에러 (0) | 2016.07.04 |
이클립스에서 안드로이드 소스코드 연결해서 보기 (0) | 2011.09.01 |
에뮬레이터와 Socket 통신 (1) | 2011.07.06 |