HDMI-IN调试:热插拔、切分辨率应用逻辑

发布于:2025-09-07 ⋅ 阅读:(17) ⋅ 点赞:(0)

这篇文章给介绍一下HDMI-IN的通路,实现的热插拔、切换分辨率是怎么实现的。HDMI-IN使用HDMI转MIPI的通路,将转接芯片当做类似camera的设备进行开发,但是通常的MIPI camera都是固定的分辨率,就算有分辨率切换也是主控端主动发起,逻辑比较容易实现,但是HDMI-IN的场景,由于外介入的信号源不可控制,所以需要实现分辨率切换与热拔插的逻辑,下面一起看下这部分的实现,我们按照安卓14,RK3576使用RK628F实现HDMI-IN,采用camera的应用框架。

1.驱动实现部分

目前的框架,驱动是按照上报事件的形式,通过V4l2框架上报拔插或者切换分辨率的事件通知上层,这里我们看下驱动的实现,主要有三个事件

需要订阅三个对应的事件,通过回调v4l2_subdev_core_ops的成员.subscribe_event来订阅需要的V4L2事件:

static int rk628_csi_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
				    struct v4l2_event_subscription *sub)
{
	switch (sub->type) {
	case V4L2_EVENT_SOURCE_CHANGE:
		return v4l2_src_change_event_subdev_subscribe(sd, fh, sub);
	case V4L2_EVENT_CTRL:
		return v4l2_ctrl_subdev_subscribe_event(sd, fh, sub);
	case RK_HDMIRX_V4L2_EVENT_SIGNAL_LOST:
		return v4l2_event_subscribe(fh, sub, 0, NULL);
	default:
		return -EINVAL;
	}
}

更新上报分辨率变化的事件:需要在信号分辨率发生变换之后,上报事件通知应用。

static int rk628_csi_format_change(struct v4l2_subdev *sd)
{
	struct rk628_csi *csi = to_csi(sd);
	struct v4l2_dv_timings timings;
	const struct v4l2_event rk628_csi_ev_fmt = {
		.type = V4L2_EVENT_SOURCE_CHANGE,
		.u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION,
	};
	int ret;

	ret = rk628_csi_get_detected_timings(sd, &timings);
	if (ret) {
		v4l2_dbg(1, debug, sd, "%s: get timing fail\n", __func__);
		return LOCK_FAIL;
	}
	if (!v4l2_match_dv_timings(&csi->timings, &timings, 0, false)) {
		/* automatically set timing rather than set by userspace */
		rk628_csi_s_dv_timings(sd, &timings);
		v4l2_print_dv_timings(sd->name,
				"rk628_csi_format_change: New format: ",
				&timings, false);
	}

	if (sd->devnode)
		v4l2_subdev_notify_event(sd, &rk628_csi_ev_fmt);

	return LOCK_OK;
}

更新热拔插的事件:热插拔的出了需要更新事件之外,还需要更新当前的状态,指示目前是拔出的事件还是插入的事件,这里我们使用V4L2的控制变量来设置,每次发生热拔插的中断的时候去更新这个变量值,如果这个值发生变化的话,框架会自动上报事件,并且也可以读取到对应的状态值。

static void rk628_csi_delayed_work_enable_hotplug(struct work_struct *work)
{
....
	plugin = tx_5v_power_present(sd);
	v4l2_ctrl_s_ctrl(csi->detect_tx_5v_ctrl, plugin);
....
}

更新信号丢失的事件:出了上述两个事件,驱动框架还自定义了一个signal lost的事件,这个事件用于应用来判断信号是否丢失,一般我们在信号变化的中断或者拔出的时候上报这个事件,用于apk断第一时间刷新黑屏:

static int rk628_hdmirx_general_isr(struct v4l2_subdev *sd, u32 status, bool *handled)
{
......
	const struct v4l2_event evt_signal_lost = {
		.type = RK_HDMIRX_V4L2_EVENT_SIGNAL_LOST,
	};

......
		csi->nosignal = true;
		v4l2_event_queue(sd->devnode, &evt_signal_lost);
......
}

2.HDMI服务监听线程

应用需要启动一个HDMI服务来监听驱动的事件上报,camera框架对应的代码路径为:

vendor/rockchip/hardware/interfaces/hdmi

关键的接口实现:

findMipiHdmi:查找MIPI通路对应的设备节点,这里使用了私有的ioctl来区分与其他的v4l-subdev节点的区别:RKMODULE_GET_HDMI_MODE,获取到对应的值之后,就认为找到相应的节点设备。

getMipiStatus:获取设备节点的状态,主要用与apk端获取设备的分辨率等信息

重点关注监听事件的线程处理:获取驱动不同的事件,并通过回调的形式通知apk

bool V4L2DeviceEvent::V4L2EventThread::threadLoop() {
    ALOGV("@%s", __FUNCTION__);
    struct pollfd fds[2];
    //int retry = 3;
    //fds.events = POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM | POLLRDBAND | POLLPRI;
    fds[0].fd = pipefd[0];
    fds[0].events = POLLIN;

    fds[1].fd = mVideoFd;
    fds[1].events = POLLPRI;
    struct v4l2_event ev;
    CLEAR(ev);
    if (poll(fds, 2, 5000) < 0) {
        ALOGD("%d: poll failed: %s\n", mVideoFd, strerror(errno));
	    return false;
    }
    if (fds[0].revents & POLLIN) {
        ALOGD("%d: quit message received\n", mVideoFd);
        return false;
    }
    if (fds[1].revents & POLLPRI) {
        if (ioctl(fds[1].fd, VIDIOC_DQEVENT, &ev) == 0) {
            switch (ev.type) {
            case V4L2_EVENT_SOURCE_CHANGE:
            {
                ALOGD("%d: V4L2_EVENT_SOURCE_CHANGE event\n", mVideoFd);
                struct v4l2_subdev_format aFormat;
                int ret = ioctl(mVideoFd, VIDIOC_SUBDEV_G_FMT, &aFormat);
                if (ret < 0) {
                    ALOGE("VIDIOC_SUBDEV_G_FMT failed: %s", strerror(errno));
                    return true;
                }
                ALOGD("VIDIOC_SUBDEV_G_FMT: pad: %d, which: %d, width: %d, "
                "height: %d, format: 0x%x, field: %d, color space: %d",
                aFormat.pad,
                aFormat.which,
                aFormat.format.width,
                aFormat.format.height,
                aFormat.format.code,
                aFormat.format.field,
                aFormat.format.colorspace);
                mCurformat = new V4L2DeviceEvent::FormartSize(aFormat.format.width,aFormat.format.height,1);
            }
                break;
            case V4L2_EVENT_CTRL:{
                struct v4l2_event_ctrl* ctrl =(struct v4l2_event_ctrl*) &(ev.u);
                ALOGD("%d:  V4L2_EVENT_CTRL event %d\n", mVideoFd ,ctrl->value);
            }
                break;
            default:
                ALOGD("%d: unknown event\n", mVideoFd);
                break;
            }
            if(mCallback_ != NULL)
                    mCallback_((void*)this,ev.type,&ev);
        } else {
            ALOGD("%d: VIDIOC_DQEVENT failed: %s\n",mVideoFd, strerror(errno));
        }
    }
    return true;
}

对不同的事件,按照如下方式进行处理:这里有onDisconnect、onConnect、以及onFormatChange三个回调来与apk方面进行交互。

V4L2EventCallBack Hdmi::eventCallback(void* sender,int event_type,struct v4l2_event *event){
    ALOGD("@%s,event_type:%d",__FUNCTION__,event_type);
    std::unique_lock<std::mutex> lk(mLock);
    if (event_type == V4L2_EVENT_CTRL)
    {
        struct v4l2_event_ctrl* ctrl =(struct v4l2_event_ctrl*) &(event->u);
        if (mCb != nullptr)
        {
            if (!ctrl->value)
            {
                mCb->onDisconnect(getMipiID());
            }else{
                mCb->onConnect(getMipiID());
            }
        }
        ALOGD("V4L2_EVENT_CTRL event %d\n", ctrl->value);
    }else if (event_type == V4L2_EVENT_SOURCE_CHANGE)
    {
        if (sender!=nullptr)
        {
            V4L2DeviceEvent::V4L2EventThread* eventThread = (V4L2DeviceEvent::V4L2EventThread*)sender;
            sp<V4L2DeviceEvent::FormartSize> format = eventThread->getFormat();
            if (format!=nullptr)
            {
                ALOGD("getFormatWeight:%d,getFormatHeight:%d",format->getFormatWeight(),format->getFormatHeight());
                if (mCb != nullptr)
                {
                    mCb->onFormatChange(getMipiID(),format->getFormatWeight(),format->getFormatHeight());
                    mCb->onConnect(getMipiID());
                }
            }
        }
    } else if (event_type == RK_HDMIRX_V4L2_EVENT_AUDIOINFO) {
        ALOGD("%s do RK_HDMIRX_V4L2_EVENT_AUDIOINFO", __FUNCTION__);
        mKernelAudioStatus = !!get_HdmiAudioPresent();
        AudioChange(mAppConnected, mKernelAudioStatus);
    }
    lk.unlock();
    return 0;
}

3.APK与HDMI服务交互

看下apk是如何实现的,apk需要接收到HDMI服务的回调之后,进行重新开关预览的设置

onFormatChange:事件变化,或者最新的分辨率重新开流

onConnect:设备连接的事件,可以开流

onDisconnect:设备断连的事件,apk需要关闭

apk的实现参考:packages\apps\rkCamera2\src\com\android\rockchip\camera2\RockchipCamera2.java

主要实现如下:

V4L2EventCallBack Hdmi::eventCallback(void* sender,int event_type,struct v4l2_event *event){
    ALOGD("@%s,event_type:%d",__FUNCTION__,event_type);
    std::unique_lock<std::mutex> lk(mLock);
    if (event_type == V4L2_EVENT_CTRL)
    {
        struct v4l2_event_ctrl* ctrl =(struct v4l2_event_ctrl*) &(event->u);
        if (mCb != nullptr)
        {
            if (!ctrl->value)
            {
                mCb->onDisconnect(getMipiID());
            }else{
                mCb->onConnect(getMipiID());
            }
        }
        ALOGD("V4L2_EVENT_CTRL event %d\n", ctrl->value);
    }else if (event_type == V4L2_EVENT_SOURCE_CHANGE)
    {
        if (sender!=nullptr)
        {
            V4L2DeviceEvent::V4L2EventThread* eventThread = (V4L2DeviceEvent::V4L2EventThread*)sender;
            sp<V4L2DeviceEvent::FormartSize> format = eventThread->getFormat();
            if (format!=nullptr)
            {
                ALOGD("getFormatWeight:%d,getFormatHeight:%d",format->getFormatWeight(),format->getFormatHeight());
                if (mCb != nullptr)
                {
                    mCb->onFormatChange(getMipiID(),format->getFormatWeight(),format->getFormatHeight());
                    mCb->onConnect(getMipiID());
                }
            }
        }
    } else if (event_type == RK_HDMIRX_V4L2_EVENT_AUDIOINFO) {
        ALOGD("%s do RK_HDMIRX_V4L2_EVENT_AUDIOINFO", __FUNCTION__);
        mKernelAudioStatus = !!get_HdmiAudioPresent();
        AudioChange(mAppConnected, mKernelAudioStatus);
    }
    lk.unlock();
    return 0;
}

4.cameraHAL 延伸动态注册camera

通过上述热插拔的监听,HDMI服务完全可以监听到HDMI-IN设备的拔插、切分辨的事件,在某些场景就可以延伸事件动态注册camera,在插入信号源的时候,注册cameraID ,拔出的时候,移除cameraID,这样在CTS测试的时候,就不会因为HDMI信号源没插入而导致测试到其camera设备,从而导致很多测试项目无法通过。

目前RK的代码已经包含这个功能,hardware/rockchip/camera_aidl,如下提交修改:

Author: Jian Qiu <qiujian@rock-chips.com>
Date:   Tue Sep 10 15:36:58 2024 +0800

    Camera: mipi hdmi hotplug support
    
    Type: Function
    Redmine ID: N/A
    Associated modifications: N/A
    Test: N/A
    
    Change-Id: I337eb0d70bd102b46c88b253245c5c7c06a247a4
    Signed-off-by: Jian Qiu <qiujian@rock-chips.com>


网站公告

今日签到

点亮在社区的每一天
去签到