Audio播放音频 — 建立播放通道


简介

虽然文章标题是《建立播放通道》,其实播放通道早在AudioPolicyManager解析configuration配置文件时,openoutput业务逻辑就已经把输出通道打开并建立好了,而播放音频流程就是根据音频属性Attribute来决定使用哪个输出通道output而已,但是这个流程业务相对openoutput更加复杂,也涉及更多的音频专业知识;并且播放音频不只是选择输出通道,还涉及往这个输出通道灌音频数据,传送到设备去播放;本篇文章只涉及输出通道的选择,音频数据的传送流程放在下一篇来阐述。


音频播放的起点 — AudioTrack的基本使用

不同于MediaPlayer播放音频,AudioTrack只支持播放PCM格式的音频(当然自己可以修改framework让其支持其他格式),相比较而言其代码更加精炼,并且MediaPlayer内部也使用了AudioTrack,所以我们就从AudioTrack播放音频来入手,AudioTrack使用伪代码如下:

1. 计算缓冲区
buffersize = AudioTrack.getMinBufferSize(sampleRate,channel, format);
2. 设置音频属性
attr = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build()
format = AudioFormat.Builder().setSampleRate(22050).setEncoding(AudioFormat.ENCODING_PCM_8BIT).setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build()
track = new AudioTrack(attr, format, mode)
3. 开始播放
track.play()
4. 写入数据
track.write()
5.结束
track.stop()
track.release()

两个关键点需要我们关注:

  1. attr属性中的usage、contentType、flag等等信息,根据这些信息按图索骥查找确定它最终的输出通道:(usage、contentType、falg等)->streamType->strategy->device->output通道句柄,output句柄对应一个输出通道
  2. mode分为static和stream模式,static模式一次性写入到自己创建共享内存,在通过匿名共享内存方式拷贝到AudioFlinger进程;stream是分批次写入,多次通过匿名共享内存方式写入AudioFlinger进程

其实在attr创建时就可以指定streamType类型,但是streamType参数在前期处理时依旧会转化为其usage、contentType等属性,并不会将类型写入底层;Android这么做的原因估计是为了防止上层错误指定streamType,而要根据实际用途来确定;每个streamType代表的音频流不同,如下:

streamType 含义
STREAM_MUSIC 音乐播放的音频流
STREAM_SYSTEM 系统声音的音频流
STREAM_RING 电话铃声的音频流
STREAM_VOICE_CALL 电话通话的音频流
STREAM_ALARM 警报的音频流
STREAM_NOTIFICATION 通知的音频流
STREAM_BLUETOOTH_SCO SCO连接方式到蓝牙电话时的手机音频流
STREAM_SYSTEM_ENFORCED 在某些国家实施的系统声音的音频流
STREAM_DTMF DTMF双音多频信号音调的音频流(可以理解该音频音调能反应数字)
STREAM_TTS 文本到语音转换(TTS)的音频流

音频播放的过程 — 寻找output输出通道

先来一张总的流程图:

上面流程图列出了执行流程的主干部分,没有对每个函数执行成功或失败后的处理策略(后面会讲到),并且省略了部分类自身的调用逻辑,比如AudioTrack内部先后调用createTrack和createTrack_l,直接列出了最后一步

我们需要注意几点是:

  1. 起始传入参数大致有attr(usage、contentType)、sampleRate、channel和format等
  2. android_media_AudioTrack、AudioTrack和AudioFlinger之间共享内存如何共享(用于传递音频数据)?回调函数如何回调(从framework到java的回调)?
  3. AudioPolicyManager和Engine之间如何选择确定输出通道output?
  4. 确定输出通道之后层层返回回去,各个类如何处理返回结果?

上述流程图上的其他逻辑就是一些参数转换和依次调用了,我们只需要关注重点逻辑即可;第2点放到下篇文章数据传递去讲解;本文重点讲解3、4两点;

AudioPolicyManager之getOutputForAttrInt

其实在AudioPolicyManager中是先调用getOutputForAttr,后调用getOutputForAttrInt的,先看看这个函数的参数:

status_t AudioPolicyManager::getOutputForAttrInt(audio_attributes_t *resultAttr,audio_io_handle_t *output,audio_session_t session,const audio_attributes_t *attr,audio_stream_type_t *stream,uid_t uid,const audio_config_t *config,audio_output_flags_t *flags,audio_port_handle_t *selectedDeviceId,bool *isRequestedDeviceForExclusiveUse,std::vector<sp<SwAudioOutputDescriptor>> *secondaryDescs);

resultAttr: 这里是个空值,但是后续逻辑会拷贝第4个参数attr
output: 输出通道output句柄,空指针,最后确定好输出通道时,会将通道写入这个指针并返回回去
session: 与客户端会话的值,在AudioFlinger时就创建好得到具体值,并传递到这里,最终输出通道确定后会保存这个session,后续就可以使用这个session来确定连接关系
attr: 来自应用层传递的usage、contentType、flag等参数
stream:流类型也就是streamType,未指定或默认default类型,后续逻辑会通过attr来确定属于哪类流
uid: 客户端uid
config: 来自应用层传递的sampleRate、channel、format等参数
flags: 此flag不同于attr内的flag,它是在android_media_AudioTrack部分逻辑时确定的,初始值是AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD或者AUDIO_OUTPUT_FLAG_NONE,但是随着层层逻辑调用时也会与系统的一些状态相与或,发生变化,这个值也是相当重要
selectedDeviceId: 空指针,输出通道最终的播放audio device的id,确定哪个设备播放后会将id写入指针并返回
isRequestedDeviceForExclusiveUse: false
secondaryDescs: 空值
下面代码是getOutputForAttrInt函数的内部逻辑:

DeviceVector outputDevices;
const audio_port_handle_t requestedPortId = *selectedDeviceId;
DeviceVector msdDevices = getMsdAudioOutDevices();
//返回null
const sp<DeviceDescriptor> requestedDevice =
mAvailableOutputDevices.getDeviceFromId(requestedPortId);
/** 确保resultAttr是有值* 如果源attr有值就拷贝给resultAttr;否则查看stream是否为空,不为空则* 根据stream来创建一个attr来给resultAttr赋值* **/
status_t status = getAudioAttributes(resultAttr, attr, *stream);
if (status != NO_ERROR) {
return status;
}
if (auto it = mAllowedCapturePolicies.find(uid); it != end(mAllowedCapturePolicies)) {
resultAttr->flags |= it->second;
}
/** 这里根据attr去策略里面找,主要对比attr和ProductStrategy的attr对比,* usage、content_type、flag和tag等等;最后就能找到这些属性是哪个streamType* **/
*stream = mEngine->getStreamTypeForAttributes(*resultAttr);
......
// 这步就选择好了device,注意是多个devices
outputDevices = mEngine->getOutputDevicesForAttributes(*resultAttr, requestedDevice, false);
//如果源attr->flag包含HW_AV_SYNC,则把output的flag也要添加HW标记
if ((resultAttr->flags & AUDIO_FLAG_HW_AV_SYNC) != 0) {
*flags = (audio_output_flags_t)(*flags | AUDIO_OUTPUT_FLAG_HW_AV_SYNC);
}//如果找到的设备类型是用于电话通信传输的,且音频属于语音通话的;并且当且也处于电话通,将设置flag为
//INCALL MUSIC,独占使用也为true
if (outputDevices.types() == AUDIO_DEVICE_OUT_TELEPHONY_TX &&
(*stream == AUDIO_STREAM_MUSIC  || resultAttr->usage == AUDIO_USAGE_VOICE_COMMUNICATION) &&
audio_is_linear_pcm(config->format) &&
isInCall()) {
if (requestedPortId != AUDIO_PORT_HANDLE_NONE) {*flags = (audio_output_flags_t)AUDIO_OUTPUT_FLAG_INCALL_MUSIC;*isRequestedDeviceForExclusiveUse = true;
}
}ALOGV("%s() device %s, sampling rate %d, format %#x, channel mask %#x, flags %#x stream %s",__func__, outputDevices.toString().c_str(), config->sample_rate, config->format,config->channel_mask, *flags, toString(*stream).c_str());*output = AUDIO_IO_HANDLE_NONE;
......
if (*output == AUDIO_IO_HANDLE_NONE) {
//从已经打开的疏通通道中,根据stream、outputDevice找到数据句柄
*output = getOutputForDevices(outputDevices, session, *stream, config,flags, resultAttr->flags & AUDIO_FLAG_MUTE_HAPTIC);
}
if (*output == AUDIO_IO_HANDLE_NONE) {
return INVALID_OPERATION;
}
//取第一个设备
*selectedDeviceId = getFirstDeviceId(outputDevices);ALOGV("%s returns output %d selectedDeviceId %d", __func__, *output, *selectedDeviceId);return NO_ERROR;

代码很长,看上面代码的注释可以了解一个大概,主要是确保参数resultAttr是有效有值的,然后从以下三个点找到output输出通道:
getStreamTypeForAttributes函数:从输入参数attr是如何找到支持stream?
getOutputDevicesForAttributes函数:从attr参数如何找到支持的设备outptDevices?
getOutputForDevices:从上面两个函数结果outputDevices、stream,如何找到输出通道output?

选择StreamType之Engine getStreamTypeForAttributes(attr)

Engine是AudioPolicyManager在initialize方法中创建的,Engine主要负责解析productStrategy相关的配置文件,如果你没有阅读该块相关内容,建议你先阅读Engine模块内容。
这里简单阐述Engine在解析ProductStrategy配置文件做了啥?
ProductStrategy配置相关文件分为两块,一块是以strategy策略为主策略配置,如一个名为STRATEGY_PHONE的策略,它支持streamType为AUDIO_STREAM_VOICE_CALL,usage为AUDIO_USAGE_VOICE_COMMUNICATION,音量曲线为voice_call的配置;另一块则是以音量曲线配置为主,如一个名为voice_call的音量曲线配置支持音量大小等;而Engine就是解析并保存这些内容,并将这两个内容建立映射关系;

进入getStreamTypeForAttributes函数:

//EngineBase是Engine的基类
audio_stream_type_t EngineBase::getStreamTypeForAttributes(const audio_attributes_t &attr) const
{       //strategy配置相关真正存在在ProductStrategy类里面    return mProductStrategies.getStreamTypeForAttributes(attr);
}
audio_stream_type_t ProductStrategyMap::getStreamTypeForAttributes(const audio_attributes_t &attr) const
{//遍历所有的strategy策略,每条strategy对应一个ProductStrategy类    for (const auto &iter : *this) {audio_stream_type_t stream = iter.second->getStreamTypeForAttributes(attr);if (stream != AUDIO_STREAM_DEFAULT) {return stream;}}return  AUDIO_STREAM_MUSIC;
}
audio_stream_type_t ProductStrategy::getStreamTypeForAttributes(const audio_attributes_t &attr) const
{const auto iter = std::find_if(begin(mAttributesVector), end(mAttributesVector),[&attr](const auto &supportedAttr) {//对比usage、content_type、flags和tag是否相等return AudioProductStrategy::attributesMatches(supportedAttr.mAttributes, attr); });/** 返回其策略对应的type,也就是audio_policy_engine_product_strategies每个策略* 配置的streamType;也就是这里返回stream **/return iter != end(mAttributesVector) ? iter->mStream : AUDIO_STREAM_DEFAULT;
}bool AudioProductStrategy::attributesMatches(const audio_attributes_t refAttributes,const audio_attributes_t clientAttritubes)
{if (refAttributes == AUDIO_ATTRIBUTES_INITIALIZER) {// The default product strategy is the strategy that holds default attributes by convention.// All attributes that fail to match will follow the default strategy for routing.// Choosing the default must be done as a fallback, the attributes match shall not// select the default.return false;}/** 因为配置文件中每个stream_type的usage、content_type、flag有可能为空,所以为空就是true;* 不空就要比较是否相等了 **/return ((refAttributes.usage == AUDIO_USAGE_UNKNOWN) ||(clientAttritubes.usage == refAttributes.usage)) &&((refAttributes.content_type == AUDIO_CONTENT_TYPE_UNKNOWN) ||(clientAttritubes.content_type == refAttributes.content_type)) &&((refAttributes.flags == AUDIO_FLAG_NONE) ||(clientAttritubes.flags != AUDIO_FLAG_NONE &&(clientAttritubes.flags & refAttributes.flags) == refAttributes.flags)) &&((strlen(refAttributes.tags) == 0) ||(std::strcmp(clientAttritubes.tags, refAttributes.tags) == 0));
}

参考上面代码流程及注释,就能找到支持该attr属性的streamType了;搜索过程大体是:1)遍历每个ProductStrategy;2)遍历ProductStrategy内的每个Attributes;3)将attr参数与Attributs内的usage、contentType、flag等逐个对比,相等就返回该策略strategy配置的streamType

选择devices之Engine getOutputDevicesForAttributes(attr)

这部分代码逻辑主要在Engine类里面发生的,内部大致有这么一个执行流程:

逐个函数分析

  1. 先检查之前是否打开过相同strategy策略的客户端:
DeviceVector Engine::getOutputDevicesForAttributes(const audio_attributes_t &attributes,const sp<DeviceDescriptor> &preferredDevice,bool fromCache) const
{// 如果已经显示声明我们要使用preferredDevice这个设备,就用这个设备返回即可if (preferredDevice != nullptr) {ALOGV("%s explicit Routing on device %s", __func__, preferredDevice->toString().c_str());return DeviceVector(preferredDevice);}/**audio_policy_engine_product_strategies.xml配置文件配置了策略,解析后保存在productstrategy内* ,每个ProductStrategy配置了支持的usage与content_type,attributes中的usage和content与* productStratey内的逐个比较,相等时返回ProductStrategy实体类的id* */product_strategy_t strategy = getProductStrategyForAttributes(attributes);//所有的输出设备device,配置文件中attachDevice标签const DeviceVector availableOutputDevices = getApmObserver()->getAvailableOutputDevices();//所有已经成功打开的输出设备const SwAudioOutputCollection &outputs = getApmObserver()->getOutputs();/*strategy是通过attr来确定的,如果在此之前有其他相同strategy已经来获取过device,并且把它自己作为客户端* 放到了AudioOutputDescriptor下,作为client客户端,并且这个client是存活的,就直接去这个client输出音频* 的device**/sp<DeviceDescriptor> device = findPreferredDevice(outputs, strategy, availableOutputDevices);if (device != nullptr) {return DeviceVector(device);}//没有找到以前相同strategy策略用过的设备,就要用strategy去重新找一个;成功后也会把此次请求作//为client加入到AudioOutputDescriptor的客户端下去return fromCache? mDevicesForStrategies.at(strategy) : getDevicesForProductStrategy(strategy);
}

上述函数中的outputs、availableOutputDevices来源于解析audio_policy_configuration、和打开输出output通道的设备和output,不了解这块的需要先阅读;然后就是检查之前是否有相同策略strategy的打开流程,有的话就直接用上个打开的客户端,它持有的device;否则就需要自己根据strategy去打开一遍

  1. getDevicesForProductStrategy
DeviceVector Engine::getDevicesForProductStrategy(product_strategy_t strategy) const
{DeviceVector availableOutputDevices = getApmObserver()->getAvailableOutputDevices();DeviceVector availableInputDevices = getApmObserver()->getAvailableInputDevices();const SwAudioOutputCollection &outputs = getApmObserver()->getOutputs();//strategy是ProductStrategy的id,在Engine构造方法时,初始化mLegacyStrategyMap的key为strategy//value为gLegacyStrategy的id,是一个legacy_strategy类型枚举auto legacyStrategy = mLegacyStrategyMap.find(strategy) != end(mLegacyStrategyMap) ?mLegacyStrategyMap.at(strategy) : STRATEGY_NONE;//选择device            audio_devices_t devices = getDeviceForStrategyInt(legacyStrategy,availableOutputDevices,availableInputDevices, outputs,(uint32_t)AUDIO_DEVICE_NONE);//根据device的type来对比选择                                                  return availableOutputDevices.getDevicesFromTypeMask(devices);
}

上述流程中getDeviceForStrategyInt才是真正决定使用哪个设备的关键函数;选择devices后会从所有有效的availableOutputDevices过滤得到真正的设备,因为devices目前还只是一个device type类型如AUDIO_DEVICE_OUT_SPEAKER_SAFE,并不是DeviceDescriptor实体类对象

  1. devices设备决策函数getDeviceForStrategyInt
    这块相对复杂,主要是根据strategy类型,按照先SCO蓝牙->低功耗蓝牙->经典蓝牙->原生audio设备依次选择,选中过程中可能会遇到强制设置用某个设备的标志ForceUse,其代码如下:
audio_devices_t Engine::getDeviceForStrategyInt(legacy_strategy strategy,DeviceVector availableOutputDevices,DeviceVector availableInputDevices,const SwAudioOutputCollection &outputs,uint32_t outputDeviceTypesToIgnore) const
{uint32_t device = AUDIO_DEVICE_NONE;uint32_t availableOutputDevicesType =availableOutputDevices.types() & ~outputDeviceTypesToIgnore;switch (strategy) {case STRATEGY_TRANSMITTED_THROUGH_SPEAKER:......case STRATEGY_SONIFICATION_RESPECTFUL:.......case STRATEGY_DTMF:.......case STRATEGY_PHONE:.......case STRATEGY_SONIFICATION:.......case STRATEGY_ENFORCED_AUDIBLE:......case STRATEGY_ACCESSIBILITY:.......case STRATEGY_REROUTING:case STRATEGY_MEDIA: {uint32_t device2 = AUDIO_DEVICE_NONE;if (strategy != STRATEGY_SONIFICATION) {// no sonification on remote submix (e.g. WFD) 找找有没有这样的设备,一般是找不到的if (availableOutputDevices.getDevice(AUDIO_DEVICE_OUT_REMOTE_SUBMIX,String8("0"), AUDIO_FORMAT_DEFAULT) != 0) {device2 = availableOutputDevices.types() & AUDIO_DEVICE_OUT_REMOTE_SUBMIX;}}//如果是在接打电话的情况if (isInCall() && (strategy == STRATEGY_MEDIA)) {//则按接打电话的策略device = getDeviceForStrategyInt(STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs,outputDeviceTypesToIgnore);break;}// FIXME: Find a better solution to prevent routing to BT hearing aid(b/122931261).if ((device2 == AUDIO_DEVICE_NONE) &&(getForceUse(AUDIO_POLICY_FORCE_FOR_MEDIA) != AUDIO_POLICY_FORCE_NO_BT_A2DP)) {device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_HEARING_AID;}//A2DP 蓝牙音频高保真传输协议if ((device2 == AUDIO_DEVICE_NONE) &&//如果没有强制设置不允许蓝牙A2DP,就优先使用高保真a2dp蓝牙设备(getForceUse(AUDIO_POLICY_FORCE_FOR_MEDIA) != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&//判断outputs里面device的type类型支不支持a2dpoutputs.isA2dpSupported()) {device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;if (device2 == AUDIO_DEVICE_NONE) {//优先蓝牙耳机device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;}if (device2 == AUDIO_DEVICE_NONE) {//在蓝牙扬声器device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;}}if ((device2 == AUDIO_DEVICE_NONE) &&//如果强制设置了扬声器作为输出就选择扬声器(getForceUse(AUDIO_POLICY_FORCE_FOR_MEDIA) == AUDIO_POLICY_FORCE_SPEAKER)) {device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER;}if (device2 == AUDIO_DEVICE_NONE) {//有线耳机device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_WIRED_HEADPHONE;}if (device2 == AUDIO_DEVICE_NONE) {//这个不知道是啥设备?device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_LINE;}if (device2 == AUDIO_DEVICE_NONE) {//有线带话筒的耳机device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_WIRED_HEADSET;}if (device2 == AUDIO_DEVICE_NONE) {//usb连接的话筒耳机device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_USB_HEADSET;}if (device2 == AUDIO_DEVICE_NONE) {//usbdevice2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_USB_ACCESSORY;}if (device2 == AUDIO_DEVICE_NONE) {//usb 设备devicedevice2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_USB_DEVICE;}if (device2 == AUDIO_DEVICE_NONE) {//模拟的数字话筒耳机device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET;}if ((device2 == AUDIO_DEVICE_NONE) && (strategy != STRATEGY_SONIFICATION)) {// no sonification on aux digital (e.g. HDMI)device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_AUX_DIGITAL;}if ((device2 == AUDIO_DEVICE_NONE) &&(getForceUse(AUDIO_POLICY_FORCE_FOR_DOCK) == AUDIO_POLICY_FORCE_ANALOG_DOCK)) {device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET;}if (device2 == AUDIO_DEVICE_NONE) {//扬声器device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER;}int device3 = AUDIO_DEVICE_NONE;if (strategy == STRATEGY_MEDIA) {// ARC, SPDIF and AUX_LINE can co-exist with others.device3 = availableOutputDevicesType & AUDIO_DEVICE_OUT_HDMI_ARC;device3 |= (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPDIF);device3 |= (availableOutputDevicesType & AUDIO_DEVICE_OUT_AUX_LINE);}device2 |= device3;device |= device2;//High Definition Multimedia Interface,HDMI,高传输就是不能用speaker扬声器if ((strategy == STRATEGY_MEDIA) &&(getForceUse(AUDIO_POLICY_FORCE_FOR_HDMI_SYSTEM_AUDIO) ==AUDIO_POLICY_FORCE_HDMI_SYSTEM_AUDIO_ENFORCED)) {device &= ~AUDIO_DEVICE_OUT_SPEAKER;}// for STRATEGY_SONIFICATION:// if SPEAKER was selected, and SPEAKER_SAFE is available, use SPEAKER_SAFE insteadif ((strategy == STRATEGY_SONIFICATION) &&(device & AUDIO_DEVICE_OUT_SPEAKER) &&(availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {device |= AUDIO_DEVICE_OUT_SPEAKER_SAFE;device &= ~AUDIO_DEVICE_OUT_SPEAKER;}} break;default:ALOGW("getDeviceForStrategy() unknown strategy: %d", strategy);break;}if (device == AUDIO_DEVICE_NONE) {ALOGV("getDeviceForStrategy() no device found for strategy %d", strategy);device = getApmObserver()->getDefaultOutputDevice()->type();ALOGE_IF(device == AUDIO_DEVICE_NONE,"getDeviceForStrategy() no default device defined");}ALOGVV("getDeviceForStrategy() strategy %d, device %x", strategy, device);//最后这个device就是audio_policy_configuration里面的deviceport标签的typereturn device;
}

总结以上函数:根据strategy策略确定选择哪个设备device?这个设备device实质就对应了deviceType,也就是解析audio_policy_configuration解析deviceport标签的type;如果没有强制设置用途forceUse的话,设备选择顺序依次是:蓝牙设备->有线连接设备(如耳机)->usb连接设备->自带的音频设备;函数很长,但是大致流程就是这样;最后选择出来的是一个uint32_t类型的device,实质就是一个枚举值enum,类似于这样:一个AUDIO_DEVICE_OUT_SPEAKER或者多个这样的值取或。

是不是对最后的device有点熟悉?没错!它就是前面文章解析audio_policy_configuration中的devicePort标签中的type类型的值,因为这个getDeviceForStrategyInt返回的只是一个type,而我们是要实体类DeviceDescriptor,还需要从所有输出设备中availableOutputDevices用type来过滤(getDevicesFromTypeMask)得到DeviceDescriptor;

现在,我们从一开始播放的音频属性attr、sampleRate、format等等参数,已经可以决定了它属于哪个streamType,可能会在哪个device中播放,最后就是要确定在哪个output输出通道了

根据streamType、DeviceScriptor获取output输出通道

回到getOutputForAttrInt函数中,从Engine那边根据attr、sampleRate、format等等原始音频参数获取得到streamType和DeviceScriptor,然后通过getOutputForDevices来获取output输出通道,它是如何来通过参数转换获得的呢,我们来看看源码:

audio_io_handle_t AudioPolicyManager::getOutputForDevices(const DeviceVector &devices,audio_session_t session,audio_stream_type_t stream,const audio_config_t *config,audio_output_flags_t *flags,bool forceMutingHaptic)
{......此处省略代码,主要内容是根据streamType来决定flag标识......//如果flag不包含AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD,也就是不需要送往硬件编码器解码的if (((*flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) == 0) ||!(mEffects.isNonOffloadableEffectEnabled() || mMasterMono)) {/** 遍历所有IOProfile,如果某个IOProfile的supportDevice全部包含devices,* 且支持sample_rate、format、channel等等,就选择这个Profile* */profile = getProfileForOutput(devices,config->sample_rate,config->format,channelMask,(audio_output_flags_t)*flags,true /* directOnly */);}/*** getdevice找output,前期已经打开过了,保存在output* */   if (profile != 0) {//遍历所有已打开的输出通道,mOutput保存了所有打开的输出通道for (size_t i = 0; i < mOutputs.size(); i++) {sp<SwAudioOutputDescriptor> desc = mOutputs.valueAt(i);if (!desc->isDuplicated() && (profile == desc->mProfile)) {// 必须每个参数都相同,且session和mDirectClientSession相同,后者就很难实现了,第一次进来的话if ((config->sample_rate == desc->mSamplingRate) &&(config->format == desc->mFormat) &&(channelMask == desc->mChannelMask) &&(session == desc->mDirectClientSession)) {desc->mDirectOpenCount++;ALOGI("%s reusing direct output %d for session %d", __func__, mOutputs.keyAt(i), session);return mOutputs.keyAt(i);}}}//如果不可以多次打开,就跳转到non_direct_output选择设备if (!profile->canOpenNewIo()) {goto non_direct_output;}//以下是重新在走一遍打开输出通道流程sp<SwAudioOutputDescriptor> outputDesc =new SwAudioOutputDescriptor(profile, mpClientInterface);String8 address = getFirstDeviceAddress(devices);.......//在打开一次输出通道ouputstatus = outputDesc->open(config, devices, stream, *flags, &output);// only accept an output with the requested parametersif (status != NO_ERROR ||(config->sample_rate != 0 && config->sample_rate != outputDesc->mSamplingRate) ||(config->format != AUDIO_FORMAT_DEFAULT && config->format != outputDesc->mFormat) ||(channelMask != 0 && channelMask != outputDesc->mChannelMask)) {if (output != AUDIO_IO_HANDLE_NONE) {outputDesc->close();}// fall back to mixer output if possible when the direct output could not be openif (audio_is_linear_pcm(config->format) &&config->sample_rate  <= SAMPLE_RATE_HZ_MAX) {goto non_direct_output;}return AUDIO_IO_HANDLE_NONE;}outputDesc->mDirectOpenCount = 1;//这种方式打开要给他赋值sessionoutputDesc->mDirectClientSession = session;//添加到输出通道的集合中去mOutputsaddOutput(output, outputDesc);mPreviousOutputs = mOutputs;ALOGV("%s returns new direct output %d", __func__, output);mpClientInterface->onAudioPortListUpdate();return output;}//不能重新打开输出通道的情况下,从已经打开的设备中选择一个匹配度最高的
non_direct_output:.......if (audio_is_linear_pcm(config->format)) {// get which output is suitable for the specified stream. The actual// routing change will happen when startOutput() will be called//从已经打开的mOutputs输出通道中,遍历每个通道的,且该通道包含支持设备必须全包含devices集合SortedVector<audio_io_handle_t> outputs = getOutputsForDevices(devices, mOutputs);// at this stage we should ignore the DIRECT flag as no direct output could be found earlier*flags = (audio_output_flags_t)(*flags & ~AUDIO_OUTPUT_FLAG_DIRECT);//从已经选择出来的通道outputs集合中,根据format、channelMask、sampleRate选择一个匹配度最高作为输出通道output = selectOutput(outputs, *flags, config->format, channelMask, config->sample_rate);}return output;
}

总结一下这个函数,首先用streamType确定flag属性,其次从打开的输出通道mOutputs中遍历每个通道,如果这个通道支持所有的从Engine那边选择的设备device,且sampleRate、Channel等兼容,就选择这个通道,而且session和mDirectClientSession必须相同才行,相同就选择这个通道,不同就要分两种情况在做决策:
情况一: 当前这个已打开的输出通道的IOProfile是否支持多次打开,支持多次打开,就用这次打开参数在打开一次,把这个打开的通道赋给我们这次的音频播放逻辑即可
情况二: 不支持再次打开,就进入non_direct_output逻辑,遍历已经打开的通道,选择一个匹配度最高通道作为这次输出通道即可,且这个通道必须支持Engine选择的devices集合、sampleRate、format以及channelMask也兼容才行

到这里,输出通道基本上就确定了!总结一下这个过程就是:

  1. 根据输入attr参数到Engine模块去确定音频streamType属性,确定规则主要对比使用输入参数attr的与ProductStrategy的attr对比,依次对比contentType、usage等,相等就选择该ProductStrategy的配置streamType。
  2. 根据输入attr参数去Engine模块确定devices设备,确定规则同第一条先找到ProductStrategy,这用这个ProductStrategy策略去进行匹配具体的设备,匹配规则根据策略不同而定,一般是以下这个规则:蓝牙设备->有线连接设备(如耳机)->usb连接设备->自带的音频设备;
  3. 根据前两个步骤得到的streamType、DeviceVector以及输入attr参数确定输出通道output;确定规则先遍历所有输出流IOProfile,比较输入桉树attr、sampleRate等于IOProfile是否相等,相等返回此IOProfile,此时在遍历所有的输出通道,每个输出通道保存在SwAudioOutputDescriptor内,如果SwAudioOutputDescriptor绑定的IOProfile与前面的IOProfile相等,并且其支持的设备全包含第2个步骤的DeviceVector,就选择这个输出通道。

收尾工作保存状态

收尾工作之保存此次选择的结果

至此,通道output选择出结果了;最后就需要将此次选择过程保存好即可;保存在哪里呢?
回到getOutputForAttr函数,这个函数结尾处有:

//将此次结果stream、attr等参数用TrackClientDescriptor封装保存
sp<TrackClientDescriptor> clientDesc =new TrackClientDescriptor(*portId, uid, session, resultAttr, clientConfig,sanitizedRequestedPortId, *stream,mEngine->getProductStrategyForAttributes(resultAttr),toVolumeSource(resultAttr),*flags, isRequestedDeviceForExclusiveUse,std::move(weakSecondaryOutputDescs));//过滤所有输出通道得到SwAudioOutputDescriptor                                sp<SwAudioOutputDescriptor> outputDesc = mOutputs.valueFor(*output);client客户端,顾名思义;当前SwAudioOutputDescriptor是已经封装了包含输出通道、device等信息的实体类;是谁连接了这个通道//哪个进程uid、session等,它就是客户端,在getoutputForAttr成功找到output后,就会将客户端的信息添加到这个类里面来outputDesc->addClient(clientDesc);

这个SwAudioOutputDescriptor类很重要,所有打开的输出设备是保存在它里面,选择输出通道的客户端信息也是保存在它里面。TrackClientDescriptor里面的session参数就是客户端与服务端的唯一值,后续可通过此参数来确定哪个客户端对应哪个服务端;

以上类似的收尾工作还存在许多,比如AudioPolicyService也保存了客户端client信息,AudioFlinger也是如此,只是保存的对象不同;经过上述getOutputForAttr业务逻辑之后,整个Audio的各模块之间的引用持有情况大致如下图:

从应用层到framework层引用关系大致是:
java层AudioTrack的mNativeTrackInJavaObj成员持有native的AudioTrack引用
AudioTrack属于客户端进程,它与TrackHandler是在不同进程中,通过Binder IPC通信;
TrackHandler静态代理Track,包裹封装Track;而Track和PlaybackThread之间是相互持有对方应用,一个Track对应一个PlaybackThead,而一个PlaybackThread可能包含多个Track,通过mTracks集合保存;
TrackHandler、Track以及PlaybackThread都是在AudioFlinger进程中;后面的四个类都属于AudioPlayService进程。
在AudioPolicyService中,以key-value存储客户端信息,key是portId,value是AudioPlayClient,在AudioPlayClient保存了客户端信息;
SwAudioOutputDescriptor则很重要,它保存了之前打开的通道output,也包含了谁在往这个通道灌数据的client;

上面的TrackHandler、Track很重要,涉及到后续共享内存如何分配?分配的内存客户端、服务端如何去使用、传递音频数据,这部分在下个文章里面讲解!

名词解释

名词 意思
ALSA Advanced Linux Sound Architecture 高级Linux音频架构
A2DP Advanced Audio Distribution Profile Profile蓝牙音频传输高保真模式
EARPIECE 电话听筒
headPhone 耳机
WIRED handphone 有线耳机
handset handphone加上一个话筒,带耳机的话筒
speaker 扬声器
remote submix 内录音功能,将播放的音频数据拷贝进行保存录音
DEVICE_OUT_ANLG_DOCK_HEADSET 通过基座连接的模拟有线耳机
DEVICE_OUT_DGTL_DOCK_HEADSET 通过基座连接的数字有线耳机
AUDIO_OUTPUT_FLAG Description
AUDIO_OUTPUT_FLAG_PRIMARY 表示音频流需要输出到主输出设备,一般用于铃声类声音
AUDIO_OUTPUT_FLAG_DIRECT 表示音频流直接输出到音频设备,不需要软件混音,一般用于 HDMI 设备声音输出
AUDIO_OUTPUT_FLAG_FAST 表示音频流需要快速输出到音频设备,一般用于按键音、游戏背景音等对时延要求高的场景
AUDIO_FLAG_LOW_LATENCY 同上fast
AUDIO_OUTPUT_FLAG_DEEP_BUFFER 表示音频流输出可以接受较大的时延,一般用于音乐、视频播放等对时延要求不高的场景
AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 表示音频流没有经过软件解码,需要输出到硬件解码器,由硬件解码器进行解码
AUDIO_OUTPUT_FLAG_HW_AV_SYNC 硬件同步

Audio播放音频 --- 建立播放通道相关推荐

  1. JS播放音频 JS播放mp3 JS播放音乐 Java播放音频 Java播放音乐 Java播放mp3 的jmp123.jar包安装 语音播报 Java获取根路径

    JS播放音频 JS播放mp3 // @Bind #jsPlayMp3Btn.onClick !function(self, arg) {var mp3Url = "http://localh ...

  2. android怎么播放音频,Android播放音频的两种方式

    一种使用MediaPlayer,使用这种方式通常是播放比较长的音频,如游戏中的背景音乐. 代码如下: private MediaPlayer mPlayer = null; mPlayer = Med ...

  3. java 停止线程播放音频_Notification 播放 关闭 声音----转载

    NotificationPlayer.java 定义一个播放Notification声音的player,本质上仍然是一个MediaPlayer,这个是多线程编程的很好的例子 public class  ...

  4. Xamarin的播放音频和视频的媒体管理插件

    媒体应用程序比其他应用程序更受益于与本地平台一起工作.有些事情,比如处理音频焦点的中断.网络连接以及通知和其他回放控件之间的通信,这是一件复杂的事情. 为使Xamarin开发访问这些本地平台的功能,我 ...

  5. 10. 100ASK_V853-PRO开发板支持录音和播放音频

    0.前言 ​ 本章主要讲述如何使用板载的MIC拾音咪头录音并使用喇叭播放音频. ​ 音频_开发指南:https://tina.100ask.net/SdkModule/Linux_AudioFrequ ...

  6. VUE + howler.js 播放音频

    VUE2 + howler.js 播放音频  自动播放 <divid="audio_btn":class="audioClass"@click=" ...

  7. python 播放声音_python播放音频

    广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! 音频预处理 这一讲主要介绍些音频基本处理方式,为接下来的语音识别打基础. 三种播 ...

  8. Android Audio播放音频之数据传递

    AudioTrack之数据传递 简介 接上一篇AudioTrack播放音频之建立通道找到了通道的唯一句柄值output,这个output实质就是在AudioFlinger创建PlaybackThrea ...

  9. js控制audio音量_js控制html5 audio音频暂停播放

    js控制html5 audio音频暂停播放 音乐控制 音乐 播放/暂停 重新播放 function rbf(){ var audio = document.getElementById('music1 ...

最新文章

  1. react遇到的各种坑
  2. 【雷达书籍分享】RADAR SYSTEMS ANALYSIS AND DESIGN USING MATLAB
  3. QML笔记-KeyNavigation的使用(2种例子)
  4. 数据结构与算法之-----向量(Vector)
  5. Windows 下的坐标系
  6. 6-7 使用函数输出水仙花数_Go语言并发如何使用才更加高效
  7. 在eclipse中如何搭建ssh框架
  8. Machine Learning - week 4 - Non-linear Hypotheses
  9. 【TSP】基于matlab模拟退火算法求解31城市旅行商问题【含Matlab源码 1148期】
  10. Kali Linux渗透测试——WEB渗透(二)
  11. Windows 开发 辅助调试工具 和 方法
  12. 相关滤波的视觉目标跟踪算法学习
  13. 百度收录提交软件-百度批量收录提交入口工具免费
  14. C虾仔笔记 - ScrollView垂向滚动视图
  15. matlab读取txt数据文件
  16. PMP中工具与技术归类
  17. 常规设置-Sinon.JS
  18. 关于xftp和xshell 软件评估期已过的解决办法
  19. 微信小程序实现轮播图(超简单)
  20. CocosCreator 打包微信小游戏

热门文章

  1. 项目实训2021.07.02
  2. python葡萄酒数据_用python进行葡萄酒质量预测
  3. php python 源码安装教程,Python安装的图文教程分享
  4. HanLP极致简繁转换
  5. 比利时一年中遭受加密货币骗局损失近1000万欧元
  6. MAC常用快捷键 基本常用的都整理在这里了
  7. 《程序员的自我修养》解析第一章
  8. 2018世界物联网与智能制造高峰论坛圆满落幕,“未来制造”成为大会热词
  9. ERP项目文档--想到用时方恨少
  10. 构建U盘启动的嵌入式linux