Android 14 为优化 TimeCheck 问题做的 Audio Frameworks 设计变更

Android 14 为优化 TimeCheck 问题做的 Audio Frameworks 设计变更

Qidi Huang 07/07/2025


0. AudioFlinger/AudioPolicyService 启动逻辑变更

我们知道,在 Android 13 及更早版本的 main_audioserver.cpp 实现中,AudioFlingerAudioPolicyService 均通过 instantiate() 方法进行构造和注册。

该方法定义在 frameworks/native/include/binder/BinderService.h,如下:

template<typename SERVICE>
class BinderService
{
public:
    static status_t publish(bool allowIsolated = false,
                            int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT) {
        sp<IServiceManager> sm(defaultServiceManager());
        return sm->addService(String16(SERVICE::getServiceName()), new SERVICE(), allowIsolated,
                              dumpFlags);
    }
	......

    static void instantiate() { publish(); }
	......
};

由此可见,旧版 Android 中的 AudioFlinger 实例构造完毕、并完成服务注册后,AudioPolicyService 才会开始构造。此时 IAudioFlinger 已经处于可被用户端调用的状态。

从 Android 14 起,Google 对 AudioFlingerAudioPolicyService 的构造、注册服务时序做了调整。

代码提交记录见 此处,Commit Message 如下:

audioserver: Initialize services before making public.

This avoids inconsistencies and TimeCheck aborts when
the AudioFlinger interface is made public before
AudioPolicy is initialized.

Test: boot, check logcat, kill audioserver, check logcat
Bug: 240234432
Bug: 262821105
Change-Id: I2259105eb86110fafe4e3d7ba5820f6557382447

于是在新的 main_audioserver.cpp 文件中可以看到:

int main(int argc __unused, char **argv)
{
	......
	// Instantiating AudioFlinger (making it public, e.g. through ::initialize())
	// and then instantiating AudioPolicy (and making it public)
	// leads to situations where AudioFlinger is accessed remotely before
	// AudioPolicy is initialized.  Not only might this
	// cause inaccurate results, but if AudioPolicy has slow audio HAL
	// initialization, it can cause a TimeCheck abort to occur on an AudioFlinger
	// call which tries to access AudioPolicy.
	//
	// We create AudioFlinger and AudioPolicy locally then make it public to ServiceManager.
	// This requires both AudioFlinger and AudioPolicy to be in-proc.
	//
	const auto af = sp<AudioFlinger>::make();
	const auto afAdapter = sp<AudioFlingerServerAdapter>::make(af);
	ALOGD("%s: AudioFlinger created", __func__);
	ALOGW_IF(AudioSystem::setLocalAudioFlinger(af) != OK,
			"%s: AudioSystem already has an AudioFlinger instance!", __func__);
	const auto aps = sp<AudioPolicyService>::make();
	af->initAudioPolicyLocal(aps);
	ALOGD("%s: AudioPolicy created", __func__);
	ALOGW_IF(AudioSystem::setLocalAudioPolicyService(aps) != OK,
			 "%s: AudioSystem already has an AudioPolicyService instance!", __func__);
	// Start initialization of internally managed audio objects such as Device Effects.
	aps->onAudioSystemReady();
	// Add AudioFlinger and AudioPolicy to ServiceManager.
	sp<IServiceManager> sm = defaultServiceManager();
	sm->addService(String16(IAudioFlinger::DEFAULT_SERVICE_NAME), afAdapter,
			false /* allowIsolated */, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT);
	sm->addService(String16(AudioPolicyService::getServiceName()), aps,
			false /* allowIsolated */, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT);
	......

}

Android 14 会先构造好 AudioFlingerAudioPolicyService 这两个实例,再调用 addService() 依次向 ServiceManager 注册服务。

从提交记录和代码注释看,做这个调整的目的是为了减少 TimeCheck 超时导致 audioserver 进程退出的问题。

举个例子: AudioFlinger 注册 binder 服务后, AudioPolicyService 还未构造完成,但已经有 APP 请求播放音乐,那么就会通过 IAudioFlinger 产生对 IAudioPolicyService 接口的调用(比如 getOutputForAttr);由于 IAudioPolicyService 还未注册,于是代码开始等待,造成 TimeCheck 超时的风险。
又由于 AudioPolicyService 下属的 AudioPolicyManager 构造过程中需要依次加载 Audio Primary HALAudio A2DP HALAudio r_submix HALAudio USB HAL,这个加载耗时取决于 HAL 代码实现,可能很长,且 audioserver 对此过程耗时不可控,更进一步增加了 TimeCheck 超时风险。

我用手边的 Android 11 设备做了个测试,在这个设备上,AudioPolicyService 启动完成需要约 2800~3800ms,也就是说在最差的场景下,用户端发起的 IAudioFlinger 调用要等待近 4 秒钟。而 TimeCheck 的超时时长默认是 5 秒,并且 HAL 层处理请求还需耗时,整体上发生超时进程退出的风险相当大。

从原理上讲,Google 这个改动应该能避免一部分 TimeCheck 超时问题。

实际效果要用 Android 11 和 Android 14 设备运行相同的 HAL 代码,进行交叉对比确认。


1. AudioFlinger/AudioPolicyService 调用逻辑变更

除了启动逻辑,在对 AudioFlingerAudioPolicyService 的调用方式上,Google 在 Android 14 代码中也做了些许调整。

提交记录见 此处,Commit Message 如下:

audioserver: Bypass AIDL translation for in-proc AudioFlinger calls

Test: boots, kill audioserver.
Bug: 264463471
Change-Id: Ie755c7c29fb73c97f815b1f6f0056c5a116e7965

这笔提交主要涉及两处修改。

一是新增 AudioSystem::setLocalAudioFlinger() 方法(后续提交里又增加了 AudioSystem::setLocalAudioPolicyService())。在 main_audioserver.cpp 构造 AudioFlinger 实例后,立即调用该方法将实例传递给 AudioSystem,存储到 gLocalAudioFlinger 变量。

二是修改 AudioSystem::get_audio_flinger() 逻辑。当 AudioFlinger 调用方与 AudioFlinger 处于同一进程时,就直接返回 AudioSystem 中缓存的 gLocalAudioFlinger,而不再通过 ServiceManager 查询。

注意,Commit Message 中使用的字眼是 "bypass AIDL translation" 而不是 "bypass AIDL transaction"。尽管作者对 “translation” 含义未作详细说明,但可以推测应该是指的调用方向 ServiceManager 查询已注册的 IBinder 对象的过程,而不是指发生 onTransact() 跨进程调用的过程。

因为 Binder 机制中早就实现了 interface_cast()queryLocalInterface() 接口用以检查获取到的 IBinder 对象是否和调用方位于同一进程。见 frameworks/native/include/binder/IInterface.h:

/**
 * If this is a local object and the descriptor matches, this will return the
 * actual local object which is implementing the interface. Otherwise, this will
 * return a proxy to the interface without checking the interface descriptor.
 * This means that subsequent calls may fail with BAD_TYPE.
 */
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
    return INTERFACE::asInterface(obj);
}

......

template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:
    virtual sp<IInterface>      queryLocalInterface(const String16& _descriptor);
    virtual const String16&     getInterfaceDescriptor() const;
    typedef INTERFACE BaseInterface;

protected:
    virtual IBinder*            onAsBinder();
};

......

// Macro to be used by both IMPLEMENT_META_INTERFACE and IMPLEMENT_META_NESTED_INTERFACE
#define DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE0(ITYPE, INAME, BPTYPE)                                   \
    ::android::sp<ITYPE> ITYPE::asInterface(const ::android::sp<::android::IBinder>& obj) {        \
        ::android::sp<ITYPE> intr;               \
        if (obj != nullptr) {                    \
            intr = ::android::sp<ITYPE>::cast(obj->queryLocalInterface(ITYPE::descriptor));        \
            if (intr == nullptr) {               \
                intr = ::android::sp<BPTYPE>::make(obj);                                          \
            }                  \
        }                      \
        return intr;           \
    }

......

template<typename INTERFACE>
inline sp<IInterface> BnInterface<INTERFACE>::queryLocalInterface(
        const String16& _descriptor)
{
    if (_descriptor == INTERFACE::descriptor) return sp<IInterface>::fromExisting(this);
    return nullptr;
}

所以如果是为了避免发生跨进程调用,那就完全没有修改代码的必要了。


2. 启发

如果你正在基于 Android 14 以下的版本开发设备,并且在开关机压力测试场景下频繁遇到 TimeCheck 超时造成进程退出的问题,那么可以尝试移植上述两笔提交,看问题是否得到抑制。

posted @ 2025-07-09 17:48  Qidi_Huang  阅读(149)  评论(0)    收藏  举报