一、引言:
有时候,我们在实际处理问题中会遇到这样的需求,播放一段音频或者播报一段语音希望同时从USB/蓝牙类设备和喇叭同时出声,按照Android的audiopolicy策略选择,这是不可行的,因为同一时间,audioflinger只会往一个hal层库里面写数据,而喇叭和USB/蓝牙都不是共用一个hal层的,这种情况下有些芯片厂商是怎么做的呢?最近,正好在公司遇到了这样一个需求,期望从USB的mic模组和HDMI同时出声,因为我们使用的是海思平台,通过分析代码,发现海思做了一个不错的通路来实现了这样的一个需求,该通路叫cast,本博文着重分析了海思的cast通路。

二、cast通路原理:

海思针对usb和a2dp类设备,与HDMI共存的问题上,添加了cast通路,这样可以保证在两种设备都存在的情况下,通过静音的调控实现HDMI与插入类设备同时出声或者仅插入类设备出声的实现。Cast通路只能存在于播放器或者apk在调用海思私有通路的场景下。海思平台的产品,驱动可以通过两个通路将数据发送给硬件逻辑层:

Android Audio 多路输出 多路音频输出设备_数据


可以看到:

① 海思drv中可以使用adsp或者alsa将数据发送给硬件逻辑层,cast通路捞取的是adsp中的数据;
② Tvos上的sme播放器,4.4上面的hiplayer以及中间件厂商的dvbplayer都是走的图中右边私有通路方式,CMCC播放器或者第三方apk则是通过创建Android原生audiotrack的方式(图左)往驱动中送数据,这部分数据将无法通过cast通路获取;
三、cast通路实现:

再来看下Android中海思是如何创建cast通路的,cast通路的存在/销毁是在处理USB/蓝牙的热插拔事件中实现的:首先,cast相关操作是在hiaoservice中(这是一个binder服务),在热插拔中事件处理中会去调用hicardplayroute函数获取hiaoservice,然后hiaoservice就会去创建一个castthread,开启cast通路了:

Android Audio 多路输出 多路音频输出设备_Android Audio 多路输出_02


结合代码和框图,可以得知:

① Cast通路中开辟了一个本地buffer,用于拷贝adsp中的数据;
② Cast通路还新建了一个audiotrack(Android原生通路),本地buffer的作用就是将adsp中的数据拷贝到audiotrack中;
③ 本地buffer往audiotrack中写数据的方式是通过回调函数,而不是调用write方法;
④ USB/蓝牙设备之所以能听到声音根本原因是该声音由cast通路从adsp中捞取的,USB和HDMI设备其实一直都是有数据的,只不过HDMI设备被静音了,所以听不到;
⑤ 控制HDMI和USB同时出声只需要设置如下属性:
setprop sys.audio.usbcardmutectl false