android O的混音降音浅析
前言源于当导航的语音播报和媒体播放器混音时,只要是导航播报在播放时,出现的媒体播放器的音量默认被压低了,起初的追踪方向是AudioService中的duck机制的考量,但实际的情况是封装的setwillPausewhenduck只是一个标志位而已,而且,app并不一定调用.分析从AudioService的代码流程追踪来看,...
·
前言
源于当导航的语音播报和媒体播放器混音时,只要是导航播报在播放时,出现的媒体播放器的音量默认被压低了,起初的追踪方向是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<AudioPlaybackConfiguration> apcIterator = mPlayers.values().iterator();
final ArrayList<AudioPlaybackConfiguration> apcsToDuck =
new ArrayList<AudioPlaybackConfiguration>();
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<AudioPlaybackConfiguration> 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();
TODO
一直想去关联和jni及hal层的路由声音curve的关系,jni层的还是能直接觉察的到,但是到音频路由的状态还暂时没找到get的点,继续追踪.
更多推荐
已为社区贡献9条内容
所有评论(0)