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功能開發已介紹完畢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章