Android应用开发学习笔记——目录索引
参考android官网:
- Frame rate | Android media | Android Developers
- 多重刷新率 | Android 开源项目 | Android Open Source Project
- WindowManager.LayoutParams | Android Developers
目前市面上旗舰手机基本都是普及了LTPO屏幕,为了验证LTPO屏幕的DDIC(display driver ID)的硬件刷帧行为(屏幕硬件的刷新可以通过屏幕的AVDD电流信号/屏幕硬件内部的Vsync信号检测),写一个可以指定app出图帧率的测试程序(基于Android应用开发(35)SufaceView基本用法 与 Android应用开发(36)帧率API测试基于Surfaceview)。
一、获取屏幕支持的所有帧率
应用程序获取设备实际支持的显示刷新率,可以通过调用 Display.getSupportedModes()获取,Mode.mRefreshRate就是帧率信息,以便安全调用setFrameRate()
// Display.java
Display.Mode[] mSupportedModes = getWindowManager().getDefaultDisplay().getSupportedModes();
for (Display.Mode mode : mSupportedModes) {
Log.d(TAG, "getSupportedModes: " + mode.toString());
Log.d(TAG, "getRefreshRate: " + mode.getRefreshRate());
}
public static final class Mode implements Parcelable {
public static final Mode[] EMPTY_ARRAY = new Mode[0];
private final int mModeId;
private final int mWidth;
private final int mHeight;
private final float mRefreshRate;
@NonNull
private final float[] mAlternativeRefreshRates;
...
}
二、APP设置帧率的API
setFrameRate( )
Android 公开了多种访问和控制界面的方法,因此有多个版本的setFrameRate()
API。每个版本的 API 都采用相同的参数,并且与其他版本的工作方式相同:
- Surface.setFrameRate()
- SurfaceControl.Transaction.setFrameRate()
- ANativeWindow_setFrameRate()
- ASurfaceTransaction_setFrameRate()
要查看调用 是否会导致setFrameRate()
显示刷新率发生更改,请通过调用 DisplayManager.registerDisplayListener() 或 来注册显示更改通知AChoreographer_registerRefreshRateCallback()。
调用时setFrameRate()
,最好传递精确的帧速率,而不是四舍五入为整数。例如,当渲染以 29.97Hz 录制的视频时,请传入 29.97,而不是四舍五入到 30。
对于视频应用程序,传递给的兼容性参数setFrameRate()
应设置为Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
,以向 Android 平台提供额外提示,即该应用程序将使用pulldown来适应不匹配的显示刷新率(这将导致抖动)。
surface.setFrameRate(contentFrameRate,
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
// 调用setFrameRate()时,最好传入准确的帧速率,而不是四舍五入为整数。例如,在渲染以 29.97Hz 录制的视频时,传入 29.97 而不是四舍五入为 30。
参数:
Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE仅适用于视频应用程序。对于非视频用途,请使用FRAME_RATE_COMPATIBILITY_DEFAULT.
选择改变帧速率的策略:
google强烈建议应用在显示电影等长时间运行的视频时调用setFrameRate(fps , FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, CHANGE_FRAME_RATE_ALWAYS) ,其中 fps 是视频的帧速率。
当您预计视频播放持续几分钟或更短时间时,我们强烈建议您不要调用应用程序setFrameRate()。CHANGE_FRAME_RATE_ALWAYS
SurfaceControl.Transaction.setFrameRate()
参数和
1
是一样的
setFrameRate( ) 与 PreferredDisplayModeId
WindowManager.LayoutParams.preferredDisplayModeId 是应用程序向平台指示其帧速率的另一种方式。某些应用程序只想更改显示刷新率,而不是更改其他显示模式设置,例如显示分辨率。一般情况下,使用 setFrameRate()
代替preferredDisplayModeId
. 该setFrameRate()
功能更易于使用,因为应用程序不需要搜索显示模式列表来查找具有特定帧速率的模式。
setFrameRate()
在存在多个以不同帧速率运行的表面的情况下,使平台有更多机会选择兼容的帧速率。例如,考虑这样一种场景:两个应用程序在 Pixel 4 上以分屏模式运行,其中一个应用程序正在播放 24Hz 视频,另一个应用程序向用户显示可滚动列表。设备支持两种显示刷新率:60Hz 和 90Hz。使用preferredDisplayModeId
API,视频表面被迫选择 60Hz 或 90Hz。通过使用 setFrameRate()
24Hz 调用,视频表面为平台提供了有关源视频帧速率的更多信息,使平台能够选择 90Hz 的显示刷新率,在此场景中这比 60Hz 更好。
然而,有些场景preferredDisplayModeId
应该使用 来代替setFrameRate()
,例如:
- 如果应用程序想要更改分辨率或其他显示模式设置,请使用
preferredDisplayModeId
。 setFrameRate()
如果模式切换是轻量级的并且不太可能被用户注意到,则平台只会响应调用来切换显示模式 。如果应用程序更喜欢切换显示刷新率,即使它需要大量模式切换(例如,在 Android TV 设备上),请使用preferredDisplayModeId
.- 无法处理以应用程序帧速率倍数运行的显示的应用程序(这需要在每个帧上设置演示时间戳)应使用
preferredDisplayModeId
.
WindowManager.LayoutParams params = getWindow().getAttributes();
params.preferredDisplayModeId = mSupportedModes[position].getModeId();
Log.d(TAG,
"setRefreshRate:"
+ mSupportedModes[position].getRefreshRate());
getWindow().setAttributes(params);
setFrameRate( ) 与 PreferredRefreshRate
WindowManager.LayoutParams#preferredRefreshRate 在应用程序窗口上设置首选帧速率,并且该速率适用于窗口内的所有表面。无论设备支持的刷新率如何,应用程序都应指定其首选帧速率,类似于 setFrameRate()
,以便为调度程序更好地提示应用程序的预期帧速率。
preferredRefreshRate
对于使用 的表面将被忽略setFrameRate()
。如果可能的话一般使用setFrameRate()
。
PreferredRefreshRate 与 PreferredDisplayModeId
如果应用程序只想更改首选刷新率,则最好使用 preferredRefreshRate
,而不是preferredDisplayModeId
。
避免过于频繁地调用setFrameRate( )
尽管该setFrameRate()
调用在性能方面的成本并不高,但应用程序应避免setFrameRate()
每帧调用或每秒调用多次。调用setFrameRate()
可能会导致显示刷新率发生变化,从而可能导致转换期间出现帧丢失。您应该提前计算出正确的帧速率并调用 setFrameRate()
一次。
用于游戏或其他非视频应用程序
尽管视频是该 API 的主要用例setFrameRate()
,但它也可用于其他应用程序。例如,打算不高于 60Hz 运行的游戏(以减少功耗并实现更长的游戏时间)可以调用 Surface.setFrameRate(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
. 这样,默认情况下以 90Hz 运行的设备将在游戏处于活动状态时以 60Hz 运行,这将避免游戏以 60Hz 运行而显示器以 90Hz 运行时出现的抖动。
二、代码分析
1、surface.setFrameRate()
// Surface.java (frameworks\base\core\java\android\view)
/**
* Sets the intended frame rate for this surface.
*
* <p>On devices that are capable of running the display at different refresh rates,
* the system may choose a display refresh rate to better match this surface's frame
* rate. Usage of this API won't introduce frame rate throttling, or affect other
* aspects of the application's frame production pipeline. However, because the system
* may change the display refresh rate, calls to this function may result in changes
* to Choreographer callback timings, and changes to the time interval at which the
* system releases buffers back to the application.</p>
*
* <p>Note that this only has an effect for surfaces presented on the display. If this
* surface is consumed by something other than the system compositor, e.g. a media
* codec, this call has no effect.</p>
*
* @param frameRate The intended frame rate of this surface, in frames per second. 0
* is a special value that indicates the app will accept the system's choice for the
* display frame rate, which is the default behavior if this function isn't
* called. The <code>frameRate</code> parameter does <em>not</em> need to be a valid refresh
* rate for this device's display - e.g., it's fine to pass 30fps to a device that can only run
* the display at 60fps.
*
* @param compatibility The frame rate compatibility of this surface. The
* compatibility value may influence the system's choice of display frame rate.
* This parameter is ignored when <code>frameRate</code> is 0.
*
* @param changeFrameRateStrategy Whether display refresh rate transitions caused by this
* surface should be seamless. A seamless transition is one that doesn't have any visual
* interruptions, such as a black screen for a second or two. This parameter is ignored when
* <code>frameRate</code> is 0.
*
* @throws IllegalArgumentException If <code>frameRate</code>, <code>compatibility</code> or
* <code>changeFrameRateStrategy</code> are invalid.
*/
public void setFrameRate(@FloatRange(from = 0.0) float frameRate,
@FrameRateCompatibility int compatibility,
@ChangeFrameRateStrategy int changeFrameRateStrategy) {
synchronized (mLock) {
checkNotReleasedLocked();
int error = nativeSetFrameRate(mNativeObject, frameRate, compatibility,
changeFrameRateStrategy);
if (error == -EINVAL) {
throw new IllegalArgumentException("Invalid argument to Surface.setFrameRate()");
} else if (error != 0) {
throw new RuntimeException("Failed to set frame rate on Surface");
}
}
}
/**
* Sets the intended frame rate for this surface. Any switching of refresh rates is
* most probably going to be seamless.
*
* @see #setFrameRate(float, int, int)
*/
public void setFrameRate(
@FloatRange(from = 0.0) float frameRate, @FrameRateCompatibility int compatibility) {
setFrameRate(frameRate, compatibility, CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
}
// android_view_Surface.cpp (frameworks\base\core\jni)
static jint nativeSetFrameRate(JNIEnv* env, jclass clazz, jlong nativeObject, jfloat frameRate,
jint compatibility, jint changeFrameRateStrategy) {
Surface* surface = reinterpret_cast<Surface*>(nativeObject);
ANativeWindow* anw = static_cast<ANativeWindow*>(surface);
// Our compatibility is a Surface.FRAME_RATE_COMPATIBILITY_* value, and
// NATIVE_WINDOW_SET_FRAME_RATE takes an
// ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_* value. The values are identical
// though, so no need to explicitly convert.
return anw->perform(surface, NATIVE_WINDOW_SET_FRAME_RATE, double(frameRate), compatibility,
int(changeFrameRateStrategy));
}
// Surface.cpp (frameworks\native\libs\gui)
int Surface::perform(int operation, va_list args)
{
case NATIVE_WINDOW_SET_FRAME_RATE:
res = dispatchSetFrameRate(args);
break;
int Surface::dispatchSetFrameRate(va_list args) {
float frameRate = static_cast<float>(va_arg(args, double));
int8_t compatibility = static_cast<int8_t>(va_arg(args, int));
int8_t changeFrameRateStrategy = static_cast<int8_t>(va_arg(args, int));
return setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
}
status_t Surface::setFrameRate(float frameRate, int8_t compatibility,
int8_t changeFrameRateStrategy) {
ATRACE_CALL();
ALOGV("Surface::setFrameRate");
if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy,
"Surface::setFrameRate")) {
return BAD_VALUE;
}
return composerService()->setFrameRate(mGraphicBufferProducer, frameRate, compatibility,
changeFrameRateStrategy);
}
// ISurfaceComposer.cpp (frameworks\native\libs\gui)
status_t setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,
int8_t compatibility, int8_t changeFrameRateStrategy) override {
..
status_t err = remote()->transact(BnSurfaceComposer::SET_FRAME_RATE, data, &reply);
}
status_t BnSurfaceComposer::onTransact(
...setFrameRate(surface, frameRate, compatibility, changeFrameRateStrategy);
// SurfaceFlinger.cpp (miui\frameworks\native\services\surfaceflinger)
status_t SurfaceFlinger::setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,
int8_t compatibility, int8_t changeFrameRateStrategy)
layer->setFrameRate(Layer::FrameRate(Fps{frameRate}, Layer::FrameRate::convertCompatibility(compatibility), strategy))
1、surfaceflinger layerinfo保存app传递下来的结构体是FrameRate
struct FrameRate {
using Seamlessness = scheduler::Seamlessness;
Fps rate;
FrameRateCompatibility type;
Seamlessness seamlessness;
}
enum class FrameRateCompatibility {
Default, // 图层没有指定任何具体的处理策略
Exact, // 图层需要准确的帧速率。如视频类
ExactOrMultiple, // 图层需要精确的帧速率(或它的倍数)来呈现内容。 任何其他值都将导致下拉。
NoVote, // 图层对刷新率没有任何要求,不考虑显示刷新率
};
// The seamlessness requirement of a Layer.
enum class Seamlessness {
// Indicates a requirement for a seamless mode switch.
OnlySeamless,
// Indicates that both seamless and seamed mode switches are allowed.
SeamedAndSeamless,
// Indicates no preference for seamlessness. For such layers the system will
// prefer seamless switches, but also non-seamless switches to the group of the
// default config are allowed.
Default
};
2、SurfaceControl.Transaction.setFrameRate()
// SurfaceControl.java (frameworks\base\core\java\android\view)
/**
* Sets the intended frame rate for this surface. Any switching of refresh rates is
* most probably going to be seamless.
*
* @see #setFrameRate(SurfaceControl, float, int, int)
*/
@NonNull
public Transaction setFrameRate(@NonNull SurfaceControl sc,
@FloatRange(from = 0.0) float frameRate,
@Surface.FrameRateCompatibility int compatibility) {
return setFrameRate(sc, frameRate, compatibility,
Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
}
// android_view_SurfaceControl.cpp (frameworks\base\core\jni)
static void nativeSetFrameRate(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
jfloat frameRate, jint compatibility, jint changeFrameRateStrategy) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
const auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
// Our compatibility is a Surface.FRAME_RATE_COMPATIBILITY_* value, and
// Transaction::setFrameRate() takes an ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_* value. The
// values are identical though, so no need to convert anything.
transaction->setFrameRate(ctrl, frameRate, static_cast<int8_t>(compatibility),
static_cast<int8_t>(changeFrameRateStrategy));
}
// SurfaceComposerClient.cpp (frameworks\native\libs\gui)
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFrameRate(
const sp<SurfaceControl>& sc, float frameRate, int8_t compatibility,
int8_t changeFrameRateStrategy) {
layer_state_t* s = getLayerState(sc);
if (!s) {
mStatus = BAD_INDEX;
return *this;
}
// Allow privileged values as well here, those will be ignored by SF if
// the caller is not privileged
if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy,
"Transaction::setFrameRate",
/*privileged=*/true)) {
mStatus = BAD_VALUE;
return *this;
}
s->what |= layer_state_t::eFrameRateChanged;
s->frameRate = frameRate;
s->frameRateCompatibility = compatibility;
s->changeFrameRateStrategy = changeFrameRateStrategy;
return *this;
}
// SurfaceFlinger.cpp (frameworks\native\services\surfaceflinger)
applyTransactionState
setClientStateLocked
uint32_t SurfaceFlinger::setClientStateLocked(
const FrameTimelineInfo& frameTimelineInfo, const ComposerState& composerState,
int64_t desiredPresentTime, bool isAutoTimestamp, int64_t postTime, uint32_t permissions,
std::unordered_set<ListenerCallbacks, ListenerCallbacksHash>& outListenerCallbacks) {
...
if (what & layer_state_t::eFrameRateChanged) {
if (ValidateFrameRate(s.frameRate, s.frameRateCompatibility, s.changeFrameRateStrategy,
"SurfaceFlinger::setClientStateLocked", privileged)) {
const auto compatibility =
Layer::FrameRate::convertCompatibility(s.frameRateCompatibility);
const auto strategy =
Layer::FrameRate::convertChangeFrameRateStrategy(s.changeFrameRateStrategy);
if (layer->setFrameRate(Layer::FrameRate(Fps(s.frameRate), compatibility, strategy))) {
flags |= eTraversalNeeded;
}
}
}
3、preferredDisplayModeId
// RefreshRatePolicy.java (mframeworks\base\services\core\java\com\android\server\wm)
int getPreferredModeId(WindowState w) {
// If app is animating, it's not able to control refresh rate because we want the animation
// to run in default refresh rate.
if (w.isAnimating(TRANSITION | PARENTS)) {
return 0;
}
return w.mAttrs.preferredDisplayModeId;
}
// DisplayContent.java (frameworks\base\services\core\java\com\android\server\wm)
会保存到
private static final class ApplySurfaceChangesTransactionState 的preferredModeId
void applySurfaceChangesTransaction() {
if (!mWmService.mDisplayFrozen) {
mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
mLastHasContent,
mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
mTmpApplySurfaceChangesTransactionState.preferredModeId,
mTmpApplySurfaceChangesTransactionState.preferredMinRefreshRate,
mTmpApplySurfaceChangesTransactionState.preferredMaxRefreshRate,
mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing,
true /* inTraversal, must call performTraversalInTrans... below */);
}
// DisplayManagerService.java (frameworks\base\services\core\java\com\android\server\display)
public void setDisplayProperties(int displayId, boolean hasContent,
float requestedRefreshRate, int requestedMode, float requestedMinRefreshRate,
float requestedMaxRefreshRate, boolean requestedMinimalPostProcessing,
boolean inTraversal) {
setDisplayPropertiesInternal(displayId, hasContent, requestedRefreshRate,
requestedMode, requestedMinRefreshRate, requestedMaxRefreshRate,
requestedMinimalPostProcessing, inTraversal);
}
setDisplayPropertiesInternal
|--> mDisplayModeDirector.getAppRequestObserver().setAppRequest(
displayId, requestedModeId, requestedMinRefreshRate, requestedMaxRefreshRate);
// DisplayModeDirector.java (frameworks\base\services\core\java\com\android\server\display)
public void setAppRequest(int displayId, int modeId, float requestedMinRefreshRateRange,
float requestedMaxRefreshRateRange) {
synchronized (mLock) {
setAppRequestedModeLocked(displayId, modeId);
setAppPreferredRefreshRateRangeLocked(displayId, requestedMinRefreshRateRange,
requestedMaxRefreshRateRange);
}
}
setAppRequest 最后通过:
updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE_RANGE, vote);
还就是最后调用surfaceflinger的:
setDesiredDisplayModeSpecs // surface的标准API接口
三、写一个APP验证API
运行界面介绍
四、测试程序
完整源码
百度网盘链接:百度网盘 请输入提取码 提取码:test
RefreshRateSurfaceViewTest目录
点此查看Android应用开发学习笔记的完整目录