安卓11-HDMI插拔检测流程,安卓11 HDMI插拔检测流程详解

马肤

温馨提示:这篇文章已超过442天没有更新,请注意相关的内容是否还可用!

摘要:安卓11系统的HDMI插拔检测流程包括以下几个步骤:系统通过硬件接口识别HDMI设备的插入;启动相应的驱动程序以建立与HDMI设备的通信;系统通过驱动程序发送检测信号到HDMI设备,验证设备的连接状态;系统根据接收到的反馈信号判断HDMI设备是否成功连接。整个流程旨在确保安卓系统与外部显示设备之间的顺畅通信。

hdmi从插入到拔出经过底层一系列检测到应用层,应用层获取hdmi插入状态后又会做出一系列相应的动作,下面梳理了从应用层到底层一步步追踪到芯片的hpd-pin的检测过程。其大致原理就是framework层通过检测/sys/class/extcon/hdmi/state 来获取hdmi插入与否,具体更新这个状态的地方再kernel层,kernel层通过一个dw_hdmi_connector_detect轮询函数不断的查询hpd状态然后更新sys/class/extcon/hdmi/state,后面分析了hdmi插入拔出状态改变后系统所做的其它动作。

安卓11-HDMI插拔检测流程,安卓11 HDMI插拔检测流程详解 第1张
(图片来源网络,侵删)
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java +775
    private class HdmiVideoExtconUEventObserver extends ExtconStateObserver {
        private static final String HDMI_EXIST = "HDMI=1";
        private static final String NAME = "hdmi";
        private final ExtconInfo mHdmi = new ExtconInfo(NAME);  //判断/sys/class/extcon/hdmi文件是否存在
        private boolean init() {
            boolean plugged = false;
            try {
                plugged = parseStateFromFile(mHdmi); //解析sys/class/extcon/hdmi/state这个文件内容
            } catch (FileNotFoundException e) {
                Slog.w(TAG, mHdmi.getStatePath()
                        + " not found while attempting to determine initial state", e);
            } catch (IOException e) {
                Slog.e(
                        TAG,
                        "Error reading " + mHdmi.getStatePath()
                                + " while attempting to determine initial state",
                        e);
            }
            startObserving(mHdmi);  //开始监控/sys/class/extcon/hdmi
            return plugged;
        }
        @Override
        public void updateState(ExtconInfo extconInfo, String eventName, Boolean state) {//通过这里更新hdmi plug状态,这个转态来自hal层
                 //这里的变化来自kernel层dw-hdmi.c
						mDefaultDisplayPolicy.setHdmiPlugged(state);
        }
        @Override
        public Boolean parseState(ExtconInfo extconIfno, String state) {
            // extcon event state changes from kernel4.9
            // new state will be like STATE=HDMI=1
            return state.contains(HDMI_EXIST);   //判断这里是否sys/class/extcon/hdmi/state  HDMI=1 是的话返回true
        }
    }
     void initializeHdmiStateInternal() {
        boolean plugged = false;
        // watch for HDMI plug messages if the hdmi switch exists
        if (new File("/sys/devices/virtual/switch/hdmi/state").exists()) {
            mHDMIObserver.startObserving("DEVPATH=/devices/virtual/switch/hdmi");
            final String filename = "/sys/class/switch/hdmi/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) {
                    }
                }
            }
        } else if (ExtconUEventObserver.extconExists()   //走这里  判断sys/class/extcon是否存在
                && ExtconUEventObserver.namedExtconDirExists(HdmiVideoExtconUEventObserver.NAME)) {
            Log.i("fan","xtconUEventObserver.extconExists");
            HdmiVideoExtconUEventObserver observer = new HdmiVideoExtconUEventObserver();//新建一个hdmi观察者,检测hdmi hpd引脚的变化
            plugged = observer.init();
            mHDMIObserver = observer;
        } else if (localLOGV) {
            Slog.v(TAG, "Not observing HDMI plug state because HDMI was not found.");
        }
        // This dance forces the code in setHdmiPlugged to run.
        // Always do this so the sticky intent is stuck (to false) if there is no hdmi.
        mDefaultDisplayPolicy.setHdmiPlugged(plugged, true /* force */);
    }   
 frameworks/base/services/core/java/com/android/server/ExtconStateObserver.java   
    public void onUEvent(ExtconInfo extconInfo, UEvent event) {
        if (LOG) Slog.d(TAG, extconInfo.getName() + " UEVENT: " + event);
        String name = event.get("NAME");
        S state = parseState(extconInfo, event.get("STATE"));
        Slog.d("fan","onUEvent get name="+name+"state="+state);
        if (state != null) {
            updateState(extconInfo, name, state);
        }
    }
public abstract S parseState(ExtconInfo extconInfo, String state);  //在PhoneWindowManager.java里实现判断sys/class/extcon/hdmi/state  是否与HDMI=1相等
frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java    
	String ACTION_HDMI_PLUGGED = "android.intent.action.HDMI_PLUGGED";
    public void setHdmiPlugged(boolean plugged) {
        setHdmiPlugged(plugged, false /* force */);
    }
    public void setHdmiPlugged(boolean plugged, boolean force) {
        if (force || mHdmiPlugged != plugged) {
            mHdmiPlugged = plugged;
            mService.updateRotation(true /* alwaysSendConfiguration */, true /* forceRelayout */);
            final Intent intent = new Intent(ACTION_HDMI_PLUGGED);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
            intent.putExtra(EXTRA_HDMI_PLUGGED_STATE, plugged);
            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);//通知系统hdmi插入状态
        }
    }
    boolean isHdmiPlugged() {
        return mHdmiPlugged;
    }    
frameworks/base/services/core/java/com/android/server/ExtconUEventObserver.java    
     public static boolean extconExists() {
        File extconDir = new File("/sys/class/extcon"); //检查这个文件是否存在,对应上面的else if (ExtconUEventObserver.extconExists() 
        return extconDir.exists() && extconDir.isDirectory();
    }
kernel/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
static const struct drm_connector_funcs dw_hdmi_connector_funcs = {
        .fill_modes = drm_helper_probe_single_connector_modes,
        .detect = dw_hdmi_connector_detect, //通过这里检测hdmi变化
        .destroy = drm_connector_cleanup,
        .force = dw_hdmi_connector_force,
        .reset = drm_atomic_helper_connector_reset,
        .set_property = dw_hdmi_connector_set_property,
        .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
        .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
        .atomic_set_property = dw_hdmi_atomic_connector_set_property,
        .atomic_get_property = dw_hdmi_atomic_connector_get_property,
};
static enum drm_connector_status
dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
        struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
                                             connector);
        enum drm_connector_status result;
        if (!hdmi->force_logo) {
                mutex_lock(&hdmi->mutex);
                hdmi->force = DRM_FORCE_UNSPECIFIED;
                dw_hdmi_update_power(hdmi);
                dw_hdmi_update_phy_mask(hdmi);
                mutex_unlock(&hdmi->mutex);
        }
        result = hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data);
        if (result == connector_status_connected)
                extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, true);
        else
                extcon_set_state_sync(hdmi->extcon, EXTCON_DISP_HDMI, false);
.....
kernel/drivers/extcon/extcon.c
int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id, bool state)
{
        int ret, index;
        unsigned long flags;
        index = find_cable_index_by_id(edev, id);
        if (index lock, flags);
        ret = is_extcon_changed(edev, index, state);
        spin_unlock_irqrestore(&edev->lock, flags);
        if (!ret)
                return 0;
        ret = extcon_set_state(edev, id, state);
        if (ret fd;
	if (fd[0] = 0)
		pfd[0].events = POLLIN | POLLRDNORM | POLLPRI;
	while (true) {
                usleep(1000);
		int err = poll(&pfd[0], 1, 20);
		if (!err) {
			continue;
		} else if(err > 0) {
			if (!ctx->enable || !ctx->system_control)
				continue;
			ALOGD("poll revent:%02x\n", pfd[0].revents);
			memset(&cec_event, 0, sizeof(hdmi_event_t));
			if (pfd[0].revents & (POLLIN)) {
				struct cec_msg cecframe;
				ALOGD("poll receive msg\n");
				ret = ioctl(pfd[0].fd, CEC_RECEIVE, &cecframe);
				if (!ret) {
					cec_event.type = HDMI_EVENT_CEC_MESSAGE;
					cec_event.dev = &ctx->device;
					cec_event.cec.initiator = (cec_logical_address_t)(cecframe.msg[0] >> 4);
					cec_event.cec.destination = (cec_logical_address_t)(cecframe.msg[0] & 0x0f);
					cec_event.cec.length = cecframe.len - 1;
					cec_event.cec.body[0] = cecframe.msg[1];
					if (!validcecmessage(cec_event)) {
						for (ret = 0; ret event_callback)
							ctx->event_callback(&cec_event, ctx->cec_arg);
					} else {
						ALOGE("%s cec_event length > 15 ", __func__);
					}
				} else {
					ALOGE("%s hdmi cec read error", __FUNCTION__);
				}
			}
			if (pfd[0].revents & (POLLPRI)) {
				int state = -1;
				struct cec_event event;
				ALOGI("poll receive event\n");
				ret = ioctl(pfd[0].fd, CEC_DQEVENT, &event);//取得一个cec事件,然后判断事件的状态,此部分内容在内核层
				if (!ret) {
					ALOGD("event:%d\n", event.event);
					if (event.event == CEC_EVENT_PIN_HPD_LOW) {//获取底层hpdin管教状态
						ALOGI("CEC_EVENT_PIN_HPD_LOW\n");
						ctx->hotplug = false;
						cec_event.type = HDMI_EVENT_HOT_PLUG;
						cec_event.dev = &ctx->device;
						cec_event.hotplug.connected = HDMI_NOT_CONNECTED;
						cec_event.hotplug.port_id = HDMI_CEC_PORT_ID;
						if (ctx->event_callback)
							ctx->event_callback(&cec_event, ctx->cec_arg);
					} else if (event.event == CEC_EVENT_PIN_HPD_HIGH) {//高为连接
						ALOGI("CEC_EVENT_PIN_HPD_HIGH\n");
						ctx->hotplug = true;
						cec_event.type = HDMI_EVENT_HOT_PLUG;
						cec_event.dev = &ctx->device;
						cec_event.hotplug.connected = HDMI_CONNECTED;
						cec_event.hotplug.port_id = HDMI_CEC_PORT_ID;
						if (ctx->event_callback)
							ctx->event_callback(&cec_event, ctx->cec_arg);
					} else if (event.event == CEC_EVENT_STATE_CHANGE) {
						ALOGD("adapt state change,phy_addr:%x,flags:%x\n", event.state_change.phys_addr, event.flags);
						/*
						 * Before cec HAL is initialized, hdmi hpd state may be
						 * changed. So we should confirm the hpd status
						 * after cec is initialized(Kernel will report
						 * CEC_EVENT_FL_INITIAL_STATE to notify HAL that
						 * initialization is done).
						 */
						if (event.flags & CEC_EVENT_FL_INITIAL_STATE) {
							ALOGD("cec adapter init complete, get connect state\n");
							ctx->hotplug = get_hpd_state_from_node(ctx);
							ctx->cec_init = true;
							/*
							 * Framework will start la polling when box turn on,
							 * In addition, as soon as framewrok receives hdmi
							 * plug in, it will start la polling immediately.
							 * There is not need to report plug in event if hdmi
							 * is connecting when box turn on. So we should report
							 * hdmi plug out only.
							 */
							if (!ctx->hotplug)
								report_hdp_event(ctx, ctx->hotplug);
						}
						ctx->phy_addr = event.state_change.phys_addr;
					}
				} else {
					ALOGE("%s cec event get err, ret:%d\n", __func__, ret);
				}
			}
		} else {
			ALOGE("%s: cec poll failed errno: %s", __FUNCTION__,
			strerror(errno));
			continue;
		}
	}
	return NULL;
}
Kernel/drivers/media/cec/cec-api.c 
static long cec_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 case CEC_DQEVENT:
                return cec_dqevent(adap, fh, block, parg);  //hal层调用这里获取一个cec事件,既然有获取事件就有把事件放入事件队列的地方
 drivers/media/cec/cec-adap.c
                
void cec_queue_pin_hpd_event(struct cec_adapter *adap, bool is_high, ktime_t ts)
{
        struct cec_event ev = {
                .event = is_high ? CEC_EVENT_PIN_HPD_HIGH :
                                   CEC_EVENT_PIN_HPD_LOW,
        };
        struct cec_fh *fh;
        if (!adap)
                return;
        /* hdmi HPD may occur before devnode is registered */
        if (!adap->devnode.registered)
                return;
        mutex_lock(&adap->devnode.lock);
        list_for_each_entry(fh, &adap->devnode.fhs, list)
                cec_queue_event_fh(fh, &ev, ktime_to_ns(ts)); //插入一个cec事件,把这个事件放入到ece事件队列,供hal层获取,hal层获取后传到framework层
        mutex_unlock(&adap->devnode.lock);
}
hardware/rockchip/hdmicec/hdmicec_event.cpp
static void report_hdp_event(hdmi_cec_context_t* ctx, bool hpd)
{
        hdmi_event_t cec_event;
        cec_event.type = HDMI_EVENT_HOT_PLUG;
        cec_event.dev = &ctx->device;
        if (hpd)
                cec_event.hotplug.connected = HDMI_CONNECTED;
        else
                cec_event.hotplug.connected = HDMI_NOT_CONNECTED;
        cec_event.hotplug.port_id = HDMI_CEC_PORT_ID;
        if (ctx->event_callback)
                ctx->event_callback(&cec_event, ctx->cec_arg);
}
最终通过event_callback通知应用层,下面查找下callback
hardware/rockchip/hdmicec/hdmi_cec.cpp
static void hdmi_cec_register_event_callback(const struct hdmi_cec_device* dev,
                                             event_callback_t callback, void* arg)
{
        struct hdmi_cec_context_t* ctx = (struct hdmi_cec_context_t*)dev;
        ALOGI("%s", __func__);
        ctx->event_callback = callback;
        ctx->cec_arg = arg;
}	
static int hdmi_cec_device_open(const struct hw_module_t* module, const char* name,
                                struct hw_device_t** device)
{  
	....
	  dev->device.register_event_callback = hdmi_cec_register_event_callback; //在HdmiCec.cpp完成注册
	  ......
hardware/interfaces/tv/cec/1.0/default/HdmiCec.cpp 
Return HdmiCec::setCallback(const sp& callback) {  //接口在IHdmiCecCallback.hal中
    if (mCallback != nullptr) {
        mCallback->unlinkToDeath(this);
        mCallback = nullptr;
    }
    if (callback != nullptr) {
        mCallback = callback;
        mCallback->linkToDeath(this, 0 /*cookie*/);
        mDevice->register_event_callback(mDevice, eventCallback, nullptr);  //注册回调通知framework层 eventCallback看hardware/interfaces/tv/cec/1.0/default/HdmiCec.h
    }
    return Void();
}
IHdmiCec* HIDL_FETCH_IHdmiCec(const char* hal) {
    hdmi_cec_device_t* hdmi_cec_device;
    int ret = 0;
    const hw_module_t* hw_module = nullptr;
    ret = hw_get_module (HDMI_CEC_HARDWARE_MODULE_ID, &hw_module);
    if (ret == 0) {
        ret = hdmi_cec_open (hw_module, &hdmi_cec_device);
        if (ret != 0) {
            LOG(ERROR)  0,
                    .portId = static_cast(event->hotplug.port_id)
                };
                mCallback->onHotplugEvent(hotplugEvent);  //实现在HdmiCecController.java 看下面
            }
        }  
frameworks/base/services/core/java/com/android/server/hdmi/HdmiCecController.java 
import android.hardware.tv.cec.V1_0.IHdmiCecCallback;
private IHdmiCec mHdmiCec;
    final class HdmiCecCallback extends IHdmiCecCallback.Stub { //接口在hal层IHdmiCecCallback.hal
        @Override
        public void onCecMessage(CecMessage message) throws RemoteException {
            byte[] body = new byte[message.body.size()];
            for (int i = 0; i  handleIncomingCecCommand(message.initiator, message.destination, body));
        }
        @Override
        public void onHotplugEvent(HotplugEvent event) throws RemoteException {
            runOnServiceThread(() -> handleHotplug(event.portId, event.connected));
        }
    }
    private final HdmiControlService mService;
    @ServiceThreadOnly
    private void handleHotplug(int port, boolean connected) {
        assertRunOnServiceThread();
        HdmiLogger.debug("Hotplug event:[port:%d, connected:%b]", port, connected);
        addHotplugEventToHistory(port, connected);
        mService.onHotplug(port, connected);
    }
    
private void init(NativeWrapper nativeWrapper) {
        mIoHandler = new Handler(mService.getIoLooper());
        mControlHandler = new Handler(mService.getServiceLooper());
        nativeWrapper.setCallback(new HdmiCecCallback());
    }
   public void setCallback(HdmiCecCallback callback) {  //应用层调用
            try {
                mHdmiCec.setCallback(callback);
            } catch (RemoteException e) {
                HdmiLogger.error("Couldn't initialise tv.cec callback : ", e);
            }
        }
frameworks/base/services/core/java/com/android/server/hdmi/HdmiControlService.java
 void onHotplug(int portId, boolean connected) {
        assertRunOnServiceThread();
        if (connected && !isTvDevice()
                && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
            if (isSwitchDevice()) {
                initPortInfo();
                HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx.");
            }
            ArrayList localDevices = new ArrayList();
            for (int type : mLocalDevices) {
                if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
                        && isHdmiCecNeverClaimPlaybackLogicAddr) {
                    continue;
                }
                HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
                if (localDevice == null) {
                    localDevice = HdmiCecLocalDevice.create(this, type);
                    localDevice.init();
                }
                localDevices.add(localDevice);
            }
            allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
        }
        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
            device.onHotplug(portId, connected);
        }
        announceHotplugEvent(portId, connected);
    }
    private void announceHotplugEvent(int portId, boolean connected) {
        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
        synchronized (mLock) {
            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
                invokeHotplugEventListenerLocked(record.mListener, event);
            }
        }
    }
    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
            HdmiHotplugEvent event) {
        try {
            listener.onReceived(event);
        } catch (RemoteException e) {
            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
        }
    }
frameworks/base/core/java/android/hardware/hdmi/HdmiControlManager.java
    private IHdmiHotplugEventListener getHotplugEventListenerWrapper(
            final HotplugEventListener listener) {
        return new IHdmiHotplugEventListener.Stub() {
            @Override
            public void onReceived(HdmiHotplugEvent event) {
                listener.onReceived(event);;
            }
        };
    }
    private final class ClientHotplugEventListener implements HotplugEventListener {
        @Override
        public void onReceived(HdmiHotplugEvent event) {
            List ports = new ArrayList();
            try {
                ports = mService.getPortInfo();
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
            if (ports.isEmpty()) {
                Log.e(TAG, "Can't find port info, not updating connected status. "
                        + "Hotplug event:" + event);
                return;
            }
            // If the HDMI OUT port is plugged or unplugged, update the mLocalPhysicalAddress
            for (HdmiPortInfo port : ports) {
                if (port.getId() == event.getPort()) {
                    if (port.getType() == HdmiPortInfo.PORT_OUTPUT) {
                        setLocalPhysicalAddress(
                                event.isConnected()
                                ? port.getAddress()
                                : INVALID_PHYSICAL_ADDRESS);
                    }
                    break;
                }
            }
        }
    }        
        
	  
	
	
	
安卓11-HDMI插拔检测流程,安卓11 HDMI插拔检测流程详解 第2张
(图片来源网络,侵删)

0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码