1. 構(gòu)造函數(shù)
關(guān)于CarAudioService的啟動過程,我們就不在這里描述了,首先看CarAudioService的構(gòu)造函數(shù)茄唐,Android10.0與Android9.0相比孝扛,這里多了mUidToZoneMap,這是一個(gè)Map的集合乎芳,主要是與音區(qū)相關(guān)遵蚜。
public CarAudioService(Context context) {
mContext = context;
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
mPersistMasterMuteState = mContext.getResources().getBoolean(
R.bool.audioPersistMasterMuteState);
mUidToZoneMap = new HashMap<>();
}
2. init()函數(shù)
構(gòu)造函數(shù)結(jié)束之后,就是init()函數(shù)奈惑,init函數(shù)里面吭净,首先是通過AudioManager.getDevices獲取到輸出設(shè)備,并存儲在deviceInfos 數(shù)組中肴甸,然后根據(jù)type類型是否是TYPE_BUS來對deviceInfos數(shù)組進(jìn)行篩選寂殉,把篩選的結(jié)果放在busToCarAudioDeviceInfo,拿到busToCarAudioDeviceInfo之后原在,就調(diào)用setupDynamicRouting方法友扰,進(jìn)行動態(tài)路由的選擇彤叉。
流程圖如下:
/**
* Dynamic routing and volume groups are set only if
* {@link #mUseDynamicRouting} is {@code true}. Otherwise, this service runs in legacy mode.
*/
@Override
public void init() {
synchronized (mImplLock) {
if (mUseDynamicRouting) {
// Enumerate all output bus device ports
AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(
AudioManager.GET_DEVICES_OUTPUTS);
if (deviceInfos.length == 0) {
Log.e(CarLog.TAG_AUDIO, "No output device available, ignore");
return;
}
SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo = new SparseArray<>();
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,
// the bus number should be no less than zero.
if (carInfo.getBusNumber() >= 0) {
busToCarAudioDeviceInfo.put(carInfo.getBusNumber(), carInfo);
Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
}
}
}
setupDynamicRouting(busToCarAudioDeviceInfo);
} else {
Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not enabled, run in legacy mode");
setupLegacyVolumeChangedListener();
}
// Restore master mute state if applicable
if (mPersistMasterMuteState) {
boolean storedMasterMute = Settings.Global.getInt(mContext.getContentResolver(),
VOLUME_SETTINGS_KEY_MASTER_MUTE, 0) != 0;
setMasterMute(storedMasterMute, 0);
}
}
}
2.1 setupDynamicRouting()函數(shù)
setupDynamicRouting做的事情比較多村怪,我們分布進(jìn)行梳理
1.讀取CarAudio的配置
2.創(chuàng)建CarAudioZonesHelper秽浇,同步當(dāng)前音量
3.通過usage創(chuàng)建動態(tài)路由
private void setupDynamicRouting(SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo) {
//創(chuàng)建AudioPolicy
final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
builder.setLooper(Looper.getMainLooper());
//讀取Car Audio的配置
mCarAudioConfigurationPath = getAudioConfigurationPath();
if (mCarAudioConfigurationPath != null) {
try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
//創(chuàng)建CarAudioZonesHelper
CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mContext, inputStream,
busToCarAudioDeviceInfo);
mCarAudioZones = zonesHelper.loadAudioZones();
} catch (IOException | XmlPullParserException e) {
throw new RuntimeException("Failed to parse audio zone configuration", e);
}
} else {
// In legacy mode, context -> bus mapping is done by querying IAudioControl HAL.
final IAudioControl audioControl = getAudioControl();
if (audioControl == null) {
throw new RuntimeException(
"Dynamic routing requested but audioControl HAL not available");
}
CarAudioZonesHelperLegacy legacyHelper = new CarAudioZonesHelperLegacy(mContext,
R.xml.car_volume_groups, busToCarAudioDeviceInfo, audioControl);
mCarAudioZones = legacyHelper.loadAudioZones();
}
for (CarAudioZone zone : mCarAudioZones) {
if (!zone.validateVolumeGroups()) {
throw new RuntimeException("Invalid volume groups configuration");
}
// Ensure HAL gets our initial value
zone.synchronizeCurrentGainIndex();
Log.v(CarLog.TAG_AUDIO, "Processed audio zone: " + zone);
}
// Setup dynamic routing rules by usage
final CarAudioDynamicRouting dynamicRouting = new CarAudioDynamicRouting(mCarAudioZones);
dynamicRouting.setupAudioDynamicRouting(builder);
// Attach the {@link AudioPolicyVolumeCallback}
builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);
if (sUseCarAudioFocus) {
// Configure our AudioPolicy to handle focus events.
// This gives us the ability to decide which audio focus requests to accept and bypasses
// the framework ducking logic.
mFocusHandler = new CarZonesAudioFocus(mAudioManager,
mContext.getPackageManager(),
mCarAudioZones);
builder.setAudioPolicyFocusListener(mFocusHandler);
builder.setIsAudioFocusPolicy(true);
}
mAudioPolicy = builder.build();
if (sUseCarAudioFocus) {
// Connect the AudioPolicy and the focus listener
mFocusHandler.setOwningPolicy(this, mAudioPolicy);
}
int r = mAudioManager.registerAudioPolicy(mAudioPolicy);
if (r != AudioManager.SUCCESS) {
throw new RuntimeException("registerAudioPolicy failed " + r);
}
}
2.1.1 getAudioConfigurationPath()
首先加載vendor/etc下的car_audio_configuration.xml,如果沒有則會加載system/etc下的car_audio_configuration.xml甚负,找到之后則會返回路徑柬焕,然后后面根據(jù)path進(jìn)行解析,然后會創(chuàng)建CarAudioZonesHelper
// CarAudioService reads configuration from the following paths respectively.
// If the first one is found, all others are ignored.
// If no one is found, it fallbacks to car_volume_groups.xml resource file.
private static final String[] AUDIO_CONFIGURATION_PATHS = new String[] {
"/vendor/etc/car_audio_configuration.xml",
"/system/etc/car_audio_configuration.xml"
};
/**
* Read from {@link #AUDIO_CONFIGURATION_PATHS} respectively.
* @return File path of the first hit in {@link #AUDIO_CONFIGURATION_PATHS}
*/
@Nullable
private String getAudioConfigurationPath() {
for (String path : AUDIO_CONFIGURATION_PATHS) {
File configuration = new File(path);
if (configuration.exists()) {
return path;
}
}
return null;
}
下面我們看一下car_audio_configuration.xml,后面會有專門的章節(jié)分析該配置文件
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!--
Defines the audio configuration in a car, including
- Audio zones
- Display to audio zone mappings
- Context to audio bus mappings
- Volume groups
in the car environment.
-->
<carAudioConfiguration version="1">
<zones>
<zone name="primary zone" isPrimary="true">
<volumeGroups>
<group>
<device address="bus0_media_out">
<context context="music"/>
</device>
<device address="bus3_call_ring_out">
<context context="call_ring"/>
</device>
<device address="bus6_notification_out">
<context context="notification"/>
</device>
<device address="bus7_system_sound_out">
<context context="system_sound"/>
</device>
</group>
<group>
<device address="bus1_navigation_out">
<context context="navigation"/>
</device>
<device address="bus2_voice_command_out">
<context context="voice_command"/>
</device>
</group>
<group>
<device address="bus4_call_out">
<context context="call"/>
</device>
</group>
<group>
<device address="bus5_alarm_out">
<context context="alarm"/>
</device>
</group>
</volumeGroups>
<displays>
<display port="0"/>
</displays>
<!-- to specify displays associated with this audio zone, use the following tags
<displays>
<display port="1"/>
<display port="2"/>
</displays>
where port is the physical port of the display (See DisplayAddress.Phyisical)
-->
</zone>
<zone name="rear seat zone">
<volumeGroups>
<group>
<device address="bus100_rear_seat">
<context context="music"/>
<context context="navigation"/>
<context context="voice_command"/>
<context context="call_ring"/>
<context context="call"/>
<context context="alarm"/>
<context context="notification"/>
<context context="system_sound"/>
</device>
</group>
</volumeGroups>
<displays>
<display port="1"/>
</displays>
</zone>
</zones>
</carAudioConfiguration>
2.1.2 CarAudioZonesHelper()
CarAudioZonesHelper()的構(gòu)造函數(shù)比較簡單腊敲,創(chuàng)建完畢之后击喂,調(diào)用loadAudioZones()函數(shù),這里主要是對傳入的path進(jìn)行解析
解析過程如下:
1.先找name和isPrimary碰辅,在CarAudioZone中isPrimary只能有一個(gè)懂昂,如果isPrimary為true,則id是CarAudioManager.PRIMARY_AUDIO_ZONE没宾,否則id是mNextSecondaryZoneId凌彬,因?yàn)閙NextSecondaryZoneId初始為1,也就是說primary為true的id=0,其他的從1開始累加
2.根據(jù)zone標(biāo)簽循衰,我們知道最終會創(chuàng)建多少個(gè)CarAudioZone铲敛。每個(gè)CarAudioZone包含一個(gè)VolumeGroups的集合,而集合的size是由<group>的數(shù)量決定会钝,每個(gè)<group>下的device以及他們下面的所有context組成了CarVolumeGroup伐蒋,每個(gè)CarVolumeGroup包含了一個(gè)contextNumber和busNumber組成的map,以及busNumber和CarAudioDeviceInfo組成的map迁酸,每個(gè)device下的音量最大先鱼,最小,當(dāng)前音量奸鬓,默認(rèn)音量都是一樣的焙畔,每個(gè)group下的所有音量的步長都是一樣的。
CarAudioZonesHelper(Context context, @NonNull InputStream inputStream,
@NonNull SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo) {
mContext = context;
mInputStream = inputStream;
mBusToCarAudioDeviceInfo = busToCarAudioDeviceInfo;
mNextSecondaryZoneId = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
mPortIds = new HashSet<>();
}
CarAudioZone[] loadAudioZones() throws IOException, XmlPullParserException {
List<CarAudioZone> carAudioZones = new ArrayList<>();
parseCarAudioZones(carAudioZones, mInputStream);
return carAudioZones.toArray(new CarAudioZone[0]);
}
上面根據(jù)配置文件串远,解析出CarAudioZone宏多,我們看CarAudioZone里面都有什么,通過CarAudioZone的構(gòu)造函數(shù)來看澡罚,CarAudioZone中會有一個(gè)CarVolumeGroups,這是CarVolumeGroup的集合
private final int mId;
private final String mName;
private final List<CarVolumeGroup> mVolumeGroups;
private final List<DisplayAddress.Physical> mPhysicalDisplayAddresses;
CarAudioZone(int id, String name) {
mId = id;
mName = name;
mVolumeGroups = new ArrayList<>();
mPhysicalDisplayAddresses = new ArrayList<>();
}
下面我們看一下CarVolumeGroup里面都有什么
mZoneId就是前面分析的zoneId
mId是我們每次mVolumeGroups .add的時(shí)候傳入的從0開始累加的一個(gè)數(shù)伸但,也就是list的索引
mStoredGainIndex 是數(shù)據(jù)庫存儲的值
/**
* Constructs a {@link CarVolumeGroup} instance
* @param context {@link Context} instance
* @param zoneId Audio zone this volume group belongs to
* @param id ID of this volume group
*/
CarVolumeGroup(Context context, int zoneId, int id) {
mContentResolver = context.getContentResolver();
mZoneId = zoneId;
mId = id;
mStoredGainIndex = Settings.Global.getInt(mContentResolver,
CarAudioService.getVolumeSettingsKeyForGroup(mZoneId, mId), -1);
}
busNumber和context在CarAudioZonesHelper有個(gè)map關(guān)系,有了
busNumber還可以通過mBusToCarAudioDeviceInfo.get(busNumber)找到CarAudioDeviceInfo留搔。這樣我們就可以做CarVolumeGroup的bind了
static {
CONTEXT_NAME_MAP = new HashMap<>();
CONTEXT_NAME_MAP.put("music", ContextNumber.MUSIC);
CONTEXT_NAME_MAP.put("navigation", ContextNumber.NAVIGATION);
CONTEXT_NAME_MAP.put("voice_command", ContextNumber.VOICE_COMMAND);
CONTEXT_NAME_MAP.put("call_ring", ContextNumber.CALL_RING);
CONTEXT_NAME_MAP.put("call", ContextNumber.CALL);
CONTEXT_NAME_MAP.put("alarm", ContextNumber.ALARM);
CONTEXT_NAME_MAP.put("notification", ContextNumber.NOTIFICATION);
CONTEXT_NAME_MAP.put("system_sound", ContextNumber.SYSTEM_SOUND);
}
private CarVolumeGroup parseVolumeGroup(XmlPullParser parser, int zoneId, int groupId)
throws XmlPullParserException, IOException {
final CarVolumeGroup group = new CarVolumeGroup(mContext, zoneId, groupId);
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
if (TAG_AUDIO_DEVICE.equals(parser.getName())) {
String address = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS);
parseVolumeGroupContexts(parser, group,
CarAudioDeviceInfo.parseDeviceAddress(address));
} else {
skip(parser);
}
}
return group;
}
private void parseVolumeGroupContexts(
XmlPullParser parser, CarVolumeGroup group, int busNumber)
throws XmlPullParserException, IOException {
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
if (TAG_CONTEXT.equals(parser.getName())) {
group.bind(
parseContextNumber(parser.getAttributeValue(NAMESPACE, ATTR_CONTEXT_NAME)),
busNumber, mBusToCarAudioDeviceInfo.get(busNumber));
}
// Always skip to upper level since we're at the lowest.
skip(parser);
}
}
在bind函數(shù)中更胖,因?yàn)間roup下的音量都是一個(gè)步長,所以步長只賦值一次。mContextToBus把contextNumber和busNumber存入map函喉,mBusToCarAudioDeviceInfo則是把busNumber和info保存下來,這樣就可以找到context與device之間的對應(yīng)關(guān)系荣月,一個(gè)device下不管有多少個(gè)context管呵,volume對應(yīng)的都是一次賦值。
/**
* Binds the context number to physical bus number and audio device port information.
* Because this may change the groups min/max values, thus invalidating an index computed from
* a gain before this call, all calls to this function must happen at startup before any
* set/getGainIndex calls.
*
* @param contextNumber Context number as defined in audio control HAL
* @param busNumber Physical bus number for the audio device port
* @param info {@link CarAudioDeviceInfo} instance relates to the physical bus
*/
void bind(int contextNumber, int busNumber, CarAudioDeviceInfo info) {
if (mBusToCarAudioDeviceInfo.size() == 0) {
mStepSize = info.getAudioGain().stepValue();
} else {
Preconditions.checkArgument(
info.getAudioGain().stepValue() == mStepSize,
"Gain controls within one group must have same step value");
}
mContextToBus.put(contextNumber, busNumber);
mBusToCarAudioDeviceInfo.put(busNumber, info);
if (info.getDefaultGain() > mDefaultGain) {
// We're arbitrarily selecting the highest bus default gain as the group's default.
mDefaultGain = info.getDefaultGain();
}
if (info.getMaxGain() > mMaxGain) {
mMaxGain = info.getMaxGain();
}
if (info.getMinGain() < mMinGain) {
mMinGain = info.getMinGain();
}
if (mStoredGainIndex < getMinGainIndex() || mStoredGainIndex > getMaxGainIndex()) {
// We expected to load a value from last boot, but if we didn't (perhaps this is the
// first boot ever?), then use the highest "default" we've seen to initialize
// ourselves.
mCurrentGainIndex = getIndexForGain(mDefaultGain);
} else {
// Just use the gain index we stored last time the gain was set (presumably during our
// last boot cycle).
mCurrentGainIndex = mStoredGainIndex;
}
}
本篇文章先分析到這里哺窄,接下來我們會分析動態(tài)路由部分,也就是setupAudioDynamicRouting()
// Setup dynamic routing rules by usage
final CarAudioDynamicRouting dynamicRouting = new CarAudioDynamicRouting(mCarAudioZones);
dynamicRouting.setupAudioDynamicRouting(builder);