Android音频系统之USB设备通路(Android 5.1)

一、引言:
输入/输出通路选择是Android音频中非常重要的一个内容,正常的一个Android系统,会支持喇叭,外放,USB设备或者蓝牙等等输出模组,所以,经常会有项目需要改变原有的策略选择,这类问题通常让人头大,在Android 5.1上面,策略选择是由audiopolicy来做的,audioflinger去执行下面的输入/输出设备的打开,所以,在实际处理中,一定要根据具体问题,多去分析audiopolicy的策略选择,才能解决,我现在的工作,对这类问题接触的很少,因为没有多余的设备,所以,这篇博文旨在分析一些代码逻辑和部分常见问题的处理思路。

二、代码分析:
【1.通过alsa指令来分析声卡】:
Android 5.1之后,audio的hal层主要是tinyalsa了,所以,对于当前Android音频系统,我们首先需要学习一些alsa的指令来帮助我们分析。
【a.查看当前系统连接的声卡】:

cat /proc/asound/cards

比如我当前环境中插入了一个USB耳机和一个自带的声卡:
Android音频系统之USB设备通路(Android 5.1)_第1张图片
可以看到声卡的名字和声卡ID;
【b.查看当前声卡是否是录音或者播放】:

cat /proc/asound/card2/pcm0p/sub0/status

比如我当前插入了USB耳机,并且进行了播放,那么从Android系统来讲,策略会选择USB耳机:
Android音频系统之USB设备通路(Android 5.1)_第2张图片
可以看到数据hw_ptr是不停地在写数据,我们对比看下声卡0的状态:
在这里插入图片描述
的确是关闭的,同样,如果是录音的话,将指令修改一下即可(pcm0p改为pcm0c):

cat /proc/asound/card2/pcm0c/sub0/status

【2.热插拔事件响应】:
热插拔事件是经常出现的一种场景,毕竟,你的Android设备不可能永远有着蓝牙或者USB耳机,热插拔处理是由上层服务来执行的,关于这个的讲解网上很多,我们就直接从framework层开始,以Android 5.1为例,上层会调入setDeviceConnectionState@\AudioPolicyManager.cpp中:

status_t AudioPolicyManager::setDeviceConnectionState(audio_devices_t device,
                                                          audio_policy_dev_state_t state,
                                                  const char *device_address)
{
    status_t ret = NO_ERROR;

    ALOGD("setDeviceConnectionState() device: %x, state %d, address %s",
            device, state, device_address != NULL ? device_address : "");

    if (device == AUDIO_DEVICE_IN_REMOTE_SUBMIX && device_address)  {
        AudioParameter parameters = AudioParameter(String8(device_address));
        int forceValue;

        if (parameters.getInt(String8("force"), forceValue) == OK)  {
            ALOGD("setDeviceConnectionState() forceValue = %d", forceValue);
            mForceSubmixInputSelection = forceValue !=  0;
        }
    }

    if (device != AUDIO_DEVICE_IN_REMOTE_SUBMIX) {
        ret = setDeviceConnectionStateInt(device, state, device_address);
    }

    return  ret;
}

首先,我们需要注意,java层中有一个文件叫AudioSystem.java,里面维护了我们经常遇到的流类型,设备类型和强制通路切换等变量,这都是跟framework层是一一对应的,所以,上层在识别了热插拔处理后,也会正确判断插入设备的device,然后传下来,setDeviceConnectionState仅仅是个中转,我们继续往下看setDeviceConnectionStateInt,这个函数有点长:

status_t AudioPolicyManager::setDeviceConnectionStateInt(audio_devices_t device,
                                                         audio_policy_dev_state_t state,
                                                         const char *device_address)
{
	...
    /* 处理输出 */
    if (audio_is_output_device(device)) {	
		...
        switch (state)
        {
        /* 处理输出设备连接 */
        case AUDIO_POLICY_DEVICE_STATE_AVAILABLE: {		
            /* 首先是通过device去找到module */
            index = mAvailableOutputDevices.add(devDesc);
            if (index >= 0) {
                sp<HwModule> module = getModuleForDevice(device);
                if (module == 0) {
                    ALOGD("setDeviceConnectionState() could not find HW module for device %08x",
                          device);
                    mAvailableOutputDevices.remove(devDesc);
                    return INVALID_OPERATION;
                }
                mAvailableOutputDevices[index]->mId = nextUniqueId();
                mAvailableOutputDevices[index]->mModule = module;
            } else {
                return NO_MEMORY;
            }
            /* 这里会去hal层打开这个output,看是否是OK的 */
            if (checkOutputsForDevice(devDesc, state, outputs, devDesc->mAddress) != NO_ERROR) {
                mAvailableOutputDevices.remove(devDesc);
                return INVALID_OPERATION;
            } 
            ...       
		}
		/* 处理输出设备断开连接 */
        case AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE: {
		...
		checkOutputsForDevice(devDesc, state, outputs, devDesc->mAddress);
            } break;	
		}
		/* 确认策略 */
        checkA2dpSuspend();
        checkOutputForAllStrategies();
        ...
    }
    /* 处理输入 */
    if (audio_is_input_device(device)) {
		...		
	}
	...
}

函数比较冗杂,分为输入类型和输出类型进行处理,这里需要注意一下,有的设备同时支持输入和输出,比如AUDIO_DEVICE_IN_USB_DEVICE,在调试代码的时候你会发现处理两次setDeviceConnectionStateInt,就是分别针对输入和输出的,我们以输出为例分析一下是怎么做的,首先,要通过device是找到对应的module,这个module很重要,它对应了不同的hal层库,比如我的系统当前有三个hal层库,分别是primary,usb和a2dp(即蓝牙,支持立体声播放的),既然找到了module,那么必然要尝试按照这个device去hal层打开输出流,看是否OK,不同的module对应的device在哪里看,也就是audiopolicyservice启机时候加载的audio_policy.conf文件,如果在实际问题处理中,你认为的device没有进入到对应的hal层时,请注意在audio_policy.conf中对应的module下面去添加device。
如何去打开输入输出流,那么肯定是需要audioflinger来了,checkOutputsForDevice函数中,往下就会去调用audioflinger去执行了:

checkOutputsForDevice@AudioPolicyManager.cpp:

{
	...
	status_t status = mpClientInterface->openOutput(profile->mModule->mHandle,
                                                            &output,
                                                            &config,
                                                            &desc->mDevice,
                                                            address,
                                                            &desc->mLatency,
                                                            desc->mFlags);
	...
}

Android 5.1开始,引入了profile的概念,使得hal层更加的规范,所以,对于device及其配置,都是按照profile中的设置去到hal层中打开的。
再次回到setDeviceConnectionStateInt函数,我们看到最后还有策略选择,这是因为,同时存在多个device情况,系统需要按照优先级来进行选择,这里就不具体分析了,因为之前在audiopolicy的时候已经分析过了,尤其是注意你有蓝牙,USB和speaker时,优先级蓝牙是最高的,所以如果你的需求是想同时从蓝牙和speaker输出,那么,你是需要强制修改Android策略的。
再往下的分析就不进行了,下面针对以前处理过的一些问题说下思路。

三、案例分析:
【1.留心蓝牙设备可能是USB类型】:
这类案例的特点就是蓝牙接收器,比如蓝牙语音遥控器,dongle,他们有一个usb接口的蓝牙接收头,接收头和遥控器之间使用的是蓝牙协议,然后,整个蓝牙设备与Android之间通信使用的是USB协议,这类设备的识别就是看alsa的声卡信息,看类型是否是USB的,如果看到是USB类型的,那么统一按照USB设备处理;

【2.USB多输入选择】:
这类问题遇到的更多一些,比如用户同时插入了两个及以上的USB类型设备,一个USB耳机,还有带录音功能的摄像头等等,而Android本身的策略是选择第一个识别到的USB设备进行加载,进行录音或者播放,客户往往想定向选择,只能调节USB插拔的顺序,非常麻烦,而且,如果是插着USB设备启机,就不能进行定向的选择了,那么,这类问题,从什么地方进行策略修改呢?
不同的Android版本会有不同的策略安排,但修改都是在hal层中实现的,在Android 5.1上,Android已经支持了USB通路,所以,我们需要去usb的hal层进行修改,这里还需要注意一点就是,两个USB设备都存在的情况下,因为都是走的相同的hal层库,所以不可能支持动态设备切换的情况,必须在录音/播放之前就确定好声卡,原理是拓展java接口,经过JNI然后到达framework层,在这一层可以设置一个property作为标记,之后,当录音或者播放时,肯定都会走入到hal层的write函数中,在这里面添加选择声卡的函数,获取java接口设下来的property,然后通过读取前面的alsa指令来确认声卡ID,那么,在write的时候就可以写到对应的设备中去了。下面给一个原理框图:
Android音频系统之USB设备通路(Android 5.1)_第3张图片
1.自行扩展java接口,可通过JNI查询和设置期望的usb声卡数;
2.native层建议使用binder机制,这样java层随时设置下面binder机制都能收到;
3.JNI通路的最终目的是设置一个property,因为这条通路你不会跟audiotrack有交集;
4.hal层的最终目的,就是audioflinger的数据写下来时,通过getprop获取期望的USB声卡,然后通过访问alsa的设备节点信息找到那个对应的USB设备声卡号,再往里面写数据即可;
5.一般来说,USB设备不会有相同的类型名字,比如摄像头和USB耳机,名字肯定是不一样的;

Android4.4上面因为走的是legacy通路,所以audiopolicy这些文件有些不同,但是原理是一样的,在4.4上面的策略和5.1一样,只不过我们hal层的处理不同,因为4.4没有原生的usb的hal层,所以对于usb设备录音/播放,你需要到对应的hal层中去做,比如我这边有个4.4的代码,usb通路走的是alsa_sound,那么,我上述hal层的实现就是在alsa_default.cpp中去实现的。

你可能感兴趣的:(Android音频)