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()

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

  1. attr属性中的usage、contentType、flag等等信息,根据这些信息按图索骥查找确定它最终的输出通道:(usage、contentType、flag等)->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(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类里面发生的,内部大致有这么一个执行流程:
在这里插入图片描述

逐个函数分析

  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类型支不支持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也兼容才行

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

  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 硬件同步

猜你喜欢

转载自blog.csdn.net/jackzhouyu/article/details/122461008
今日推荐