Audio播放音频 --- 建立播放通道
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()
两个关键点需要我们关注:
- attr属性中的usage、contentType、flag等等信息,根据这些信息按图索骥查找确定它最终的输出通道:(usage、contentType、flag等)->streamType->strategy->device->output通道句柄,output句柄对应一个输出通道
- 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,直接列出了最后一步
我们需要注意几点是:
- 起始传入参数大致有attr(usage、contentType)、sampleRate、channel和format等
- android_media_AudioTrack、AudioTrack和AudioFlinger之间共享内存如何共享(用于传递音频数据)?回调函数如何回调(从framework到java的回调)?
- AudioPolicyManager和Engine之间如何选择确定输出通道output?
- 确定输出通道之后层层返回回去,各个类如何处理返回结果?
上述流程图上的其他逻辑就是一些参数转换和依次调用了,我们只需要关注重点逻辑即可;第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(type)来创建一个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等等,productStrategy就是解析相关xml文件后的c++实例
* 最后就能找到这些属性是哪个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策略为主策略配置,用于选择设备device的,如一个名为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类里面发生的,内部大致有这么一个执行流程:
逐个函数分析
- 先检查之前是否打开过相同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去打开一遍
- 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实体类对象
- 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类型支不支持a2dp
outputs.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) {
//usb
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_USB_ACCESSORY;
}
if (device2 == AUDIO_DEVICE_NONE) {
//usb 设备device
device2 = 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 instead
if ((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标签的type
return 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);
.......
//在打开一次输出通道ouput
status = outputDesc->open(config, devices, stream, *flags, &output);
// only accept an output with the requested parameters
if (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 open
if (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;
//这种方式打开要给他赋值session
outputDesc->mDirectClientSession = session;
//添加到输出通道的集合中去mOutputs
addOutput(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也兼容才行
到这里,输出通道基本上就确定了!总结一下这个过程就是:
- 根据输入attr参数到Engine模块去确定音频streamType属性,确定规则主要对比使用输入参数attr的与ProductStrategy的attr对比,依次对比contentType、usage等,相等就选择该ProductStrategy的配置streamType。
- 根据输入attr参数去Engine模块确定devices设备,确定规则同第一条先找到ProductStrategy,这用这个ProductStrategy策略去进行匹配具体的设备,匹配规则根据策略不同而定,一般是以下这个规则:蓝牙设备->有线连接设备(如耳机)->usb连接设备->自带的音频设备;
- 根据前两个步骤得到的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 | 硬件同步 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探