白话Android音频系统原理

一、基本原理

  1. 谁来发起(录音和播放)?AudioRecorder,AudioTrack。
  2. 谁来处理(录音和播放)? AudioFlinger!
  3. 怎么处理?AudioPolicyService !
  4. 什么是output?为了便于管理, 把一个设备上具有相同参数的一组播放device组织称为一个output。它描述一些实际支持的设备(有实际硬件)一个output对应一个处理线程thread。

二、初始化准备工作

  1. 从frameworks/av/media/audioserver/main_audioserver.cpp开始,系统启动后,会初始化AudioFlinger和AudioPolicyService。先初始化AudioFlinger,后面AudioPolicyService初始化的时候,会用到AudioFlinger里面的方法。
  2. 初始化AudioPolicyService的时候,实际上创建了一个AudioPolicyManager,主要做3件事:
    a. 加载配置文件 audio_policy.conf (AudioPolicyManager完成
    b. 加载audio policy硬件抽象库(AudioFlinger完成
    c. openOutput(创建一个MixerThread线程与对应的output相关联,output对应着设备。即:将创建的线程添加到播放线程列表 mPlaybackThreads中,之后app就可以把数据传递给线程了,进而传递给硬件设备了。每一个线程都对应一个output 。AudioFlinger完成

三、播放流程

  1. Java层的AudioTrack的使用流程是:
    a. 构建参数并传递给AudioTrack进行初始化。
    b. 执行AudioTrack.wrtie写入数据。
    c. 执行AudioTrack.start操作开始播放。

  2. AudioTrack创建
    第一步,创建启动AudioTrackThread线程来提供音频数据。
    即:AudioTrack中的AudioTrackThread提供数据。
    第二步,根据APP构造AudioTrack时指定的声音属性找到对应的output。
    即:先通过声音属性获取strategy类别,再通过strategy类别,确定播放设备(耳机/蓝牙/喇叭),最后根据设备device,去查找哪些output上有这些device,然后从output中找到最合适的那个。
    第三步,创建playbackthread中的Track
    即:根据第二步获取到的output来确定对应的PlaybackThread类型的播放线程,在playbackthread中创建新的track,并把track加入到PlayBackThread的mTracks表中。这一步代表着从app中的AudioTrack创建会同时让AudioFlinger中的与output相关联的PlaybackThread内也创建一个track,app中的AudioTrack提供数据,PlaybackThread内的track处理数据。这二者通过跨进程共享的缓冲区buffer 实现共享进行通信。

  3. “生产者-消费者”数据交互
    a. 这里的生产者实际是App中的AudioTrack。app不断给AudioTrack wrtie数据,提供数据。后文我们称AudioTrack提供了数据。
    b. 消费者是AudioFlinger中的与output相关联的playbackThread中的track。后文我们称Ctrack消费了数据。
    c. AudioTrack( APP应用端)通过 AudioTrackClientProxy向共享 buffer 写入数据, Ctrack(server 端)通过 AudioTrackServerProxy从共享内存中 读出数据。这样 client、server 通过 proxy 对共享内存 形成了生产者、消费者模型。

  4. 往AudioTrack中写数据支持2种不同的模式,MODE_STREAM 和 MODE_STATIC。这两种模式在共享内存方式上有所不同。
    a. MODE_STATIC模式:一次性提前提供数据。APP创建共享内存, APP一次性填充数据。Ctrack等数据构造好,取出数据就可以直接使用了,不存在同步问题。对应的Ctrack工作是:获得含有数据的obtainBuffer(APP一次性提交共享内存的数据有可能很多,Ctrack需要进行多次播放)。完成后释放buffer。
    b. MODE_STREAM模式:边播放边提供。由playbackThread创建共享内存。APP使用obtainBuffer获得空buffer, 填充数据后使用releaseBuffer释放buffer。Ctrack使用obtainBuffer获取填充了数据的buffer,Ctrack一样需要进行多次播放。只不过这里使用的是 环形缓冲区机制来,不断传递数据。完成后releaseBuffer释放buffer。
    c. MODE_STREAM模式 会使用到环形缓存区来同步数据,一个生产数据,一个消费数据,这个时候使用环形缓冲区是最可靠的。实际上就是 ClientProxy::obtainBuffer、ClientProxy::releaseBuffer、ServerProxy::obtainBuffer、ServerProxy::releaseBuffer之间的协作。

    Client 端:
    AudioTrackClientProxy:: obtainBuffer()从 audio buffer 获取连续的空buffer;
    AudioTrackClientProxy:: releaseBuffer ()将填充了数据的 buffer 放回 audio buffer。

    Server 端:
    AudioTrackServerProxy:: obtainBuffer()从 audio buffer 获取连续的填充了数据的 buffer;
    AudioTrackServerProxy:: releaseBuffer ()将使用完的空buffer 放回 audio buffer

  5. AudioTrackThread实现两个核心功能:
    a. AudioTrack与AudioFlinger之间 数据传输,AudioFlinger启动了一个线程专门用于接收客户端的 音频数据,同时客户端也需要一个线程来“不断”的传送音频数据
    b. 用于报告数据的传输状态,AudioTrack中保存了一个callback_t类型的回调函数(即全局变量mCbf)用于事件发生时进行回传。

  6. 混音
    多个应用程序播放,每个APP端都会创建AudioTrack,每个AudioTrack都会 通过共享内存 和 播放线程的Track 传递数据,每个应用发送的音频数据 格式可能都不相同,而声卡仅支持固定的几种格式,除非发送的格式就是 声卡本身支持的格式,否则这里都需要进行重采样/混音(playbackthread中使用mAudioMixer的一些操作把硬件不支持的音频格式转化为硬件支持的音频格式,这个过程叫做重采样)。
    AudioPolicyService创建之初就开始创建对应output的线程(创建MixerThread,是playbackthread的子线程)。MixerThread主要做3件事,准备混音,处理数据(比如重采样)、混音,输出声音到声卡上。

四、录音流程

  1. 录音流程
    a. 创建AudioRecord
    b. AudioRecord->start启动对应的线程开始录音
    c. AudioRecord->read 循环从底层读取音频数据, 将音频数据写入指定文件中
    d. AudioRecord->stop停止对应线程

  2. 初始化AudioRecord
    第一步,基本上AudioRecord同AudioTrack一样,在创建、设置AudioRecord, 指定了声音来源inputSource( 比如: AUDIO_SOURCE_MIC),还指定了采样率、通道数、格式等参数。同时会创建一个AudioRecordThread。
    第二步,AudioPolicyManager通过设置的参数属性,获取到对应的Input。Input对应着一组属性相同的device,根据device找到profile(audio_policy.conf产生的),根据profile找到module,即对应一个声卡,然后加载对应声卡的HAL文件。调用AudioFlinger->openInput创建RecordThread录音线程,绑定对应的input索引和声卡,并将它加入到mRecordThreads中。
    第三步,调用AudioFlinger->openRecord。即通过 RecordThread 会创建recordTrack,类似于playbackThread创建自己的Track。APP的AudioRecord 与 RecordThread内部的RecordTrack 通过共享内存传递数据。

  3. 开始录音
    mAudioRecord->start的实现(mAudioRecord是audioFlinger->openRecord返回得到的 RecordHandle 类型,RecordHandle使用recordTrack初始化),实际上 调用的是AudioFlinger::RecordThread::RecordTrack::start方法。 在这个start方法中调用到了recordThread的start方法。
    这里会唤醒线程,线程的主要内容是从HAL中得到数据, 再通过内部的RecordTrack把数据传给APP的AudioRecord。

  4. AudioRecord的read方法
    这里也是使用obtainBuffer和releaseBuffer来操作共享内存,从audioBuffer中读取数据到Buffer中。

  5. 在原生代码中,APP的一个AudioRecord会导致创建一个RecordThread,在一个device上有可能存在多个RecordThread,任意时刻只能有一个RecordThread在运行(在AudioPolicyManager这一层做了限制,限制线程的数量只能有一个),所以只能有一个APP在录音,不能多个APP同时录音。

  6. 原生AudioRecord接口是完全不支持多channel录音数据的采集的,硬件上、驱动上设置双声道; 但是系统接口只接了一个MIC,所以驱动程序录音时得到的双声道数据中,其中一个声道数据恒为0。

猜你喜欢

转载自blog.csdn.net/hshuaijun55/article/details/122931783