常规Android系统Audio可插拔设备有:耳机(headset、headphones)、USB Audio设备、HDMI Audio设备,以耳机为例:Android耳机插拔可以有两个机制实现:
Android 系统中根据Android10.0/frameworks/base/core/res/res/values/config.xml中 config_useDevInputEventForAudioJack配置项决定InputEvent或UEvent,默认为false,即不使用InputEvent方式。
InputEvent的处理主要在frameworks/base/services/java/com/android/server/input/InputManagerService.java
该类的成员mUseDevInputEventForAudioJack决定选择 InputEvent 还是 UEvent,
在InputManagerService构造函数中会读取config.xml 中config_useDevInputEventForAudioJack值。
/** Whether to use the dev/input/event or uevent subsystem for the audio jack. */
309 final boolean mUseDevInputEventForAudioJack;
310
311 public InputManagerService(Context context) {
312 this.mContext = context;
313 this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
314
315 mUseDevInputEventForAudioJack =
316 context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
317 Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
318 + mUseDevInputEventForAudioJack);
319 ........
327 }
InputManagerService当收到native inputevent事件会调用函数:notifySwitch
// Native callback.
1753 private void notifySwitch(long whenNanos, int switchValues, int switchMask) {
1759 ........// 代码省略
1769 if (mUseDevInputEventForAudioJack && (switchMask & SW_JACK_BITS) != 0) {
1770 mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues,
1771 switchMask);
1772 }
1774 ........// 代码省略
1782 }
然后转到frameworks/base/services/core/java/com/android/server/WiredAccessoryManager.java 的
notifyWiredAccessoryChanged中
public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask) {
146 ........// 代码省略
182 updateLocked(NAME_H2W, "",
183 (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) | headset);
184 }
185 }
private void updateLocked(String newName, String address, int newState) {
210 // Retain only relevant bits
216 ........// 代码省略
270 Message msg;
271 // send a combined name, address string separated by |
272 if (newName.startsWith(NAME_DP_AUDIO)) {
273 int pseudoHeadsetState = mHeadsetState;
274 if (dpBitState && (newDpState != 0)) {
275 // One DP already connected, so allow request to connect second.
276 pseudoHeadsetState = mHeadsetState & (~BIT_HDMI_AUDIO);
277 }
278 msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState,
279 pseudoHeadsetState,
280 NAME_DP_AUDIO+"/"+address);
281
282 if ((headsetState == 0) && (mDpCount != 0)) {
283 // Atleast one DP is connected, so keep mHeadsetState's DP bit set.
284 headsetState = headsetState | BIT_HDMI_AUDIO;
285 }
286 } else {
287 msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState,
288 mHeadsetState,
289 newName+"/"+address);
290 }
291 mHandler.sendMessage(msg);
292
293 mHeadsetState = headsetState;
294 }
private final Handler mHandler = new Handler(Looper.myLooper(), null, true) {
297 @Override
298 public void handleMessage(Message msg) {
299 switch (msg.what) {
300 case MSG_NEW_DEVICE_STATE:
301 setDevicesState(msg.arg1, msg.arg2, (String) msg.obj);
302 mWakeLock.release();
303 break;
304 ........// 代码省略
308 }
309 }
310 };
311
312 private void setDevicesState(
313 int headsetState, int prevHeadsetState, String headsetNameAddr) {
314 synchronized (mLock) {
315 int allHeadsets = SUPPORTED_HEADSETS;
316 for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
317 if ((curHeadset & allHeadsets) != 0) {
318 setDeviceStateLocked(curHeadset, headsetState, prevHeadsetState,
319 headsetNameAddr);
320 allHeadsets &= ~curHeadset;
321 }
322 }
323 }
324 }
325
326 private void setDeviceStateLocked(int headset,
327 int headsetState, int prevHeadsetState, String headsetNameAddr) {
328
........// 代码省略
338
339 if (headset == BIT_HEADSET) {
340 outDevice = AudioManager.DEVICE_OUT_WIRED_HEADSET;
341 inDevice = AudioManager.DEVICE_IN_WIRED_HEADSET;
342 } else if (headset == BIT_HEADSET_NO_MIC) {
343 outDevice = AudioManager.DEVICE_OUT_WIRED_HEADPHONE;
344 } else if (headset == BIT_LINEOUT) {
345 outDevice = AudioManager.DEVICE_OUT_LINE;
346 } else if (headset == BIT_USB_HEADSET_ANLG) {
347 outDevice = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET;
348 } else if (headset == BIT_USB_HEADSET_DGTL) {
349 outDevice = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET;
350 } else if (headset == BIT_HDMI_AUDIO) {
351 outDevice = AudioManager.DEVICE_OUT_HDMI;
352 } else {
353 Slog.e(TAG, "setDeviceState() invalid headset type: " + headset);
354 return;
355 }
356
361
362 String[] hs = headsetNameAddr.split("/");
363 if (outDevice != 0) {
368 mAudioManager.setWiredDeviceConnectionState(outDevice, state,
369 (hs.length > 1 ? hs[1] : ""), hs[0]);
370 }
371 if (inDevice != 0) {
372
373 mAudioManager.setWiredDeviceConnectionState(inDevice, state,
374 (hs.length > 1 ? hs[1] : ""), hs[0]);
375 }
376 }
377 }
378
最终将设备插拔事件上报到AudioManager,最终完成设备切换。
通过上述流程分析,发现 inputevent 机制组要处理的设备类型有BIT_HEADSET、BIT_HEADSET_NO_MIC、BIT_LINEOUT这三种类型,所以HDMI Audio设备插拔事件应该选择UEvent 机制,而在Android Q中 kernel 3.5 以后改用 Extcon 取代switch class,所以HDMI Audio最终采用Extcon节点状态检测插拔事件。
首先确定何时建立插拔事件,那就要看下kernel 中 HDMI 驱动逻辑,以我目前用的lt9611芯片为例:首先驱动probe时,注册extcon:调用devm_extcon_hdmi_dev_register;然后当中断发生时同步state状态:调用extcon_set_state_sync;具体如下:
static int lt9611_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
........// 代码省略
ret = lt9611_gpio_configure(pdata, true);// 配置gpio引脚
........// 代码省略
ret = request_threaded_irq(pdata->irq, NULL, lt9611_irq_thread_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "lt9611", pdata); // 注册中断
i2c_set_clientdata(client, pdata);// 设置初始参数
dev_set_drvdata(&client->dev, pdata);// 设置初始参数
........// 代码省略
ret = lt9611_sysfs_init(&client->dev);
........// 代码省略
/* create switch device to update audio modules */
sdev_audio = extcon_dev_allocate(hdmi_ext_disp_supported_cable); // 分配extcon资源
if (!sdev_audio)
goto err_sysfs_init;
ret = extcon_dev_register(sdev_audio); //注册extcon
sdev_audio->name = "hdmi_audio";
// 同步初始状态
lt9611_write(pdata, 0xff, 0x82);
lt9611_read(pdata, 0x5e, ®_val, 1);
connected = (reg_val & BIT(2));
pr_info("connected = %d\n", !!connected);
extcon_set_state_sync(sdev_audio, EXTCON_DISP_HDMI, !!connected);
return ret;
当中断发生时(HDMI 插入/拔出),同步extcon节点状态:
static irqreturn_t lt9611_irq_thread_handler(int irq, void *dev_id)
{
........// 代码省略
/* hpd changed low */
if (irq_flag3 & 0x80) {
pr_info("hdmi cable disconnected\n");
........// 代码省略
extcon_set_state_sync(sdev_audio, EXTCON_DISP_HDMI, 0);
}
/* hpd changed high */
if (irq_flag3 & 0x40) {
pr_info("hdmi cable connected\n");
........// 代码省略
extcon_set_state_sync(sdev_audio, EXTCON_DISP_HDMI, 1);
}
........// 代码省略
return IRQ_HANDLED;
}
下面具体看下devm_extcon_dev_allocate、devm_extcon_hdmi_dev_register、extcon_set_state_sync实现。
代码路径 kernel/msm-4.9/drivers/extcon/extcon.c
1044 struct extcon_dev *extcon_dev_allocate(const unsigned int *supported_cable)
1045{
1046 struct extcon_dev *edev;
1047
1048 if (!supported_cable)
1049 return ERR_PTR(-EINVAL);
1050
1051 edev = kzalloc(sizeof(*edev), GFP_KERNEL);// 分配extcon资源
1052 if (!edev)
1053 return ERR_PTR(-ENOMEM);
1054
1055 edev->max_supported = 0;
1056 edev->supported_cable = supported_cable;
1057
1058 return edev;
1059}
其中supported_cable在驱动中定义:
static const unsigned int hdmi_ext_disp_supported_cable[] = {
EXTCON_DISP_HDMI,
EXTCON_NONE,
};
再看extcon_dev_register
1080 int extcon_dev_register(struct extcon_dev *edev)
1081{
1082 ........// 代码省略
1090
1091 if (!edev || !edev->supported_cable)
1092 return -EINVAL;
1093
1094 for (; edev->supported_cable[index] != EXTCON_NONE; index++);
1095
1096 edev->max_supported = index;
1097 if (index > SUPPORTED_CABLE_MAX) {
1098 dev_err(&edev->dev,
1099 "exceed the maximum number of supported cables\n");
1100 return -EINVAL;
1101 }
1102
1103 edev->dev.class = extcon_class;
1104 edev->dev.release = extcon_dev_release;
1105
1106 edev->name = dev_name(edev->dev.parent);// 设置name
1107 if (IS_ERR_OR_NULL(edev->name)) {
1108 dev_err(&edev->dev,
1109 "extcon device name is null\n");
1110 return -EINVAL;
1111 }
1112 dev_set_name(&edev->dev, "extcon%lu",
1113 (unsigned long)atomic_inc_return(&edev_no)); // 设置节点路径
1115
1116 ........// 代码省略
1117
1234 ret = device_register(&edev->dev);// device注册
1235 if (ret) {
1236 put_device(&edev->dev);
1237 goto err_dev;
1238 }
1239 ........// 代码省略
1269
1270 return 0;
1290}
最后来看下extcon_set_state_sync
594 int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id,
595 bool cable_state)
596{
597 int ret, index;
598 unsigned long flags;
599
600 index = find_cable_index_by_id(edev, id);
601 if (index < 0)
602 return index;
603
604 /* Check whether the external connector's state is changed. */
605 spin_lock_irqsave(&edev->lock, flags);
606 ret = is_extcon_changed(edev, index, cable_state);
607 spin_unlock_irqrestore(&edev->lock, flags);
608 if (!ret)
609 return 0;
610
611 ret = extcon_set_state(edev, id, cable_state);
612 if (ret < 0)
613 return ret;
614
615 return extcon_sync(edev, id);
616}
实现上述代码,就会在/sys/class/extcon生成extcon?节点,其中state和name比较重要
xxxx:/sys/class/extcon/extcon1 # ls
cable.0 cable.1 device name power state subsystem uevent
根据之前描述,framework中WiredAccessoryManager.java主要负责对插拔事件的管理
首先来看下WiredAccessoryManager的构造函数;
100 public WiredAccessoryManager(Context context, InputManagerService inputManager) {
101 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
102 mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryManager");
103 mWakeLock.setReferenceCounted(false);
104 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
105 mInputManager = inputManager;
106
107 mUseDevInputEventForAudioJack =
108 context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
109
110 mExtconObserver = new WiredAccessoryExtconObserver();//接受extcon事件
111 mObserver = new WiredAccessoryObserver();//接受uevent事件,已经废弃
112 }
对于HDMI extcon事件管理应该分为两个部分:1.系统启动时对连接状态的判断;2.系统启动后对插拔动作的判断,首先先来看第一种:
当Android系统启动时,WiredAccessoryManager对象就会创建,同时会调用onSystemReady函数
113 private void onSystemReady() {
114 // Inputevent 事件初始状态
115 if (mUseDevInputEventForAudioJack) {
116 .......// 代码省略
128 notifyWiredAccessoryChanged(0, switchValues,
129 SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_LINEOUT_INSERT_BIT);
130 }
131
132 // extcon和Uevent事件状态
133 if (ExtconUEventObserver.extconExists() && mExtconObserver.uEventCount() > 0) {
134 if (mUseDevInputEventForAudioJack) {
135 Log.w(TAG, "Both input event and extcon are used for audio jack,"
136 + " please just choose one.");
137 }
138 mExtconObserver.init();
139 } else {
140 mObserver.init();
141 }
142 }
可以看出当 ExtconUEventObserver.extconExists() && mExtconObserver.uEventCount() > 0 时会调用mExtconObserver.init();那么如何才能使之成立呢,那就要看WiredAccessoryExtconObserver构造函数。
744 WiredAccessoryExtconObserver() {
745 mExtconInfos = ExtconInfo.getExtconInfos(".*audio.*");
746 }
然后会调用getExtconInfos初始化mExtconInfos
93 public static List<ExtconInfo> getExtconInfos(@Nullable String regex) {
94 if (!extconExists()) {
95 return new ArrayList<>(0); // Always return a new list.
96 }
97 Pattern p = regex == null ? null : Pattern.compile(regex);
98 File file = new File("/sys/class/extcon");// 查选节点路径
99 File[] files = file.listFiles();
100 if (files == null) {
101 Slog.wtf(TAG, file + " exists " + file.exists() + " isDir " + file.isDirectory()
102 + " but listFiles returns null. "
103 + SELINUX_POLICIES_NEED_TO_BE_CHANGED);
104 return new ArrayList<>(0); // Always return a new list.
105 } else {
106 ArrayList list = new ArrayList(files.length);
107 for (File f : files) {
108 String name = f.getName();// 获取节点目录名并与regex做比较
109 if (p == null || p.matcher(name).matches()) {
110 ExtconInfo uei = new ExtconInfo(name);
111 list.add(uei);
112 if (LOG) Slog.d(TAG, name + " matches " + regex);
113 } else {
114 if (LOG) Slog.d(TAG, name + " does not match " + regex);
115 }
116 }
117 return list;
118 }
119 }
同上述代码可以看出,该函数会从/sys/class/extcon路径下找寻有无regex目录,到现在可以发现存在一下两个问题:1.此时regex为".audio.",表示带audio的任何目录,而通过上文extcon节点创建目录为:/sys/class/extcon/extcon#,#号为顺序号不固定,所以再回看extcon节点目录创建的地方
1112 dev_set_name(&edev->dev, "extcon%lu",
1113 (unsigned long)atomic_inc_return(&edev_no)); // 设置节点路径
1115
如果调用原生extcon注册接口创建只可能为extcon#目录,于是我想到两个修改方案:
1.修改原生接口实现:
1112 dev_set_name(&edev->dev, "extcon%lu",
1113 (unsigned long)atomic_inc_return(&edev_no)); // 设置节点路径
1115
1112 dev_set_name(&edev->dev, "audio%lu",
1113 (unsigned long)atomic_inc_return(&edev_no)); // 设置节点路径为audio
1115
但是,考虑到,除了HDMI audio extcon时间外其他extcon 例如usb host也调用此接口,所以此方案不可行。
2. 模拟原生接口,实现extcon注册过程:
1080int extcon_dev_register(struct extcon_dev *edev)
1081{
1082 ........// 代码省略
1112 dev_set_name(&edev->dev, "extcon%lu",
1113 (unsigned long)atomic_inc_return(&edev_no)); // 设置节点路径
1115 ........// 代码省略
1290}
1080int extcon_hdmi_dev_register(struct extcon_dev *edev)
1081{
1082 ........// 代码省略e
1112 dev_set_name(&edev->dev, "audio%lu",
1113 (unsigned long)atomic_inc_return(&edev_no)); // 设置节点路径audio
1115 ........// 代码省略
1290}
函数内部实现只改动节点路径audio,其他不变,此方案验证可行。
xxxx:/sys/class/extcon # ls
audio0 extcon0 extcon1
同时观查插拔HDMI 设备state节点变化
插入
xxxx:cat /sys/class/extcon/audio0/state
HDMI=1
拔出
xxxx:cat /sys/class/extcon/audio0/state
HDMI=0
然后,再重新回到WiredAccessoryExtconObserver,现在虽然extcon节点已经创建,并与WiredAccessoryExtconObserver,然后使得if (ExtconUEventObserver.extconExists() && mExtconObserver.uEventCount() > 0) 条件满足,然后调用mExtconObserver.init()
749 private void init() {
750 for (ExtconInfo extconInfo : mExtconInfos) {
751 Pair<Integer, Integer> state = null;
752 try {
753 state = parseStateFromFile(extconInfo);
754 } catch (FileNotFoundException e) {
755 Slog.w(TAG, extconInfo.getStatePath()
756 + " not found while attempting to determine initial state", e);
757 } catch (IOException e) {
758 Slog.e(
759 TAG,
760 "Error reading " + extconInfo.getStatePath()
761 + " while attempting to determine initial state",
762 e);
763 }
764 if (state != null) {
765 updateState(extconInfo, extconInfo.getName(), state);
766 }
767 if (LOG) Slog.d(TAG, "observing " + extconInfo.getName());
768 startObserving(extconInfo);
769 }
770
771 }
然后读取state状态
45 public S parseStateFromFile(ExtconInfo extconInfo) throws IOException {
46 String statePath = extconInfo.getStatePath();
47 return parseState(
48 extconInfo,
49 FileUtils.readTextFile(new File(statePath), 0, null).trim());
50 }
但此时抛出异常,通过分析发现由于selinux机制导致,没有权限访问该目录,所以尝试加入selinux权限,过程比较痛苦,这里不详细介绍,有兴趣可以查询selinux相关知识,这里只提一点:
尝试在/sys/class/extcon/audio0(/.*)? 失败,查看原因为改路径存在链接,实际路径为:
/sys/devices/platform/soc/7af6000.i2c/i2c-6/6-003b/extcon/audio0(/.*)?
以上具体针对系统实际情况作修改。
完成上述爬坑过程,最终获取到节点state状态值,然后调用updateState,并startObserving这个节点变化。
792 public void updateState(ExtconInfo extconInfo, String name,
793 Pair<Integer, Integer> maskAndState) {
794 synchronized (mLock) {
795 int mask = maskAndState.first;
796 int state = maskAndState.second;
797 updateLocked(name, "", mHeadsetState & ~(mask & ~state) | (mask & state));
798 return;
799 }
800 }
*/
209 private void updateLocked(String newName, String address, int newState) {
210 // Retain only relevant bits
211 int headsetState = newState & SUPPORTED_HEADSETS;
212 int newDpState = newState & BIT_HDMI_AUDIO;
213 int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG;
214 int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL;
215 int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT);
216 ........// 代码省略
228
229 if (mHeadsetState == headsetState && !newName.startsWith(NAME_DP_AUDIO)) {
230 Log.e(TAG, "No state change.");
231 return;
232 }
233
234 // reject all suspect transitions: only accept state changes from:
235 // - a: 0 headset to 1 headset
236 // - b: 1 headset to 0 headset
237 if (h2w_headset == (BIT_HEADSET | BIT_HEADSET_NO_MIC | BIT_LINEOUT)) {
238 Log.e(TAG, "Invalid combination, unsetting h2w flag");
239 h2wStateChange = false;
240 }
241 ........// 代码省略
269
270 Message msg;
271 // send a combined name, address string separated by |
272 ........// 代码省略
286 } else {
287 msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState,
288 mHeadsetState,
289 newName+"/"+address);
290 }
291 mHandler.sendMessage(msg);
292
293 mHeadsetState = headsetState;
294 }
296 private final Handler mHandler = new Handler(Looper.myLooper(), null, true) {
297 @Override
298 public void handleMessage(Message msg) {
299 switch (msg.what) {
300 case MSG_NEW_DEVICE_STATE:
301 setDevicesState(msg.arg1, msg.arg2, (String) msg.obj);
302 mWakeLock.release();
303 break;
304 ........// 代码省略
308 }
309 }
310 };
312 private void setDevicesState(
313 int headsetState, int prevHeadsetState, String headsetNameAddr) {
314 synchronized (mLock) {
315 int allHeadsets = SUPPORTED_HEADSETS;
316 for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
317 if ((curHeadset & allHeadsets) != 0) {
318 setDeviceStateLocked(curHeadset, headsetState, prevHeadsetState,
319 headsetNameAddr);
320 allHeadsets &= ~curHeadset;
321 }
322 }
323 }
324 }
private void setDeviceStateLocked(int headset,
327 int headsetState, int prevHeadsetState, String headsetNameAddr) {
328 if ((headsetState & headset) != (prevHeadsetState & headset)) {
329 int outDevice = 0;
330 int inDevice = 0;
331 int state;
332
333 if ((headsetState & headset) != 0) {
334 state = 1;
335 } else {
336 state = 0;
337 }
338
339 if (headset == BIT_HEADSET) {
340 outDevice = AudioManager.DEVICE_OUT_WIRED_HEADSET;
341 inDevice = AudioManager.DEVICE_IN_WIRED_HEADSET;
342 } else if (headset == BIT_HEADSET_NO_MIC) {
343 outDevice = AudioManager.DEVICE_OUT_WIRED_HEADPHONE;
344 } else if (headset == BIT_LINEOUT) {
345 outDevice = AudioManager.DEVICE_OUT_LINE;
346 } else if (headset == BIT_USB_HEADSET_ANLG) {
347 outDevice = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET;
348 } else if (headset == BIT_USB_HEADSET_DGTL) {
349 outDevice = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET;
350 } else if (headset == BIT_HDMI_AUDIO) {
351 outDevice = AudioManager.DEVICE_OUT_HDMI;
352 } else {
353 Slog.e(TAG, "setDeviceState() invalid headset type: " + headset);
354 return;
355 }
356
357 ........// 代码省略
361
362 String[] hs = headsetNameAddr.split("/");
363 if (outDevice != 0) {
364 ........// 代码省略
368 mAudioManager.setWiredDeviceConnectionState(outDevice, state,
369 (hs.length > 1 ? hs[1] : ""), hs[0]);
370 }
371 if (inDevice != 0) {
372
373 mAudioManager.setWiredDeviceConnectionState(inDevice, state,
374 (hs.length > 1 ? hs[1] : ""), hs[0]);
375 }
376 }
377 }
最终完成系统启动是的HDMI audio设备状态上报;
下面来看下,HDMI audio设备插拔事件上报:
当uevent事件检测到HDMI audio设备插拔事件时,WiredAccessoryExtconObserver通过 onUEvent得知,WiredAccessoryExtconObserver继承的是ExtconStateObserver.java。
public void onUEvent(ExtconInfo extconInfo, UEvent event) {
54 if (LOG) Slog.d(TAG, extconInfo.getName() + " UEVENT: " + event);
55 String name = event.get("NAME");
56 S state = parseState(extconInfo, event.get("STATE"));
57 if (state != null) {
58 updateState(extconInfo, name, state);
59 }
60 }
也会调用updateState函数,然后完成插拔事件上报;
最终通过AudioManager的setWiredDeviceConnectionState接口将HDMI Audio devices状态上报值framework层,完成audio竞合、设备切换,具体内容会在以后文章中体现。
本文为原创文章,转载请注明出处!!!!