最近这几年一直从事车载相关的开发,国内一般车载项目使用最多的系统目前基本应该就是Andoid了,尤其新兴的一些新能源汽车基本搭载的车载系统都是基于Android深度定制的。其实谷歌也搞了套车载东西,今天我们继续说说与汽车相关的 Android的音频架构。
正题
上一篇分析了CarAudioService的启动过程,今天继续,先分析下CarAudioService的init过程,相比于Android8.0,这部分改动还是挺大的。
@Override
public void init() {
synchronized (mImplLock) {
if (!mUseDynamicRouting) {
Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not configured, run in legacy mode");
setupLegacyVolumeChangedListener();
} else {
setupDynamicRouting();
setupVolumeGroups();
}
}
}
如果我们使用的是AUDIO_DEVICE_OUT_BUS,那么mUseDynamicRouting这个值一定是true的,mUseDynamicRouting是怎么来的呢?
mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
是在CarAudioService的构造函数中,通过xml读出来的,如果我们想使用AUDIO_DEVICE_OUT_BUS这种方式来实现音频输出策略,记得把这个值改为true
packages/services/Car/service/res/values/config.xml
<bool name="audioUseDynamicRouting">false</bool>
但很少有人会改动这个目录下的xml,通常厂商在做定制的时候,一般都会通过在device目录下overlay的方式来实现修改。
其实CarAudioService的init只做了两件事情,setupDynamicRouting()和 setupVolumeGroups(),我们今天的重点就是setupDynamicRouting(),先看下代码:
private void setupDynamicRouting() {
//audiocontrol 对应在native的 hal层,和java层的交互通过HIDL的方式实现,(HIDL在ANdroid的新版本应用越来越广泛起来了)这部分一般都会被定制。
final IAudioControl audioControl = getAudioControl();
if (audioControl == null) {
return;
}
//AudioPolicy 是Android很重要的一个组成部分,做为native对外的api,我们可以直接拿来使用,定制自己声音路由策略,以及音频焦点优先级策略。
AudioPolicy audioPolicy = getDynamicAudioPolicy(audioControl);
int r = mAudioManager.registerAudioPolicy(audioPolicy);
if (r != AudioManager.SUCCESS) {
throw new RuntimeException("registerAudioPolicy failed " + r);
}
mAudioPolicy = audioPolicy;
}
这部分代码逻辑不是很复杂,只是将AudioControl的实例传给了getDynamicAudioPolicy()方法,继续分析getDynamicAudioPolicy(),代码如下:
@Nullable
private AudioPolicy getDynamicAudioPolicy(@NonNull IAudioControl audioControl) {
//构建audiopolicy
AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
builder.setLooper(Looper.getMainLooper());
// 1st, enumerate all output bus device ports
//这部分是获取device是输出设备的的设备信息,而这些设备的信息,是存在audiopolicy配置文件中的,
//在Android7.0之后使用的是audio_policy_configuration.xml配置文件。关于AudioPolicy对于这些文件的加载参照
// [Android9.0AudioPolicy之audio_policy_configuration.xml解析(一)](https://segmentfault.com/a/1190000020109354)
AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
if (deviceInfos.length == 0) {
Log.e(CarLog.TAG_AUDIO, "getDynamicAudioPolicy, no output device available, ignore");
return null;
}
//拿到这些输出的outdevice信息后,继续过滤出device是AUDIO_DEVICE_OUT_BUS的设备信息
for (AudioDeviceInfo info : deviceInfos) {
Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s",
info.getId(), info.getAddress(), info.getType()));
if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info);
// See also the audio_policy_configuration.xml and getBusForContext in
// audio control HAL, the bus number should be no less than zero.
// 创建CarAudioDeviceInfo 并将这些放入集合中,BusNumber就是audio_policy_configuration中device标签下
// address属性里bus后面的那个数字,一般定义都是BUS1,BUS2或者BUS001,BUS002等BUS后面的数字最多3位,
// 要是搞个BUS1111那就error啦。
if (carInfo.getBusNumber() >= 0) {
mCarAudioDeviceInfos.put(carInfo.getBusNumber(), carInfo);
Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
}
}
}
// 2nd, map context to physical bus
// 通过英文源码的注释我们也能大体明白了,就是开始做路由bus策略的映射。可以简单理解为AudioAttribute的usage与
// AUDIO_OUT_DEVICE_BUS的映射,虽然说的不太准确但最终目的是这样的。
try {
for (int contextNumber : CONTEXT_NUMBERS) {
// 将contextNumber通过AudioControl与busNumber做map映射,同时存入集合,这里简单举个例子,方便理解,
// 比如第CONTEXT_NUMBERS中的第一个元素ContextNumber.MUSIC(值为1),对应到AudioControl中的BUS为sContextToBusMap[1] =0,即 mContextToBus.put(1, 0).
int busNumber = audioControl.getBusForContext(contextNumber);
mContextToBus.put(contextNumber, busNumber);
CarAudioDeviceInfo info = mCarAudioDeviceInfos.get(busNumber);
if (info == null) {
Log.w(CarLog.TAG_AUDIO, "No bus configured for context: " + contextNumber);
}
}
} catch (RemoteException e) {
Log.e(CarLog.TAG_AUDIO, "Error mapping context to physical bus", e);
}
// 3rd, enumerate all physical buses and build the routing policy.
// Note that one can not register audio mix for same bus more than once.
// 我们拿到了out的device,又拿到了contextNumber和busNumber的映射,那么第三部分就是真正实现他们的组装。
// 我们也是通过一个例子来说明下
for (int i = 0; i < mCarAudioDeviceInfos.size(); i++) {
//对应的第一个busNumber,值是0.
int busNumber = mCarAudioDeviceInfos.keyAt(i);
boolean hasContext = false;
CarAudioDeviceInfo info = mCarAudioDeviceInfos.valueAt(i);
AudioFormat mixFormat = new AudioFormat.Builder()
.setSampleRate(info.getSampleRate())
.setEncoding(info.getEncodingFormat())
.setChannelMask(info.getChannelCount())
.build();
AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
for (int j = 0; j < mContextToBus.size(); j++) {
//刚才已经说明mContextToBus.put的第一个元素是(1,0),mContextToBus.valueAt(j)即等于busNumber
if (mContextToBus.valueAt(j) == busNumber) {
hasContext = true;
// 此时 contextNumber是1
int contextNumber = mContextToBus.keyAt(j);
// 通过contextNumber拿到AudioAttriute的usage。为什么我之前说
// contextNumber和busnumber是usage和bus的映射是不准确的,因为contextNumber在这里才需真正映射到对应的usage上,
// 这里为AudioAttributes.USAGE_MEDIA
int[] usages = getUsagesForContext(contextNumber);
for (int usage : usages) {
// 将usage封装到AudioMixingRule中,
mixingRuleBuilder.addRule(
new AudioAttributes.Builder().setUsage(usage).build(),
AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
}
Log.i(CarLog.TAG_AUDIO, "Bus number: " + busNumber
+ " contextNumber: " + contextNumber
+ " sampleRate: " + info.getSampleRate()
+ " channels: " + info.getChannelCount()
+ " usages: " + Arrays.toString(usages));
}
}
//我们知道mContextToBus.valueAt(j) == busNumber,因此hasContext为true
if (hasContext) {
// It's a valid case that an audio output bus is defined in
// audio_policy_configuration and no context is assigned to it.
// In such case, do not build a policy mix with zero rules.
// 最终将封装了AudioAttribute的AudioMixingRule与对应的AudioDeviceInfo一同组装到AudioMix中。
// 也就是把输出通路与AudioAttribute映射在一起了,这样我们通过Audiotrack或者mediaplayer等音视频播放器播放时,
// 只要指定了AudioAttributes,也就指定了输出设备。
AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
.setFormat(mixFormat)
.setDevice(info.getAudioDeviceInfo())
.setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
.build();
// 将AudioMix add到AudioPolicy中
builder.addMix(audioMix);
}
}
// 4th, attach the {@link AudioPolicyVolumeCallback}
builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);
return builder.build();
}
上边代码有些长,但是核心的东西。我们把AudioAttribute和AUDIO_DEVICE_OUT_BUS映射起来,存入AudioMix并通过mAudioManager.registerAudioPolicy(audioPolicy)注册下去,最终会在Audio PolicyManagerj进行路由策略时优先对应我们注册下来的这些策略。
总结
我们做Android原生的定制的时候,我们知道新增一个路由策略,我们要从java层新追加定义AudioStream开始,一步一步通过AudioTrack到jni到AudioPolicy,到Engine的strategy。我们全部都要定义,对于初学是难度很大的,因为每一步的逻辑都是需要完全理解的。而AUDIO_DEVICE_OUT_BUS的形式会大大缩短我们的定制风险和难度。当然并不是每一种方案都是完美的,比如基于AUDIO_DEVICE_OUT_BUS的这种策略我们可以满足不同声音不同通路输出的需求,但如果在想通过软件来调节音量可能有些情况就不能满足了,因此我们还需要根据具体需求去做具体处理。后面我们会继续分析AUDIO_DEVICE_OUT_BUS的策略修改与定制