Android Q CarAudio 汽车音频学习笔记

Android Q CarAudio 汽车音频学习笔记

本文为梳理CarAudio源码的随手笔记,也是在学习阶段,如果有错误或者理解偏差,欢迎指正

一、Q的行为变更

1.1动态路由支持

在Car的领域里,使用设置audioUseDynamicRouting属性在config.xml来打开动态音频路由,默认为false,谷歌建议打开

/packages/services/Car/service/res/values/config.xml
在这里插入图片描述

dynamic routing 开启

Audio devices 按照(zones)区域进行分组

最少有一个通用的的Zone(域),扩展第二个Zone例如RSE(Reat?Rear Seat Entertainment)后排娱乐

每一个域中的audio devices都被分到volume groups用来控制音频,且每个zone里有独立的audiofocus

按照AudioAttributes里的usage制定音频到audio device

dynamic routing 关闭

只有一个通用的域

每一个音频组都代表一个Stream Type,然后调用AudioManager进行设置音量

初始化设置详见3.3 3.4内容

1.2动态路由区域AOSP示例

在这里插入图片描述

在这里插入图片描述

传统音量组分配

在这里插入图片描述

1.3车辆音频焦点

CarZoneAudioFocus

单独管理每一个zone的音频焦点,并和UID进行交互

具体策略详见四、音频焦点AudioFocus

二、对外接口CarAudioManager

2.1 API列表

返回值 方法 参数 描述
boolean isDynamicRoutingEnabled 返回是否动态路由是否可用的
void setGroupVolume int groupId, int index, int flags 设置组音量的音量值到primary zone
void setGroupVolume int zoneId, int groupId, int index, int flags 设置组音量的音量值
int getGroupMaxVolume int groupId 获取通用域的传入groupId最大组音量
int getGroupMaxVolume int zoneId, int groupId 获取传入zoneId域里的groupId最大组音量
int getGroupMinVolume int groupId 获取通用域的传入groupId最小组音量
int getGroupMinVolume int zoneId, int groupId 获取zoneId域里的groupId最小组音量
int getGroupVolume int groupId 获取通用域的传入groupId音量值
int getGroupVolume int zoneId, int groupId 获取传入zoneId域里的groupId音量值
void setFadeTowardFront float value 设置前后音量偏移,0.0是平衡,1.0是前面
void setBalanceTowardRight float value 设置左右音量偏移,0.0是平衡,1.0是右面
String[] getExternalSources 获取外部音源,除麦克风外的输入设备(警报音、DVD、收音机等)
CarAudio
PatchHandle
createAudioPatch String sourceAddress, @AudioAttributes.AttributeUsage int usage, int gainInMillibels 通过getExternalSources给出的input port,创建一个外部音源到output的补丁,返回一个CarAudioPatchHandle
void releaseAudioPatch CarAudioPatchHandle patch 释放input port和output的关联
int getVolumeGroupCount 获取通用域的可用音量组数目
int getVolumeGroupCount int zoneId 获取zoneId指定域的可用音量组数目
int getVolumeGroupIdForUsage @AudioAttributes.AttributeUsage int usage 获取传入音频用例对应的音量组Id
int getVolumeGroupIdForUsage int zoneId @AudioAttributes.AttributeUsage int usage 获取zoneId指定域传入音频用例对应的音量组Id
int[] getAudioZoneIds 获取所有音频域的id
int getZoneIdForUid int uid 获取uid映射的zoneId,没有映射返回primaryId
boolean setZoneIdForUid int zoneId, int uid 设置zoneId和uid的映射
boolean clearZoneIdForUid int uid 清除uid的映射
int getZoneIdForDisplay Display display 获取指定display的zoneId,没有找到返回primaryId
int getZoneIdForDisplayPortId byte displayPortId 获取指定display端口ID所对应的zoneId,没有找到返回primaryId
int[] getUsagesForVolumeGroupId int groupId 获取通用域里指定groupId所有的音频用例
int[] getUsagesForVolumeGroupId int zoneId, int groupId 获取指定zoneId域里指定groupId所有的音频用例
void registerCarVolumeCallback CarVolumeCallback callback 注册音量callback,添加到CarAudioManager维护的Callback组里,有onGroupVolumeChanged和onMasterMuteChanged的回调
void unregisterCarVolumeCallback CarVolumeCallback callback 注销音量callback,从Callback组里删除

2.2CarAudioManager的总结

CarAudiManager的构造方法里通过AIDL方式获取CarAudioService,并注册音量监听。并将音量监回调到其他注册CarAudioManager的Callback。

CarAudioManager实现了CarManagerBase的接口,即实现了onCarDisconnected()方法,在onCarDisConnected里注销CarAudioService里的音量监听。

需要Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME权限

获取CarAudioManager的步骤,首先要获取Car,然后mCar.getCarManager(Car.AUDIO_SERVICE);

import android.car.Car;

Car mCar;
CarAudioManager mAudioManager;
private void setup(){
   
    mCar = Car.createCar(mContext, mConnectionCallbacks); 
    mCar.connect();
}

private ServiceConnection mConnectionCallbacks = new ServiceConnection() {
   
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
   
        Log.d(TAG, "onServiceConnected: ");
        mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
   
        Log.d(TAG, "onServiceDisconnected: ");
    }
};

CarAudioManager初始化过程,以及连接状态的维护

onCarDisconnectedpublic Object getCarManager(String serviceName) {
   
    CarManagerBase manager;
    ICar service = getICarOrThrow();
    synchronized (mCarManagerLock) {
   
        manager = mServiceMap.get(serviceName);
        if (manager == null) {
   
            try {
   
                IBinder binder = service.getCarService(serviceName);
                if (binder == null) {
   
                    Log.w(CarLibLog.TAG_CAR, "getCarManager could not get binder for service:" +
                          serviceName);
                    return null;
                }
                manager = createCarManager(serviceName, binder);
                if (manager == null) {
   
                    Log.w(CarLibLog.TAG_CAR,
                          "getCarManager could not create manager for service:" +
                          serviceName);
                    return null;
                }
                //用来回调onCarDisconnected
                mServiceMap.put(serviceName, manager);
            } catch (RemoteException e) {
   
                throw e.rethrowFromSystemServer();
            }
        }
    }
    return manager;
}
private CarManagerBase createCarManager(String serviceName, IBinder binder) {
   
    CarManagerBase manager = null;
    switch (serviceName) {
   
        case AUDIO_SERVICE:
            manager = new CarAudioManager(binder, mContext, mEventHandler);
            break;
    }
}
private void tearDownCarManagers() {
   
    synchronized (mCarManagerLock) {
   
        for (CarManagerBase manager: mServiceMap.values()) {
   
            //回调所有的CarManagerBase
            manager.onCarDisconnected();
        }
        mServiceMap.clear();
    }
}

三、内部服务CarAudioService

3.1CarAudioService总述

3.2CarAudioService初始化

CarAudioService构造函数

    public CarAudioService(Context context) {
   
        mContext = context;
        //用于读取通话状态
        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
        //CarAudioManger音量控制最后调用到AudioManager
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        //从配置里读取是否支持动态路由
        mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
        //是否保持主静音状态
        mPersistMasterMuteState = mContext.getResources().getBoolean(
                R.bool.audioPersistMasterMuteState);
        //uid 到 zoneId的映射map表
        mUidToZoneMap = new HashMap<>();
    }

CarAudioService实现了CarServiceBase的init、release、dump三个接口、vehicleHalReconnected、dumpMetrics空方法的重写。CarAudioManagerService在ICarImpl构造函数创建,init里初始化

public ICarImpl(Context serviceContext, IVehicle vehicle, SystemInterface systemInterface,
            CanBusErrorNotifier errorNotifier, String vehicleInterfaceName) {
   
    //创建CarAudioService        
    mCarAudioService = new CarAudioService(serviceContext);
    mSystemStateControllerService = new SystemStateControllerService(
        serviceContext, mCarAudioService, this);
    
    allServices.add(mCarAudioService);
    mAllServices = allServices.toArray(new CarServiceBase[allServices.size()]);
}
    @MainThread
    void init() {
   
        mBootTiming = new TimingsTraceLog(VHAL_TIMING_TAG, Trace.TRACE_TAG_HAL);
        mHal.init();
        for (CarServiceBase service : mAllServices) {
   
            //CarAudioService 初始化
            service.init();
        }
        mSystemInterface.reconfigureSecondaryDisplays();
    }

CarAudioService的init方法

@Override
    public void init() {
   
        synchronized (mImplLock) {
   
            if (mUseDynamicRouting) {
   
                // Enumerate all output bus device ports
                // 获取所有的output device ports
                AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(
                        AudioManager.GET_DEVICES_OUTPUTS);
                if (deviceInfos.length == 0) {
   
                    Log.e(CarLog.TAG_AUDIO, "No output device available, ignore");
                    return;
                }
                SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo = new SparseArray<>();
                for (AudioDeviceInfo info : deviceInfos) {
   
                    Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s",
                            info.getId(), info.getAddress(), info.getType()));
                    //枚举Type bus 类型 创建 CarAudioDeviceInfo 整合放入busNumber和carInfo的数字映射里
                    if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
   
                        final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info);
                        // See also the audio_policy_configuration.xml,
                        // the bus number should be no less than zero.
                        if (carInfo.getBusNumber() >= 0) {
   
                            busToCarAudioDeviceInfo.put(carInfo.getBusNumber(), carInfo);
                            Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
                        }
                    }
                }
                // 根据 busToCarAudioDeviceInfo设置动态路由
                setupDynamicRouting(busToCarAudioDeviceInfo);
            } else {
   
                Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not enabled, run in legacy mode");
                //不支持就设置传统音量监听
                setupLegacyVolumeChangedListener();
            }

            // Restore master mute state if applicable
            if (mPersistMasterMuteState) {
   
                boolean storedMasterMute = Settings.Global.getInt(mContext.getContentResolver(),
                        VOLUME_SETTINGS_KEY_MASTER_MUTE, 0) != 0;
                setMasterMute(storedMasterMute, 0);
            }
        }
    }

3.3设置动态路由setupDynamicRouting

1.动态路由加载这个两个文件 “/vendor/etc/car_audio_configuration.xml”, “/system/etc/car_audio_configuration.xml”

2.如果都没有就是传统模式,加载/packages/services/Car/service/res/xml/car_volume_groups.xml

最后都是一个CarAudioZone的组,CarAudioZone[] mCarAudioZones;

CarAudioZone通过getVolumeGroups就能获取所有的CarVolumeGroup,用于查询

private void setupDynamicRouting(SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo) {
   
        final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
        builder.setLooper(Looper.getMainLooper());
/*
            "/vendor/etc/car_audio_configuration.xml",
            "/system/etc/car_audio_configuration.xml"
            getAudioConfigurationPath 优先选第一个
*/
        mCarAudioConfigurationPath = getAudioConfigurationPath();
        if (mCarAudioConfigurationPath != null) {
   
            try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
   
                CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mContext, inputStream,
                        busToCarAudioDeviceInfo);
                mCarAudioZones = zonesHelper.loadAudioZones();
            } catch (IOException | XmlPullParserException e) {
   
                throw new RuntimeException("Failed to parse audio zone configuration", e);
            }
        } else {
   
            // In legacy mode, context -> bus mapping is done by querying IAudioControl HAL.
            final IAudioControl audioControl = getAudioControl();
            if (audioControl == null) {
   
                throw new RuntimeException(
                        "Dynamic routing requested but audioControl HAL not available");
            }
            //传统模式使用 R.xml.car_volume_groups里的配置
            CarAudioZonesHelperLegacy legacyHelper = new CarAudioZonesHelperLegacy(mContext,
                    R.xml.car_volume_groups, busToCarAudioDeviceInfo, audioControl);
            mCarAudioZones = legacyHelper.loadAudioZones();
        }
        for (CarAudioZone zone : mCarAudioZones) {
   
            if (!zone.validateVolumeGroups()) {
   
                throw new RuntimeException("Invalid volume groups configuration");
            }
            // Ensure HAL gets our initial value
            zone.synchronizeCurrentGainIndex();
            /*  get当前音量再设置回去
                void synchronizeCurrentGainIndex() {
                    for (CarVolumeGroup group : mVolumeGroups) {
                        group.setCurrentGainIndex(group.getCurrentGainIndex());
                    }
                }
            */
            Log.v(CarLog.TAG_AUDIO, "Processed audio zone: " + zone);
        }

        // Setup dynamic routing rules by usage
        final CarAudioDynamicRouting dynamicRouting = new CarAudioDynamicRouting(mCarAudioZones);
        // 传入AudioPolicy builder
        /* 遍历zones
           	遍历groups
        		遍历group里的busNumber,得到mixFormat 从CarAudioDeviceInfo
        		创建mixingRuleBuilder
        			遍历bus对应的context,
        				遍历context对应的usage,创建新的AudioAttributes setUsage,
        				并将这个AudioAttributes addRule到mixingRuleBuilder里
   				根据mixFormat和mixingRuleBuilder创建audioMix,在传入的AudioPolicy builder里addMix
    
        */
    	dynamicRouting.setupAudioDynamicRouting(builder);

        // Attach the {@link AudioPolicyVolumeCallback}
    	//设置音量回调监听 onVolumeAdjustment
    	/* AudioManager.ADJUST_LOWER  AudioManager.ADJUST_RAISE
    	   AudioManager.ADJUST_MUTE   AudioManager.ADJUST_UNMUTE
    	   AudioManager.ADJUST_TOGGLE_MUTE AudioManager.ADJUST_SAME
    	*/
        builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);
	   //使用CarAudioFocus
        if (sUseCarAudioFocus) {
   
            // Configure our AudioPolicy to handle focus events.
            // This gives us the ability to decide which audio focus requests to accept and bypasses
            // the framework ducking logic.
            mFocusHandler = new CarZonesAudioFocus(mAudioManager,
                    mContext.getPackageManager(),
                    mCarAudioZones);
            //mFocusHandler 是继承AudioPolicy.AudioPolicyFocusListener的CarZonesAudioFocus实例
            //即从来接管该AudioPolicy的AudioFocus的策略制定
            builder.setAudioPolicyFocusListener(mFocusHandler);
            builder.setIsAudioFocusPolicy(true);
        }

        mAudioPolicy = builder.build();
        if (sUseCarAudioFocus) {
   
            // Connect the AudioPolicy and the focus listener
            mFocusHandler.setOwningPolicy(this, mAudioPolicy);
        }
	    //将该AudioPolicy注册到AudioManager里
        int r = mAudioManager.registerAudioPolicy(mAudioPolicy);
        if (r != AudioManager.SUCCESS) {
   
            throw new RuntimeException("registerAudioPolicy failed " + r);
        }
    }

3.4配置文件

3.4.1car_audio_configuration.xml

动态路由的配置

源码中的位置/device/generic/car/emulator/audio/car_audio_configuration.xml

<?xml version="1.0" encoding="utf-8"?>
<!--
  Defines the audio configuration in a car, including
    - Audio zones
    - Display to audio zone mappings
    - Context to audio bus mappings
    - Volume groups
  in the car environment.
-->
<carAudioConfiguration version="1">
    <zones>
        <zone name="primary zone" isPrimary="true">
            <volumeGroups>
                <group>
                    <device address="bus0_media_out">
                        <context context="music"/>
                    </device>
                    <device address="bus3_call_ring_out">
                        <context context="call_ring"/>
                    </device>
                    <device address="bus6_notification_out">
                        <context context="notification"/>
                    </device>
                    <device address="bus7_system_sound_out">
                        <context context="system_sound"/>
                    </device>
                </group>
                <group>
                    <device address="bus1_navigation_out">
                        <context context="navigation"/>
                    </device>
                    <device address="bus2_voice_command_out">
                        <context context="voice_command"/>
                    </device>
                </group>
                <group>
                    <device address="bus4_call_out">
                        <context context="call"/>
                    </device>
                </group>
                <group>
                    <device address="bus5_alarm_out">
                        <context context="alarm"/>
                    </device>
                </group>
            </volumeGroups>
            <displays>
                <display port="0"/>
            </displays>
            <!-- to specify displays associated with this audio zone, use the following tags
                <displays>
                    <display port="1"/>
                    <display port="2"/>
                </displays>
                where port is the physical port of the display (See DisplayAddress.Phyisical)
            -->
        </zone>
        <zone name="rear seat zone">
            <volumeGroups>
                <group>
                    <device address="bus100_rear_seat">
                        <context context="music"/>
                        <context context="navigation"/>
                        <context context="voice_command"/>
                        <context context="call_ring"/>
                        <context context="call"/>
                        <context context="alarm"/>
                        <context context="notification"/>
                        <context context="system_sound"/>
                    </device>
                </group>
            </volumeGroups>
            <displays>
                <display port="1"/>
            </displays>
        </zone>
    </zones>
</carAudioConfiguration>

参考/device/generic/car/emulator/audio/car_audio_configuration.x

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值