切换刷新率调研

调研资料

https://www.youtube.com/watch?v=YSDBKqoL0O8

这个视频中的设备,是能够直接在系统中切换外接显示器的刷新率的,后续我的小米如果能 Root 的话,我也想研究下 AOSP 官方镜像(就是刷 ROM)支不支持切换
也就是系统层,其实是有入口可以切换刷新率的,这个入口指 Android 比较底层的 API

而国内厂商嘛,任何 Google 本身系统有的东西,当然是全砍了,不然怎么能让你知道用的他们的手机

部分调研源码

https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/display/DisplayManagerShellCommand.java

https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/services/core/java/com/android/server/display/DisplayManagerService.java

https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/display/LocalDisplayAdapter.java

https://android.googlesource.com/platform/frameworks/base.git/+/master/core/java/android/view/SurfaceControl.java

前言

最后调研时间为2024.10.01

中途每每发现一些名称上看着可行的函数,便又会开始一波又一波大安卓源代码的阅读

结局都已失败告终

安卓 15 原生可支持切换副屏刷新率和大小,好像也能移动鼠标

https://www.androidauthority.com/android-15-external-display-settings-3474503/

目前我已知的是红魔和努比亚的设备可以切换刷新率和分辨率,这俩其实是一家的,技术应该是公用的

据朋友下载 ROM 加一系列的反编译,大概推断出是对内核做了支持

为什么要做这个调研? 如果成功,意义又是什么?

国内现在支持 USB3 的设备已经开始变多了,基本有 USB3 就会有 DP 输出视频的功能

虽然硬件支持了,但各个厂商,包括小米,对这个功能的软件适配就是一坨屎

鼠标不能再多个显示器间移动,不能设置外接显示器的刷新率,更甚者,像小米,把一个 Activity 在外接显示器上启动的时候(am start -n com.xxx.xxx/.xxxActivity --display x),

还不能占满全屏,真的是狗屎,换其他的支持 DP 输出的设备都没有这个问题

所以如果能有基于 SHELL 的方式,更改外接显示器的刷新率,外接显示器可用性也会大大提高

60hz -> 120hz,这个提升是非常明显的,并且像小米14,无线串流跑满 1080p 120hz 还是没问题的

其中最显著的例子是手机直连 AR 眼镜,很多 AR 眼镜都是支持 120hz 的,但是接上手机都只有 60hz

基本每一家 AR 眼镜都有对应的 App,他们可以通过硬件协议,快速的调整 AR 眼镜的刷新率,分辨率,例如 2D 切换到 3D 等,但这些 API 他们不愿意开放给开发者,目前 ROKID 是唯一一个愿意开放的,但是他们的 API 也不支持切换刷新率

更狗屎的是,类似于 XREAL,不愿意给你提供接口就算了,对开发者还及其的不尊重,后面我必贴完整聊天记录

几种方式总结

1.App 普通权限模式下设置

第一种,就是不借助任何的 SHELL 权限,直接在应用内通过 Android 给的方式进行切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

void changeDisplay() {
WindowManager windowManager = getWindowManager();
Display display = windowManager.getDefaultDisplay();
float[] supportedRefreshRates = display.getSupportedRefreshRates();
for (float rate : supportedRefreshRates) {
Log.d(TAG, "Supported refresh rate: " + rate + "Hz");
}
float desiredRefreshRate = 120.00001f;
boolean isSupported = false;
for (float rate : supportedRefreshRates) {
if (rate == desiredRefreshRate) {
isSupported = true;
break;
}
}
if (isSupported) {
WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
layoutParams.preferredRefreshRate = desiredRefreshRate;
getWindow().setAttributes(layoutParams);
Log.d(TAG, "Refresh rate set to: " + desiredRefreshRate + "Hz");
} else {
Log.d(TAG, "Desired refresh rate is not supported.");
}

}

包括设置 layoutParams.preferredDisplayModeId ,都没有效果

2.SHELL 下反射 DisplayManager/DisplayManagerGlobal

这两个类是可以直接反射出来的,并且一些调用链路是 DisplayManager -> DisplayManagerGlobal

主要函数
下面都是反射能看到的函数

DisplayManagerGlobal.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
android.view.DisplayInfo getDisplayInfoLocked(int arg0)
float getBrightness(int arg0)
int[] getDisplayIds()
int[] getDisplayIds(boolean arg0)
android.view.DisplayInfo getDisplayInfo(int arg0)
android.view.Display getRealDisplay(int arg0)
int getRefreshRateSwitchingType()
android.view.Display$Mode getSystemPreferredDisplayMode(int arg0)
android.view.Display$Mode getUserPreferredDisplayMode(int arg0)
boolean shouldAlwaysRespectAppRequestedMode()
void setBrightness(int arg0,float arg1)
void setRefreshRateSwitchingType(int arg0)
void setShouldAlwaysRespectAppRequestedMode(boolean arg0)
void setUserPreferredDisplayMode(int arg0,android.view.Display$Mode arg1)
void setVirtualDisplaySurface(interface android.hardware.display.IVirtualDisplayCallback arg0,android.view.Surface arg1)

DisplayManager.java

1
2
3
4
5
6
7
8
9
10
android.view.Display getOrCreateDisplay(int arg0,boolean arg1)
float getBrightness(int arg0)
android.view.Display getDisplay(int arg0)
android.view.Display[] getDisplays()
android.view.Display[] getDisplays(String arg0)
android.view.Display$Mode getGlobalUserPreferredDisplayMode()
int getMatchContentFrameRateUserPreference()
void setGlobalUserPreferredDisplayMode(android.view.Display$Mode arg0)
void setShouldAlwaysRespectAppRequestedMode(boolean arg0)
void setRefreshRateSwitchingType(int arg0)

一些调用链路

1
2
3
4
5
6
DisplayManager.createVirtualDisplay -> DisplayManagerGlobal.createVirtualDisplay
DisplayManager.setBrightness -> DisplayManagerGlobal.setBrightness
DisplayManager.getBrightness -> DisplayManagerGlobal.getBrightness
DisplayManager.setGlobalUserPreferredDisplayMode -> DisplayManagerGlobal.setUserPreferredDisplayMode
DisplayManager.setRefreshRateSwitchingType -> DisplayManagerGlobal.setRefreshRateSwitchingType
DisplayManager.setShouldAlwaysRespectAppRequestedMode -> DisplayManagerGlobal.setShouldAlwaysRespectAppRequestedMode

然后我把跟切换刷新率有用的调了个遍,没有效果~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public static Object invokeMethod(Object object, String methodName, Object... args) {
try {
Class[] classes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Integer) {
classes[i] = int.class;
} else if (args[i] instanceof Boolean) {
classes[i] = boolean.class;
} else {
classes[i] = args[i].getClass();
}
}
Method method = object.getClass().getDeclaredMethod(methodName, classes);
method.setAccessible(true);
return method.invoke(object, args);
} catch (Exception e) {
e.printStackTrace();

}
return null;
}
/** @noinspection DataFlowIssue*/
@SuppressLint("PrivateApi")
void tryChangeDisplayConfig() {
L.d("bindServer invoke");
Class<?> clazz = null;
try {
clazz = Class.forName("android.hardware.display.DisplayManagerGlobal");
@SuppressLint("DiscouragedPrivateApi") java.lang.reflect.Method getInstanceMethod = clazz.getDeclaredMethod("getInstance");
Object dmg = getInstanceMethod.invoke(null);
ReflectUtil.invokeMethod(dmg, "setRefreshRateSwitchingType", 2);
//getRefreshRateSwitchingType
int type = (int) ReflectUtil.invokeMethod(dmg, "getRefreshRateSwitchingType");
L.d("RefreshRateSwitchingType -> " + type);
//noinspection JavaReflectionMemberAccess
DisplayManager displayManager = DisplayManager.class.getDeclaredConstructor(Context.class).newInstance(FakeContext.get());
int matchContentFrameRateUserPreference = (int) ReflectUtil.invokeMethod(displayManager, "getMatchContentFrameRateUserPreference");
L.d("matchContentFrameRateUserPreference -> " + matchContentFrameRateUserPreference);
// getSystemPreferredDisplayMode
Object systemMode = ReflectUtil.invokeMethod(dmg, "getSystemPreferredDisplayMode", 0);
L.d("SystemPreferredDisplayMode -> " + systemMode);
// same with adb shell cmd display get-user-preferred-display-mode 0
Object userMode = ReflectUtil.invokeMethod(dmg, "getUserPreferredDisplayMode", 0);
L.d("UserPreferredDisplayMode -> " + userMode);
// getGlobalUserPreferredDisplayMode
Object globalUserMode = ReflectUtil.invokeMethod(displayManager, "getGlobalUserPreferredDisplayMode");
L.d("GlobalUserPreferredDisplayMode -> " + globalUserMode);
ReflectUtil.invokeMethod(dmg, "setShouldAlwaysRespectAppRequestedMode", true);
boolean should = (boolean) ReflectUtil.invokeMethod(dmg, "shouldAlwaysRespectAppRequestedMode");
L.d("shouldAlwaysRespectAppRequestedMode -> " + should);
for (Display display : displayManager.getDisplays()) {
if (display.getDisplayId() != 0) {
L.d("display -> " + display);
L.d("");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
Display.Mode[] supportedModes = display.getSupportedModes();
for (Display.Mode mode : supportedModes) {
L.d("mode -> " + mode);
L.d("");
if (mode.getRefreshRate() > 60) {
L.d("set mode -> " + mode);
ReflectUtil.invokeMethod(dmg, "setUserPreferredDisplayMode", display.getDisplayId(), mode);
ReflectUtil.invokeMethod(displayManager, "setGlobalUserPreferredDisplayMode", mode);
}
}
}
}
}

} catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException |
IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
}

3.SHELL 下反射 DisplayManagerService

安卓上有一系列的 CMD,例如执行 adb shell cmd display 会得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
➜  ~ adb shell cmd display
Display manager commands:
help
Print this help text.

set-brightness BRIGHTNESS
Sets the current brightness to BRIGHTNESS (a number between 0 and 1).
reset-brightness-configuration
Reset the brightness to its default configuration.
ab-logging-enable
Enable auto-brightness logging.
ab-logging-disable
Disable auto-brightness logging.
dwb-logging-enable
Enable display white-balance logging.
dwb-logging-disable
Disable display white-balance logging.
dmd-logging-enable
Enable display mode director logging.
dmd-logging-disable
Disable display mode director logging.
dwb-set-cct CCT
Sets the ambient color temperature override to CCT (use -1 to disable).
set-user-preferred-display-mode WIDTH HEIGHT REFRESH-RATE DISPLAY_ID (optional)
Sets the user preferred display mode which has fields WIDTH, HEIGHT and REFRESH-RATE. If DISPLAY_ID is passed, the mode change is applied to displaywith id = DISPLAY_ID, else mode change is applied globally.
clear-user-preferred-display-mode DISPLAY_ID (optional)
Clears the user preferred display mode. If DISPLAY_ID is passed, the mode is cleared for display with id = DISPLAY_ID, else mode is cleared globally.
get-user-preferred-display-mode DISPLAY_ID (optional)
Returns the user preferred display mode or null if no mode is set by user.If DISPLAY_ID is passed, the mode for display with id = DISPLAY_ID is returned, else global display mode is returned.
get-active-display-mode-at-start DISPLAY_ID
Returns the display mode which was found at boot time of display with id = DISPLAY_ID
set-match-content-frame-rate-pref PREFERENCE
Sets the match content frame rate preference as PREFERENCE
adb shell cmd display get-match-content-frame-rate-pref
get-match-content-frame-rate-pref
Returns the match content frame rate preference
set-user-disabled-hdr-types TYPES...
Sets the user disabled HDR types as TYPES
get-user-disabled-hdr-types
Returns the user disabled HDR types
get-displays [CATEGORY]
Returns the current displays. Can specify string category among
DisplayManager.DISPLAY_CATEGORY_*; must use the actual string value.
dock
Sets brightness to docked + idle screen brightness mode
undock
Sets brightness to active (normal) screen brightness mode
...more
➜ ~

而这个命令的实现,就是来自 DisplayManagerShellCommand.java

像 setMatchContentFrameRateUserPreference 的调用链路是

DisplayManagerShellCommand.setMatchContentFrameRateUserPreference -> DisplayManager.setMatchContentFrameRateUserPreference

而其他的一些设置,则来自 DisplayManagerService.java

DisplayManagerService 有一个构造函数

1
2
3
public DisplayManagerService(Context context) {
this(context, new Injector());
}

但是这个类是在系统框架里面的,也就是对应 ROM 中的 /system/framework/services.jar

需要这样拿实例

1
2
3
4
5
6
7
8
9
10
11
Class<?> classLoaderFactoryClass = Class.forName("com.android.internal.os.ClassLoaderFactory");
Method createClassLoaderMethod = classLoaderFactoryClass.getDeclaredMethod(
"createClassLoader", String.class, String.class, String.class,ClassLoader.class, int.class, boolean.class, String.class
);
ClassLoader classLoader = (ClassLoader) createClassLoaderMethod.invoke(null, "/system/framework/services.jar", null, null,
ClassLoader.getSystemClassLoader(), 0, true, null);
Method loadMethod = Runtime.class.getDeclaredMethod("loadLibrary0", Class.class, String.class);
loadMethod.setAccessible(true);
Class cl = classLoader.loadClass("com.android.server.display.DisplayManagerService");
loadMethod.invoke(Runtime.getRuntime(), cl, "android_servers");
Object dms = cl.getDeclaredConstructor(android.content.Context.class).newInstance(FakeContext.get());

FakeContext 是来自 Scrcpy 的代码

其中一些有用的函数

1
2
3
4
5
6
7
8
9
10
11
android.view.Display$Mode getActiveDisplayModeAtStart(int arg0)
com.android.server.display.DisplayDeviceInfo getDisplayDeviceInfoInternal(int arg0)
int getRefreshRateSwitchingTypeInternal()
android.view.Display$Mode getSystemPreferredDisplayModeInternal(int arg0)
android.view.Display$Mode getUserPreferredDisplayModeInternal(int arg0)
void setUserPreferredModeForDisplayLocked(int arg0,android.view.Display$Mode arg1)
void setVirtualDisplayStateInternal(interface android.os.IBinder arg0,boolean arg1)
void setVirtualDisplaySurfaceInternal(interface android.os.IBinder arg0,android.view.Surface arg1)
void setRefreshRateSwitchingTypeInternal(int arg0)
void setShouldAlwaysRespectAppRequestedModeInternal(boolean arg0)
void setUserPreferredDisplayModeInternal(int arg0,android.view.Display$Mode arg1)

然后我又掉了个通,无果~

4.SHELL 下反射 SurfaceControl.setBootDisplayMode

上一节发现 DisplayManagerService 有 getActiveDisplayModeAtStart 函数,
我们也可以使用 DisplayManagerShellCommand 的 get-active-display-mode-at-start

1
2
$ adb shell cmd display get-active-display-mode-at-start 2
Boot display mode: 1920 1080 60.000004

这个结果即为 AR 眼镜连接到手机时的分辨率和刷新率
如果能更改这个值,是不是也是可行的?

然后发现 SurfaceControl 有一个 setBootDisplayMode 函数

函数原型

1
2
3
4
5
6
7
public static void setBootDisplayMode(IBinder displayToken, int displayModeId) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
}

nativeSetBootDisplayMode(displayToken, displayModeId);
}

要两个东西,一个是 Token,一个是 ModeId

这里也有坑,Token 要从 DisplayControl.getPhysicalDisplayToken 获取

DisplayControl 也来自框架层,不过 scrcpy 中有现成的代码

而后者的 displayModeId 也并不是 DisplayInfo 中的 Display.Mode [] 数组中的 ModeId

先来看 Token 的获取吧,参考 scrcpy 的代码,这个 token 非常好获取

注意传入的 ID 是 DisplayControl.getPhysicalDisplayIds() 得到的 ID,普通的 Display ID 是 int,这个数据类型是 long

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static Method getGetPhysicalDisplayTokenMethod() throws NoSuchMethodException {
if (getPhysicalDisplayTokenMethod == null) {
getPhysicalDisplayTokenMethod = CLASS.getMethod("getPhysicalDisplayToken", long.class);
}
return getPhysicalDisplayTokenMethod;
}

public static IBinder getPhysicalDisplayToken(long physicalDisplayId) {
try {
Method method = getGetPhysicalDisplayTokenMethod();
return (IBinder) method.invoke(null, physicalDisplayId);
} catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e);
return null;
}
}

问题在于 displayModeId

需要先获取 Display 的 DynamicDisplayInfo

SurfaceControl.java

1
2
3
public static DynamicDisplayInfo getDynamicDisplayInfo(long displayId) {
return nativeGetDynamicDisplayInfo(displayId);
}

DynamicDisplayInfo 的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static final class DynamicDisplayInfo {
public DisplayMode[] supportedDisplayModes;
public int activeDisplayModeId;
public float renderFrameRate;

public int[] supportedColorModes;
public int activeColorMode;

public Display.HdrCapabilities hdrCapabilities;

public boolean autoLowLatencyModeSupported;
public boolean gameContentTypeSupported;

public int preferredBootDisplayMode;

// 省略一些代码
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(supportedDisplayModes), activeDisplayModeId,
renderFrameRate, activeColorMode, hdrCapabilities);
}
}

DynamicDisplayInfo 的 supportedDisplayModes 中的 DisplayMode 的 id,才是我们调用 setBootDisplayMode 需要的 ModeId

然后调用后,无果~

adb shell cmd display get-active-display-mode-at-start 2 的结果也没有变化

ROKID API 调研结果

我手上有雷鸟的 AR 眼镜,有 XREAL 的 AR 眼镜

这些眼镜厂商都有自己的安卓 App,然后配合眼镜内的固件

插个题外话,这两家厂商都不开放 Android SDK,硬生生的把自己的生态给堵死

他们从未想过如果开放 API,会有开发者借助 API 开发出各种各样的可以加强他们眼镜功能的 App

也不知道他们怎么想的吧,程序员永远理解不了产品经理

觉得自己有几百人的研发,能把自己的生态弄得很好

大概是制定一些协议,通过串口通信的方式,来控制眼镜

从表现上来说,当接上对应的眼镜进入 App 时,眼镜会自动切换为 3D 模式

这个就是 AR 的 SDK 完成的

我问过 XREAL 要过接口,因为他们只有一些 Unity 的 SDK,可很多功能又不需要引入 Unity,不愿意给态度还及其高傲,声称就算他的 SDK 是汇编写的,也会有人用

抛开 AR 眼镜本身来看,他们的生态就是一坨屎,Beam Pro 的设计也是一坨屎,最基本的功能都没做就发布了

闭关锁国,取死之道

Rokid 是跟我合作的,唯一一个愿意提供 Android SDK 的,但是整个过程也不是很顺畅

通过这个 SDK 可以控制 Rokid 的 AR 眼镜

这个时候,我其实是有点期望的,以为通过 SDK 可以直接修改刷新率

还是由于各种信息差导致的

SDK 给到我后

并不能更改到 2D 1080P 90/120 hz 的刷新率

后来他把他们的研发拉了进来

沟通上也不是很顺畅

并且从研发到研发的沟通,都还有很多信息差

细节后面补上

他们的接口是隐藏的

并且很多参数是枚举类,只有到最底层,才通过枚举的值来调用 JNI 的函数

SDK 导入到 Android 工程后,根本无法看到下面的接口

经过我对 SDK 的逆向,才写出了这些代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
RKGlassDevice mRKGlassDevice = RKGlassDevice.getInstance();
Class<?> rkGlassDeviceClass = mRKGlassDevice.getClass();
// 获取mGlassControl字段
Field mGlassControlField = rkGlassDeviceClass.getDeclaredField("mGlassControl");
mGlassControlField.setAccessible(true);
// 获取字段值
Object mGlassControl = mGlassControlField.get(mRKGlassDevice);
Log.d(TAG, "mGlassControl:" + mGlassControl);

Field mGlassSDKField = mGlassControl.getClass().getDeclaredField("mGlassSDK");
mGlassSDKField.setAccessible(true);
Object mGlassSDK = mGlassSDKField.get(mGlassControl);

// 反射出mGlassSDK的NativeHandle(long)
Field mNativeHandleField = mGlassControl.getClass().getDeclaredField("NativeHandle");
mNativeHandleField.setAccessible(true);
long mNativeHandle = mNativeHandleField.getLong(mGlassControl);
Log.d(TAG, "mNativeHandle" + mNativeHandle);

// 反射出mGlassSDK的GlassSetDisplayMode方法,返回boolean,参数是long,int
Method glassSetDisplayModeMethod = mGlassSDK.getClass().getDeclaredMethod("GlassSetDisplayMode", long.class, int.class);
glassSetDisplayModeMethod.setAccessible(true);
boolean result = (boolean) glassSetDisplayModeMethod.invoke(mGlassSDK, mNativeHandle, 3);
Log.d(TAG, "result" + result);

// 反射出mGlassSDK的GetDisplayMode方法,返回int,参数是long
Method getDisplayModeMethod = mGlassSDK.getClass().getDeclaredMethod("GetDisplayMode", long.class);
getDisplayModeMethod.setAccessible(true);
int displayMode = (int) getDisplayModeMethod.invoke(mGlassSDK, mNativeHandle);
Log.d(TAG, "displayMode" + displayMode);

通过一系列的反射,终于把这个函数给调通了,但是我再通过 Android API 获取 Display 的刷新率,并没有任何变化

只发现有一些值不一样
adb shell cmd display get-displays

1
2
3
4
5
# 调用接口前
Display id 11: DisplayInfo{\"HDMI 屏幕\", displayId 11, displayGroupId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_PRESENTATION, FLAG_TRUSTED, real 1920 x 1080, largest app 1920 x 1920, smallest app 964 x 964, appVsyncOff 1000000, presDeadline 16666666, mode 6560.000004, defaultMode 65, modes [{id=65, width=1920, height=1080, fps=60.000004, alternativeRefreshRates=[], supportedHdrTypes=[1, 2, 3]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[1, 2, 3], mMaxLuminance=500.0, mMaxAverageLuminance=250.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state ON, committedState ON}, DisplayMetrics{density=1.3312501, width=1920, height=964, scaledDensity=1.3312501, xdpi=304.8, ydpi=304.8}, isValid=true

# 调用接口后
Display id 11: DisplayInfo{\"HDMI 屏幕\", displayId 11, displayGroupId 0, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_PRESENTATION, FLAG_TRUSTED, real 1920 x 1080, largest app 1920 x 1920, smallest app 964 x 964, appVsyncOff 1000000, presDeadline 16666666, mode 6160.000004, defaultMode 61, modes [{id=61, width=1920, height=1080, fps=60.000004, alternativeRefreshRates=[120.00001], supportedHdrTypes=[1, 2, 3]}, {id=62, width=1920, height=1200, fps=120.00001, alternativeRefreshRates=[60.000004], supportedHdrTypes=[1, 2, 3]}, {id=63, width=1920, height=1200, fps=60.000004, alternativeRefreshRates=[120.00001], supportedHdrTypes=[1, 2, 3]}, {id=64, width=1920, height=1080, fps=120.00001, alternativeRefreshRates=[60.000004], supportedHdrTypes=[1, 2, 3]}], hdrCapabilities HdrCapabilities{mSupportedHdrTypes=[1, 2, 3], mMaxLuminance=500.0, mMaxAverageLuminance=250.0, mMinLuminance=0.0}, userDisabledHdrTypes [], minimalPostProcessingSupported false, rotation 0, state ON, committedState ON}, DisplayMetrics{density=1.3312501, width=1920, height=964, scaledDensity=1.3312501, xdpi=304.8, ydpi=274.32}, isValid=true

啊这,调用接口后,眼镜 DisplayInfo 中的 Mode 变多了,仅此而已

而 XREAL 的眼镜默认就有多种 Mode,我调用接口后得到了其他家眼镜默认的状态?

也就是说,这个接口并不能切换刷新率

折腾半天,得出一个结论

调用这个接口和长按眼镜的音量+键,效果一样

我直接 What,那最初是不是就应该告知我这个信息

而为什么调用接口又能设置为 3D 3840x1080 90hz 呢

因为对于 3D 模式,通过串口通信到眼镜内的固件后,这个眼镜就变成了一个只有 3D 模式的眼镜

AR 眼镜对手机还是 PC 来说,都只是一个普通的显示器

如果这个显示器只支持 3840x1080 90hz,你接上手机,它应该亮还是不亮

如果亮的话,眼镜的显示信息也就必然是 3840x1080 90hz

这个对于 PC 来说也是一样

所以安卓自然而然的切换到了 3D,并且有高刷

所以关键是,如果要实现通过 AR SDK 修改刷新率,硬件那边就将设备设置为只有某个刷新率的 Mode 即可,但是很显然,这个就得给他们加需求了

也不知道他们愿不愿意配合,因为大部分 AR 厂商的任何人,包括产品,研发,运营,都会觉得这些都是伪需求,自己的生态已经无敌了,几句沟通就要把同行踩个遍(指XREAL)

所以,这条路,估计也难走通

作者

梦魇兽

发布于

2024-09-05

更新于

2024-10-02

许可协议

评论