2021-05-10

原帖地址:
https://blog.csdn.net/sepnic/article/details/79383323




1. 音频框图概述

| Front End PCMs    |  SoC DSP  | Back End DAIs | Audio devices |
                <span class="hljs-comment">*************</span>

PCM0 <------------> <----DAI0-----> Codec Headset

PCM1 <------------> <----DAI1-----> Codec Speakers/Earpiece
DSP
PCM2 <------------> <----DAI2-----> MODEM

PCM3 <------------> <----DAI3-----> BT

<----DAI4-----> DMIC

<----DAI5-----> FM
*************

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

  • Front End PCMs:音频前端,一个前端对应着一个 PCM 设备
  • Back End DAIs:音频后端,一个后端对应着一个 DAI 接口,一个 FE PCM 能够连接到一个或多个 BE DAI
  • Audio Device:有 headset、speaker、earpiece、mic、bt、modem 等;不同的设备可能与不同的 DAI 接口连接,也可能与同一个 DAI 接口连接(如上图,Speaker 和 Earpiece 都连接到 DAI1)
  • Soc DSP:本文范围内实现路由功能:连接 FE PCMs 和 BE DAIs,例如连接 PCM0 与 DAI1:
                    *************
PCM0 <============> *<====++    * <----DAI0-----> Codec Headset
                    *     ||    *
PCM1 <------------> *     ++===>* <====DAI1=====> Codec Speakers/Earpiece
                    *           *
PCM2 <------------> *           * <----DAI2-----> MODEM
                    *    DSP    *
PCM3 <------------> *           * <----DAI3-----> BT
                    *           *
                    *           * <----DAI4-----> DMIC
                    *           *
                    *           * <----DAI5-----> FM
                    *************
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

高通 MSM8996 音频框图:
MSM8996_Audio

  • FE PCMs

  • deep_buffer
  • low_latency
  • mutil_channel
  • compress_offload
  • audio_record
  • usb_audio
  • a2dp_audio
  • voice_call
  • BE DAIs

    • SLIM_BUS
    • Aux_PCM
    • Primary_MI2S
    • Secondary_MI2S
    • Tertiary_MI2S
    • Quatermary_MI2S

    2. HAL 中的 usecase 和 device

    usecase 通俗表示音频场景,对应着音频前端,比如:

    • low_latency:按键音、触摸音、游戏背景音等低延时的放音场景
    • deep_buffer:音乐、视频等对时延要求不高的放音场景
    • compress_offload:mp3、flac、aac等格式的音源播放场景,这种音源不需要软件解码,直接把数据送到硬件解码器(aDSP),由硬件解码器(aDSP)进行解码
    • record:普通录音场景
    • record_low_latency:低延时的录音场景
    • voice_call:语音通话场景
    • voip_call:网络通话场景
    /* These are the supported use cases by the hardware.
     * Each usecase is mapped to a specific PCM device.
     * Refer to pcm_device_table[].
     */
    enum {
        USECASE_INVALID = -1,
        /* Playback usecases */
        USECASE_AUDIO_PLAYBACK_DEEP_BUFFER = 0,
        USECASE_AUDIO_PLAYBACK_LOW_LATENCY,
        USECASE_AUDIO_PLAYBACK_MULTI_CH,
        USECASE_AUDIO_PLAYBACK_OFFLOAD,
        USECASE_AUDIO_PLAYBACK_ULL,
    
    <span class="hljs-comment">/* FM usecase */</span>
    USECASE_AUDIO_PLAYBACK_FM,
    
    <span class="hljs-comment">/* HFP Use case*/</span>
    USECASE_AUDIO_HFP_SCO,
    USECASE_AUDIO_HFP_SCO_WB,
    
    <span class="hljs-comment">/* Capture usecases */</span>
    USECASE_AUDIO_RECORD,
    USECASE_AUDIO_RECORD_COMPRESS,
    USECASE_AUDIO_RECORD_LOW_LATENCY,
    USECASE_AUDIO_RECORD_FM_VIRTUAL,
    
    <span class="hljs-comment">/* Voice usecase */</span>
    USECASE_VOICE_CALL,
    
    <span class="hljs-comment">/* Voice extension usecases */</span>
    USECASE_VOICE2_CALL,
    USECASE_VOLTE_CALL,
    USECASE_QCHAT_CALL,
    USECASE_VOWLAN_CALL,
    USECASE_VOICEMMODE1_CALL,
    USECASE_VOICEMMODE2_CALL,
    USECASE_COMPRESS_VOIP_CALL,
    
    USECASE_INCALL_REC_UPLINK,
    USECASE_INCALL_REC_DOWNLINK,
    USECASE_INCALL_REC_UPLINK_AND_DOWNLINK,
    
    USECASE_AUDIO_PLAYBACK_AFE_PROXY,
    USECASE_AUDIO_RECORD_AFE_PROXY,
    
    USECASE_AUDIO_PLAYBACK_EXT_DISP_SILENCE,
    
    AUDIO_USECASE_MAX
    

    };

    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    device 表示音频端点设备,包括输出端点(如 speaker、headphone、earpiece)和输入端点(如 headset-mic、builtin-mic)。高通 HAL 对音频设备做了扩展,比如 speaker 分为:

    • SND_DEVICE_OUT_SPEAKER:普通的外放设备
    • SND_DEVICE_OUT_SPEAKER_PROTECTED:带保护的外放设备
    • SND_DEVICE_OUT_VOICE_SPEAKER:普通的通话免提设备
    • SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED:带保护的通话免提设备

    类似还有很多,详见 platform.h 音频设备定义,下面仅列举一部分:

    /* Sound devices specific to the platform
     * The DEVICE_OUT_* and DEVICE_IN_* should be mapped to these sound
     * devices to enable corresponding mixer paths
     */
    enum {
        SND_DEVICE_NONE = 0,
    
    <span class="hljs-comment">/* Playback devices */</span>
    SND_DEVICE_MIN,
    SND_DEVICE_OUT_BEGIN = SND_DEVICE_MIN,
    SND_DEVICE_OUT_HANDSET = SND_DEVICE_OUT_BEGIN,
    SND_DEVICE_OUT_SPEAKER,
    SND_DEVICE_OUT_HEADPHONES,
    SND_DEVICE_OUT_HEADPHONES_DSD,
    SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES,
    SND_DEVICE_OUT_SPEAKER_AND_LINE,
    SND_DEVICE_OUT_VOICE_HANDSET,
    SND_DEVICE_OUT_VOICE_SPEAKER,
    SND_DEVICE_OUT_VOICE_HEADPHONES,
    SND_DEVICE_OUT_VOICE_LINE,
    SND_DEVICE_OUT_HDMI,
    SND_DEVICE_OUT_DISPLAY_PORT,
    SND_DEVICE_OUT_BT_SCO,
    SND_DEVICE_OUT_BT_A2DP,
    SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP,
    SND_DEVICE_OUT_AFE_PROXY,
    SND_DEVICE_OUT_USB_HEADSET,
    SND_DEVICE_OUT_USB_HEADPHONES,
    SND_DEVICE_OUT_SPEAKER_AND_USB_HEADSET,
    SND_DEVICE_OUT_SPEAKER_PROTECTED,
    SND_DEVICE_OUT_VOICE_SPEAKER_PROTECTED,
    SND_DEVICE_OUT_END,
    
    <span class="hljs-comment">/* Capture devices */</span>
    SND_DEVICE_IN_BEGIN = SND_DEVICE_OUT_END,
    SND_DEVICE_IN_HANDSET_MIC  = SND_DEVICE_IN_BEGIN, <span class="hljs-comment">// 58</span>
    SND_DEVICE_IN_SPEAKER_MIC,
    SND_DEVICE_IN_HEADSET_MIC,
    SND_DEVICE_IN_VOICE_SPEAKER_MIC,
    SND_DEVICE_IN_VOICE_HEADSET_MIC,
    SND_DEVICE_IN_BT_SCO_MIC,
    SND_DEVICE_IN_CAMCORDER_MIC,
    SND_DEVICE_IN_END,
    
    SND_DEVICE_MAX = SND_DEVICE_IN_END,
    

    };

    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    扩展这么多是为了方便设置 acdb id,比如外放和通话免提虽然都用了同样的喇叭设备,但是这两种情景会使用不同的算法,因此需要设置不同的 acdb id 到 aDSP,区分 SND_DEVICE_OUT_SPEAKER 和 SND_DEVICE_OUT_VOICE_SPEAKER 是为了匹配到各自的 acdb id。

    由于高通 HAL 定义的音频设备与 Android Framework 定义的不一致,所以在高通 HAL 中会根据音频场景对框架层传入的音频设备进行转换,详见:

    • platform_get_output_snd_device()
    • platform_get_input_snd_device()

    在高通 HAL 中,我们只看到 usecase(即 FE PCM)和 device,那么上一个节中提到的 BE DAI 为什么没有被提及?很简单,device 和 BE DAI 是“多对一”的关系,device 连接着唯一的 BE DAI(反过来就不成立了,BE DAI 可能连接着多个 device),所以确定了 device 也就能确定所连接的 BE DAI。

    3. 音频通路连接

    简单描述下高通 HAL 层音频通路的连接流程。如 音频框图概述 所示,音频通路分为三大块:FE PCMs、BE DAIs、Devices,这三块均需要打开并串联起来才能完成一个音频通路的设置。

    FE_PCMs <=> BE_DAIs <=> Devices
     
     
    • 1

    3.1. 打开 FE PCM

    FE PCMs 是在音频流打开时设置的,我们首先要了解一个音频流对应着一个 usecase,具体细节请参考:Android 音频系统:从 AudioTrack 到 AudioFlinger

    AudioTrack、AudioFlinger Threads、AudioHAL Usecases、AudioDriver PCMs 的关系如下图所示:

    AudioUsecase

    start_output_stream() 代码分析:

    // 根据 usecase 找到对应 FE PCM id
    int platform_get_pcm_device_id(audio_usecase_t usecase, int device_type)
    {
        int device_id = -1;
        if (device_type == PCM_PLAYBACK)
            device_id = pcm_device_table[usecase][0];
        else
            device_id = pcm_device_table[usecase][1];
        return device_id;
    }
    

    int start_output_stream(struct stream_out *out)
    {
    int ret = 0;
    struct audio_usecase *uc_info;
    struct audio_device *adev = out->dev;

    <span class="hljs-comment">// 根据 usecase 找到对应 FE PCM id</span>
    out-&gt;pcm_device_id = platform_get_pcm_device_id(out-&gt;usecase, PCM_PLAYBACK);
    <span class="hljs-keyword">if</span> (out-&gt;pcm_device_id &lt; <span class="hljs-number">0</span>) {
        ALOGE(<span class="hljs-string">"%s: Invalid PCM device id(%d) for the usecase(%d)"</span>,
              __func__, out-&gt;pcm_device_id, out-&gt;usecase);
        ret = -EINVAL;
        <span class="hljs-keyword">goto</span> error_open;
    }
    
    <span class="hljs-comment">// 为这个音频流新建一个 usecase 实例</span>
    uc_info = (<span class="hljs-keyword">struct</span> audio_usecase *)<span class="hljs-built_in">calloc</span>(<span class="hljs-number">1</span>, <span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">struct</span> audio_usecase));
    
    <span class="hljs-keyword">if</span> (!uc_info) {
        ret = -ENOMEM;
        <span class="hljs-keyword">goto</span> error_config;
    }
    
    uc_info-&gt;id = out-&gt;usecase; <span class="hljs-comment">// 音频流对应的 usecase</span>
    uc_info-&gt;type = PCM_PLAYBACK; <span class="hljs-comment">// 音频流的流向</span>
    uc_info-&gt;stream.out = out;
    uc_info-&gt;devices = out-&gt;devices; <span class="hljs-comment">// 音频流的初始设备</span>
    uc_info-&gt;in_snd_device = SND_DEVICE_NONE;
    uc_info-&gt;out_snd_device = SND_DEVICE_NONE;
    list_add_tail(&amp;adev-&gt;usecase_list, &amp;uc_info-&gt;<span class="hljs-built_in">list</span>); <span class="hljs-comment">// 把新建的 usecase 实例添加到链表中</span>
    
    <span class="hljs-comment">// 根据 usecase、out-&gt;devices,为音频流选择相应的音频设备</span>
    select_devices(adev, out-&gt;usecase);
    
    ALOGV(<span class="hljs-string">"%s: Opening PCM device card_id(%d) device_id(%d) format(%#x)"</span>,
          __func__, adev-&gt;snd_card, out-&gt;pcm_device_id, out-&gt;config.format);
    <span class="hljs-keyword">if</span> (!is_offload_usecase(out-&gt;usecase)) {
        <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> flags = PCM_OUT;
        <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> pcm_open_retry_count = <span class="hljs-number">0</span>;
        <span class="hljs-keyword">if</span> (out-&gt;usecase == USECASE_AUDIO_PLAYBACK_AFE_PROXY) {
            flags |= PCM_MMAP | PCM_NOIRQ;
            pcm_open_retry_count = PROXY_OPEN_RETRY_COUNT;
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (out-&gt;realtime) {
            flags |= PCM_MMAP | PCM_NOIRQ;
        } <span class="hljs-keyword">else</span>
            flags |= PCM_MONOTONIC;
    
        <span class="hljs-keyword">while</span> (<span class="hljs-number">1</span>) {
            <span class="hljs-comment">// 打开 FE PCM</span>
            out-&gt;pcm = pcm_open(adev-&gt;snd_card, out-&gt;pcm_device_id,
                               flags, &amp;out-&gt;config);
            <span class="hljs-keyword">if</span> (out-&gt;pcm == NULL || !pcm_is_ready(out-&gt;pcm)) {
                ALOGE(<span class="hljs-string">"%s: %s"</span>, __func__, pcm_get_error(out-&gt;pcm));
                <span class="hljs-keyword">if</span> (out-&gt;pcm != NULL) {
                    pcm_close(out-&gt;pcm);
                    out-&gt;pcm = NULL;
                }
                <span class="hljs-keyword">if</span> (pcm_open_retry_count-- == <span class="hljs-number">0</span>) {
                    ret = -EIO;
                    <span class="hljs-keyword">goto</span> error_open;
                }
                usleep(PROXY_OPEN_WAIT_TIME * <span class="hljs-number">1000</span>);
                <span class="hljs-keyword">continue</span>;
            }
            <span class="hljs-keyword">break</span>;
        }<div class="hljs-button {2}" data-title="复制"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li><li style="color: rgb(153, 153, 153);">12</li><li style="color: rgb(153, 153, 153);">13</li><li style="color: rgb(153, 153, 153);">14</li><li style="color: rgb(153, 153, 153);">15</li><li style="color: rgb(153, 153, 153);">16</li><li style="color: rgb(153, 153, 153);">17</li><li style="color: rgb(153, 153, 153);">18</li><li style="color: rgb(153, 153, 153);">19</li><li style="color: rgb(153, 153, 153);">20</li><li style="color: rgb(153, 153, 153);">21</li><li style="color: rgb(153, 153, 153);">22</li><li style="color: rgb(153, 153, 153);">23</li><li style="color: rgb(153, 153, 153);">24</li><li style="color: rgb(153, 153, 153);">25</li><li style="color: rgb(153, 153, 153);">26</li><li style="color: rgb(153, 153, 153);">27</li><li style="color: rgb(153, 153, 153);">28</li><li style="color: rgb(153, 153, 153);">29</li><li style="color: rgb(153, 153, 153);">30</li><li style="color: rgb(153, 153, 153);">31</li><li style="color: rgb(153, 153, 153);">32</li><li style="color: rgb(153, 153, 153);">33</li><li style="color: rgb(153, 153, 153);">34</li><li style="color: rgb(153, 153, 153);">35</li><li style="color: rgb(153, 153, 153);">36</li><li style="color: rgb(153, 153, 153);">37</li><li style="color: rgb(153, 153, 153);">38</li><li style="color: rgb(153, 153, 153);">39</li><li style="color: rgb(153, 153, 153);">40</li><li style="color: rgb(153, 153, 153);">41</li><li style="color: rgb(153, 153, 153);">42</li><li style="color: rgb(153, 153, 153);">43</li><li style="color: rgb(153, 153, 153);">44</li><li style="color: rgb(153, 153, 153);">45</li><li style="color: rgb(153, 153, 153);">46</li><li style="color: rgb(153, 153, 153);">47</li><li style="color: rgb(153, 153, 153);">48</li><li style="color: rgb(153, 153, 153);">49</li><li style="color: rgb(153, 153, 153);">50</li><li style="color: rgb(153, 153, 153);">51</li><li style="color: rgb(153, 153, 153);">52</li><li style="color: rgb(153, 153, 153);">53</li><li style="color: rgb(153, 153, 153);">54</li><li style="color: rgb(153, 153, 153);">55</li><li style="color: rgb(153, 153, 153);">56</li><li style="color: rgb(153, 153, 153);">57</li><li style="color: rgb(153, 153, 153);">58</li><li style="color: rgb(153, 153, 153);">59</li><li style="color: rgb(153, 153, 153);">60</li><li style="color: rgb(153, 153, 153);">61</li><li style="color: rgb(153, 153, 153);">62</li><li style="color: rgb(153, 153, 153);">63</li><li style="color: rgb(153, 153, 153);">64</li><li style="color: rgb(153, 153, 153);">65</li><li style="color: rgb(153, 153, 153);">66</li><li style="color: rgb(153, 153, 153);">67</li><li style="color: rgb(153, 153, 153);">68</li><li style="color: rgb(153, 153, 153);">69</li><li style="color: rgb(153, 153, 153);">70</li><li style="color: rgb(153, 153, 153);">71</li><li style="color: rgb(153, 153, 153);">72</li><li style="color: rgb(153, 153, 153);">73</li><li style="color: rgb(153, 153, 153);">74</li><li style="color: rgb(153, 153, 153);">75</li><li style="color: rgb(153, 153, 153);">76</li><li style="color: rgb(153, 153, 153);">77</li></ul></pre>
    

    语音通话的情景有所不同,它不是传统意义的音频流,流程大概是这样的:

    1. 进入通话时,上层会先设置音频模式为 AUDIO_MODE_IN_CALL(HAL 接口是 adev_set_mode()),再传入音频设备 routing=$device(HAL 接口是 out_set_parameters())
    2. out_set_parameters() 中检查音频模式是否为 AUDIO_MODE_IN_CALL,是则调用 voice_start_call() 打开语音通话的 FE_PCM

    3.2. 路由选择

    我们在 mixer_pahts.xml 中看到 usecase 相关的通路:

       <path name="deep-buffer-playback speaker">
            <ctl name="QUAT_MI2S_RX Audio Mixer MultiMedia1" value="1" />
        </path>
    
    <span class="hljs-tag">&lt;<span class="hljs-title">path</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"deep-buffer-playback headphones"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-title">ctl</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"TERT_MI2S_RX Audio Mixer MultiMedia1"</span> <span class="hljs-attribute">value</span>=<span class="hljs-value">"1"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-title">path</span>&gt;</span>
    
    <span class="hljs-tag">&lt;<span class="hljs-title">path</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"deep-buffer-playback earphones"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-title">ctl</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"QUAT_MI2S_RX Audio Mixer MultiMedia1"</span> <span class="hljs-attribute">value</span>=<span class="hljs-value">"1"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-title">path</span>&gt;</span>
    
    <span class="hljs-tag">&lt;<span class="hljs-title">path</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"low-latency-playback speaker"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-title">ctl</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"QUAT_MI2S_RX Audio Mixer MultiMedia5"</span> <span class="hljs-attribute">value</span>=<span class="hljs-value">"1"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-title">path</span>&gt;</span>
    
    <span class="hljs-tag">&lt;<span class="hljs-title">path</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"low-latency-playback headphones"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-title">ctl</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"TERT_MI2S_RX Audio Mixer MultiMedia5"</span> <span class="hljs-attribute">value</span>=<span class="hljs-value">"1"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-title">path</span>&gt;</span>
    
    <span class="hljs-tag">&lt;<span class="hljs-title">path</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"low-latency-playback earphones"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-title">ctl</span> <span class="hljs-attribute">name</span>=<span class="hljs-value">"QUAT_MI2S_RX Audio Mixer MultiMedia5"</span> <span class="hljs-attribute">value</span>=<span class="hljs-value">"1"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-title">path</span>&gt;</span><div class="hljs-button {2}" data-title="复制"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li><li style="color: rgb(153, 153, 153);">12</li><li style="color: rgb(153, 153, 153);">13</li><li style="color: rgb(153, 153, 153);">14</li><li style="color: rgb(153, 153, 153);">15</li><li style="color: rgb(153, 153, 153);">16</li><li style="color: rgb(153, 153, 153);">17</li><li style="color: rgb(153, 153, 153);">18</li><li style="color: rgb(153, 153, 153);">19</li><li style="color: rgb(153, 153, 153);">20</li><li style="color: rgb(153, 153, 153);">21</li><li style="color: rgb(153, 153, 153);">22</li><li style="color: rgb(153, 153, 153);">23</li></ul></pre>
    

    这些通路其实就是连接 usecase、device 之间的路由。比如 “deep-buffer-playback speaker” 是连接 deep-buffer-playback FE PCM、speaker Device 之间的路由,打开 “deep-buffer-playback speaker”,则把 deep-buffer-playback FE PCM 和 speaker Device 连接起来;关闭 “deep-buffer-playback speaker”,则断开 deep-buffer-playback FE PCM 和 speaker Device 的连接。

    之前提到“device 连接着唯一的 BE DAI,确定了 device 也就能确定所连接的 BE DAI”,因此这些路由通路其实都隐含着 BE DAI 的连接:FE PCM 并非直接到 device 的,而是 FE PCM 先连接到 BE DAI,BE DAI 再连接到 device。这点有助于理解路由控件,路由控件面向的是 FE PCM 和 BE DAI 之间的连接,回放类型的路由控件名称一般是: $BE_DAI Audio Mixer $FE_PCM,录制类型的路由控件名称一般是:$FE_PCM Audio Mixer $BE_DAI,这很容易分辨。

    例如 “deep-buffer-playback speaker” 通路中的路由控件:

    <ctl name="QUAT_MI2S_RX Audio Mixer MultiMedia1" value="1" />
     
     
    • 1
    • MultiMedia1:deep_buffer usacase 对应的 FE PCM
    • QUAT_MI2S_RX:speaker device 所连接的 BE DAI
    • Audio Mixer:表示 DSP 路由功能
    • value:1 表示连接,0 表示断开连接

    这个控件的意思是:把 MultiMedia1 PCM 与 QUAT_MI2S_RX DAI 连接起来。这个控件并没有指明 QUAT_MI2S_RX DAI 与 speaker device 之间的连接,因为 BE DAIs 与 Devices 之间并不需要路由控件,如之前所强调”device 连接着唯一的 BE DAI,确定了 device 也就能确定所连接的 BE DAI“。

    路由控件的开关不仅仅影响 FE PCMs、BE DAIs 的连接或断开,同时会使能或禁用 BE DAIs,要深入理解这点的话需要去研究 ALSA DPCM(Dynamic PCM) 机制,这里稍作了解即可。

    路由操作函数是 enable_audio_route()/disable_audio_route(),这两个函数名称很贴合,控制 FE PCMs 与 BE DAIs 的连接或断开。

    代码流程很简单,把 usecase 和 device 拼接起来就是路由的 path name 了,然后再调用 audio_route_apply_and_update_path() 来设置路由通路:

    const char * const use_case_table[AUDIO_USECASE_MAX] = {
        [USECASE_AUDIO_PLAYBACK_DEEP_BUFFER] = "deep-buffer-playback",
        [USECASE_AUDIO_PLAYBACK_LOW_LATENCY] = "low-latency-playback",
        //...
    };
    

    const char * const backend_tag_table[SND_DEVICE_MAX] = {
    [SND_DEVICE_OUT_HANDSET] = “earphones”;
    [SND_DEVICE_OUT_SPEAKER] = “speaker”;
    [SND_DEVICE_OUT_SPEAKER] = “headphones”;
    //…
    };

    void platform_add_backend_name(char *mixer_path, snd_device_t snd_device,
    struct audio_usecase *usecase)
    {
    if ((snd_device < SND_DEVICE_MIN) || (snd_device >= SND_DEVICE_MAX)) {
    ALOGE("%s: Invalid snd_device = %d", func, snd_device);
    return;
    }

    <span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> * suffix = backend_tag_table[snd_device];
    
    <span class="hljs-keyword">if</span> (suffix != NULL) {
        strlcat(mixer_path, <span class="hljs-string">" "</span>, MIXER_PATH_MAX_LENGTH);
        strlcat(mixer_path, suffix, MIXER_PATH_MAX_LENGTH);
    }
    

    }

    int enable_audio_route(struct audio_device *adev,
    struct audio_usecase *usecase)
    {
    snd_device_t snd_device;
    char mixer_path[MIXER_PATH_MAX_LENGTH];

    <span class="hljs-keyword">if</span> (usecase == NULL)
        <span class="hljs-keyword">return</span> -EINVAL;
    
    ALOGV(<span class="hljs-string">"%s: enter: usecase(%d)"</span>, __func__, usecase-&gt;id);
    
    <span class="hljs-keyword">if</span> (usecase-&gt;type == PCM_CAPTURE)
        snd_device = usecase-&gt;in_snd_device;
    <span class="hljs-keyword">else</span>
        snd_device = usecase-&gt;out_snd_device;
    
    strlcpy(mixer_path, use_case_table[usecase-&gt;id], MIXER_PATH_MAX_LENGTH);
    platform_add_backend_name(mixer_path, snd_device, usecase);
    ALOGD(<span class="hljs-string">"%s: apply mixer and update path: %s"</span>, __func__, mixer_path);
    audio_route_apply_and_update_path(adev-&gt;audio_route, mixer_path);
    ALOGV(<span class="hljs-string">"%s: exit"</span>, __func__);
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
    

    }

    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    3.3. 打开 Device

    Android 音频框架层中,音频设备仅表示输入输出端点,它不关心 BE DAIs 与 端点之间都经过了哪些部件(widget)。但我们做底层的必须清楚知道:从BE DAIs 到端点,整条通路经历了哪些部件。如下图的外放通路 :

    WM8998_SPK

    为了使得声音从 speaker 端点输出,我们需要打开 AIF1、DAC1、SPKOUT 这些部件,并把它们串联起来,这样音频数据才能顺着这条路径(AIF1>DAC1>SPKOUT>SPEAKER)一路输出到 speaker。

    在音频硬件驱动中,定义各种控件用于部件的开关或连接,比如控件 “SPKL DAC1 Switch” 用于控制 SPKL、DAC1 的连接或断开。具体细节请参考:Linux ALSA 音频系统:物理链路篇

    我们在 mixer_pahts.xml 中看到 speaker 通路:

       <path name="speaker">
            <ctl name="SPKL DAC1 Switch" value="1" />
            <ctl name="DAC1L AIF1RX1 Switch" value="1" />
            <ctl name="DAC1R AIF1RX2 Switch" value="1" />
        </path>
     
     
    • 1
    • 2
    • 3
    • 4
    • 5

    这些设备通路由 enable_snd_device()/disable_snd_device() 设置:

    int enable_snd_device(struct audio_device *adev,
                          snd_device_t snd_device)
    {
        int i, num_devices = 0;
        snd_device_t new_snd_devices[SND_DEVICE_OUT_END];
        char device_name[DEVICE_NAME_MAX_SIZE] = {0};
    
    <span class="hljs-keyword">if</span> (snd_device &lt; SND_DEVICE_MIN ||
        snd_device &gt;= SND_DEVICE_MAX) {
        ALOGE(<span class="hljs-string">"%s: Invalid sound device %d"</span>, __func__, snd_device);
        <span class="hljs-keyword">return</span> -EINVAL;
    }
    
    <span class="hljs-comment">// 设备引用计数累加</span>
    adev-&gt;snd_dev_ref_cnt[snd_device]++;
    
    <span class="hljs-comment">// 根据 snd_device 找到对应的 device_name</span>
    <span class="hljs-keyword">if</span>(platform_get_snd_device_name_extn(adev-&gt;platform, snd_device, device_name) &lt; <span class="hljs-number">0</span> ) {
        ALOGE(<span class="hljs-string">"%s: Invalid sound device returned"</span>, __func__);
        <span class="hljs-keyword">return</span> -EINVAL;
    }
    <span class="hljs-comment">// 设备已经被打开了,直接返回,不会重复打开设备</span>
    <span class="hljs-keyword">if</span> (adev-&gt;snd_dev_ref_cnt[snd_device] &gt; <span class="hljs-number">1</span>) {
        ALOGV(<span class="hljs-string">"%s: snd_device(%d: %s) is already active"</span>,
              __func__, snd_device, device_name);
        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
    }
    
    
    <span class="hljs-comment">// 如果是带保护的设备,那么先停止校准操作</span>
    <span class="hljs-keyword">if</span> (audio_extn_spkr_prot_is_enabled())
         audio_extn_spkr_prot_calib_cancel(adev);
    
    <span class="hljs-keyword">if</span> (platform_can_enable_spkr_prot_on_device(snd_device) &amp;&amp;
         audio_extn_spkr_prot_is_enabled()) {
       <span class="hljs-comment">// 检查带保护的设备有无合法的 acdb id,如果没有合法的 acdb id,那么保护算法无法被调用的</span>
       <span class="hljs-keyword">if</span> (platform_get_spkr_prot_acdb_id(snd_device) &lt; <span class="hljs-number">0</span>) {
           adev-&gt;snd_dev_ref_cnt[snd_device]--;
           <span class="hljs-keyword">return</span> -EINVAL;
       }
       audio_extn_dev_arbi_acquire(snd_device);
       <span class="hljs-comment">// 打开带保护的设备,保护算法也开始运作</span>
       <span class="hljs-keyword">if</span> (audio_extn_spkr_prot_start_processing(snd_device)) {
            ALOGE(<span class="hljs-string">"%s: spkr_start_processing failed"</span>, __func__);
            audio_extn_dev_arbi_release(snd_device);
            <span class="hljs-keyword">return</span> -EINVAL;
        }
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (platform_split_snd_device(adev-&gt;platform,
                                         snd_device,
                                         &amp;num_devices,
                                         new_snd_devices) == <span class="hljs-number">0</span>) {
        <span class="hljs-comment">// 铃声模式下,多设备分割:比如 SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES 先分割为</span>
        <span class="hljs-comment">// SND_DEVICE_OUT_SPEAKER + SND_DEVICE_OUT_HEADPHONES,然后再一一打开 speaker</span>
        <span class="hljs-comment">// 和 headphones 设备</span>
        <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; num_devices; i++) {
            enable_snd_device(adev, new_snd_devices[i]);
        }
    } <span class="hljs-keyword">else</span> {
        ALOGD(<span class="hljs-string">"%s: snd_device(%d: %s)"</span>, __func__, snd_device, device_name);
    
       <span class="hljs-comment">// A2DP:打开蓝牙设备端</span>
       <span class="hljs-keyword">if</span> ((SND_DEVICE_OUT_BT_A2DP == snd_device) &amp;&amp;
           (audio_extn_a2dp_start_playback() &lt; <span class="hljs-number">0</span>)) {
           ALOGE(<span class="hljs-string">" fail to configure A2dp control path "</span>);
           <span class="hljs-keyword">return</span> -EINVAL;
       }
    
        audio_extn_dev_arbi_acquire(snd_device);
        <span class="hljs-comment">// 设置设备通路</span>
        audio_route_apply_and_update_path(adev-&gt;audio_route, device_name);
    }
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
    

    }

    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    值得注意的点有:

    • 设备引用计数:每个设备都有各自的引用计数 snd_dev_ref_cnt,引用计数在 enable_snd_device() 中累加,如果大于 1,则表示该设备已经被打开了,那么就不会重复打开该设备;引用计数在 disable_snd_device() 中累减,如果为 0,则表示没有 usecase 需要该设备了,那么就关闭该设备。
    • 带保护的外放设备:带 “audio_extn_spkr_prot” 前缀的函数是带保护的外放设备的相关函数,这些带保护的外放设备和其他设备不一样,它虽然属于输出设备,但往往还需要打开一个 PCM_IN 作为 I/V Feedback,有了 I/V Feedback 保护算法才能正常运作。
    • 多输出设备的分割:多输出设备,一般指铃声模式下,外放设备与其他设备同时输出的情形;platform_split_snd_device() 把多输出设备分割,比如 SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES 分割为 SND_DEVICE_OUT_SPEAKER + SND_DEVICE_OUT_HEADPHONES,然后再一一打开 speaker、headphones。为什么要把多输出设备分割为 外放设备+其他设备 的形式?现在智能手机的外放设备一般都是带保护的,需要跑喇叭保护算法,而其他设备如蓝牙耳机也可能需要跑 aptX 算法,如果没有分割的话,只能下发一个 acdb id,无法把喇叭保护算法和 aptX 算法都调度起来。多输出设备分割时,还需要遵循一个规则:如果这些设备均连接到同一个 BE DAI,则无须分割。
    int platform_split_snd_device(void *platform,
                                  snd_device_t snd_device,
                                  int *num_devices,
                                  snd_device_t *new_snd_devices)
    {
        int ret = -EINVAL;
        struct platform_data *my_data = (struct platform_data *)platform;
        if (NULL == num_devices || NULL == new_snd_devices) {
            ALOGE("%s: NULL pointer ..", __func__);
            return -EINVAL;
        }
    
    <span class="hljs-comment">/*
     * If wired headset/headphones/line devices share the same backend
     * with speaker/earpiece this routine returns -EINVAL.
     */</span>
    <span class="hljs-keyword">if</span> (snd_device == SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES &amp;&amp;
        !platform_check_backends_match(SND_DEVICE_OUT_SPEAKER, SND_DEVICE_OUT_HEADPHONES)) {
        *num_devices = <span class="hljs-number">2</span>;
    
        new_snd_devices[<span class="hljs-number">0</span>] = SND_DEVICE_OUT_SPEAKER;
        new_snd_devices[<span class="hljs-number">1</span>] = SND_DEVICE_OUT_HEADPHONES;
        ret = <span class="hljs-number">0</span>;<div class="hljs-button {2}" data-title="复制"></div></code><ul class="pre-numbering" style=""><li style="color: rgb(153, 153, 153);">1</li><li style="color: rgb(153, 153, 153);">2</li><li style="color: rgb(153, 153, 153);">3</li><li style="color: rgb(153, 153, 153);">4</li><li style="color: rgb(153, 153, 153);">5</li><li style="color: rgb(153, 153, 153);">6</li><li style="color: rgb(153, 153, 153);">7</li><li style="color: rgb(153, 153, 153);">8</li><li style="color: rgb(153, 153, 153);">9</li><li style="color: rgb(153, 153, 153);">10</li><li style="color: rgb(153, 153, 153);">11</li><li style="color: rgb(153, 153, 153);">12</li><li style="color: rgb(153, 153, 153);">13</li><li style="color: rgb(153, 153, 153);">14</li><li style="color: rgb(153, 153, 153);">15</li><li style="color: rgb(153, 153, 153);">16</li><li style="color: rgb(153, 153, 153);">17</li><li style="color: rgb(153, 153, 153);">18</li><li style="color: rgb(153, 153, 153);">19</li><li style="color: rgb(153, 153, 153);">20</li><li style="color: rgb(153, 153, 153);">21</li><li style="color: rgb(153, 153, 153);">22</li><li style="color: rgb(153, 153, 153);">23</li></ul></pre>
    

    4. 音频设备切换

    • 回放场景,框架层回调 HAL 层接口 out_set_parameters(“routing=$device”) 来切换输出设备
    • 录制场景,框架层回调 HAL 层接口 in_set_parameters(“routing=$device”) 来切换输入设备

    这两个函数最终都是调用 select_device() 来实现设备切换的,select_device() 函数非常复杂,这里仅阐述下主干流程。

    select_devices
      disable_audio_route
      disable_snd_device
      check_usecases_codec_backend 检查其他usecase是否也跟随切换设备
          platform_check_backends_match
          disable_audio_route
          disable_snd_device
          enable_snd_device
          enable_audio_route
      enable_snd_device
      enable_audio_route
     
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4.1. 单 usecase 情景的设备切换

    4.2. 多 usecase 情景的设备切换

    –to be continued

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值