Android 音频系统:从 AudioTrack 到 AudioFlinger
1. Android 音频框架概述
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 进行编码。
2. AudioTrack API 概述
播放声音可以使用 MediaPlayer 和 AudioTrack,两者都提供 Java API 给应用开发者使用。两者的差别在于:
MediaPlayer 可以播放多种格式的音源,如 mp3、flac、wma、ogg、wav 等
而 AudioTrack 只能播放解码后的 PCM 数据流.
从上面 Android 音频系统架构图来看:
MediaPlayer 在 Native 层会创建对应的音频解码器和一个 AudioTrack,解码后的数据交由 AudioTrack 输出。
所以 MediaPlayer 的应用场景更广,一般情况下使用它也更方便;只有一些对声音时延要求非常苛刻的应用场景才需要用到 AudioTrack。
2.1. AudioTrack Java API
AudioTrack Java API 两种数据传输模式:
Transfer Mode | Description |
---|---|
MODE_STATIC | 应用进程将回放数据一次性付给 AudioTrack,适用于数据量小、时延要求高的场景 |
MODE_STREAM | 用进程需要持续调用 write() 写数据到 FIFO,写数据时有可能遭遇阻塞 (等待 AudioFlinger::PlaybackThread 消费之前的数据),基本适用所有的音频场景 |
AudioTrack Java API 音频流类型:
Stream Type | Description |
---|---|
STREAM_VOICE_CALL | 电话语音 |
STREAM_SYSTEM | 系统声音 |
STREAM_RING | 铃声声音,如来电铃声、闹钟铃声等 |
STREAM_MUSIC | 音乐声音 |
STREAM_ALARM | 警告音 |
STREAM_NOTIFICATION | 通知音 |
STREAM_DTMF | DTMF 音(拨号盘按键音) |
Android 为什么要定义这么多的流类型?这与 Android 的音频管理策略有关,例如:
① 音频流的音量管理,调节一个类型的音频流音量,不会影响到其他类型的音频流
② 根据流类型选择合适的输出设备:比如插着有线耳机期间,音乐声(STREAM_MUSIC)只会输出到有线耳机,而铃声(STREAM_RING)会同时输出到有线耳机和外放
这些属于 AudioPolicyService 的内容, 应用开发者应该根据应用场景选择相应的流类型,以便系统为这道流选择合适的输出设备。
一个 AudioTrack Java API 的测试例子(MODE_STREAM 模式):
//Test case 1: setStereoVolume() with max volume returns SUCCESS
@LargeTest
public void testSetStereoVolumeMax() throws Exception {
// constants for test
final String TEST_NAME = "testSetStereoVolumeMax";
final int TEST_SR = 22050;
final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
final int TEST_MODE = AudioTrack.MODE_STREAM;
final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
//-------- initialization --------------
// 稍后详细分析 getMinBufferSize
int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
// 创建一个 AudioTrack 实例
AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
minBuffSize, TEST_MODE);
byte data[] = new byte[minBuffSize/2];
//-------- test --------------
// 调用 write() 写入回放数据
track.write(data, 0, data.length);
track.write(data, 0, data.length);
// 调用 play() 开始播放
track.play();
float maxVol = AudioTrack.getMaxVolume();
assertTrue(TEST_NAME, track.setStereoVolume(maxVol, maxVol) == AudioTrack.SUCCESS);
//-------- tear down --------------
// 播放完成后,调用 release() 释放 AudioTrack 实例
track.release();
}
详细说明下 getMinBufferSize() 接口: 字面意思是返回最小数据缓冲区的大小,它是声音能正常播放的最低保障,从函数参数来看,返回值取决于采样率、采样深度、声道数这三个属性。
MODE_STREAM 模式下,应用程序重点参考其返回值然后确定分配多大的数据缓冲区。
如果数据缓冲区分配得过小,那么播放声音会频繁遭遇 underrun,
underrun 是指生产者(AudioTrack)提供数据的速度跟不上消费者(AudioFlinger::PlaybackThread)消耗数据的速度,
反映到现实的后果就是声音断续卡顿,严重影响听觉体验。
如下是getMinBufferSize()的代码实现:
// AudioTrack.java
/**
* Returns the estimated minimum buffer size required for an AudioTrack
* object to be created in the {@link #MODE_STREAM} mode.
* The size is an estimate because it does not consider either the route or the sink,
* since neither is known yet. Note that this size doesn't
* guarantee a smooth playback under load, and higher values should be chosen according to
* the expected frequency at which the buffer will be refilled with additional data to play.
* For example, if you intend to dynamically set the source sample rate of an AudioTrack
* to a higher value than the initial source sample rate, be sure to configure the buffer size
* based on the highest planned sample rate.
* @param sampleRateInHz the source sample rate expressed in Hz.
* {@link AudioFormat#SAMPLE_RATE_UNSPECIFIED} is not permitted.
* @param channelConfig describes the configuration of the audio channels.
* See {@link AudioFormat#CHANNEL_OUT_MONO} and
* {@link AudioFormat#CHANNEL_OUT_STEREO}
* @param audioFormat the format in which the audio data is represented.
* See {@link AudioFormat#ENCODING_PCM_16BIT} and
* {@link AudioFormat#ENCODING_PCM_8BIT},
* and {@link AudioFormat#ENCODING_PCM_FLOAT}.
* @return {@link #ERROR_BAD_VALUE} if an invalid parameter was passed,
* or {@link #ERROR} if unable to query for output properties,
* or the minimum buffer size expressed in bytes.
*/
static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
int channelCount = 0;
switch(channelConfig) {
case AudioFormat.CHANNEL_OUT_MONO:
case AudioFormat.CHANNEL_CONFIGURATION_MONO:
channelCount = 1; // 单声道
break;
case AudioFormat.CHANNEL_OUT_STEREO:
case AudioFormat.CHANNEL_CONFIGURATION_STEREO:
channelCount = 2; // 双声道
break;
default:
if (!isMultichannelConfigSupported(channelConfig)) {
loge("getMinBufferSize(): Invalid channel configuration.");
return ERROR_BAD_VALUE;
} else {
channelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig);
}
}
if (!AudioFormat.isPublicEncoding(audioFormat)) {
loge("getMinBufferSize(): Invalid audio format.");
return ERROR_BAD_VALUE;
}
// sample rate, note these values are subject to change
// Note: AudioFormat.SAMPLE_RATE_UNSPECIFIED is not allowed
if ( (sampleRateInHz < AudioFormat.SAMPLE_RATE_HZ_MIN) ||
(sampleRateInHz > AudioFormat.SAMPLE_RATE_HZ_MAX) ) {
loge("getMinBufferSize(): " + sampleRateInHz + " Hz is not a supported sample rate.");
return ERROR_BAD_VALUE; // 采样率支持:4KHz~192KHz
}
// 调用 JNI 方法,下面分析该函数
int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
if (size <= 0) {
loge("getMinBufferSize(): error querying hardware");
return ERROR;
}
else {
return size;
}
}
// android_media_AudioTrack.cpp
// ----------------------------------------------------------------------------
// returns the minimum required size for the successful creation of a streaming AudioTrack
// returns -1 if there was an error querying the hardware.
static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env, jobject thiz,
jint sampleRateInHertz, jint channelCount, jint audioFormat) {
size_t frameCount;
// 调用 AudioTrack::getMinFrameCount,这里不深究,到 native 层再分析
// 这个函数用于确定至少设置多少个 frame 才能保证声音正常播放,也就是最低帧数
const status_t status = AudioTrack::getMinFrameCount(&frameCount, AUDIO_STREAM_DEFAULT,
sampleRateInHertz);
if (status != NO_ERROR) {
ALOGE("AudioTrack::getMinFrameCount() for sample rate %d failed with status %d",
sampleRateInHertz, status);
return -1;
}
const audio_format_t format = audioFormatToNative(audioFormat);
if (audio_has_proportional_frames(format)) {
const size_t bytesPerSample = audio_bytes_per_sample(format);
return frameCount * channelCount * bytesPerSample; // PCM 数据最小缓冲区大小
} else {
return frameCount;
}
}
可见最小缓冲区的大小 = 最低帧数 * 声道数 * 采样深度,(采样深度以字节为单位)
到这里大家应该有所明悟了吧,在视频中,如果帧数过低,那么画面会有卡顿感,对于音频,道理也是一样的。最低帧数如何求得,我们到 native 层再解释。
关于 MediaPlayer、AudioTrack,更多更详细的 API 接口说明请参考 Android Developer:
MediaPlayer:https://developer.android.com/reference/android/media/MediaPlayer.html
AudioTrack:https://developer.android.com/reference/android/media/AudioTrack.html
2.2. AudioTrack Native API
AudioTrack Native API 四种数据传输模式:
Transfer Mode | Description |
---|---|
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 Type | Description |
---|---|
AUDIO_STREAM_VOICE_CALL | 电话语音 |
AUDIO_STREAM_SYSTEM | 系统声音 |
AUDIO_STREAM_RING | 铃声声音,如来电铃声、闹钟铃声等 |
AUDIO_STREAM_MUSIC | 音乐声音 |
AUDIO_STREAM_ALARM | 警告音 |
AUDIO_STREAM_NOTIFICATION | 通知音 |
AUDIO_STREAM_DTMF | DTMF 音(拨号盘按键音) |
AudioTrack Native API 输出标识:
AUDIO_OUTPUT_FLAG | Description |
---|---|
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。
一个 AudioTrack Natvie API 的测试例子(MODE_STATIC/TRANSFER_SHARED 模式),
代码文件位置:frameworks/base/media/tests/audiotests/shared_mem_test.cpp:
int AudioTrackTest::Test01() {
sp<MemoryDealer> heap;
sp<IMemory> iMem;
uint8_t* p;
short smpBuf[BUF_SZ];
long rate = 44100;
unsigned long phi;
unsigned long dPhi;
long amplitude;
long freq = 1237;
float f0;
f0 = pow(2., 32.) * freq / (float)rate;
dPhi = (unsigned long)f0;
amplitude = 1000;
phi = 0;
Generate(smpBuf, BUF_SZ, amplitude, phi, dPhi); // fill buffer
for (int i = 0; i < 1024; i++) {
// 分配一块匿名共享内存
heap = new MemoryDealer(1024*1024, "AudioTrack Heap Base");
iMem = heap->allocate(BUF_SZ*sizeof(short));
// 把音频数据拷贝到这块匿名共享内存上
p = static_cast<uint8_t*>(iMem->pointer());
memcpy(p, smpBuf, BUF_SZ*sizeof(short));
// 构造一个 AudioTrack 实例,该 AudioTrack 的数据方式是 MODE_STATIC
// 音频数据已经一次性拷贝到共享内存上了,不用再调用 track->write() 填充数据了
sp<AudioTrack> track = new AudioTrack(AUDIO_STREAM_MUSIC,// stream type
rate,
AUDIO_FORMAT_PCM_16_BIT,// word length, PCM
AUDIO_CHANNEL_OUT_MONO,
iMem);
// 检查 AudioTrack 实例是否构造成功饿了
status_t status = track->initCheck();
if(status != NO_ERROR) {
track.clear();
ALOGD("Failed for initCheck()");
return -1;
}
// start play
ALOGD("start");
track->start(); // 开始播放
usleep(20000);
ALOGD("stop");
track->stop(); // 停止播放
iMem.clear();
heap.clear();
usleep(20000);
}
return 0;
}
上个小节还存在一个问题:AudioTrack::getMinFrameCount() 如何计算最低帧数呢?