Android ALSA音频系统架构分析(1)----从Loopback了解Audio

1. android tinyalsa 理解 ALSA 是linux上的音频框架,音频设备按照此框架实现自己的驱动 alsa-driver,在应用层即可以通过统一的alsa-lib 来读写控制音频设备。 tinyalsa, 就是简化版的alsa-lib ,用于调试查看 linux内核里面alsa 驱动下的音频设备,其原理,即读写 dev/snd/pcmxx 设备节点,ioctl操作。如果hal层的具体实现不经过alsa,那么这个tinyalsa也就没法调试到这些音频设备。而hal层,是不开源的 阅读详情
AI摘要

本文详细解析了MTK Android音频系统架构,重点介绍了Loopback功能的实现原理及流程,包括参数传递、设备开启、音量调节等关键步骤。

   
  1. /***********************************
  2. * Author:刘江明
  3. * Environment:MTK Android 6.0
  4. * Date:2017年05月25日
  5. ***********************************/

一.  前述

        Android音频系统是一套基于Linux ALSA上二次封装开发的一套音频系统,中间进行了很多的功能封装,但最终会用到Linux ALSA。所以在Hal层的类名都会包含ALSA。对于MTK的Android audio,MTK也有一定的介绍,先来大体了解一下:
                  
图1  音频系统的Framework图
        提供给应用的功能接口有:AudioTrack与AudioRecord两个类,分别是播放与录音的功能接口。这两个功能接口会通过AudioSystem调用AudioFlinger。AudioFlinger对管控着所有的Buf与播放,同时AudioFlinger也受AudioSystem管理,其中的管理策略来自于AudioPolicy,AudioPolicy会决定这个是什么类型的音频以及音频的rounting,是音乐,还是打电话,音乐是能打开什么播放设备,是打开喇叭还是打开听筒播放,电话来了是否要把音乐关掉等等的音频策略,包括MTK的音频参数也会在这里加载进来
        
  图2  音频系统的Hal图

        图一的AudioFlinger会进入到Hal层,Audio Hal层有几大重要的类:
        AudioALSAStreamManager是入口管理下面的AudioALSAStreamIn和AudioALSAStreamOut
         AudioALSAStream Out管理着AudioALSAPlaybackXXXX
           AudioALSAStreamIn 管理着AudioALSACaptureXXXX,
          AudioALSAPlaybackXXXX与 AudioALSACaptureXXXX 这两个类里面的主要函数是open(),read()和write(),主要是负责对PCM buf的读写到Linux 的ALSA里面。
           AudioALSASpeechXXXX类是Aduio的一个算法处理。
           AudioALSAHardwareResourceManager这个类主要用于打开和关闭硬件设备,如MIC,喇叭等
        AudioALSAVolumeController,这个类在上图没有体现,但是也很常用,主要用于Audio系统的音量控制,音量补偿,音频参数也在此得到应用
        
         HAL与ALSA对接使用了TinyALSA库,这个很重要。TinyALSA是一个轻量级的封装库,对ALSA接口进行了二次封装,简化了对ALSA的操作,具体源码目录在/external/tinyalsa。这个库衔接了Hal与Linux,这个是连接驱动的关键,一开始我针对Linux ALSA在HAL一顿狂找结果还是吃了闭门羹
        网上有个说就法,Google为了避免与Linux有版权的争议,自己能在源码上有更多的自己说话的权利,对Linux原生的ALSA进行了大量的修改,裁剪,去掉Linux GPL等协议。所有的Buf的处理交给了AudioTrack去处理,所以会有高延迟,低速率等问题,以至于Android手机无法做出高音质的手机。

二. 了解音频系统架构

        音频系统的AudioFlinger和AudioPolicy里有很复杂的Buf调动和Buf管理,也有很复杂的音频策略,里面考虑到了音频能遇到的各种状况。从这两个类下手很容易掉到坑里出不来。Loopback是MTK音频系统提供的一个工厂测试方法,其使用方法简单粗暴,运行过程也简单粗暴。使能它之后就可以任意的让主副MIC与喇叭听筒组合出声。它就相当于HAL层的一个 直接使用HAL层API的 应用。对我们了解Android Audio系统代码的分布与功能有很大的帮助。从Loopback下手再回到AudioFlinger和AudioPolicy会更好。Loopback也分为两种模式一种是AFE模式,一种是Acoustic,通俗的说法就是前者是吹所模式,只能响应吹气声音,后者就是普通的声音输出。两者在流程上大体是一至的,声音模式会比吹气模式多出了一个Speech的控制,也就是多出了一个语音的算法处理。先从简单的下手,两种模式的比较也会让我们更容易了解代码。

(1)调用Loopback流程

        涉及到的文件:
        frameworks/av/media/libmedia/AudioSystem.cpp
        frameworks/av/services/audioflinger/AudioFlinger.cpp
        vendor/mediatek/proprietary/hardware/audio/common/V3/aud_drv/AudioALSAHardware.cpp
        vendor/mediatek/proprietary/hardware/audio/common/V3/aud_drv/LoopbackManager.cpp
        vendor/mediatek/proprietary/hardware/audio/common/V3/aud_drv/AudioALSALoopbackController.cpp
        vendor/mediatek/proprietary/hardware/audio/common/V3/aud_drv/AudioALSADeviceConfigManager.cpp
        vendor//mediatek/proprietary/hardware/audio/mt6735/aud_drv/AudioALSAHardwareResourceManager.cpp
        vendor//mediatek/proprietary/hardware/audio/mt6735/aud_drv/ AudioALSAVolumeController .cpp    

        Loopback使用方法是在APP中直接使用这个方法:AudioSystem.setParameters(“SET_LOOPBACK_TYPE=Type, OutputDevice”);。要启用主MIC进喇叭出的吹气模式Type为1,OutputDevice为3。这个参数会层层调用到HAL启动Loopback。我们可以跟踪这个参数的流向了解一下Audio系统是怎么分布的。
        前面一小段的流程大致是这样:AudioSystem.java-->android_media_AudioSystem.cpp-->AudioSystem.cpp
   
  1. //AudioSystem.cpp
  2. status_t AudioSystem::setParameters(const String8 &keyValuePairs)
  3. {
  4. //第一个参数为:AUDIO_IO_HANDLE_NONE
  5. return setParameters(AUDIO_IO_HANDLE_NONE, keyValuePairs);
  6. }
  7. status_t AudioSystem::setParameters(audio_io_handle_t ioHandle, const String8 &keyValuePairs)
  8. {
  9. //省略掉无关逻辑
  10. const sp<IAudioFlinger> &af = AudioSystem::get_audio_flinger();
  11. if (af == 0) return PERMISSION_DENIED;
  12. ret = af->setParameters(ioHandle, keyValuePairs);
  13. }
        上面直接调用到AudioFlinger的setParameters()   
   
  1. //AudioFlinger.cpp
  2. status_t AudioFlinger::setParameters(audio_io_handle_t ioHandle, const String8& keyValuePairs)
  3. {
  4. //上面传下来的ioHandle = AUDIO_IO_HANDLE_NONE
  5. if (ioHandle == AUDIO_IO_HANDLE_NONE) {
  6. mHardwareStatus = AUDIO_HW_SET_PARAMETER;
  7. for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
  8. //mAudioHwDevs数组是HAL层“Dev”的集合,这里的Dev并不是指真正的具体设备
  9. //而是把HAL的一个大模块抽象为一个“Dev”,例如,MTK音频HAL可以取名为“MTK Audio HAL”
  10. //然后还有高通的“Qual Audio HAL”等等,还能细分为Wifi,USB,A2DP.....
  11. audio_hw_device_t *dev = mAudioHwDevs.valueAt(i)->hwDevice();
  12. status_t result = dev->set_parameters(dev, keyValuePairs.string());
  13. }
  14. }
  15. }
        mAudioHwDevs这个数组又是怎么来的呢?在AudioPolicyManager的构造函数里,会向本地文件加载一个audio_policy.conf。该config文件会决定音频系统有哪些通路,USB,A2DP等等,这些通路下面有哪些设备,还有一些设备的参数。大概摘抄一点
    
  1. audio_hw_modules {
  2. primary {
  3. global_configuration {
  4. attached_output_devices AUDIO_DEVICE_OUT_SPEAKER|AUDIO_DEVICE_OUT_EARPIECE
  5. default_output_device AUDIO_DEVICE_OUT_SPEAKER
  6. attached_input_devices AUDIO_DEVICE_IN_BUILTIN_MIC|AUDIO_DEVICE_IN_FM_TUNER|AUDIO_DEVICE_IN_VOICE_CALL
  7. audio_hal_version 3.0
  8. }
  9. devices {
  10. headset {
  11. type AUDIO_DEVICE_OUT_WIRED_HEADSET
  12. gains {
  13. gain_1 {
  14. mode AUDIO_GAIN_MODE_JOINT
  15. channel_mask AUDIO_CHANNEL_OUT_STEREO
  16. min_value_mB -6400
  17. max_value_mB 0
  18. default_value_mB 0
  19. step_value_mB 100
  20. min_ramp_ms 0
  21. max_ramp_ms 0
  22. }
  23. }
  24. }
  25. ......
       这些模块名字加载好后会跟据这些名字循环地去查找HAL模块,把找到的模块填入到 mAudioHwDevs中。这段逻辑目前看得不是很仔细,有些逻辑不严谨
    
  1. //AudioFlinger.cpp
  2. audio_module_handle_t AudioFlinger::loadHwModule_l(const char *name)
  3. {
  4. audio_hw_device_t *dev;
  5. int rc = load_audio_interface(name, &dev);
  6. mAudioHwDevs.add(handle, new AudioHwDevice(handle, name, dev, flags));
  7. }
  8. static int load_audio_interface(const char *if_name, audio_hw_device_t **dev)
  9. {
  10. rc = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, if_name, &mod);
  11. }
          mAudioHwDevs数组是找到了如何初始化,setParameters()调用了该元素里的set_parameters()函数继续往下传参数。我还要找到具体的HAL模块才知道参数如何往下传。  hw_get_module_by_class()这个API会通过ID去找到HAL注册了哪些模块。我们搜一下上面的AUDIO_HARDWARE_MODULE_ID就可以找到具体是加载了哪些模块,在Vendor目录下现有两个HAL模块
     
  1. (1)MTK自己实现的Audio HAL,名字和ID如下
  2. id: AUDIO_HARDWARE_MODULE_ID,
  3. name: "MTK Audio HW HAL"
  4. (2)还有一个是A2DP的
  5. id: AUDIO_HARDWARE_MODULE_ID,
  6. name: "A2DP Audio HW HAL"
        就这样把HAL模块加载出来了,所有的HAL模块都明非常标准和非常明确的接口定义,对于HAL以上的逻辑,只需找到ID和相应的名字即可找到需要使用的模块,即使你有100个厂商,100个厂商又有100个模块,还是依照明确的标准去走,这个就是面向对象编程中的一个核心理念,面向接口编程,不管你逻辑如何变,接口一定不能变!这样就能确保软件的低耦合,可移植。
        我们用的是“MTK Audio HW HAL”这个Audio HAL module。很容易地就在里面找到了set_parameters函数指针。指向adev_set_parameters()函数         
   
  1. static int adev_set_parameters(struct audio_hw_device *dev, const char *kvpairs)
  2. {
  3. struct legacy_audio_device *ladev = to_ladev(dev);
  4. //结过转换,调用到的是AudioALSAHardware.cpp
  5. return ladev->hwif->setParameters(String8(kvpairs));
  6. }
        AudioALSAHardware::setParameters()是Audio setParameters()的最终执行函数,也是一个很有趣的函数。这个函数就像Audio系统的“后门”,里面可以粗暴有力的设置Audio参数,而影响整个Audio系统的运行。例如,可以调整音量,打开关闭MIC,FM的音频开关等等,知道这些暗门不知道能不能干一点坏事。就像我们强制打开Loopback,声音就开始从MIC进喇叭出,普通用户还没有办法关闭,除非重启平板,细思极恐。然后里面还保留着一些音频设备的测试方法和校准方法。
    
  1. status_t AudioALSAHardware::setParameters(const String8 &keyValuePairs)
  2. {
  3. // Loopback
  4. if (param.get(keySET_LOOPBACK_TYPE, value_str) == NO_ERROR)
  5. {
  6. if (loopback_type == NO_LOOPBACK) // close loopback
  7. {
  8. LoopbackManager::GetInstance()->SetLoopbackOff();
  9. }
  10. else // open loopback
  11. {
  12. LoopbackManager::GetInstance()->SetLoopbackOn(loopback_type, loopback_output_device);
  13. }
  14. }
  15. }
         至此我们APP设备的参数就成功地传递到了HAL层,并开始操控Loopback。从下面的方法开始,正式进入到Loopback中
            LoopbackManager::GetInstance()->SetLoopbackOn(loopback_type, loopback_output_device);

(2)Loopback工作流程

       (A)源码流程

        从 SetLoopbackOn()开始,先看一下SetLoopbackOn的流程,再从此处展开
   
  1. //LoopbackManager.cpp
  2. status_t LoopbackManager::SetLoopbackOn(loopback_t loopback_type, loopback_output_device_t loopback_output_device)
  3. {
  4. //关闭所有的音频输出流,并重新开始准备
  5. //也就是我们之前说的pcm_write,把数据定到ALSA中
  6. //但是Loopback中并没有用到StreamManager
  7. AudioALSAStreamManager::getInstance()->setAllStreamsSuspend(true);
  8. AudioALSAStreamManager::getInstance()->standbyAllStreams();
  9. // get loopback dev 获得Input dev为AUDIO_DEVICE_IN_BUILTIN_MIC,这个值后面有用
  10. audio_devices_t input_device = GetInputDeviceByLoopbackType(loopback_type);
  11. audio_devices_t output_device = GetOutputDeviceByLoopbackType(loopback_type, loopback_output_device);
  12. // set specific mic type
  13. if (loopback_type == AP_MAIN_MIC_AFE_LOOPBACK || loopback_type == MD_MAIN_MIC_ACOUSTIC_LOOPBACK)
  14. {
  15. //设备主MIC,这函数仅是设备一个变量mBuiltInMicSpecificType,后面真正打开MIC的时候会用到这个参数
  16. AudioALSAHardwareResourceManager::getInstance()->setBuiltInMicSpecificType(BUILTIN_MIC_MIC1_ONLY);
  17. }
  18. ....
  19. //这里就是语音模式下的Modem与Speech算法,吹气模式并没有用到这里
  20. if (CheckIsModemLoopback(loopback_type) == true)
  21. {
  22. SpeechDriverInterface *pSpeechDriver = SpeechDriverFactory::GetInstance()->GetSpeechDriverByIndex(mWorkingModemIndex);
  23. if (pSpeechDriver->CheckModemIsReady() == false) // modem is sleep...
  24. {
  25. for (int modem_index = MODEM_1; modem_index < NUM_MODEM; modem_index++) // get working modem index
  26. {
  27. pSpeechDriver = SpeechDriverFactory::GetInstance()->GetSpeechDriverByIndex((modem_index_t)modem_index);
  28. if (pSpeechDriver != NULL && pSpeechDriver->CheckModemIsReady() == true)
  29. {
  30. mWorkingModemIndex = (modem_index_t)modem_index;
  31. SpeechDriverFactory::GetInstance()->SetActiveModemIndex(mWorkingModemIndex);
  32. break;
  33. }
  34. }
  35. }
  36. }
  37. ......
  38. // 打开关闭MIC降噪,吹气模式没有用到
  39. if (loopback_type == MD_DUAL_MIC_ACOUSTIC_LOOPBACK_WITHOUT_DMNR ||
  40. loopback_type == MD_DUAL_MIC_ACOUSTIC_LOOPBACK_WITH_DMNR)
  41. {
  42. mMaskCopy = SpeechEnhancementController::GetInstance()->GetSpeechEnhancementMask(); // copy DMNR mask
  43. sph_enh_mask_struct_t mask = mMaskCopy;
  44. if (loopback_type == MD_DUAL_MIC_ACOUSTIC_LOOPBACK_WITHOUT_DMNR)
  45. {
  46. mask.dynamic_func &= (~SPH_ENH_DYNAMIC_MASK_DMNR);
  47. }
  48. else if (loopback_type == MD_DUAL_MIC_ACOUSTIC_LOOPBACK_WITH_DMNR)
  49. {
  50. mask.dynamic_func |= SPH_ENH_DYNAMIC_MASK_DMNR;
  51. }
  52. SpeechDriverFactory::GetInstance()->GetSpeechDriverByIndex(mWorkingModemIndex)->SetSpeechEnhancementMask(mask);
  53. }
  54. ......
  55. // Enable loopback function
  56. switch (loopback_type)
  57. {
  58. case AP_MAIN_MIC_AFE_LOOPBACK:
  59. {
  60. //吹气模式,打开Loopback
  61. AudioALSALoopbackController::getInstance()->open(output_device, input_device);
  62. break;
  63. }
  64. case MD_MAIN_MIC_ACOUSTIC_LOOPBACK:
  65. {
  66. //语音模式,打开Loopback,同使全能Speech。吹气模式与语音模式最大的不同是Speech的介入
  67. #if defined(MTK_AUDIO_SPH_LPBK_PARAM)
  68. AudioALSAStreamManager::getInstance()->UpdateSpeechLpbkParams();
  69. #endif
  70. AudioALSASpeechLoopbackController::getInstance()->open(output_device, input_device);
  71. break;
  72. }
  73. }
  74. // save opened loobpack type
  75. mLoopbackType = loopback_type;
  76. // VolumeController 对音量的总控制类
  77. if ((loopback_type != AP_BT_LOOPBACK) && (loopback_type != AP_BT_LOOPBACK_NO_CODEC) && (loopback_type != MD_BT_LOOPBACK) && (loopback_type != MD_BT_LOOPBACK_NO_CODEC))
  78. {
  79. //语音模式音量
  80. if (CheckIsModemLoopback(loopback_type) == true)
  81. {
  82. mVoiceVolumeCopy = mAudioALSAVolumeController->getVoiceVolume();
  83. mAudioALSAVolumeController->setVoiceVolume(kVoiceVolumeForLoopback, AUDIO_MODE_IN_CALL, output_device);
  84. }
  85. else
  86. {
  87. //吹气模式音量
  88. mMasterVolumeCopy = mAudioALSAVolumeController->getMasterVolume();
  89. mAudioALSAVolumeController->setMasterVolume(kMasterVolumeForLoopback, AUDIO_MODE_NORMAL, output_device);
  90. }
  91. }
  92. }
        我们去到吹气模式的Loopback打开AudioALSALoopbackController::getInstance()->open(output_device, input_device);         
    
  1. status_t AudioALSALoopbackController::open(const audio_devices_t output_devices, const audio_devices_t input_device)
  2. {
  3. // DL loopback setting
  4. memset(&mConfig, 0, sizeof(mConfig));
  5. mConfig.channels = 2;
  6. mConfig.rate = 48000;
  7. mConfig.period_size = 1024;
  8. mConfig.period_count = 2;
  9. mConfig.format = PCM_FORMAT_S16_LE;
  10. mConfig.start_threshold = 0;
  11. mConfig.stop_threshold = 0;
  12. mConfig.silence_threshold = 0;
  13. //pcmInIdx pcmOutIdx cardIndex
  14. int pcmInIdx = AudioALSADeviceParser::getInstance()->GetPcmIndexByString(keypcmUlDlLoopback);
  15. int pcmOutIdx = AudioALSADeviceParser::getInstance()->GetPcmIndexByString(keypcmUlDlLoopback);
  16. int cardIndex = AudioALSADeviceParser::getInstance()->GetCardIndexByString(keypcmUlDlLoopback);
  17. mPcmUL = pcm_open(cardIndex, pcmInIdx, PCM_IN, &mConfig);
  18. mPcmDL = pcm_open(cardIndex, pcmOutIdx, PCM_OUT, &mConfig);
  19. //调用TinyALSA接口打开播放录音的设备节点
  20. pcm_start(mPcmUL);
  21. pcm_start(mPcmDL);
  22. //打开输入输出设备
  23. mHardwareResourceManager->startInputDevice(input_device);
  24. mHardwareResourceManager->startOutputDevice(output_devices, mConfig.rate);
  25. }
        通过TinyALSA库调用底层设备节点的逻辑已经暴露,也就相当于打开我们常说的“声卡”,该节点在/dev/snd下面,我们来看看该目录   
    
  1. root@table:/ # ls dev/snd/
  2. adsp audio comprC0D23
  3. controlC0 dsp mixer
  4. pcmC0D0p pcmC0D10p pcmC0D11p
  5. pcmC0D12c pcmC0D13c pcmC0D14p
  6. pcmC0D15c pcmC0D16c pcmC0D17c
  7. pcmC0D17p pcmC0D18p pcmC0D19p
  8. pcmC0D1c pcmC0D20p pcmC0D21c
  9. pcmC0D21p pcmC0D22c pcmC0D22p
  10. pcmC0D24p pcmC0D2c pcmC0D2p
  11. pcmC0D3c pcmC0D3p pcmC0D4c
  12. pcmC0D4p pcmC0D5c pcmC0D5p
  13. pcmC0D6c pcmC0D6p pcmC0D7c
  14. pcmC0D7p pcmC0D8p pcmC0D9c
  15. seq sequencer sequencer2
  16. timer
        比较通用的设备节点有  
     
  1. controlC0 --> 用于声卡的控制,例如通道选择,混音,麦克风的控制等
  2. midiC0D0 --> 用于播放midi音频
  3. pcmC0D0c --〉 用于录音的pcm设备
  4. pcmC0D0p --〉 用于播放的pcm设备
  5. seq --〉 音序器
  6. timer --〉 定时器
         pcm节点的介绍,我们Loopback吹气模式打开的是pcmC0D4c和pcmC0D4p
      
  1. pcm设备,通过阅读tinyalsa的代码和查看Android下的音频设备节点,
  2. 可知在Android中一个pcm设备最多可有一个mixer设备"/dev/snd/controlC%u"
  3. (一般是controlC0)和32个/dev/snd/pcmC%uD%uc(一般是pcmC0D0c)、/dev/snd/pcmC%uD%u%p(一般是pcmC0D0p),
  4. pcm设备中的C代表card,D代表device,c代表capture,p代表playback。
  5. 当我们新增一个pcm声卡C的值会+1,D还是从0开始,
  6. 可能只有c(pcmC1D0c 例如麦克风),可能只有p(pcmC1D0p 例如音响),
  7. 可能同时存在c和p(pcmC1D0c pcmC1D0p )。
          到 AudioALSALoopbackController::open()执行完毕,Loopback就正式被打开完成,pcm_start()之后应该还会有一个pcm_write和pcm_read两个函数不断地从音频输入设备与音频输出设备读写buf,至少正常的播放与录音是如此的。但是从Loopback打开完成也没有看到在哪里pcm_read/write,有可能是哪算细节我遗漏掉了,或者还有一个猜想是在驱动的编解码芯片通过I2S就完成了这些简单的数据流读写,这里留个疑问, 关于ALSA PCM的讨论到此为止,再往下就是驱动层了,有空再继续深入。

        (B)打开设备流程

        继续回到 AudioALSALoopbackController::open(),里面还有输入(Mic)与输出(Speak)设备的打开,以打开MIC为例,我们来看一下是以一个怎么样的方式打开设备,打开设备的方法为:
      
  1. mHardwareResourceManager->startInputDevice(input_device);
  2. mHardwareResourceManager->startOutputDevice(output_devices, mConfig.rate);
        打开Input设备会复杂一些,我们以此为例。回忆一下我们之前的两个参数,一个是mBuiltInMicSpecificType=BUILTIN_MIC_MIC1_ONLY,另一个是input_device = AUDIO_DEVICE_IN_BUILTIN_MIC; 
     
  1. status_t AudioALSAHardwareResourceManager::startInputDevice(const audio_devices_t new_device)
  2. {
  3. /*Audior打开和关闭设备用了很多的标志位,打开和关闭只是用了简单计数方式,
  4. 在一些高强度的测试的时候,这些计数就容易出问题*/
  5. if (((mInputDevice & new_device) & ~AUDIO_DEVICE_BIT_IN) != 0)
  6. {
  7. if (new_device != AUDIO_DEVICE_IN_SPK_FEED)
  8. {
  9. mStartInputDeviceCount++;
  10. }
  11. }
  12. if (new_device == AUDIO_DEVICE_IN_BUILTIN_MIC)
  13. {
  14. //设置MIC的模式
  15. setMIC1Mode(false);
  16. setMIC2Mode(false);
  17. //打开MIC
  18. if (mBuiltInMicSpecificType == BUILTIN_MIC_MIC1_ONLY)
  19. {
  20. //主副MIC反转,我们并不反转
  21. if (mMicInverse == true)
  22. {
  23. mDeviceConfigManager->ApplyDeviceTurnonSequenceByName(AUDIO_DEVICE_BUILTIN_MIC_MIC1_INVERSE);
  24. }
  25. else
  26. {
  27. mDeviceConfigManager->ApplyDeviceTurnonSequenceByName(AUDIO_DEVICE_BUILTIN_MIC_MIC1);
  28. }
  29. }
  30. }
  31. }
        先来看看,setMIC1Mode();
      
  1. void AudioALSAHardwareResourceManager::setMIC1Mode(bool isphonemic)
  2. {
  3. //从Log知道,micmode = AUDIO_MIC_MODE_ACC
  4. if (micmode == AUDIO_MIC_MODE_ACC)
  5. {
  6. mDeviceConfigManager->ApplyDeviceSettingByName(AUDIOMIC1_TYPE_ACCMODE);
  7. }
  8. }
        这里主要调用了mDeviceConfigManager->ApplyDeviceSettingByName(AUDIOMIC1_TYPE_ACCMODE);设置MIC1的工作模式。那个宏定义如下
      
  1. #define AUDIOMIC1_TYPE_ACCMODE "Mic1TypeACCMode"
        先不进入到ApplyDeviceSettingByName()函数中去,我们先记住上面的宏定义。然后再看一下mDeviceConfigManager->ApplyDeviceTurnonSequenceByName(AUDIO_DEVICE_BUILTIN_MIC_MIC1);上面的是设置MIC的模式,在这里就真正地打开MIC。 ApplyDeviceTurnonSequenceByName与刚才的 ApplyDeviceSettingByName都是在AudioALSADeviceConfigManager中,他们的逻辑和工作方式都是基本是一至的。我们只需要看 ApplyDeviceTurnonSequenceByName即可。同样地,记住他传入的参数
      
  1. #define AUDIO_DEVICE_BUILTIN_MIC_MIC2 "builtin_Mic_Mic2"
       ApplyDeviceTurnonSequenceByName的实现
       
  1. status_t AudioALSADeviceConfigManager::ApplyDeviceTurnonSequenceByName(const char *DeviceName)
  2. {
  3. //通过名字查找设备描述符
  4. DeviceCtlDescriptor *descriptor = GetDeviceDescriptorbyname(DeviceName);
  5. if (descriptor->DeviceStatusCounter == 0)
  6. {
  7. for (count = 0; count < descriptor->mDeviceCltonVector.size(); count += 2)
  8. {
  9. String8 cltname = descriptor->mDeviceCltonVector.itemAt(count);
  10. String8 cltvalue = descriptor->mDeviceCltonVector.itemAt(count + 1);
  11. //调用TinyALSA把设置参数传入底层,以达到打开和设置的目地
  12. mixer_ctl_set_enum_by_string(mixer_get_ctl_by_name(mMixer, cltname.string()), cltvalue.string());
  13. }
  14. }
  15. descriptor->DeviceStatusCounter++;
  16. }
        在设置的/system/etc/audio/audio_device.xml里对所有的设备,所有设置参数有一个详细的记录。ApplyDeviceTurnonSequenceByName()的原理就是查找这个文件里与传入参数匹配的节点,然后把该节点下面的子节点里面的参数通过TinyALSA设置到底层驱动。我们把上述的参数在该文件里查找一下便知。还是以打开MIC为例,传入的参数为 builtin_Mic_Mic2,在audio_device.xml里找到如下字段
      
  1. <path name="builtin_Mic_Mic2" value="turnon">
  2. <kctl name="Audio_MicSource1_Setting" value="ADC1" />
  3. <kctl name="Audio_ADC_1_Switch" value="On" />
  4. <kctl name="Audio_ADC_2_Switch" value="On" />
  5. <kctl name="Audio_Preamp1_Switch" value="IN_ADC3" />
  6. <kctl name="Audio_Preamp2_Switch" value="IN_ADC3" />
  7. </path>
  8. <path name="builtin_Mic_Mic2" value="turnoff">
  9. <kctl name="Audio_Preamp1_Switch" value="OPEN" />
  10. <kctl name="Audio_Preamp2_Switch" value="OPEN" />
  11. <kctl name="Audio_ADC_1_Switch" value="Off" />
  12. <kctl name="Audio_ADC_2_Switch" value="Off" />
  13. </path>
        可以很清晰地见到 name为builtin_Mic_Mic2的value=turnon,下面对应着要打开该MIC对应的动作,打开ADC1,打开ADC2等等。 ApplyDeviceSettingByName()也是同样的原理。至此,打开流程完毕
      
(C)音量调节流程
        回到LoopbackManager,我们用的是吹气模式,设置音量调用的是这条方法:mAudioALSAVolumeController->setMasterVolume(kMasterVolumeForLoopback, AUDIO_MODE_NORMAL, output_device);
   
  1. status_t AudioALSAVolumeController::setMasterVolume(float v, audio_mode_t mode, uint32_t devices)
  2. {
  3. int MapVolume = AudioALSAVolumeController::logToLinear(v);
  4. mMasterVolume = v;
  5. mMode = mode;
  6. mOutputDevices = devices;
  7. switch (mode)
  8. {
  9. //传入的模式是volume
  10. case AUDIO_MODE_NORMAL : // normal mode
  11. {
  12. switch (devices)
  13. {
  14. //输出设置是喇叭
  15. case (AUDIO_DEVICE_OUT_SPEAKER) :
  16. {
  17. ApplyExtAmpHeadPhoneGain(MapVolume, mode, Audio_Speaker);
  18. }
  19. }
  20. break;
  21. }
  22. //来电铃声,通话声音大小的音量设置
  23. case AUDIO_MODE_RINGTONE :
  24. case AUDIO_MODE_IN_CALL :
  25. case AUDIO_MODE_IN_CALL_2 :
  26. case AUDIO_MODE_IN_CALL_EXTERNAL:
  27. }
  28. }
  29. }
        ApplyExtAmpHeadPhoneGain()实现。
    
  1. void AudioALSAVolumeController::ApplyExtAmpHeadPhoneGain(int Gain, uint32_t mode, uint32_t device)
  2. {
  3. //从音频参数中获取增益值
  4. int DegradedBGain = mVolumeRange[device];
  5. DegradedBGain = DegradedBGain + (DEVICE_VOLUME_RANGE - DegradedBGain) * ((VOLUME_MAPPING_STEP - Gain) / VOLUME_MAPPING_STEP);
  6. //通过TinyALSA设置喇叭增益
  7. SetLinoutLGain(DegradedBGain);
  8. SetLinoutRGain(DegradedBGain);
  9. //通过TinyALSA设置耳机增益
  10. SetHeadPhoneLGain(DegradedBGain);
  11. SetHeadPhoneRGain(DegradedBGain);
  12. }
        吹气模式下的问题的音量设置比语音loopback和普通的播放录制的音量设置都要简单得好多,没有计算耳机阻抗大小,再根据得出的阻抗再匹配相应的增益补偿。上面的音量设置也只是动用到了音频参数文件,在initVolumeController()初始化了音频参数
    
  1. status_t AudioALSAVolumeController::initVolumeController()
  2. {
  3. //从NVRAM里读取音频参数
  4. GetVolumeVer1ParamFromNV(&mVolumeParam);
  5. GetNBSpeechParamFromNVRam(&mSphParamNB);
  6. GetWBSpeechParamFromNVRam(&mSphParamWB);
  7. ...
  8. for (int i = 0 ; i < SPEAKER_VOLUME_TYPE_MAX ; i++)
  9. {
  10. ALOGD("speakeraudiovolume %d = %d", i, mVolumeParam.speakeraudiovolume[i]);
  11. }
  12. ....
  13. //把音频参数数据设置到mVolumeRange数组中
  14. float MaxdB = 0.0, MindB = 0.0, micgain = 0.0;
  15. int degradegain = 0;
  16. degradegain = (unsigned char)MampVoiceBufferVolume(mVolumeParam.normalaudiovolume[NORMAL_AUDIO_BUFFER]);
  17. SetVolumeRange(Audio_Earpiece, DEVICE_VOICE_MAX_VOLUME , DEVICE_VOICE_MIN_VOLUME, degradegain);
  18. degradegain = (unsigned char)MampAudioBufferVolume(mVolumeParam.headsetaudiovolume[HEADSET_AUDIO_BUFFER]);
  19. SetVolumeRange(Audio_Headset, DEVICE_MAX_VOLUME , DEVICE_MIN_VOLUME, degradegain);
  20. SetVolumeRange(Audio_Headphone, DEVICE_MAX_VOLUME , DEVICE_MIN_VOLUME, degradegain);
  21. }
        
        Loopback的工作流程大致就是这样子了。其中贯穿了一些功能类,了解了音频工作内容的分布,还留下两个问题,
        1. 有了pcm_start(),为什么没有pcm_read()/pcm_write(),有两种可能,不是我看漏了,就是在驱动层完成了数据流
        2. speech用了些共享内存和线程,工作方式有些复杂,它的工作原理是怎么样子的,是通过pcm_read()读出Buf吗?
------------------------------------------------------
参考文章


        


Android/Linux音频架构开发ALSA-篇3 文章介绍了ALSA的架构知识,讲述如何创建一个声卡snd_card,如何理解声卡。 阅读详情

相关推荐

mtk平台 声音从mic到speaker loadspeaker handset间的关系

关于audio相关的名称解释如下: 1. mic( 麦克风):声音输入源; 2. speaker(听筒)loadspeaker(喇叭) handset(耳机):声音输出源; 3. 音频解码器(eg:ALC5651):音频解码器分为纯音频解码器和AV影音解码器两类,主要作用分别是把读取的数字音频信息转换成模拟音频信号输出,是把录音时经过编码的多声道音频信息作解码还原 4. 音频功放IC(eg...

c_L_j_的博客 4712

Android音频子系统(十)------MTK Audio录音流程代码解析

你好!这里是风筝的博客, 欢迎和我一起交流。 Android framework中的代码每个平台基本都是大同小异,只有Hal上代码才是厂商特制,每个平台都不相同,这里以MTK平台为例,记录下MTK平台Hal audio音频录音代码流程。 调用大致流程如下 AudioALSAHardwa: openInputStream() AudioALSAStreamManager: openInputStream() AudioALSAStreamIn: AudioALSAStreamIn //new Audi

风筝 9902

嵌入式设备MIC/SPK音频功能验证:从硬件到应用的全链路测试策略

在嵌入式系统与物联网设备开发中,音频功能验证是确保产品体验的关键环节。音频链路涉及从模拟信号拾取、数字处理到输出的完整过程,其核心在于验证硬件电路、驱动适配、中间件配置及应用层调用的协同工作。理解音频编解码器(Codec)、ALSA(Advanced Linux Sound Architecture)驱动框架及音频路由策略是基础。通过分层验证与场景覆盖,可以系统性地评估性能指标与稳定性,这对于提升语音唤醒、通话、媒体播放等场景下的用户体验至关重要。本文聚焦于LUBAN平台,详细阐述了针对MIC(麦克风)和S

weixin_33889665的博客 373

9 linux利用alsa loopback录制应用声音

Linux系统ALSA音频录制教程:通过snd-aloop模块实现声音内录。步骤包括:1)加载loopback模块;2)设置音频输出为模拟Loopback;3)增大音量;4)播放测试音源;5)使用arecord命令录制20秒音频;6)用aplay播放测试录音。文档提供了详细命令参数说明和相关技术参考链接,适用于需要系统内录或音频测试的Linux用户。

u012906122的专栏 3949

嵌入式音频入门:从I2S到PDM,在RK3566上玩转ES7202 ADC录音芯片

本文详细介绍了在RK3566平台上开发基于ES7202 ADC芯片的PDM录音系统的实践指南。通过对比PDM与I2S协议的差异,解析ASoC框架配置,提供设备树设置和性能优化技巧,帮助开发者快速实现高质量的嵌入式音频采集方案,适用于智能家居、安防设备等场景。

weixin_30463341的博客 587

Pulseaudio之load-module加载module-always-sink(十四)

PulseAudio是一个声音服务器,一个后台进程从一个或多个音源(进程或输入设备)接受声音输入 然后重定向声音到一个或多个槽(声卡,远程网络PulseAudio服务,或其他进程)。 PulseAudio的一个目的通过它是重定向所有声音流,包括那些试图访问硬件的进程(像那些遗留的基于OSS的软件)。PulseAudio通过提供适配器给那些使用不同的声音系统,像aRts和ESounD。 在Linux下的典型方案,用户提供配置ALSA来使用PulseAudio提供的虚拟设备。尽管应用使用ALSA会将输...

Android系统攻城狮 1231

Android Audio HAL 接口介绍之 adev_set_parameters()和out_set_parameters()

前言 这几天一直再解决BUG,是两个usease同时存在的场景,插入耳机来实现音频切换的问题。有Recroding+HFP_Call场景,有两个Recording同时存在的场景。当插入有线耳机后,音频设备切换出现问题。由于有HFP Call,所有就涉及了 adev_set_parameters(),HFP的开始和停止都是通过它来控制的。插入耳机后,音频设备切换时通过out_set_paramet...

hdzhang429的博客 8027

android P MTK Audio流程分析

audio流程分析

zjy764219923的专栏 1889

Loopback 回环测试调用流程

App 调用时,参数经 JNI → libaudioclient → Binder → AudioFlinger 透传,最终到达),由开启 AP 侧 AFE 音频回环。是MTK 厂商私有 HAL 参数,AOSP Framework 不做专门解析,仅负责权限校验与参数透传。

qq_45146250的博客 250

深入Android音频系统:ALSA框架解析

在当今移动计算设备中,音频处理不仅限于简单的音频播放和录制,而是成为了一个复杂且功能丰富的子系统。Android作为一个开源且被广泛应用的移动操作系统,其音频系统在为用户提供高质量音频体验方面扮演着关键角色。本章将对Android音频系统的基本构成和工作原理进行介绍,为读者深入了解后续章节内容打下基础。ALSA是一套为Linux内核设计的音频架构,旨在提供稳定且高效的音频服务。它支持广泛的音频硬件,包括声卡、混音器和MIDI设备。

weixin_33419305的博客 1392

macOS 27下PlayCover“没有权限打开”的完整修复指南

macOS系统通过Gatekeeper、TCC(透明、同意与控制)和安全启动等机制,对第三方应用进行层层校验,以防范恶意代码。这些机制会检查应用的隔离属性、签名状态以及所需系统权限,当任一环节不满足时,就会出现“没有权限打开”的提示。对于开发者工具或未上架App Store的开源软件,这类拦截尤为常见。尤其在macOS 27上,系统进一步收紧了绕过策略,导致PlayCover等工具无法正常启动。本文以PlayCover为例,详细解析权限链路上的三道关卡,并给出清除隔离属性、调整SIP状态、重置TCC记录等完

weixin_32612263的博客 740

Snapcast 多房间音频同步:3 条命令快速搭好全屋同步系统

Snapcast 是同步多房间音频系统:一台机器抓取音频,所有音箱按同一时间播放。你从客厅走到厨房,音乐不断、不偏移。这篇帮你装好服务器和客户端、接入现有播放器,并把同步延迟调到最低。 ## 一帧音频的旅程:先看懂 Snapcast 同步原理 [![Snapcast 多房间同步架构图](https://raw.gitcode.com/gh_mirrors/sn/snapcast/raw/4fe

gitblog_00664的博客 842

Autosar结合simulink的汽车应用层软件开发实例

本文基于汽车应用开发案例,并结合Autosar相关知识,生成具有Autosar标准的汽车应用层开发代码,以供学习和参考。以上就是本篇内容,主要是利用Simulink创建Autosar接口并进行端口映射的方法。

m0_64899535的博客 2152

音频 Alsa、Framework及Android

目录 1 Alsa、Framework及Android 1.1 ALSA架构简介图1 1.2 ALSA架构简介图 2 1.3 ALSA架构简介图 3 1.4 ALSA架构简介图 4 1.5 ALSA架构简介图 5 1.6 ALSA架构简介图 6 1.8 音频代码 1 Alsa、Framework及Android 目前,linux系统常用的音频驱动有两种形式:alsa和oss。Alsa:现在是linux下音频驱动的主要形式,与简单的oss兼容。oss:过去的形式音频设备接口包括PC

zhangzty111的博客 5145

Catia二次开发QueryInterface的操蛋问题

探索了很久,尝试录制宏,打开部件属性窗口,然后随意设置一些属性(必须改变一些东西才能录制成功,否则不会录制到任何东西),再看录制的结果,发现原来CATIAAnalyze是从Product那里来的。例如获得一个部件的体积,找到相关接口CATIAAnalyze,但是这个部件的CATIAAnalyze的对象不知道如何获取。总结一下,写Catia二开代码,就像漂亮国那款《莫里秀》 又名《爸爸死哪了》的综艺节目,大部分时间都是在找爸爸!Catia二开很痛苦,明明已经找到接口类,但是却不知道怎么获得这个接口类的对象。

gergul的专栏 747

Excel COUNTIF函数精确统计全解析:从模糊匹配到精准计数

在数据处理与办公自动化领域,条件计数是数据分析的基础操作,其核心原理在于对特定范围内满足设定条件的单元格进行识别与汇总。Excel中的COUNTIF函数是实现这一功能的常用工具,它通过文本匹配逻辑与通配符机制来执行条件判断。然而,其默认的模糊匹配行为常导致统计结果偏离预期,这正是精确统计的技术价值所在——确保数据汇总的准确性与可靠性,直接影响业务报告、薪酬核算、销售分析等应用场景的决策质量。本文聚焦COUNTIF函数的精确匹配技巧,深入剖析通配符(如星号*和问号?)的转义使用(通过波浪号~),并针对大小写敏

weixin_29040367的博客 353

Android音频驱动学习(一) Audio HAL

Hal加载过程 加载audio hal需要分三步 1、hw_get_module_by_class :加载hal module 2、audio_hw_device_open:调用audio device open 3、open_output_stream:打开output DevicesFactory::loadAudioInterface(const char *if_name, audio_hw_device_t **dev)   rc = hw_get_module_by_class(AUDIO

junluoyu000的博客 2万+

python读取并可视化npy格式的深度图文件以及将其保存为jpg图片的方法

  npy文件是无法直接打开的,它里面包含的是一个矩阵格式的数据,可以用于存储图像。为了对其进行可视化,需要将其转换为图像。这里需要用到numpy包以及matplotlib包,没有的话就pip install一下吧。现进入包含有npy文件的目录(我的该目录下有一个名为0000.npy的文件),然后打开一个终端,执行python进入pythn命令行模式,依次输入如下代码即可: import numpy as np import matplotlib.pyplot as plt depthmap = np.lo

zeeq的博客 5万+

Audio系统扫盲

From 1.1、响度(Loudness) 1.2、音调(Pitch) 1.3、音色(Quality) 1.4、样本(Sample) 1.5、采样器(Sampler) 1.6、量化(Quantization) 1.7、编码(Coding) 1.8、采样率(samplerate) 1.9、量化精度(位宽) 1.10、 声道数(channels) 1.11...

Android系统攻城狮 4772

ZYNQ PS裸机开发RS485

RS485 是一种通用的通信标准,广泛用于数据采集和控制应用中。它的主要优点之一是它允许将多个 RS485 设备放在同一条总线上,这使得多个节点可以相互连接。RS-485是美国电子工业协会(EIA)在1983年批准了一个新的平衡传输标准(balanced transmission standard),EIA一开始将RS(Recommended Standard)做为标准的前缀,不过后来为了便于识别标准的来源,已将RS改为EIA/TIA。

baidu_34971492的博客 831

深入探究ASP.NET Core读取Request.Body的正确方式

前言# 相信大家在使用ASP.NET Core进行开发的时候,肯定会涉及到读取Request.Body的场景,毕竟我们大部分的POST请求都是将数据存放到Http的Body当中。因为笔者日常开发所使用的主要也是ASP.NET Core所以笔者也遇到这这种场景,关于本篇文章所套路的内容,来自于在开发过程中我遇到的关于Request.Body的读取问题。在之前的使用的时候,基本上都是借助搜索引擎搜索的答案,并没有太关注这个,发现自己理解的和正确的使用之间存在很大的误区。故有感而发,便写下此文,以作记录。学无止境

萌兔兔MMQ!! 777

Android随笔之AudioManger与HAL之间的联系

本文以android 9.0为实例,介绍AudioManger与HAL之间的联系

hmz0303hf的博客 5771

【PINN高级教程】 第1讲:函数空间与逼近理论

结论:在解决偏微分方程(PDE)时,解往往属于某个 Sobolev 空间 HsHs,其正则性受限于 PDE 的系数与边界条件;而 PINN 在训练过程中实际上是在逼近 Barron 空间中的函数。由于 Barron 空间允许高频信息的压缩,PINN 可以在参数规模较小的情况下捕捉到 PDE 解所需的细节,但其正则性保证仍然比 Sobolev 更弱。因此,在设计 PINN 时,需要通过正则化或网络结构(如残差连接、归一化层)来“逼近”Sobolev 正则性。

u012133341的博客 237

mt6735 [Audio Loopback]产线加大MIC2测试声音

在产线,测试发现MIC2声音比较小,基本都听不到,需要加大测试

szx940213的博客 1071

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

Android 5.1 Audio HAL分析

初始化工程 1) AudioPlicyService被第一次实例化后,将会调用onFirstRef,其中实例化了两个全局变量,一个是mAudioPolicyClient,一个是mAudioPolicyManager。 2) mAudioPolicyClient是AudioPolicyClient类型的,具体实现在AudioPolicyClientImpl.cpp,该类中保存了AudioP

moonshine2016的博客 1万+

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

mtk audio笔记

对于HAL以上的逻辑,只需找到ID和相应的名字即可找到需要使用的模块,即使你有100个厂商,100个厂商又有100个模块,还是依照明确的标准去走,这个就是面向对象编程中的一个核心理念,面向接口编程,不管你逻辑如何变,接口一定不能变!这样就能确保软件的低耦合,可移植 .id = AUDIO_HARDWARE_MODULE_ID, .name = “MTK Audio HW HAL”, ...

温柔一刀 6350

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

上一篇: SELinux/SEAndroid 实例简述(三)实例看SELinux/SEAndroid
下一篇: gin源码分析(1)--初始化中间件,路由组与路由树
shell812
博客等级 码龄18年 202粉丝 28原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值