一、引言:
输入/输出通路选择是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中去实现的。

更多相关文章

  1. android设备常用的adb命令
  2. 在 Android 设备上搭建 Web 服务器
  3. Settings中蓝牙连接流程
  4. Android 8 蓝牙打开过程
  5. Android 7.1使用以太网口共享本机4G网络给其他设备
  6. Android 获取设备的物理尺寸
  7. Afaria 做Android设备lock时候发给GCM的指令
  8. 客户要求整个android设备只有一个APP
  9. Android设备抓包命令及分析

随机推荐

  1. Android高手进阶教程(十七)之---Android
  2. Android(安卓)图片缩放-Matrix
  3. Android之父Andy Rubin:被乔布斯羡慕嫉妒
  4. Android的DrawerLayout全屏滑动显示
  5. Android键盘“enter”键设置为“下一项”
  6. [置顶] Android基本框架学习之defaultSer
  7. Framework篇 - Android(安卓)系统介绍和
  8. Android开发工具全面转向Android(安卓)St
  9. Android中UI设计的一些技巧!!!
  10. 写给Android开发者的Kotlin入门