Android TV HDMI开发

  在进行机顶盒ROM开发时,HDMI相关功能是常见的功能模块,本篇文章就简单介绍一些常见的HDMI相关需求开发。
  常见的HDMI相关功能可分为三大块:(通过HDMI线获取/设置)分辨率、(通过HDMI线获取)电视机信息、(HDMI)待机。

一、分辨率

  机顶盒通过HDMI线与TV相连时,是通过HDMI线(获取TV的EDID)来获取TV支持的分辨率情况的,所以与此相关的功能有获取/设置分辨率、初始化默认分辨率等。

1.1、获取/设置分辨率

  该功能的相关代码代码看上去和HDMI“没多大关系”,属于Android通用性功能,其实不然,该功能与HDMI息息相关。接下来就以Hi3798MV300和Amlogic905两种平台来简单介绍。
  先以Hi3798MV300为例,获取分辨率用到的接口和Android原生流程是一样的,主要来自于frameworks/base/core/java/android/os/display/DisplayManager.java,关键接口如下:

    /*所有分辨率*/
    private int[] mAllDisplayStandard = {
        DISPLAY_STANDARD_1080P_60,
        DISPLAY_STANDARD_1080P_50,
        DISPLAY_STANDARD_1080P_30,
        DISPLAY_STANDARD_1080P_25,
        DISPLAY_STANDARD_1080P_24,
        DISPLAY_STANDARD_1080I_60,
        DISPLAY_STANDARD_1080I_50,
        DISPLAY_STANDARD_720P_60,
        DISPLAY_STANDARD_720P_50,
        DISPLAY_STANDARD_576P_50,
        DISPLAY_STANDARD_480P_60,
        DISPLAY_STANDARD_PAL,
        DISPLAY_STANDARD_NTSC,
        DISPLAY_STANDARD_3840_2160P_24,
        DISPLAY_STANDARD_3840_2160P_25,
        DISPLAY_STANDARD_3840_2160P_30,
        DISPLAY_STANDARD_3840_2160P_50,
        DISPLAY_STANDARD_3840_2160P_60,
        DISPLAY_STANDARD_4096_2160P_24,
        DISPLAY_STANDARD_4096_2160P_25,
        DISPLAY_STANDARD_4096_2160P_30,
        DISPLAY_STANDARD_4096_2160P_50,
        DISPLAY_STANDARD_4096_2160P_60,
     };	
     
     /*DisplayManager构造函数,该函数中会初始化当前TV所支持分辨率数组mStandard*/
    public DisplayManager(IDisplayManager server) {
        mdisplay = server;
        //see display.c::get_hdmi_capability
        //                  hisi format value                fmt cap index
        mMapEncFmtToIndex.put(ENC_FMT_1080P_60                  ,   1 );
        mMapEncFmtToIndex.put(ENC_FMT_1080P_50                  ,   2 );
        mMapEncFmtToIndex.put(ENC_FMT_1080P_30                  ,   3 );
        mMapEncFmtToIndex.put(ENC_FMT_1080P_25                  ,   4 );
        mMapEncFmtToIndex.put(ENC_FMT_1080P_24                  ,   5 );
        mMapEncFmtToIndex.put(ENC_FMT_1080i_60                  ,   6 );
        mMapEncFmtToIndex.put(ENC_FMT_1080i_50                  ,   7 );
        mMapEncFmtToIndex.put(ENC_FMT_720P_60                   ,   8 );
        mMapEncFmtToIndex.put(ENC_FMT_720P_50                   ,   9 );
        mMapEncFmtToIndex.put(ENC_FMT_576P_50                   ,   10);
        mMapEncFmtToIndex.put(ENC_FMT_480P_60                   ,   11);
        mMapEncFmtToIndex.put(ENC_FMT_PAL                       ,   12);
        mMapEncFmtToIndex.put(ENC_FMT_NTSC                      ,   15);
        mMapEncFmtToIndex.put(ENC_FMT_3840X2160_24              ,   44);
        mMapEncFmtToIndex.put(ENC_FMT_3840X2160_25              ,   45);
        mMapEncFmtToIndex.put(ENC_FMT_3840X2160_30              ,   46);
        mMapEncFmtToIndex.put(ENC_FMT_3840X2160_50              ,   47);
        mMapEncFmtToIndex.put(ENC_FMT_3840X2160_60              ,   48);
        mMapEncFmtToIndex.put(ENC_FMT_4096X2160_24              ,   49);
        mMapEncFmtToIndex.put(ENC_FMT_4096X2160_25              ,   50);
        mMapEncFmtToIndex.put(ENC_FMT_4096X2160_30              ,   51);
        mMapEncFmtToIndex.put(ENC_FMT_4096X2160_50              ,   52);
        mMapEncFmtToIndex.put(ENC_FMT_4096X2160_60              ,   53);

        try{
            int[] dispCapability = mdisplay.getDisplayCapability();
            for(int i = 0; i < dispCapability.length; i++){
                Log.d("DisplayManager.java", "dispCapability[" + i + "]=" + dispCapability[i]);
            }
            /*过滤分辨率列表,把当前TV支持的分辨率赋值给mStandard*/
            if(dispCapability != null){
                int supportFmtCnt = 0;
                int[] supportFmt = new int[mAllDisplayStandard.length];
                for(int i = 0; i < mAllDisplayStandard.length; i++){
                    if(dispCapability[mMapEncFmtToIndex.get(covertCMCCFmtToHisi(mAllDisplayStandard[i]))] == 1){
                        supportFmt[supportFmtCnt] = mAllDisplayStandard[i];
                        supportFmtCnt++;
                        Log.d("DisplayManager.java", "supportFmt:" + mAllDisplayStandard[i]);
                    }
                    Log.d("DisplayManager.java", "supportFmtCnt:" + supportFmtCnt);
                }
                mStandard = new int[supportFmtCnt];
                System.arraycopy(supportFmt, 0, mStandard, 0, supportFmtCnt);
            }else{
                mStandard = new int[0];
            }
        }
        catch(Exception ex){
            mStandard = new int[0];
        }
    }
    
    /*获取当前TV支持的所有分辨率*/
    public int[] getAllSupportStandards() {
        return mStandard;
    }
    
    /*是否支持某一分辨率*/
    public boolean isSupportStandard(int standard) {
        boolean ret = false;
        for (int i = 0; i < mStandard.length; i++) {
            if(standard == mStandard[i]){
                ret = true;
                break;
            }
        }
        return ret;
    }
    
    /*设置某一分辨率*/
    public void setDisplayStandard(int standard) {
        int hisiFmt = -1;
        int ret = -1;
        /*设置分辨率前,要检测当前TV是否支持该分辨率*/
        if(isSupportStandard(standard)){
            try {
                hisiFmt = covertCMCCFmtToHisi(standard);
                if (hisiFmt >= ENC_FMT_1080P_60) {
                    ret = mdisplay.setFmt(hisiFmt);
                }
            } catch(Exception ex) {
                Log.e(TAG,"setDisplayStandard: " + ex);
            }
        } else {
            Log.e(TAG, "setDisplayStandard: unsupport(" + standard + ")");
        }
        Log.i(TAG, "setDisplayStandard: standard=" + standard + ", ret=" + ret);
    }
    
    /*获取当前分辨率*/
    public int getCurrentStandard() {
        int hisiFmt = -1;
        int cmccFmt = -1;
        try {
            hisiFmt = mdisplay.getFmt();
            cmccFmt = covertHisiFmtToCMCC(hisiFmt);
        } catch (RemoteException e) {
            Log.e(TAG, "getCurrentStandard: " + e);
        }
        if(isSupportStandard(cmccFmt)){
            return cmccFmt;
        } else {
            Log.e(TAG, "getCurrentStandard: CMCC unsupport(" + hisiFmt + ")");
            return -1;
        }
    }

  在Hi3798MV300上,使用以上接口,再结合一些其他的HDMI功能,就可以实现一个较为复杂的需求,比如:将机顶盒上的HDMI线连接到某一TV,判断该TV是否支持4K分辨率,如果支持,则切换到4K分辨率,否则不处理。关键代码示例如下:

    /*检测、设置4K分辨率*/
    private BroadcastReceiver mHdmiReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            /*监听HDMI插拔广播*/
            if (action.equals("android.intent.action.HDMI_PLUGGED")) {
                boolean state = intent.getBooleanExtra("state", false);
				Log.d(TAG,"HDMI status:"+state+",getHdmiSwitchSet():"+getHdmiSwitchSet());
                if (state && getHdmiSwitchSet()) {
                    checkTVResolution();
                }
            }
        }
    };
    
    /*获取当前TV所支持的所有分辨率,如果支持4K,则设置为2060P50hz*/
    private void checkTVResolution(){
        int DISPLAY_STANDARD_3840_2160P_24 = 256;
        boolean is4kResolutionSupported = false;
        int[] supportList =  mDisplayManager.getAllSupportStandards();
        Arrays.sort(supportList);
        
        for(int i=0;i<supportList.length;i++){
	        Log.d(TAG,"supportList["+i+"]:"+supportList[i]);
	        /*所支持的分辨率列表中包含大于DISPLAY_STANDARD_3840_2160P_24的数值,则代表支持2160P,即假4K分辨率*/
            if(supportList[i] >= DISPLAY_STANDARD_3840_2160P_24){
                is4kResolutionSupported = true; 
                break;
            }
        }
        
        if(is4kResolutionSupported){
            int best4kResolution = 260;
            if(supportList[supportList.length-1] >= 260)
                    best4kResolution = 260;
            mDisplayManager.setDisplayStandard(best4kResolution);
            mDisplayManager.saveParams();
        }	
    }
    
    /*检测HDMI插拔状态*/
    private static boolean getHdmiSwitchSet() {
        File switchFile = new File("/sys/devices/virtual/switch/hdmi/state");
        if (!switchFile.exists()) {
            switchFile = new File("/sys/class/switch/hdmi/state");
        }
        try {
            Scanner switchFileScanner = new Scanner(switchFile);
            int switchValue = switchFileScanner.nextInt();
            switchFileScanner.close();
            return switchValue > 0;
        } catch (Exception e) {
            return false;
        }
    }

  在Amlogic905上,用的不是原生的DisplayManager来处理分辨率,有一套别的实现方式,主要实现代码在frameworks/base/services/java/com/android/server/MboxOutputModeService.java,关键接口如下:

    /*获取当前TV支持的分辨率列表,供上层调用*/
    public String getSupportResoulutionList() {
        if (isHDMIPlugged()) {
            ArrayList<OutputMode> mOutputModeList = readSupportList();
            Slog.w(TAG, "getSupportResoulutionList error, output list is null!");
            return null;
        } 
    }
    
    /*读取当前TV支持的分辨率列表*/
    private ArrayList<OutputMode> readSupportList() {
        String str = null;
        ArrayList<OutputMode> mOutputModeList = new ArrayList<OutputMode>();
        try {
            /*从相关节点读取分辨率*/
            FileReader fr = new FileReader(HDMI_SUPPORT_LIST_SYSFS);//HDMI_SUPPORT_LIST_SYSFS = /sys/class/amhdmitx/amhdmitx0/disp_cap
            BufferedReader br = new BufferedReader(fr);
            try {
                while ((str = br.readLine()) != null) {
                    if(str != null){
                        //if(DEBUG) Slog.i(TAG, "Output: " + str);
                        boolean filter = false;
                        OutputMode output = new OutputMode();
                        if(str.contains("null edid")) {
                            Slog.w(TAG, "readSupportList error, disp_cap: " + str);
                            return null;
                        }
                        if(str.contains("*")) {
                            output.mode = new String(str.substring(0, str.length()-1));
                            output.isBestMode = true;
                        } else {
                            output.mode = new String(str);
                            output.isBestMode = false;
                        }
                        //if(DEBUG) Slog.i(TAG, "readSupportList, Output: " + output.mode + ", isBestMode: " + output.isBestMode);
                        if(isOutputFilter(output.mode)) {
                            Slog.w(TAG, "readSupportList, filter this mode: " + output.mode);
                        } else {
                            mOutputModeList.add(output);
                        }
                    }
                };
                fr.close();
                br.close();
                return resolutionSort(mOutputModeList);
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    /*获取当前分辨率*/
    public String getCurrentOutPutMode() {
        String curMode = readSysfs(OutputModeFile);//OutputModeFile = "/sys/class/display/mode"
		Slog.e(TAG,"getCurrentOutPutMode:" + curMode);
		return curMode;		
    }

  从上面的代码可以大致看出Amlogic905在分辨率方面的代码特征,即把相关信息存储在不同的节点中。

1.2、初始化默认分辨率

  机顶盒设置的初始分辨率策略,一般有两种:最优分辨率与最大分辨率。

1.2.1、最优分辨率

  此处的最优并不是TV支持的最大分辨率,而是ROM评估出来的、适合的一个分辨率,一般为720P或1080P,该策略一般也是ROM中的默认设置。
  先以Hi3798MV300为例,该策略的主要实现代码在device/hisilicon/bigfish/frameworks/hidisplaymanager/hal/hi_adp_hdmi.c,以Hdmicap_NativeFormat_Strategy方法为例,关键代码如下:

            else if (strcmp("p50hz", perfer) == 0)
            {
                if (HI_TRUE == is_format_support(&stSinkCap,HI_UNF_ENC_FMT_4096X2160_50))
                {
                    stSinkCap.enNativeFormat = HI_UNF_ENC_FMT_4096X2160_50;
                }
                else if (HI_TRUE == is_format_support(&stSinkCap,HI_UNF_ENC_FMT_4096X2160_25))
                {
                    stSinkCap.enNativeFormat = HI_UNF_ENC_FMT_4096X2160_25;
                }
                //since tv capability is upgrade now , add max fmt lever to 3840X2160 P50
                else if (HI_TRUE == is_format_support(&stSinkCap,HI_UNF_ENC_FMT_3840X2160_50))
                {
                    stSinkCap.enNativeFormat = HI_UNF_ENC_FMT_3840X2160_50;
                }
                else if (HI_TRUE == is_format_support(&stSinkCap,HI_UNF_ENC_FMT_3840X2160_25))
                {
                    stSinkCap.enNativeFormat = HI_UNF_ENC_FMT_3840X2160_25;
                }
                else if (HI_TRUE == is_format_support(&stSinkCap,HI_UNF_ENC_FMT_1080P_50))
                {
                    stSinkCap.enNativeFormat = HI_UNF_ENC_FMT_1080P_50;
                }
                else if (HI_TRUE == is_format_support(&stSinkCap,HI_UNF_ENC_FMT_720P_50))
                {
                    stSinkCap.enNativeFormat = HI_UNF_ENC_FMT_720P_50;
                }
                else //if(stSinkCap.enNativeFormat = HI_UNF_ENC_FMT_576P_50)
                {
                    ALOGI("Lowest default to 576p");
                    stSinkCap.enNativeFormat = HI_UNF_ENC_FMT_576P_50;
                }
            }

  该段代码中,is_format_support函数是芯片的最优分辨率策略实现函数,一般当persist.sys.optimalfmt.perfer设置为"p50hz"时,默认分辨率是720P50hz。
  Amlogic905的最优分辨率的实现方式也较为底层,framework层代码(frameworks/base/services/java/com/android/server/MboxOutputModeService.java)是根据节点的值来实现的,具体如下:

    /*获取最适合分辨率*/
    public String getBestMatchResolution() {
        /*获取支持的分辨率列表*/
        ArrayList<OutputMode> mOutputModeList = readSupportList();
        if (mOutputModeList != null && isHDMIPlugged()){
                int size = mOutputModeList.size();
                if(DEBUG) Slog.i(TAG, "getBestMatchResolution, output size: " + size);
                for (int index = 0; index < size; index++) {
                    OutputMode output = mOutputModeList.get(index);
                    if (DEBUG) Slog.i(TAG,"getBestMatchResolution, output: " + output.mode + " isBestMode: " + output.isBestMode);
                    if (output.isBestMode) {
                        Slog.i(TAG, "getBestMatchResolution, return best mode: " + output.mode);
                        return output.mode;
                    }
                }
        }else if(!isHDMIPlugged()){
            /*插AV线时返回固定分辨率*/
            return "576cvbs";
        }
        /*以上读取均出问题时,采用默认分辨率*/
        String default_mode = getPropertyString("ro.platform.best_outputmode", DEFAULT_OUTPUT_MODE);
        Slog.w(TAG, "getBestMatchResolution, return defalut outputmode: " + default_mode);
        return default_mode;
    }

  Amlogic905方案中,当前TV所支持的分辨率列表是写在/sys/class/amhdmitx/amhdmitx0/disp_cap节点的,该节点值示例如下:

480i60hz
480p60hz
576i50hz
576p50hz
720p60hz
1080i60hz
1080p60hz*
720p50hz
1080i50hz
1080p50hz
1080p24hz

  从上述内容可以看出所有支持的分辨率。其中后面加"*"符号的分辨率(该例子中的1080p60hz)就是系统默认的最适合分辨率,对应到代码中就是该分辨率对应的output.isBestMode为true。

1.2.2、最大分辨率

  除了系统的默认最优分辨率策略,现在也常常使用最大分辨率策略,即自适应到当前TV支持的最大分辨率,该功能主要针对的是4K电视,在连接TV时,可以自适应到4K分辨率。
  先以Hi3798MV300为例,自适应到最大分辨率,设置两个属性即可:

persist.sys.optimalfmt.enable=1
persist.sys.optimalfmt.perfer=max_fmt

  persist.sys.optimalfmt.enable属性代表的意思是分辨率自适应开关,当该属性设置为1时,代表自适应分辨率功能打开;设置为0时,代表关闭。persist.sys.optimalfmt.perfer属性设置为max_fmt,即代表自适应到最高分辨率。该功能涉及的代码为device/hisilicon/bigfish/frameworks/hidisplaymanager/hal/hi_adp_hdmi.c,相关代码如下:

    /*persist.sys.optimalfmt.perfer属性值为max_fmt时,获取最大分辨率*/
    else if (0 == strcmp("max_fmt", perfer))
    {
       stSinkCap.enNativeFormat = getCurrentMaxSupportFmt();
    }

    /*获取最大分辨率*/
    HI_S32 getCurrentMaxSupportFmt()
    {
         HI_S32 listCount = 0;
         HI_S32 MaxFmtListLen = 0;
         HI_BOOL bSupport = HI_FALSE;

         MaxFmtListLen = sizeof(HDMI_TV_MAX_SUPPORT_FMT) / sizeof(HDMI_TV_MAX_SUPPORT_FMT[0]);
         ALOGI("MaxFmtListLen = %d", MaxFmtListLen);

        for (listCount = 0; listCount < MaxFmtListLen; listCount++)
        {
            if (HI_TRUE == is_format_support(&stSinkCap,HDMI_TV_MAX_SUPPORT_FMT[listCount]))
            {
                 ALOGI("max support fmt is:%d",HDMI_TV_MAX_SUPPORT_FMT[listCount]);
                 return HDMI_TV_MAX_SUPPORT_FMT[listCount];
            }
       }

       ALOGI("Can't Find Max Support Format, getCurrentMaxFormat return:720P_50 !");
       return HI_UNF_ENC_FMT_720P_50;
    }

  在Hi3798MV300上,除了直接设置上述两个属性外,还可以在上层应用实现自适应4K功能,相关代码参考1.1章节。
  在Amlogic905上,可以在frameworks/base/services/java/com/android/server/MboxOutputModeService.java中的getBestMatchResolution中直接遍历所有分辨率,返回最大分辨率即可。

二、电视机信息

  除了第一节提到的分辨率相关功能,还可以获取到TV相关信息,如型号、品牌等。
  以Hi3798MV300为例,对于TV信息的获取是在底层做的,对于的代码为device/hisilicon/bigfish/frameworks/hidisplaymanager/hal/hi_adp_hdmi.c,关键代码如下:

void setTVproperty(display_format_e format)
{
    int w = 0;
    int h = 0;
    int newhdrsuport = 2;
    int oldhdrsport = 2;
    char hei[5] ={0};
    char dpi[15] = {0};
    char size[10] = {0};
    char buffer[BUFLEN] = {0};

    framebuffer_get_max_screen_resolution(format,&w,&h);
    ALOGI("format %d , w: %d  ,h: %d,",format, w, h);
    sprintf(dpi, "%d", w);
    strcat(dpi,"*");
    sprintf(hei, "%d", h);
    strcat(dpi,hei);
    ALOGI("dpi: %s", dpi);

    sprintf(size,"%d",(int)(sqrt(TVHMax*TVHMax +TVWMax*TVWMax)/2.54 +0.5));
    ALOGE("TVWidth:%d,Height:%d,TVSzie:%s", TVWMax, TVHMax, size);

    property_set("persist.sys.tv.name",stSinkCap.stMfrsInfo.u8MfrsName);  //代表电视机的品牌
    property_set("persist.sys.tv.type",stSinkCap.stMfrsInfo.u8pSinkName); //代表电视机的具体型号
    property_set("persist.sys.tv.size",size);  //代表电视机的尺寸,即电视机对角线长度                         
    property_set("persist.sys.tv.dpi",dpi);    //代表电视机DPI,即每英寸像素点数

    memset(buffer, 0, sizeof(buffer));
    property_get("persist.sys.tv.Supporthdr", buffer , "2");

    oldhdrsport = atoi(buffer);
    if(HI_TRUE == stSinkCap.bHdrSupport && stSinkCap.stHdr.stEotf.bEotfSmpteSt2084)
        newhdrsuport = 1;//yes 1
    else if (HI_FALSE == stSinkCap.bHdrSupport)
        newhdrsuport = 2;//no 2
    else
        newhdrsuport = 0;//other 0

    //HSCP2018042722609
    if(newhdrsuport != oldhdrsport)
    {
        sprintf(buffer, "%d", newhdrsuport);
        property_set("persist.sys.tv.Supporthdr",buffer);
    }

}

  在Amlogic905上,电视机相关的信息是写在节点sys/class/amhdmitx/amhdmitx0/edid里的,示例内容如下:

Rx Brand Name: PHL
Rx Product Name: PHILIPS
Physcial size(cm): 52 x 29
Manufacture Week: 1
Manufacture Year: 2015
EDID Verison: 1.3
EDID block number: 0x1
blk0 chksum: 0x2c
Source Physical Address[a.b.c.d]: 1.0.0.0
native Mode f1, VIC (native 16):
ColorDeepSupport 2
31 16 20 5 19 4 2 3 32 22 18 6 7 1
Audio {format, channel, freq, cce}
{1, 1, 7, 7}
{10, 7, 6, 0}
Speaker Allocation: 1
Vendor: 0xc03
MaxTMDSClock1 290 MHz
SCDC: 0
RR_Cap: 0
LTE_340M_Scramble: 0
checkvalue: 0x2cc80000

  Rx Brand Name代表的是电视机品牌;Rx Product Name代表的是电视机型号;Physcial size(cm)代表的是电视机长宽。

三、待机

  此处主要的指的是"HDMI待机"功能,即电视机关机,机顶盒也跟着待机。
  在Hi3798MV300上,一般设置两个属性即可:

persist.hdmi.suspend.enable = 1
persist.hdmi.suspend.time = 5

  persist.hdmi.suspend.enable属性值代表HDMI待机开关,0代表关闭,1代表打开。persist.hdmi.suspend.time属性值代表待机时间,即电视机关机后多久,机顶盒进入待机,单位为分钟。该功能的实现也在device/hisilicon/bigfish/frameworks/hidisplaymanager/hal/hi_adp_hdmi.c,待机代码如下:

void HDMI_Suspend_Timeout()
{
    ALOGI("HDMI_Suspend_Timeout: hdmi connect status flag hdmi_enable =%d",hdmi_enable);
    char  buffer[BUFLEN] = {0};

    HI_UNF_HDMI_STATUS_S  hdmiStatus;
    //icsl init
    hdmiStatus.bConnected = HI_FALSE;
    HI_U8 is_under_cec_suspend = get_HDMI_CEC_Suspend_Status();
    HI_UNF_HDMI_GetStatus(0,&hdmiStatus);
    ALOGI("hdmiStatus.bConnected =%d",hdmiStatus.bConnected);

    //hdmi uplug  or hdmi rsen disconnect event suspend time out, send a power key to system

        //end by lizheng 20190326 to solve 32a suspend
        HDMI_Suspend_ReportKeyEvent(KEY_POWER, KPC_EV_KEY_PRESS);
        HDMI_Suspend_ReportKeyEvent(KEY_POWER, KPC_EV_KEY_RELEASE);

        ALOGW("\033[31mHDMI_Suspend_Timeout: send power key to suspend \33[0m\n");
        property_get("ro.product.target", buffer, "0");
        if(strcmp(buffer,"shcmcc")==0)
        {
            sleep(4);//unit : second
            HDMI_Suspend_ReportKeyEvent(KEY_RIGHT, KPC_EV_KEY_PRESS);
            HDMI_Suspend_ReportKeyEvent(KEY_RIGHT, KPC_EV_KEY_RELEASE);
            sleep(2);
            HDMI_Suspend_ReportKeyEvent(KEY_ENTER, KPC_EV_KEY_PRESS);
            HDMI_Suspend_ReportKeyEvent(KEY_ENTER, KPC_EV_KEY_RELEASE);
        }

}

  从上面代码可以看出,进行机顶盒待机功能是通过模拟电源键按键操作来实现的。
  在Amlogic905上,HDMI待机功能是在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java中实现的。简单流程为,在init函数中,初始化HDMI相关的信息,代码如下:

    void initializeHdmiState() {
       boolean plugged = false;
       // watch for HDMI plug messages if the hdmi switch exists
       if (new File("/sys/class/switch/hdmi_hpd/state").exists()) {
           if (SystemProperties.getBoolean("ro.platform.has.mbxuimode", false)){
               SystemProperties.set("sys.boot.logo", "android");
               if(SystemProperties.getBoolean("ro.hw.cvbs.onboard", true) && !SystemProperties.getBoolean("ro.hdmiplugdetect.dis", false)){
                   mMboxOutputModeManager.initOutputMode();
                   mHDMIObserver.startObserving(HDMI_TX_PLUG_UEVENT);
               }
           } else {
               mHDMIObserver.startObserving(HDMI_TX_PLUG_UEVENT);
           }


           final String filename = "/sys/class/switch/hdmi_hpd/state";
           FileReader reader = null;
           try {
               reader = new FileReader(filename);
               char[] buf = new char[15];
               int n = reader.read(buf);
               if (n > 1) {
                   plugged = 0 != Integer.parseInt(new String(buf, 0, n-1));
               }
           } catch (IOException ex) {
               Slog.w(TAG, "Couldn't read hdmi state from " + filename + ": " + ex);
           } catch (NumberFormatException ex) {
               Slog.w(TAG, "Couldn't read hdmi state from " + filename + ": " + ex);
           } finally {
               if (reader != null) {
                   try {
                       reader.close();
                   } catch (IOException ex) {
                   }
               }
           }
       }
       
       mHdmiHwPlugged =  plugged;
       if (!SystemProperties.getBoolean("ro.vout.dualdisplay", false)) {
           if (getCurDisplayMode().equals("panel") || !plugged || SystemProperties.getBoolean("ro.platform.has.mbxuimode", false)) {
               plugged = false;
           }
       }
       
       if (SystemProperties.getBoolean("ro.vout.dualdisplay", false)) {
           setDualDisplay(plugged);
       }
       
       if (SystemProperties.getBoolean("ro.vout.dualdisplay2", false)) {
           plugged = false;
           setDualDisplay(plugged);
       }        
   	 
       Intent it = new Intent(WindowManagerPolicy.ACTION_HDMI_PLUGGED);
       it.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
       it.putExtra(WindowManagerPolicy.EXTRA_HDMI_PLUGGED_STATE, plugged);
       mContext.sendStickyBroadcastAsUser(it, UserHandle.OWNER);
   }

  此函数中有mHDMIObserver.startObserving(HDMI_TX_PLUG_UEVENT),开始监听了HDMI的状态, UEventObserver代码如下:

    private UEventObserver mHDMIObserver = new UEventObserver() {
       @Override
       public void onUEvent(UEventObserver.UEvent event) {
           Log.d(TAG , "mHDMIObserver");
           setHdmiHwPlugged("1".equals(event.get("SWITCH_STATE")));
       }
   };

  setHdmiHwPlugged中有段关键代码为:

        if(SystemProperties.getBoolean("persist.sys.autosuspend.hdmi", false)) {  
            if (plugged && !isTvSuspend) {
                 disableAutoSuspend();
            } else {
                enableAutoSuspend();
            }
         }

  persist.sys.autosuspend.hdmi属性为HDMI待机开关,然后当判断HDMI线拔出时,就调用enableAutoSuspend进入待机流程,代码如下:

  public void enableAutoSuspend() {
      disableAutoSuspend();
      int def_timeout = 2 * 60 * 1000; //default 2min
      
      if (timeout > 0) {
          Slog.d(TAG, "enable auto suspend");
          mAutoSuspendTimer = new Timer();
          TimerTask task = new TimerTask(){
              public void run() {
                  Slog.d(TAG, "goto auto suspend");
                  String proj_type = SystemProperties.get("sys.proj.type", "ott");
                  String tender_type = SystemProperties.get("sys.proj.tender.type", null);
                  Log.i(TAG, "Auto suspend sys.proj.type: " + proj_type + ", sys.proj.tender.type: " + tender_type);
                  if ("telecom".equals(proj_type) && "jicai".equals(tender_type)) {
                      sendKeyEvent(KeyEvent.KEYCODE_HOME);
                  }
                  String rx_sense = mSystemWriteManager.readSysfs("/sys/class/switch/hdmi_rxsense/state");
                  if("0".equals(rx_sense))
                      mPowerManager.goToSleep(SystemClock.uptimeMillis());
              }
          };

          mAutoSuspendTimer.schedule(task, timeout);
          isTvSuspend = false;
      }
  }

  至此,常用HDMI功能开发已介绍完毕。

猜你喜欢

转载自blog.csdn.net/m0_37741420/article/details/106423969