audio 一些基本概念


Audio 是整个 Android 平台非常重要的一个组成部分,负责音频数据的采集和输出、音频流的控制、音频设备的管理、音量调节等,主要包括如下部分:

  • Audio Application Framework:音频应用框架 
    • AudioTrack:负责回放数据的输出,属 Android 应用框架 API 类
    • AudioRecord:负责录音数据的采集,属 Android 应用框架 API 类
    • AudioSystem: 负责音频事务的综合管理,属 Android 应用框架 API 类
  • Audio Native Framework:音频本地框架 
    • AudioTrack:负责回放数据的输出,属 Android 本地框架 API 类
    • AudioRecord:负责录音数据的采集,属 Android 本地框架 API 类
    • AudioSystem: 负责音频事务的综合管理,属 Android 本地框架 API 类
  • Audio Services:音频服务 
    • AudioPolicyService:音频策略的制定者,负责音频设备切换的策略抉择、音量调节策略等
    • AudioFlinger:音频策略的执行者,负责输入输出流设备的管理及音频流数据的处理传输
  • Audio HAL:音频硬件抽象层,负责与音频硬件设备的交互,由 AudioFlinger 直接调用

与 Audio 强相关的有 MultiMedia,MultiMedia 负责音视频的编解码,MultiMedia 将解码后的数据通过 AudioTrack 输出,而 AudioRecord 采集的录音数据交由 MultiMedia 进行编码。


AudioTrack Java API 两种数据传输模式:

Transfer ModeDescription
MODE_STATIC应用进程将回放数据一次性付给 AudioTrack,适用于数据量小、时延要求高的场景
MODE_STREAM用进程需要持续调用 write() 写数据到 FIFO,写数据时有可能遭遇阻塞(等待 AudioFlinger::PlaybackThread 消费之前的数据),基本适用所有的音频场景

audio playback sample

int minBuffSize = AudioTrack.getMinBufferSize(22050, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT);

    AudioTrack track = new AudioTrack(STREAM_MUSIC, 22050, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT, minBuffSize, MODE_STREAM);

byte data[] = new byte[minBuffSize/2];

    track.write(data, 0, data.length);

    track.play();

float maxVol = AudioTrack.getMaxVolume();

    track.release();


最小缓冲区的大小 = 最低帧数 * 声道数 * 采样深度,(采样深度以字节为单位)


2.2. AudioTrack Native API

AudioTrack Native API 四种数据传输模式:

Transfer ModeDescription
TRANSFER_CALLBACK在 AudioTrackThread 线程中通过 audioCallback 回调函数主动从应用进程那里索取数据,ToneGenerator 采用这种模式
TRANSFER_OBTAIN应用进程需要调用 obtainBuffer()/releaseBuffer() 填充数据,目前我还没有见到实际的使用场景
TRANSFER_SYNC应用进程需要持续调用 write() 写数据到 FIFO,写数据时有可能遭遇阻塞(等待 AudioFlinger::PlaybackThread 消费之前的数据),基本适用所有的音频场景;对应于 AudioTrack Java API 的 MODE_STREAM 模式
TRANSFER_SHARED应用进程将回放数据一次性付给 AudioTrack,适用于数据量小、时延要求高的场景;对应于 AudioTrack Java API 的 MODE_STATIC 模式


AudioTrack Native API 音频流类型:

Stream TypeDescription
AUDIO_STREAM_VOICE_CALL电话语音
AUDIO_STREAM_SYSTEM系统声音
AUDIO_STREAM_RING铃声声音,如来电铃声、闹钟铃声等
AUDIO_STREAM_MUSIC音乐声音
AUDIO_STREAM_ALARM警告音
AUDIO_STREAM_NOTIFICATION通知音
AUDIO_STREAM_DTMFDTMF 音(拨号盘按键音)


AudioTrack Native API 输出标识:

AUDIO_OUTPUT_FLAGDescription
AUDIO_OUTPUT_FLAG_DIRECT表示音频流直接输出到音频设备,不需要软件混音,一般用于 HDMI 设备声音输出
AUDIO_OUTPUT_FLAG_PRIMARY表示音频流需要输出到主输出设备,一般用于铃声类声音
AUDIO_OUTPUT_FLAG_FAST表示音频流需要快速输出到音频设备,一般用于按键音、游戏背景音等对时延要求高的场景
AUDIO_OUTPUT_FLAG_DEEP_BUFFER表示音频流输出可以接受较大的时延,一般用于音乐、视频播放等对时延要求不高的场景
AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD表示音频流没有经过软件解码,需要输出到硬件解码器,由硬件解码器进行解码


我们根据不同的播放场景,使用不同的输出标识,如按键音、游戏背景音对输出时延要求很高,那么就需要置

AUDIO_OUTPUT_FLAG_FAST,具体可以参考 ToneGenerator、SoundPool 和 OpenSL ES。


首先要了解音频领域中,帧(frame)的概念:帧表示一个完整的声音单元,所谓的声音单元是指一个采样样本;

如果是双声道,那么一个完整的声音单元就是 2 个样本,如果是 5.1 声道,那么一个完整的声音单元就是 6 个样本了。

帧的大小(一个完整的声音单元的数据量)等于声道数乘以采样深度,即 frameSize = channelCount * bytesPerSample

帧的概念非常重要,无论是框架层还是内核层,都是以帧为单位去管理音频数据缓冲区的。

其次还得了解音频领域中,传输延迟(latency)的概念:传输延迟表示一个周期的音频数据的传输时间。

可能有些读者一脸懵逼,一个周期的音频数据,这又是啥?我们再引入周期(period)的概念:

Linux ALSA 把数据缓冲区划分为若干个块,dma 每传输完一个块上的数据即发出一个硬件中断,cpu 收到中断信号后,

再配置 dma 去传输下一个块上的数据;一个块即是一个周期,周期大小(periodSize)即是一个数据块的帧数。

再回到传输延迟(latency),传输延迟等于周期大小除以采样率,即 latency = periodSize / sampleRate

最后了解下音频重采样:音频重采样是指这样的一个过程——把一个采样率的数据转换为另一个采样率的数据。

Android 原生系统上,音频硬件设备一般都工作在一个固定的采样率上(如 48 KHz),因此所有音轨数据都需要重采样

到这个固定的采样率上,然后再输出。为什么这么做?系统中可能存在多个音轨同时播放,而每个音轨的采样率可能是不一致的;

比如在播放音乐的过程中,来了一个提示音,这时需要把音乐和提示音混音并输出到硬件设备,而音乐的采样率和提示音的

采样率不一致,问题来了,如果硬件设备工作的采样率设置为音乐的采样率的话,那么提示音就会失真;

因此最简单见效的解决方法是:硬件设备工作的采样率固定一个值,所有音轨在 AudioFlinger 都重采样到这个采样率上,

混音后输出到硬件设备,保证所有音轨听起来都不失真。


重采样计算的:afFrameCount 是硬件设备处理单个数据块的帧数,afSampleRate 是硬件设备配置的采样率,sampleRate 是音轨的采样率,

如果要把音轨数据重采样到 afSampleRate 上,那么反推算出应用程序最少传入的帧数为 afFrameCount * sampleRate / afSampleRate,

而为了播放流畅,实际上还要大一点,所以再乘以一个系数(可参照 framebuffer 双缓冲,一个缓冲缓存当前的图像,一个缓冲准备下一幅的图像,

这样图像切换更流畅),然后就得出一个可以保证播放流畅的最低帧数

 minFrameCount = (afFrameCount * sampleRate / afSampleRate) * minBufCount

    

几个重要的类

  • AudioResampler.cpp:重采样处理类,可进行采样率转换和声道转换;由录制线程 AudioFlinger::RecordThread 直接使用
  • AudioMixer.cpp:混音处理类,包括重采样、音量调节、声道转换等,其中的重采样复用了 AudioResampler;由回放线程 AudioFlinger::MixerThread 直接使用
  • Effects.cpp:音效处理类
  • Tracks.cpp:音频流管理类,可控制音频流的状态,如 start、stop、pause
  • Threads.cpp:回放线程和录制线程类;回放线程从 FIFO 读取回放数据并混音处理,然后写数据到输出流设备;录制线程从输入流设备读取录音数据并重采样处理,然后写数据到 FIFO
  • AudioFlinger.cpp:AudioFlinger 对外提供的服务接口


AudioFlinger 对外提供的主要的服务接口如下:

InterfaceDescription
sampleRate获取硬件设备的采样率
format获取硬件设备的音频格式
frameCount获取硬件设备的周期帧数
latency获取硬件设备的传输延迟
setMasterVolume调节主输出设备的音量
setMasterMute静音主输出设备
setStreamVolume调节指定类型的音频流的音量,这种调节不影响其他类型的音频流的音量
setStreamMute静音指定类型的音频流
setVoiceVolume调节通话音量
setMicMute静音麦克风输入
setMode切换音频模式:音频模式有 4 种,分别是 Normal、Ringtone、Call、Communicatoin
setParameters设置音频参数:往下调用 HAL 层相应接口,常用于切换音频通道
getParameters获取音频参数:往下调用 HAL 层相应接口
openOutput打开输出流:打开输出流设备,并创建 PlaybackThread 对象
closeOutput关闭输出流:移除并销毁 PlaybackThread 上面挂着的所有的 Track,退出 PlaybackThread,关闭输出流设备
openInput打开输入流:打开输入流设备,并创建 RecordThread 对象
closeInput关闭输入流:退出 RecordThread,关闭输入流设备
createTrack新建输出流管理对象: 找到对应的 PlaybackThread,创建输出流管理对象 Track,然后创建并返回该 Track 的代理对象 TrackHandle
openRecord新建输入流管理对象:找到 RecordThread,创建输入流管理对象 RecordTrack,然后创建并返回该 RecordTrack 的代理对象 RecordHandle

可以归纳出 AudioFlinger 响应的服务请求主要有:

  • 获取硬件设备的配置信息
  • 音量调节
  • 静音操作
  • 音频模式切换
  • 音频参数设置
  • 输入输出流设备管理
  • 音频流管理

AudioFlinger 回放录制线程

  • ThreadBase:PlaybackThread 和 RecordThread 的基类
  • RecordThread:录制线程类,由 ThreadBase 派生
  • PlaybackThread:回放线程基类,同由 ThreadBase 派生
  • MixerThread:混音回放线程类,由 PlaybackThread 派生,负责处理标识为 AUDIO_OUTPUT_FLAG_PRIMARY、AUDIO_OUTPUT_FLAG_FAST、AUDIO_OUTPUT_FLAG_DEEP_BUFFER 的音频流,MixerThread 可以把多个音轨的数据混音后再输出
  • DirectOutputThread:直输回放线程类,由 PlaybackThread 派生,负责处理标识为 AUDIO_OUTPUT_FLAG_DIRECT 的音频流,这种音频流数据不需要软件混音,直接输出到音频设备即可
  • DuplicatingThread:复制回放线程类,由 MixerThread 派生,负责复制音频流数据到其他输出设备,使用场景如主声卡设备、蓝牙耳机设备、USB 声卡设备同时输出
  • OffloadThread:硬解回放线程类,由 DirectOutputThread 派生,负责处理标识为 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 的音频流,这种音频流未经软件解码的(一般是 MP3、AAC 等格式的数据),需要输出到硬件解码器,由硬件解码器解码成 PCM 数据

从 Audio HAL 中,我们通常看到如下 4 种输出流设备,分别对应着不同的播放场景:

  • primary_out:主输出流设备,用于铃声类声音输出,对应着标识为 AUDIO_OUTPUT_FLAG_PRIMARY 的音频流和一个 MixerThread 回放线程实例
  • low_latency:低延迟输出流设备,用于按键音、游戏背景音等对时延要求高的声音输出,对应着标识为 AUDIO_OUTPUT_FLAG_FAST 的音频流和一个 MixerThread 回放线程实例
  • deep_buffer:音乐音轨输出流设备,用于音乐等对时延要求不高的声音输出,对应着标识为 AUDIO_OUTPUT_FLAG_DEEP_BUFFER 的音频流和一个 MixerThread 回放线程实例
  • compress_offload:硬解输出流设备,用于需要硬件解码的数据输出,对应着标识为 AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD 的音频流和一个 OffloadThread 回放线程实例

其中 primary_out 设备是必须声明支持的,而且系统启动时就已经打开 primary_out 设备并创建好对应的 MixerThread 实例。其他类型的输出流设备并非必须声明支持的,主要是看硬件上有无这个能力。

可能有人产生这样的疑问:既然 primary_out 设备一直保持打开,那么能耗岂不是很大?这里阐释一个概念:输出流设备属于逻辑设备,并不是硬件设备。所以即使输出流设备一直保持打开,只要硬件设备不工作,那么就不会影响能耗。那么硬件设备什么时候才会打开呢?答案是 PlaybackThread 将音频数据写入到输出流设备时。


最后看个金典的架构




  • 14
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值