• Android audio 框架 之AudioPatch



    本文主要围绕以下几点来说明

    1. audiopatchd的概念以及相关应用场景
    2. audiopatch 在应用层的使用
    3. 框架中audiopatch的流程
    4. hal中audiopatch的流程

    audiopatch的概念和使用场景

    patch顾名思义路径,audiopatch就是构建一条声音流的路径。为什么要有这样的一条路径?

    可以从两个方面考虑

    1. 降低音频数据的传输延时,比如一个场景耳返也就是录音的数据(麦克分收音)需要重新播放的耳机里面。 假设框架里面没有一条从录音直接到放音的路径,那么数据需要从框架录制到应用,然后从应用重新拷贝到播放,而有了audiopatch之后 框架里面就可以直接将录音的数据写到输出设备了。

    2. 支持外部设备的放音,比如FM收音机,声音从FM设备进来 可以通过hal层建立的patch,直接输出到设备中。
      在audioflinger 中每种情况路径连接都可以定义为一个patch 路径。

    audiopatch 在应用层的使用

    代码位置:packages/services/Car/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/CarAudioZoneInputFragment.java

    • 在AAOS的kitchensink测试项中的CarAudioZoneInpuFragment有使用patch来创建录音到播放的路径:

      int result = AudioManager.createAudioPatch(patch,
       new AudioPortConfig[] { sourceConfig },
       new AudioPortConfig[] { sinkConfig });
      
      • 1
      • 2
      • 3
      • 首先调用carAudioService的createAudioPatch,传递的是录音设备的地址和播放的usage media、其中录音设备的地址 也就是patch 中的source设备是作为输入,而patch中的sink 输出只要根据usage找到可以播的sink设备即可。

      • CarAudioService 需要根据sourceAddress构建 AudioPortConfig sinkConfig 和sourceConfig,这两个Config都是通过audioPolicyManager的getDevice返回的结构体中通过address/usage进行筛选构建的的,然后调用AudioManager的createAudioPatch 。

      • AudioMannger中会调用AudioSystem的createAudioPatch,这个通过jni调用,在jni 中会把sinkConfig 和 sourceConfig转换为path结构体中的sources 和sinks,并传递的handle指针。

      • AudioSystem会通过binder一路调用到AudioPolicyMangaer的createAudioPatchInternal
        AudioPolicyMangaer会调用到patchPanel的createAudioPatch,在这边会为sink创建playthread 为source创建recordthread。同时为这两个线程创建软件bridge

      • 软件的bridge创建好之后 就调用track start 将track 启动,也就构造了一个录音设备录制的声音会写入到播放设备的路径的测试。

    audiopatch 在框架层的使用

    代码位置:

    frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
    frameworks/av/media/libmedia/include/media/PatchBuilder.h
    frameworks/av/services/audioflinger/PatchPanel.cpp

    在AudioPolicyManager打开输出的设备后 在setOutputDevice 会为每对deviceport和mixport创建一个patch。这边的patch创建时为mixport流和deviceport 设备之间构建一个数据的通路。

    • 首先在AudioManager初始化解析到的mixport和deviceport加入到patchBuilder进行patch信息的创建。
    • 创建的流程就是构造patch的numsink 、sink 和num_sources和sources,这个sink和source 是通过前述的DeviceDescriptor进行
      toAudioPortConfig转换而来的。而在转换的过程也对ext.hwmoule进行了赋值。
            PatchBuilder patchBuilder;
            patchBuilder.addSource(outputDesc);
            ALOG_ASSERT(filteredDevices.size() <= AUDIO_PATCH_PORTS_MAX, "Too many sink ports");
            for (const auto &filteredDevice : filteredDevices) {
                patchBuilder.addSink(filteredDevice);
            }
    
    installPatch(__func__, patchHandle, outputDesc.get(), patchBuilder.patch(),
                    (muteWaitMs == 0 && outputDesc->isActive()) ? (delayMs + (outputDesc->latency() / 2)) : delayMs);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    其中addSource 和addSink的实现,具体在PatchBuilder.h中

    template
        audio_port_config& add(const sp& entity) {
        audio_port_config* added = advance();
        entity->toAudioPortConfig(added);
        return *added;
    }
    PortCfgs sinks() { return PortCfgs(&mPatch.num_sinks, mPatch.sinks); }
    PortCfgs sources() { return PortCfgs(&mPatch.num_sources, mPatch.sources}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • installpatch的执行流程

      • install patch 的执行流程跟其他所有audioPolicyManager通过client 调用audioflinger是一样的。
      • 在audiopolicyservice 中存在一个 AudioCommandThread,所有的调用都会生成一个命令AudioCommand命令会经过一系列的判断会insert到队列中。
      • 命令执行线程循环中 从队列取命令,命令取到后 大部分都是调用的audioflinger来进行处理。
      • 延时命令:延时命令的意思是 执行这个audio的命令需要delay time的时间,当有延时的命令发下来之后,执行之后命令 需要等待delaytime 才会唤醒执行下一个命令进行执行。 这边有个问题,如果当前队列不是空,上一个命令执行完成,会有一段cpu狂循环的时间。
      • createpatch: command 从队列中取出来之后 就会调用audioFlinger的createPatch 进行patch的创建这边会调用到PatchPanel.createAudioPatch。
    • creatPatch流程

      • 首先根据传递audio_patch_handle_t, 确认当前存储mPatches是不是已经有了,有的话要先把patch的连接remove, 然后断开连接。

      • 对于sink输出是device类型的 调用audioflinger的 openOutput_l创建一个播放的线程,并加入到newPatch.mPlayback.

                          sp thread = mAudioFlinger.openOutput_l(
                                                                  patch->sinks[0].ext.device.hw_module,
                                                                  &output,
                                                                  &config,
                                                                  &mixerConfig,
                                                                  outputDevice,
                                                                  outputDeviceAddress,
                                                                  flags);
                          ALOGE("mAudioFlinger.openOutput_l() returned %p", thread.get());
                          if (thread == 0) {
                              status = NO_MEMORY;
                              goto exit;
                          }
                          newPatch.mPlayback.setThread(reinterpret_cast(thread.get()));
      
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 同样对于source输入为device类型的会创建一个record的线程并加入到newpatch当中的recordthread。
      • 对于mix类型的source 或者sink 则不会创建线程。
      • 创建连接createConnections。首先会创建PatchTrack用于连接record线程和play线程的buffer,然后调用start()
        启动play和record 的线程。
          // start capture and playback
          mRecord.track()->start(AudioSystem::SYNC_EVENT_NONE, AUDIO_SESSION_NONE);
          mPlayback.track()->start();
      
      • 1
      • 2
      • 3

    高通hal中audiopatch的流程

    硬件实现方式 通过alsa的控件实现, 根据sink 和source的type的不同创建的流程也不一样。

    1. 如果sink 是 mix, source是device:说明数据是从外部的设备流入到系统内部,这种情况对应的场景 可以是录音,所以需要获取一个可用的输入流,由这个流去从设备中读取数据 写入到框架中的buffer中。
    2. 如果sink是device、source是mix:说明数据从框架中写出到外部的设备,对应的场景是播放,需要获取到一个可用的输出流, 由这个流将数据从框架中写入到外部的设备中。
    3. 如果sink 和source 都是device:说明数据直接从输入设备到输出设备,对应的场景可以是FM到BUS、高通是通过对应的alsa自定义的控件来完成

    相关问题

    • AUDIO_PORT_TYPE_DEVICE 和 AUDIO_PORT_TYPE_MIX 哪里赋值上去的,区别是什么?

    这两个类型是patchBuilder addSource 和 addSink 将加入的sink和source转换为port 时设置的。

    1. addsource 参数对应的类是SwAudioOutputDescriptor
      然后会调用其对应的toAudioPortConfig 将source赋值为AUDIO_PORT_TYPE_MIX

    2. addSink 参数对应的类是DeviceVector会调用
      DeviceDescriptorBase 的toAudioPortConfig 将sink赋值为AUDIO_PORT_TYPE_DEVICE

    也就是TYPE_DEVICE 可以理解为音频xml定义的deviceport, 而TYPE_MIX理解为之前定义的mixport。

    SwAudioOutputDescriptor类型的port转换:

    
    (const sp& outputDesc,
                                                  const DeviceVector &devices
    
    
    DeviceVector filteredDevices = outputDesc->filterSupportedDevices(devices);
    
    patchBuilder.addSource(outputDesc);
    patchBuilder.addSink(filteredDevice);
    
    
    
    void AudioOutputDescriptor::toAudioPortConfig(struct audio_port_config *dstConfig,
                                                  const struct audio_port_config *srcConfig) const
    {
        dstConfig->config_mask = AUDIO_PORT_CONFIG_SAMPLE_RATE|AUDIO_PORT_CONFIG_CHANNEL_MASK|
                                AUDIO_PORT_CONFIG_FORMAT|AUDIO_PORT_CONFIG_GAIN;
        if (srcConfig != NULL) {
            dstConfig->config_mask |= srcConfig->config_mask;
        }
        AudioPortConfig::toAudioPortConfig(dstConfig, srcConfig);
        toPolicyAudioPortConfig(dstConfig, srcConfig);
        dstConfig->role = AUDIO_PORT_ROLE_SOURCE;
        dstConfig->type = AUDIO_PORT_TYPE_MIX;
        dstConfig->ext.mix.hw_module = getModuleHandle();
        dstConfig->ext.mix.usecase.stream = AUDIO_STREAM_DEFAULT;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    device类型的toPort 转换

      void DeviceDescriptorBase::toAudioPortConfig(struct audio_port_config *dstConfig,
                                                   const struct audio_port_config *srcConfig) const
      {
          dstConfig->config_mask = AUDIO_PORT_CONFIG_GAIN;
          if (mSamplingRate != 0) {
              dstConfig->config_mask |= AUDIO_PORT_CONFIG_SAMPLE_RATE;
          }
          if (mChannelMask != AUDIO_CHANNEL_NONE) {
              dstConfig->config_mask |= AUDIO_PORT_CONFIG_CHANNEL_MASK;
          }
          if (mFormat != AUDIO_FORMAT_INVALID) {
              dstConfig->config_mask |= AUDIO_PORT_CONFIG_FORMAT;
          }
          if (srcConfig != NULL) {
              dstConfig->config_mask |= srcConfig->config_mask;
          }
          AudioPortConfig::toAudioPortConfig(dstConfig, srcConfig);
          dstConfig->role = audio_is_output_device(mDeviceTypeAddr.mType) ?
                              AUDIO_PORT_ROLE_SINK : AUDIO_PORT_ROLE_SOURCE;
          dstConfig->type = AUDIO_PORT_TYPE_DEVICE;
          dstConfig->ext.device.type = mDeviceTypeAddr.mType;
          (void)audio_utils_strlcpy_zerofill(dstConfig->ext.device.address, mDeviceTypeAddr.getAddress());
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
  • 相关阅读:
    服务器 jupyter 文件名乱码问题
    Anaconda下载安装
    Vue研习录(02)——模板语法讲解及示例分析
    el-table滚动加载、懒加载(自定义指令)
    MFC web文件 CHttpFile的使用初探
    maven或者gradle打完jar,jekins启动提示找不到问题
    基于智能合约的银行借贷方案设计与实现
    一个前端非侵入式骨架屏自动生成方案
    Java Character.SubSet hashCode()方法具有什么功能呢?
    2022/09/12、13、14 day02/03/04:HTML和CSS(二)
  • 原文地址:https://blog.csdn.net/H2008066215019910120/article/details/132792729