Android9.0CarAudio分析之而AUDIO_DEVICE_OUT_BUS

最近这几年一直从事车载相关的开发,国内一般车载项目使用最多的系统目前基本应该就是Andoid了,尤其新兴的一些新能源汽车基本搭载的车载系统都是基于Android深度定制的。其实谷歌也搞了套车载东西,今天我们继续说说与汽车相关的 Android的音频架构。

正题

上一篇分析了CarAudioService的启动过程,今天继续,先分析下CarAudioService的init过程,相比于Android8.0,这部分改动还是挺大的。

    @Override
    public void init() {
        synchronized (mImplLock) {
            if (!mUseDynamicRouting) {
                Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not configured, run in legacy mode");
                setupLegacyVolumeChangedListener();
            } else {
                setupDynamicRouting();
                setupVolumeGroups();
            }
        }
    }

如果我们使用的是AUDIO_DEVICE_OUT_BUS,那么mUseDynamicRouting这个值一定是true的,mUseDynamicRouting是怎么来的呢?

mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);

是在CarAudioService的构造函数中,通过xml读出来的,如果我们想使用AUDIO_DEVICE_OUT_BUS这种方式来实现音频输出策略,记得把这个值改为true

packages/services/Car/service/res/values/config.xml 
<bool name="audioUseDynamicRouting">false</bool>

但很少有人会改动这个目录下的xml,通常厂商在做定制的时候,一般都会通过在device目录下overlay的方式来实现修改。
其实CarAudioService的init只做了两件事情,setupDynamicRouting()和 setupVolumeGroups(),我们今天的重点就是setupDynamicRouting(),先看下代码:

    private void setupDynamicRouting() {
    	//audiocontrol 对应在native的 hal层,和java层的交互通过HIDL的方式实现,(HIDL在ANdroid的新版本应用越来越广泛起来了)这部分一般都会被定制。
        final IAudioControl audioControl = getAudioControl();
        if (audioControl == null) {
            return;
        }
        //AudioPolicy 是Android很重要的一个组成部分,做为native对外的api,我们可以直接拿来使用,定制自己声音路由策略,以及音频焦点优先级策略。
        AudioPolicy audioPolicy = getDynamicAudioPolicy(audioControl);
        int r = mAudioManager.registerAudioPolicy(audioPolicy);
        if (r != AudioManager.SUCCESS) {
            throw new RuntimeException("registerAudioPolicy failed " + r);
        }
        mAudioPolicy = audioPolicy;
    }

这部分代码逻辑不是很复杂,只是将AudioControl的实例传给了getDynamicAudioPolicy()方法,继续分析getDynamicAudioPolicy(),代码如下:

    @Nullable
    private AudioPolicy getDynamicAudioPolicy(@NonNull IAudioControl audioControl) {
        //构建audiopolicy
        AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
        builder.setLooper(Looper.getMainLooper());

        // 1st, enumerate all output bus device ports
        //这部分是获取device是输出设备的的设备信息,而这些设备的信息,是存在audiopolicy配置文件中的,
        //在Android7.0之后使用的是audio_policy_configuration.xml配置文件。关于AudioPolicy对于这些文件的加载参照
       // [Android9.0AudioPolicy之audio_policy_configuration.xml解析(一)](https://segmentfault.com/a/1190000020109354)
        AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
        if (deviceInfos.length == 0) {
            Log.e(CarLog.TAG_AUDIO, "getDynamicAudioPolicy, no output device available, ignore");
            return null;
        }
        //拿到这些输出的outdevice信息后,继续过滤出device是AUDIO_DEVICE_OUT_BUS的设备信息
        for (AudioDeviceInfo info : deviceInfos) {
            Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s",
                    info.getId(), info.getAddress(), info.getType()));
            if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
                final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info);
                // See also the audio_policy_configuration.xml and getBusForContext in
                // audio control HAL, the bus number should be no less than zero.
                // 创建CarAudioDeviceInfo 并将这些放入集合中,BusNumber就是audio_policy_configuration中device标签下
                // address属性里bus后面的那个数字,一般定义都是BUS1,BUS2或者BUS001,BUS002等BUS后面的数字最多3位,
                // 要是搞个BUS1111那就error啦。
                if (carInfo.getBusNumber() >= 0) {
                    mCarAudioDeviceInfos.put(carInfo.getBusNumber(), carInfo);
                    Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
                }
            }
        }

        // 2nd, map context to physical bus
        // 通过英文源码的注释我们也能大体明白了,就是开始做路由bus策略的映射。可以简单理解为AudioAttribute的usage与
        // AUDIO_OUT_DEVICE_BUS的映射,虽然说的不太准确但最终目的是这样的。
        try {
            for (int contextNumber : CONTEXT_NUMBERS) {
            	// 将contextNumber通过AudioControl与busNumber做map映射,同时存入集合,这里简单举个例子,方便理解,
            	// 比如第CONTEXT_NUMBERS中的第一个元素ContextNumber.MUSIC(值为1),对应到AudioControl中的BUS为sContextToBusMap[1] =0,即 mContextToBus.put(1, 0).
                int busNumber = audioControl.getBusForContext(contextNumber);
                mContextToBus.put(contextNumber, busNumber);
                CarAudioDeviceInfo info = mCarAudioDeviceInfos.get(busNumber);
                if (info == null) {
                    Log.w(CarLog.TAG_AUDIO, "No bus configured for context: " + contextNumber);
                }
            }
        } catch (RemoteException e) {
            Log.e(CarLog.TAG_AUDIO, "Error mapping context to physical bus", e);
        }

        // 3rd, enumerate all physical buses and build the routing policy.
        // Note that one can not register audio mix for same bus more than once.
        // 我们拿到了out的device,又拿到了contextNumber和busNumber的映射,那么第三部分就是真正实现他们的组装。
        // 我们也是通过一个例子来说明下
        for (int i = 0; i < mCarAudioDeviceInfos.size(); i++) {
            //对应的第一个busNumber,值是0.
            int busNumber = mCarAudioDeviceInfos.keyAt(i);
            boolean hasContext = false;
            CarAudioDeviceInfo info = mCarAudioDeviceInfos.valueAt(i);
            AudioFormat mixFormat = new AudioFormat.Builder()
                    .setSampleRate(info.getSampleRate())
                    .setEncoding(info.getEncodingFormat())
                    .setChannelMask(info.getChannelCount())
                    .build();
            AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
            for (int j = 0; j < mContextToBus.size(); j++) {
            	//刚才已经说明mContextToBus.put的第一个元素是(1,0),mContextToBus.valueAt(j)即等于busNumber
                if (mContextToBus.valueAt(j) == busNumber) {
                    hasContext = true;
                    // 此时 contextNumber是1
                    int contextNumber = mContextToBus.keyAt(j);
                    // 通过contextNumber拿到AudioAttriute的usage。为什么我之前说
                    // contextNumber和busnumber是usage和bus的映射是不准确的,因为contextNumber在这里才需真正映射到对应的usage上,
                    // 这里为AudioAttributes.USAGE_MEDIA
                    int[] usages = getUsagesForContext(contextNumber);
                    for (int usage : usages) {
                        // 将usage封装到AudioMixingRule中,
                        mixingRuleBuilder.addRule(
                                new AudioAttributes.Builder().setUsage(usage).build(),
                                AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
                    }
                    Log.i(CarLog.TAG_AUDIO, "Bus number: " + busNumber
                            + " contextNumber: " + contextNumber
                            + " sampleRate: " + info.getSampleRate()
                            + " channels: " + info.getChannelCount()
                            + " usages: " + Arrays.toString(usages));
                }
            }
            //我们知道mContextToBus.valueAt(j) == busNumber,因此hasContext为true
            if (hasContext) {
                // It's a valid case that an audio output bus is defined in
                // audio_policy_configuration and no context is assigned to it.
                // In such case, do not build a policy mix with zero rules.
                // 最终将封装了AudioAttribute的AudioMixingRule与对应的AudioDeviceInfo一同组装到AudioMix中。
                // 也就是把输出通路与AudioAttribute映射在一起了,这样我们通过Audiotrack或者mediaplayer等音视频播放器播放时,
                // 只要指定了AudioAttributes,也就指定了输出设备。
                AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
                        .setFormat(mixFormat)
                        .setDevice(info.getAudioDeviceInfo())
                        .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
                        .build();
                 // 将AudioMix add到AudioPolicy中
                builder.addMix(audioMix);
            }
        }

        // 4th, attach the {@link AudioPolicyVolumeCallback}
        builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);

        return builder.build();
    }

上边代码有些长,但是核心的东西。我们把AudioAttribute和AUDIO_DEVICE_OUT_BUS映射起来,存入AudioMix并通过mAudioManager.registerAudioPolicy(audioPolicy)注册下去,最终会在Audio PolicyManagerj进行路由策略时优先对应我们注册下来的这些策略。

总结

我们做Android原生的定制的时候,我们知道新增一个路由策略,我们要从java层新追加定义AudioStream开始,一步一步通过AudioTrack到jni到AudioPolicy,到Engine的strategy。我们全部都要定义,对于初学是难度很大的,因为每一步的逻辑都是需要完全理解的。而AUDIO_DEVICE_OUT_BUS的形式会大大缩短我们的定制风险和难度。当然并不是每一种方案都是完美的,比如基于AUDIO_DEVICE_OUT_BUS的这种策略我们可以满足不同声音不同通路输出的需求,但如果在想通过软件来调节音量可能有些情况就不能满足了,因此我们还需要根据具体需求去做具体处理。后面我们会继续分析AUDIO_DEVICE_OUT_BUS的策略修改与定制

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

轻量级LZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值