一、引言:
输入/输出通路选择是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耳机和一个自带的声卡:

可以看到声卡的名字和声卡ID;
【b.查看当前声卡是否是录音或者播放】:

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

比如我当前插入了USB耳机,并且进行了播放,那么从Android系统来讲,策略会选择USB耳机:

可以看到数据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的时候就可以写到对应的设备中去了。下面给一个原理框图:

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 蓝牙开发
  2. WordPress for Android(安卓)已更新到2.0,网站管理也移动了
  3. adb网络调试
  4. 在 Android(安卓)设备上搭建 Web 服务器
  5. android设备常用的adb命令
  6. 自动 Android* 应用测试
  7. Settings中蓝牙连接流程
  8. Android(安卓)8 蓝牙打开过程
  9. Android的drawable文件夹的说明

随机推荐

  1. 一步教你超简单设置Android(安卓)Studio
  2. Android(安卓)NDK 实现视音频播放器源码
  3. Android高级进阶之路【六】Android(安卓)
  4. android下载封装类
  5. android 开发 制作弹出等待进度条
  6. Android(安卓)重力感应选号码
  7. Android改变button背景色与获取button背
  8. sdk manage 更新与安装
  9. Android(安卓)设立全屏
  10. 在android中举例说明如何用WebView.loadU