08.音频系统:第003课_Linux音频驱动程序:第004节_切换声音通道流程

在前面两节中,我们根据耳麦的拔插,修改了系统源码,在andriod设备状态栏中显示或者隐藏状态栏的信息,并且分析了插拔耳麦导致系统的整个流程。
实际上,我们更加关注四声音通道的切换,比如说我们插上耳麦,声音就从耳机里面播放出来,这个过程是怎样的?以耳机为例,声音通道的切换有两种方式,一种是在驱动程序中切换,不如说,插上耳机之后,会有一个中断,在内核中断程序之中,设置声卡,让声音从耳机之中输出出来。第二种方式是,把输出通道的选择权,交给安卓系统。显然,第二种方式才是正统的方式,驱动程序值需要提供切换的能力就可以了,其到底切不切换应该由应用程序决定。该小节要讲解的就是第二种方式,在安卓系统之中,他是怎么去切换通道的。
我们先回顾一下,前面章节中对android音频系统的讲解,之前我们提到了一个/system/etc/audio_policy.conf与/vendor/etc/audio_policy.conf配置文件,该问题提供了这个单板能支持声卡的一些信息:

/*描述多个modules*/
audio_hw_modules {
  primary { //一个modules对应一个厂家提供的so文件
    outputs { //一个modules可以有多个output
      primary {//一个output表明他的参数(primary代表默认设备)
        ......
      }
      spdif_passthrough {   
        ......  
      }

      hdmi {
        ......
      }
    }
    inputs {//一个modules可以有多个input
      primary {
      }
    }
  }
  a2dp {
    outputs {
      a2dp {
        ......
      }
    }
  }
  usb {
    outputs {
      usb_accessory {
        ......
      }
      usb_device {
        ......
      }
    }
    inputs {
      usb_device {
        ......
      }
    }
  }
  r_submix {
    outputs {
      submix {
        ......
      }
    }
    inputs {
      submix {
        ......
      }
    }
  }
}

其中primary,a2dp,usb,r_submix节点间我们称为modle,。每个modle会对应一个库文件,以后会使用这个库操作声卡,在modle中存在一个outputs与inputs结点,在outputs中存在primary ,其中包含了:

audio_hw_modules {
  primary { //一个modules对应一个厂家提供的so文件
    outputs { //一个modules可以有多个output
      primary {//一个output表明他的参数(primary代表默认设备)
        sampling_rates  44100|48000 //采样率
        channel_masks AUDIO_CHANNEL_OUT_STEREO //通道
        formats AUDIO_FORMAT_PCM_16_BIT //格式
        /*有哪些device,如喇叭,耳机等等*/
        devices  AUDIO_DEVICE_OUT_EARPIECE|AUDIO_DEVICE_OUT_SPEAKER|AUDIO_DEVICE_OUT_WIRED_HEADSET|AUDIO_DEVICE_OUT_WIRED_HEADPHONE|AUDIO_DEVICE_OUT_ALL_SCO|AUDIO_DEVICE_OUT_AUX_DIGITAL|AUDIO_DEVICE_OUT_SPDIF
        flags AUDIO_OUTPUT_FLAG_PRIMARY
      }
      spdif_passthrough {
        sampling_rates 44100|48000
        channel_masks AUDIO_CHANNEL_OUT_STEREO
        formats AUDIO_FORMAT_PCM_16_BIT
        devices AUDIO_DEVICE_OUT_SPDIF
        flags AUDIO_OUTPUT_FLAG_DIRECT
      }

      hdmi {
        sampling_rates 44100|48000|192000
        channel_masks AUDIO_CHANNEL_OUT_5POINT1|AUDIO_CHANNEL_OUT_STEREO|AUDIO_CHANNEL_OUT_7POINT1
        formats AUDIO_FORMAT_PCM_16_BIT
        devices AUDIO_DEVICE_OUT_AUX_DIGITAL
        flags AUDIO_OUTPUT_FLAG_DIRECT
      }
    }
    inputs {//一个modules可以有多个input
      primary {
        sampling_rates 8000|11025|16000|22050|32000|44100|48000
        channel_masks AUDIO_CHANNEL_IN_MONO|AUDIO_CHANNEL_IN_STEREO
        formats AUDIO_FORMAT_PCM_16_BIT
        devices AUDIO_DEVICE_IN_BUILTIN_MIC|AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET|AUDIO_DEVICE_IN_WIRED_HEADSET
      }
    }
  }
--------------------------------------------------------------------------------------- 
  usb {
    outputs {
      usb_accessory {
        sampling_rates 44100
        channel_masks AUDIO_CHANNEL_OUT_STEREO
        formats AUDIO_FORMAT_PCM_16_BIT
        devices AUDIO_DEVICE_OUT_USB_ACCESSORY
      }
      usb_device {
        sampling_rates dynamic
        channel_masks dynamic
        formats dynamic
        devices AUDIO_DEVICE_OUT_USB_DEVICE
      }
    }
    inputs {
      usb_device {
        sampling_rates dynamic
        channel_masks AUDIO_CHANNEL_IN_STEREO
        formats AUDIO_FORMAT_PCM_16_BIT
        devices AUDIO_DEVICE_IN_USB_DEVICE
      }
    }
  }

sampling_rates ,channel_masks ,formats ,devices其中的devices表示他支持的输出方式(耳机喇叭等等)。android系统在启动的时候,就是根据配置文件打开modle对应的库,然后创建一个线程与其对应。
一般来说,对于每一个output都有一个playBackThread与他对应。

前面提到配置文件中有多个modle,一个moudle代表一个声卡,根据上面贴出,我们来说说USB声卡与我们板载primary声卡的对比:

每个声卡的ouput都对应一个playBackThread,其中primary声卡有喇叭(speaker),耳麦插孔(headset)。
假设在系统启动的时候,并没有接入USB声卡,那么应用程序,他想播放声音的时候,会创建一个AudioTrack(其会传入声音的类型),然后会找到对应的播放线程,在线程的内部创建一个Track(属于mTracks数组),AudioTrack与Track之间建立共享内存,APP通过AudioTrack传入声音,然后在经过共享内存传递到对应playBackThread的Track中。
假设同样的原理,有多个APP,图示关系如下:
在这里插入图片描述
可以看到每个应用程序APP都在playBackThread中有一个与其对应的Track。显然,每个AudioTrack不停的把数据写入自己对应的Track之中,playBackThread把这些数据去出来之后,然后混合在一起发送给硬件,这些内容都在之前的小节讲解过,不在进行深入解答。

既然每个output都对应一个playBackThread线程,就可能存在多个playBackThread,那么AudioTrack应该把数据发送给那个线程呢?在前面的视频我们讲解过:
在这里插入图片描述
APP在构建AudioTrack的时候会传入StreamType的参数,先把StreamType进行归类(Strategy),然后根据Strategy找出device,在根据device找出outputs(即有可能找到多个output:对应多个线程),然后从多个线程中选择一个线程(ouput):
在这里插入图片描述
到前面的只是回顾到此结束,现在我们开始讲解本节的内容。

我们怎么切换声音通道呢?其主要文件为AudioPolicyManager.cpp,下面我们对源码进行情景分析:
在这里插入图片描述
情景一,接上USB声卡
从配置文件中,我们可以看到usb中对应的modle中存在一个outputs其中包含了两个output(usb_accessory,usb_device)。
1.假设插上了USB声卡之后,会创建两个playBackThread(每个output对应一个)。
2.之前与板载声卡建立联系的playBackThread线程,要进行切换,切换成USB创建的playBackThread,然后在新的playBackThread中,每个APP创建对应的Track。
3.插上USB声卡之后,是从耳机还是喇叭播放?这些也需要重新做一些设置。

情景二,在primary中插上耳机
1.这种情况下他们所涉及的线程并没有改变,所以不需要重新去创建output与playBackThread
2.不需要切换output通道。
3.在原来的ouput中选择Device(headset)即可。

从上可以看出他们的流程都是差不多的,首先需要判断一下,是否需要创建output,播放的声音是否需要新的线程进行处理,在output中学着device。

下面是总结的流程:

5.4 切换声音通道流程
A. 在驱动程序中切换: 
比如: 插上耳麦发生中断, 在中断处理程序中设置声卡让声音从耳机中输出

B. 把输出通道的选择权交给android系统

驱动程序上报音频拔插事件,
该事件为某个device插入或拔出,
Android系统需要切换声音通道,


过程为(核心文件为frameworks/av/services/audiopolicy/AudioPolicyManager.cpp):
(核心函数为 setDeviceConnectionState)
a. checkOutputsForDevice
针对该device, 打开新的output, 创建新的playbackthread.
方法:
从audio_policy.conf中确定"本该有多少个output"可以支持它,
mOutputs表示"已经打开的output",
两者对比即可确定"尚未打开的output"


b. checkOutputForAllStrategies / checkOutputForStrategy
对所有的strategy分组声音,
判断是否需要迁移到新的output, 如果需要则迁移对应Track到新的output
方法: 
b.1 判断是否需要迁移
对于该strategy, 得到它的oldDevice, 进而得到它的outputs (srcOutputs);
对于该strategy, 得到它的newDevice, 进而得到它的outputs (dstOutputs);
如果这2个srcOutputs、dstOutputs不相同, 表示需要迁移

b.2 如果迁移:
把对应的Track设置为invalidate状态即可,
App写AudioTrack时发现它是invalidate状态, 就会重新创建新的Track

    audio_devices_t oldDevice = getDeviceForStrategy(strategy, true /*fromCache*/);
    audio_devices_t newDevice = getDeviceForStrategy(strategy, false /*fromCache*/);
    SortedVector<audio_io_handle_t> srcOutputs = getOutputsForDevice(oldDevice, mPreviousOutputs);
    SortedVector<audio_io_handle_t> dstOutputs = getOutputsForDevice(newDevice, mOutputs);

引起通道切换的原因,就是设备的拔插,上小节我们讲解到其会调用到AudioManager.java文件:

onSetWiredDeviceConnectionState(connectState.mType, connectState.mState,connectState.mAddress, connectState.mName, connectState.mCaller);

函数,不过我们跳过了很多东西,值分析了其中的和插拔相关的部分,现在我们再来分析:

onSetWiredDeviceConnectionState(connectState.mType, connectState.mState,connectState.mAddress, connectState.mName, connectState.mCaller);
	handleDeviceConnection(state == 1, device, address, deviceName)
	.........

根据前面贴出的时序图,最终会到达AudioPolicyManager.cpp文件中的(假设我们插入设备output_device):

status_t AudioPolicyManager::setDeviceConnectionStateInt(audio_devices_t device,audio_policy_dev_state_t state,const char *device_address,const char *device_name)
	/*判断上报的是否为output_device*/
	audio_is_output_device(device)
		/*根据device在可用的设备列表中查找*/
		sp<HwModule> module = mHwModules.getModuleForDevice(device);
		/*代表存在直接返回,否则代表为新添加的*/
		if (index >= 0) {
                return INVALID_OPERATION;
        /*添加到可用设备*/
        index = mAvailableInputDevices.add(devDesc);
	/*判断是否为输入设备*/
	audio_is_input_device(device)
	/*检查这个device是否存在outputs,如果没有这会创建outpus,与线程*/
	checkOutputsForDevice(devDesc, state, outputs, devDesc->mAddress) 
	

配置文件每个output都会被描绘成一个profile,也就是说checkOutputsForDevice会检测所有的profile(output),查找每个profile是否都存在对应的线程,如果没有则进行创建:

status_t AudioPolicyManager::checkOutputsForDevice(const sp<DeviceDescriptor> devDesc,audio_policy_dev_state_t state,SortedVector<audio_io_handle_t>& outputs,const String8 address)
	// first list already open outputs that can be routed to this device
	/*找出这个支持这个device的profile*/
    for (size_t i = 0; i < mOutputs.size(); i++) {
    /*看看支持的profile有没有对应的output*/
    for (size_t i = 0; i < mHwModules.size(); i++)
    /*如果没有output,则open其output,创建播放线程。*/
	status_t status = mpClientInterface->openOutput(profile->getModuleHandle(), &output,&config, &desc->mDevice,address,&desc->mLatency,desc->mFlags);

我们再次回的setDeviceConnectionStateInt:

status_t AudioPolicyManager::setDeviceConnectionStateInt(audio_devices_t device,audio_policy_dev_state_t state,const char *device_address,const char *device_name)
	.......//前面已分析
	checkOutputsForDevice(devDesc, state, outputs, devDesc->mAddress) 

    // checkA2dpSuspend must run before checkOutputForAllStrategies so that A2DP
    // output is suspended before any tracks are moved to it
    /*蓝牙部分*/
    checkA2dpSuspend();	

	/*检测该内声音是否需要从原来的ouput切换到新的output*/
	checkOutputForAllStrategies();
		checkOutputForStrategy()
			/*获得老的Device*/
			audio_devices_t oldDevice = getDeviceForStrategy(strategy, true /*fromCache*/);
			/*获得新的Device*/
			audio_devices_t newDevice = getDeviceForStrategy(strategy, false /*fromCache*/);
			/*得到其中的Outputs进行比较,确定是否需要切换*/
			SortedVector<audio_io_handle_t> srcOutputs = getOutputsForDevice(oldDevice, mPreviousOutputs);
    		SortedVector<audio_io_handle_t> dstOutputs = getOutputsForDevice(newDevice, mOutputs);		
    		/*需要切换,让现在播放的无效*,其实设置了一个标志位/
			mpClientInterface->invalidateStream((audio_stream_type_t)i);
				

设置标志位之后,其怎么进行切换的呢?
在这里插入图片描述
在APP重新写入数据的事就,检测到标志位的变化,就会进行相应的操作,具体流程可以根据图示流程详细分析。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

江南才尽,年少无知!

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

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

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

打赏作者

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

抵扣说明:

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

余额充值