android O的混音降音浅析

源于当导航的语音播报和媒体播放器混音时,只要是导航播报在播放时,出现的媒体播放器的音量默认被压低了,起初的追踪方向是AudioService中的焦点duck机制的考量,但实际的情况是封装的setwillPausewhenduck只是一个标志位而已,而且,app并不一定调用.AudioService作为AudioManager的音频接口的调用实现,除了常见的接口调用,还有焦点处理机制及音频播放的状态监控.

从AudioService的代码流程追踪来看,构造方法中初始化了PlaybackActivityMonitor ,按名称来解释是一个播放活跃监控器

public AudioService(Context context) {
    ...

        //初始化PlaybackActivityMonitor的对象
        mPlaybackMonitor =
            new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);

        mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
    ...

}

PlaybackActivityMonitor首先实现了媒体播放器的两个接口类PlayerDeathMonitor和PlayerFocusEnforcer

,当前只需要提的是PlayerFocusEnforcer

先分析下接口方法duckPlayer的具体流程

@Override
public boolean duckPlayers(FocusRequester winner, FocusRequester loser) {
    if (DEBUG) {
        Log.v(TAG, String.format("duckPlayers: uids winner=%d loser=%d",
                winner.getClientUid(), loser.getClientUid()));
    }
    synchronized (mPlayerLock) {
        if (mPlayers.isEmpty()) {
            return true;
        }
        // check if this UID needs to be ducked (return false if not), and gather list of
        // eligible players to duck
        //确认当前UID的player是否需要被降音(若不需要降音则返回false),总起归纳有资格被降音player的列表AudioPlaybackConfiguration
        final Iterator apcIterator = mPlayers.values().iterator();
        final ArrayList apcsToDuck =
                new ArrayList();
        while (apcIterator.hasNext()) {
            final AudioPlaybackConfiguration apc = apcIterator.next();
            if (!winner.hasSameUid(apc.getClientUid())
                    && loser.hasSameUid(apc.getClientUid())
                    && apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED)
            {
                if (apc.getAudioAttributes().getContentType() ==
                        AudioAttributes.CONTENT_TYPE_SPEECH) {
                    // the player is speaking, ducking will make the speech unintelligible
                    // so let the app handle it instead
                    Log.v(TAG, "not ducking player " + apc.getPlayerInterfaceId()
                            + " uid:" + apc.getClientUid() + " pid:" + apc.getClientPid()
                            + " - SPEECH");
                    return false;
                } else if (ArrayUtils.contains(UNDUCKABLE_PLAYER_TYPES, apc.getPlayerType())) {//特殊定义排除不需要降音的palyer类型不做降音处理
                    Log.v(TAG, "not ducking player " + apc.getPlayerInterfaceId()
                            + " uid:" + apc.getClientUid() + " pid:" + apc.getClientPid()
                            + " due to type:"
                            + AudioPlaybackConfiguration.toLogFriendlyPlayerType(
                                    apc.getPlayerType()));
                    return false;
                }
                apcsToDuck.add(apc);//符合条件的player uid添加到
            }
        }
        // add the players eligible for ducking to the list, and duck them
        // (if apcsToDuck is empty, this will at least mark this uid as ducked, so when
        //  players of the same uid start, they will be ducked by DuckingManager.checkDuck())
        mDuckingManager.duckUid(loser.getClientUid(), apcsToDuck);//最终在音频焦点中失败的uid的player被添加到降音列表,<>
                                                                  //下面还有一点需要注意的是,如果需要duck的列表为空时,相同uid的player开始播放时会先执行DuckingManager的checkDuck方法
    }
    return true;
}
静态内部类DuckingManager处理需要duck的逻辑
synchronized void duckUid(int uid, ArrayList apcsToDuck) {
    if (DEBUG) {  Log.v(TAG, "DuckingManager: duckUid() uid:"+ uid); }
    if (!mDuckers.containsKey(uid)) {
        mDuckers.put(uid, new DuckedApp(uid));
    }
    final DuckedApp da = mDuckers.get(uid);
    for (AudioPlaybackConfiguration apc : apcsToDuck) {
        da.addDuck(apc, false /*skipRamp*/);//满足条件Uid的player添加到AudioPlaybackConfiguration的list中
    }
}

继续追踪到内部类DuckedApp

            // pre-conditions:
            //  * apc != null
            //  * apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
//先决条件,audioplaybackconfiguration不为空,也就是说至少存在一个或以上的player在播放,而且还需处在播放状态
            void addDuck(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
                final int piid = new Integer(apc.getPlayerInterfaceId());
                if (mDuckedPlayers.contains(piid)) {
                    if (DEBUG) { Log.v(TAG, "player piid:" + piid + " already ducked"); }
                    return;
                }
                try {
                    sEventLogger.log((new DuckEvent(apc, skipRamp)).printLog(TAG));
                    apc.getPlayerProxy().applyVolumeShaper(//本文的核心处,就是降音曲线的调用
                            DUCK_VSHAPE,
                            skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
                    mDuckedPlayers.add(piid);
                } catch (Exception e) {
                    Log.e(TAG, "Error ducking player piid:" + piid + " uid:" + mUid, e);
                }
            }

降音曲线的初始化声明

private static final VolumeShaper.Configuration DUCK_VSHAPE =
        new VolumeShaper.Configuration.Builder()
            .setId(VOLUME_SHAPER_SYSTEM_DUCK_ID)
            .setCurve(new float[] { 0.f, 1.f } /* times */,
                new float[] { 1.f, 0.2f  } /* volumes */)//被压低的播放器声音降低至0.2f
            .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
            .setDuration(MediaFocusControl.getFocusRampTimeMs(
                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION)
                        .build()))
            .build();

一直想去关联和jni及hal层的路由声音curve的关系,jni层的还是能直接觉察的到,但是到音频路由的状态还暂时没找到get的点,继续追踪.

Original: https://blog.csdn.net/jeephao/article/details/108612076
Author: jeephao
Title: android O的混音降音浅析

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/526643/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球