最近这几年一直从事车载相关的开发,国内一般车载项目使用最多的系统目前基本应该就是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() {
final IAudioControl audioControl = getAudioControl();
if (audioControl == null) {
return;
}
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.Builder builder = new AudioPolicy.Builder(mContext);
builder.setLooper(Looper.getMainLooper());
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;
}
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);
if (carInfo.getBusNumber() >= 0) {
mCarAudioDeviceInfos.put(carInfo.getBusNumber(), carInfo);
Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
}
}
}
try {
for (int contextNumber : CONTEXT_NUMBERS) {
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);
}
for (int i = 0; i < mCarAudioDeviceInfos.size(); i++) {
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++) {
if (mContextToBus.valueAt(j) == busNumber) {
hasContext = true;
int contextNumber = mContextToBus.keyAt(j);
int[] usages = getUsagesForContext(contextNumber);
for (int usage : usages) {
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));
}
}
if (hasContext) {
AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
.setFormat(mixFormat)
.setDevice(info.getAudioDeviceInfo())
.setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
.build();
builder.addMix(audioMix);
}
}
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的策略修改与定制