音频Open Output业务逻辑

音频Open Output业务逻辑

open output顾名思义,你可以理解为打开一条输出通道,这条通道上跑着一定格式、采样率等特定的数据,通道的终点是一个音频设备,用于播放;所以这个open output业务就是初始化这条通道,为这个通道建立各种所需功能等。

openOutput总流程图

在这里插入图片描述
纠正上图的错误,上图中其实没有DeviceHalHidl和IDevice的,在android 10中,经过AudioStreamOut后,就会通过HIDL提供的服务端,通过服务端跨进程调用到hal的PrimaryDevice去打开设备


openOutput流程中的关键点

流程启动点

这里以AudioPolicyManager类的initialize函数为起点分析,当AudioPolicyManager加载解析完音频配置文件audio_policy_configuration后,然后会加载配置文件中的每个模块loadHwModule,遍历模块中所有的数据流,最后打开每条流及其支持的设备;如下代码:

status_t AudioPolicyManager::initialize() {
  
    for (const auto& hwModule : mHwModulesAll) {
        //配置文件加载进来为module,现在要加载so源码了,mpClientInterface是AudioPolicyService那边,负责加载源码
        hwModule->setHandle(mpClientInterface->loadHwModule(hwModule->getName()));
        if (hwModule->getHandle() == AUDIO_MODULE_HANDLE_NONE) {
            ALOGW("could not open HW module %s", hwModule->getName());
            continue;
        }
        mHwModules.push_back(hwModule);
        for (const auto& outProfile : hwModule->getOutputProfiles()) {
            //选择当前output流支持的优先级最高的一个,mDefaultOutputDevice > supportDevice[0]
            const DeviceVector &supportedDevices = outProfile->getSupportedDevices();
            //mAvailableOutputDevices为attached标签设备
            DeviceVector availProfileDevices = supportedDevices.filter(mAvailableOutputDevices);
            sp<DeviceDescriptor> supportedDevice = 0;
            if (supportedDevices.contains(mDefaultOutputDevice)) {
                //优先选择默认绑定的设备
                supportedDevice = mDefaultOutputDevice;
            } else {
                // choose first device present in profile's SupportedDevices also part of
                // mAvailableOutputDevices.
                if (availProfileDevices.isEmpty()) {
                    continue;
                }
                //优先取第一个
                supportedDevice = availProfileDevices.itemAt(0);
            }
            if (!mAvailableOutputDevices.contains(supportedDevice)) {
                continue;
            }
            //最终这个supportedDevice是一个DeviceDescriptor,是attached或defauleAttach标签内存在的
            sp<SwAudioOutputDescriptor> outputDesc = new SwAudioOutputDescriptor(outProfile,
                                                                                 mpClientInterface);
            audio_io_handle_t output = AUDIO_IO_HANDLE_NONE;
            status_t status = outputDesc->open(nullptr, DeviceVector(supportedDevice),
                                               AUDIO_STREAM_DEFAULT,
                                               AUDIO_OUTPUT_FLAG_NONE, &output);
            ......
            //打开成功的流加入进来
            addOutput(output, outputDesc);
            setOutputDevices(outputDesc,
                             DeviceVector(supportedDevice),
                             true,
                             0,
                             NULL);
        }
    updateDevicesAndOutputs();
    return status;
}

上述代码执行逻辑:

  1. 遍历每个HwModule,在遍历HwModule里面所有的IOProfile。
  2. IOProfile理解为一个数据流,它有可能支持多种format、sampleRate、channel等,在后面打开时需要传递这些参数,如果一个IOProfile支持多种格式那选择哪个呢?
  3. 同时,如果IOProfile是一个输出流,数据流最终会流到音频设备,支持播放这个流可能有多个设备,那么取哪一个设备?
    上面代码已经告诉我们一个IOProfile如何选择哪个音频Device,规则如下:
    a. 先获取IOProfile所有支持的设备device
    b. 用mAvailableOutputDevices来过滤上个步骤中所有的设备,也就是取个交集。这个mAvailableOutputDevices其实就是audio_policy_configuration配置文件中attachedDevices标签内的设备
    c. 如果过滤后的设备集devices中包含配置文件defaultOutputDevice标签指定的设备就选取它,否则就去集合的第一个设备作为选取设备;
    这样就确定要打开哪个设备了,并且打卡成功后,使用mOutputs成员变量存储起来;

如果你对上面说的标签、配置文件不知道是什么东西,那么你可能还需要学习这篇文章


IOProfile选择哪个格式?

格式确定逻辑在SwAudioOutputDescriptor的open函数中,如下:

status_t SwAudioOutputDescriptor::open(const audio_config_t *config,
                                       const DeviceVector &devices,
                                       audio_stream_type_t stream,
                                       audio_output_flags_t flags,
                                       audio_io_handle_t *output)
{
    mDevices = devices;
    //address一般都是空串,这里大多也是个空串
    const String8& address = devices.getFirstValidAddress();
    /* devices是一个集合,这里的types返回所有设备的type类型取或,
    * 而且type类型能反应这个设备类型、和名称,如AUDIO_DEVICE_OUT_EARPIECE
    * 就是一个输出类型设备、名字叫听筒earPiece
    **/
    audio_devices_t device = devices.types();

    audio_config_t lConfig;
    if (config == nullptr) {
        lConfig = AUDIO_CONFIG_INITIALIZER;
        /* SwAudioOutputDescriptor的IOProfile内有多个profile,支持多种格式,取哪个呢?
        * 取有效,也就是samplerate、channel、format都不为空且format最大的一个,
        * mSampleRate在构造函数时就确定了*/
        lConfig.sample_rate = mSamplingRate;
        lConfig.channel_mask = mChannelMask;
        lConfig.format = mFormat;
    } else {
        lConfig = *config;
    }
    ......
    ALOGV("opening output for device %s profile %p name %s",
          mDevices.toString().c_str(), mProfile.get(), mProfile->getName().string());
    //会到驱动层kernel,打开一个output在audioFlinger会为其创建一个线程,用键值对存储,key为返回的output,本地保存
    status_t status = mClientInterface->openOutput(mProfile->getModuleHandle(),
                                                   output,
                                                   &lConfig,
                                                   &device,
                                                   address,
                                                   &mLatency,
                                                   mFlags);
    .....
    return status;
}

上述代码已经确定了选择某个具体的设备了,你看出来了吗?
就在代码audio_devices_t device = devices.types()这里,devices是一个集合,这里的types返回所有设备的type类型取或值,而且type类型能反应这个设备类型、和名称,如AUDIO_DEVICE_OUT_EARPIECE,就是一个输出类型设备、名字叫听筒earPiece

除此之外,还直接将成员变量mSamplingRate、mChannelMask和mFormat赋值给此次open的参数,那这些成员什么时候赋初值的呢?
答案就是在构造函数里面撒:

AudioOutputDescriptor::AudioOutputDescriptor(const sp<AudioPort>& port,
                                             AudioPolicyClientInterface *clientInterface)
    : mPort(port), mClientInterface(clientInterface)
{
    if (mPort.get() != nullptr) {
        //因为mPort里面可能有多重profile,支持不同的format、sample等,取有效并且format最大的一个
        mPort->pickAudioProfile(mSamplingRate, mChannelMask, mFormat);
        ....
    }
}
void AudioPort::pickAudioProfile(uint32_t &samplingRate,
                                 audio_channel_mask_t &channelMask,
                                 audio_format_t &format) const
{
    format = AUDIO_FORMAT_DEFAULT;
    samplingRate = 0;
    channelMask = AUDIO_CHANNEL_NONE;

    // 检查集合profile至少有一个有效,也就是samplerate、channel和format同时不能为空
    if (!mProfiles.hasValidProfile()) {
        return;
    }
    //sPcmFormatCompareTable是一个已经初始化的数组,内枚举了支持的format格式,这里取最大一个采样率
    audio_format_t bestFormat = sPcmFormatCompareTable[ARRAY_SIZE(sPcmFormatCompareTable) - 1];
    ......
    //遍历每个Profile的采样率、格式等信息,取有效并且format最大的一个
    for (size_t i = 0; i < mProfiles.size(); i ++) {
        ......
        audio_format_t formatToCompare = mProfiles[i]->getFormat();
        //比较format取大值,并且当前format不能比sPcmFormatCompareTable数组最后一个值大,
        //也就是必须是sPcmFormatCompareTable支持的format才行
        if ((compareFormats(formatToCompare, format) > 0) &&
                (compareFormats(formatToCompare, bestFormat) <= 0)) {
            uint32_t pickedSamplingRate = 0;
            audio_channel_mask_t pickedChannelMask = AUDIO_CHANNEL_NONE;
            pickChannelMask(pickedChannelMask, mProfiles[i]->getChannels());
            pickSamplingRate(pickedSamplingRate, mProfiles[i]->getSampleRates());
            //上面如果选取结果有效,就把值写入到参数的指针;从而也确定了AudioOutputDescriptor的成员变量了
            if (formatToCompare != AUDIO_FORMAT_DEFAULT && pickedChannelMask != AUDIO_CHANNEL_NONE
                    && pickedSamplingRate != 0) {
                format = formatToCompare;
                channelMask = pickedChannelMask;
                samplingRate = pickedSamplingRate;
                // TODO: shall we return on the first one or still trying to pick a better Profile?
            }
        }
    }
}

总结一句话就是:选取format格式最大,且sampleRate、format和channel都有值的一个profile。

这里我们可以分析,一个openOutput实质就是要打开一条输出通道并可用,音频数据流从这条通道上流向某个设备,这个通道支持特定的sampleRate、format、channel,以及它的音频设备也要打开,最后设备可以接收音频数据后直接播放


AudioFlinger创建线程Thread

在AudioFlinger的openOutput_l方法中,openOutputStream打开成功后,会为其创建一个播放线程,但是为什么打开一个通道就要为它创建一个线程呢?
我认为因为这个通道创建成功后,后续会持续把音频数据灌进去,有时对播放音频还有实时性要求,这样的需求就为它创建一个线程来专门跑,即不影响AudioFlinger的其他任务,也保证了自己的数据独享一个线程来跑。
我是这么想的,不知对不对?

sp<AudioFlinger::ThreadBase> AudioFlinger::openOutput_l(audio_module_handle_t module,
                                                            audio_io_handle_t *output,
                                                            audio_config_t *config,
                                                            audio_devices_t devices,
                                                            const String8& address,
                                                            audio_output_flags_t flags)
{
    AudioHwDevice *outHwDev = findSuitableHwDev_l(module, devices);
    .......
    AudioStreamOut *outputStream = NULL;
    status_t status = outHwDev->openOutputStream(
            &outputStream,
            *output,
            devices,
            flags,
            config,
            address.string());

    mHardwareStatus = AUDIO_HW_IDLE;
    /**
     * 每打开一个outputs,成功后都会为它创建一个线程,会根据其flag创建不同的线程,MmapPlaybackThread、
     * OffloadThread、DirectOutputThread、MixerThread等线程,最后保存到mMmapThreads和mPlaybackThreads,
     * key就是output句柄,值就是我们创建的线程
     * */
    if (status == NO_ERROR) {
        if (flags & AUDIO_OUTPUT_FLAG_MMAP_NOIRQ) {
            sp<MmapPlaybackThread> thread =
                    new MmapPlaybackThread(this, *output, outHwDev, outputStream,
                                          devices, AUDIO_DEVICE_NONE, mSystemReady);
            mMmapThreads.add(*output, thread);
            .......
        } else {
            sp<PlaybackThread> thread;
            if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
                thread = new OffloadThread(this, outputStream, *output, devices, mSystemReady);
                ......
            } else if ((flags & AUDIO_OUTPUT_FLAG_DIRECT)
                    || !isValidPcmSinkFormat(config->format)
                    || !isValidPcmSinkChannelMask(config->channel_mask)) {
                thread = new DirectOutputThread(this, outputStream, *output, devices, mSystemReady);
                ........
            } else {
                thread = new MixerThread(this, outputStream, *output, devices, mSystemReady);
                ........
            }
            mPlaybackThreads.add(*output, thread);
            mPatchPanel.notifyStreamOpened(outHwDev, *output);
            return thread;
        }
    }
    return 0;
}

创建成功后,AudioFlinger根据去打开时参数flags创建不同的线程,最后也是用键值对类型(output, thread)来对应存储,后续拿到这个output句柄就可以把数据传入对应的线程了。主要有四类线程:

线程名称线程意义
MmapPlaybackThread不详,后续了解到了再说
OffloadThread继承DirectOutputThread,用于播放一些非PCM格式的Track,该线程上的数据需要传送到硬件解码器上解码后播放
DirectOutputThread直接输出,不能混音,只有一个Track的线程
MixerThread混音线程,可以有多个Track
以上的线程和FLAG对应,所以我们在播放时需要把音频数据送到对应的线程,则传入其FLAG即可。

除以上关键点外,openOutput其他部分流程就是逻辑的依次调用、参数转换了,没有做其他额外重要的事情了;下面用一幅图总结,openOutput流程后各个对象之间的引用图,如下:
在这里插入图片描述

值得注意的有几点:

  • 通道打开成功后,SwAudioOutputDescriptor会保存此次打开的使用参数,如SampleRate、channel、formate以及设备DeviceDescriptor等等;同时也会同步缓存策略那块,策略那块还不是很懂,需要后期再补充!
  • 打开通道的参数FLAG很重要,不同的FLAG在AudioFlinger中会创建不同的线程,后期播放时也会根据FLAG来决定走哪个线程
  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

帅气好男人_Jack

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

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

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

打赏作者

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

抵扣说明:

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

余额充值