AudioTrack详解
AudioTrack架构图.png写在前面:AudioTrack是管理和播放单一音频资源的类。AudioTrack仅仅能播放已经解码的PCM流,用于PCM音频流的回放。
AudioTrack播放PCM音频
- 配置基本参数
- StreamType音频流类型:系统声音的音频流、音乐播放的音频流、用于通话的音频流、用于通知的音频流等。该参数的含义就是告诉系统,我现在想使用的是哪种类型的声音,这样系统就可以对应管理他们了。
Android 为什么要定义这么多的流类型?这与 Android 的音频管理策略有关,例如:- 音频流的音量管理,调节一个类型的音频流音量,不会影响到其他类型的音频流
- 根据流类型选择合适的输出设备;比如插着有线耳机期间,音乐声(STREAM_MUSIC)只会输出到有线耳机,而铃声(STREAM_RING)会同时输出到有线耳机和外放。
一个典型的场景:比如你在听music的时候接到电话,这个时候music播放肯定会停止,此时你只能听到电话,如果你调节音量的话,这个调节肯定只对电话起作用。当电话打完了,再回到music,你肯定不用再调节音量了。
应用开发者应该根据应用场景选择相应的流类型,以便系统为这道流选择合适的输出设备。
- Mode模式
- AudioTrack.MODE_STREAM: STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到AudioTrack中
- AudioTrack.MODE_STATIC:STATIC就是数据一次性交付给接收方。
- 采样率mSampleRateInHz:
- 音频量化位数mAudioFormat(只支持8bit和16bit两种。)
- 通道数目mChannelConfig:最多只支持双音道:
- StreamType音频流类型:系统声音的音频流、音乐播放的音频流、用于通话的音频流、用于通知的音频流等。该参数的含义就是告诉系统,我现在想使用的是哪种类型的声音,这样系统就可以对应管理他们了。
在audioParamCheck()中
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);
}
}
}
- buffer获取最小缓冲区大小
字面意思是返回最小数据缓冲区的大小,它是声音能正常播放的最低保障,从函数参数来看,返回值取决于采样率、采样深度、声道数这三个属性。MODE_STREAM 模式下,应用程序重点参考其返回值然后确定分配多大的数据缓冲区。如果数据缓冲区分配得过小,那么播放声音会频繁遭遇 underrun,underrun 是指生产者(AudioTrack)提供数据的速度跟不上消费者(AudioFlinger::PlaybackThread)消耗数据的速度,反映到现实的后果就是声音断续卡顿,严重影响听觉体验。
static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {
....
int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);
if (size <= 0) {
loge("getMinBufferSize(): error querying hardware");
return ERROR;
}
else {
return size;
}}
可以看到,mMinBufferSize取决于采样率、声道数和采样深度三个属性,其中源码缓冲区的大小的实现在native层中:
//frameworks/base/core/jni/android_media_AudioTrack.cpp
static jint android_media_AudioTrack_get_min_buff_size(JNIEnv*env, jobject thiz,
jint sampleRateInHertz,jint nbChannels, jint audioFormat) {
int frameCount = 0;
if(AudioTrack::getMinFrameCount(&frameCount, AUDIO_STREAM_DEFAULT,sampleRateInHertz) != NO_ERROR) {
return -1;
}
return frameCount * nbChannels * (audioFormat ==javaAudioTrackFields.PCM16 ? 2 : 1);
}
使用示例
int sampleRateInHz=32000;
int nb_channels=2;
//固定格式的音频码流
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
//声道布局
int channelConfig;
if(nb_channels == 1){
channelConfig = android.media.AudioFormat.CHANNEL_OUT_MONO;
}else if(nb_channels == 2){
channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
}else{
channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
}
int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
AudioTrack audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC,// 指定流的类型
sampleRateInHz,// 设置音频数据的採样率 32k,假设是44.1k就是44100
channelConfig,// 设置输出声道为双声道立体声,而CHANNEL_OUT_MONO类型是单声道
audioFormat,// 设置音频数据块是8位还是16位。这里设置为16位。
bufferSizeInBytes,//缓冲区大小
AudioTrack.MODE_STREAM // 设置模式类型,在这里设置为流类型,第二种MODE_STATIC貌似没有什么效果
);
audio.play(); // 启动音频设备。以下就能够真正開始音频数据的播放了
// 打开mp3文件,读取数据,解码等操作省略 ...
byte[] buffer = new buffer[4096];
int count;
while(true)
{
// 最关键的是将解码后的数据,从缓冲区写入到AudioTrack对象中
audio.write(buffer, 0, 4096);
if(文件结束) break;
}
//关闭并释放资源
audio.stop();
audio.release();
AudioTrack源码分析
创建AudioTrack对象
取得mMinBufferSize后,可以创建一个AudioTrack对象了:
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)throws IllegalArgumentException {
this(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mode, AudioManager.AUDIO_SESSION_ID_GENERATE);
}
这里是调用了该
public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes, int mode, int sessionId)
构造方法:
public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int mode, int sessionId)
throws IllegalArgumentException {
super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);
.....
// native initialization
int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/);
if (initResult != SUCCESS) {
loge("Error code "+initResult+" when initializing AudioTrack.");
return; // with mState == STATE_UNINITIALIZED
}
mSampleRate = sampleRate[0];
mSessionId = session[0];
if (mDataLoadMode == MODE_STATIC) {
mState = STATE_NO_STATIC_DATA;
} else {
mState = STATE_INITIALIZED;
}
baseRegisterPlayer();}
AudioTrack对象的创建过程涉及到
native_setup方法
:
//*frameworks/base/core/jni/android_media_AudioTrack.cpp
static int android_media_AudioTrack_native_setup(JNIEnv*env, jobject thiz, jobject weak_this,
jint streamType, jintsampleRateInHertz, jint javaChannelMask,
jint audioFormat, jintbuffSizeInBytes, jint memoryMode, jintArray jSession)
{
.....
sp<AudioTrack>lpTrack = new AudioTrack();
.....
AudioTrackJniStorage* lpJniStorage =new AudioTrackJniStorage();
//frameworks/av/media/libaudioclient/AudioTrack.cpp
status_t AudioTrack::createTrack_l()
{
status_t status;
bool callbackAdded = false;
const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
…
sp<IAudioTrack> track = audioFlinger->createTrack(input, output, &status);
}
//frameworks/av/media/libaudioclient/include/media/IAudioFlinger.h
virtual sp<IAudioTrack> createTrack(const CreateTrackInput& input,
CreateTrackOutput& output,
status_t *status) = 0;
每一个音频流对应着一个AudioTrack类的一个实例,每个AudioTrack会在创建时注册到 AudioFlinger中,由AudioFlinger把所有的AudioTrack进行混合(Mixer),然后输送到AudioHardware中进行播放,目前Android同时最多可以创建32个音频流,也就是说,Mixer最多会同时处理32个AudioTrack的数据流:
示意图.png
AudioPolicyService 与 AudioFlinger 是 Android 音频系统的两大基本服务。前者是音频系统策略的制定者,负责音频设备切换的策略抉择、音量调节策略等;后者是音频系统策略的执行者,负责音频流设备的管理及音频流数据的处理传输,所以 AudioFlinger 也被认为是 Android 音频系统的引擎。
- AudioFlinger服务启动
//frameworks/av/media/audioserver/main_audioserver.cpp
android::hardware::configureRpcThreadpool(4, false /*callerWillJoin*/);
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm = defaultServiceManager();
ALOGI("ServiceManager: %p", sm.get());
AudioFlinger::instantiate();
AudioPolicyService::instantiate();
//可见 audioserver 把音频相关的服务都加载了,包括 AudioFlinger、AudioPolicyService、RadioService、SoundTriggerHwService。
// AAudioService should only be used in OC-MR1 and later.
// And only enable the AAudioService if the system MMAP policy explicitly allows it.
// This prevents a client from misusing AAudioService when it is not supported.
aaudio_policy_t mmapPolicy = property_get_int32(AAUDIO_PROP_MMAP_POLICY,
AAUDIO_POLICY_NEVER);
if (mmapPolicy == AAUDIO_POLICY_AUTO || mmapPolicy == AAUDIO_POLICY_ALWAYS) {
AAudioService::instantiate();
}
SoundTriggerHwService::instantiate();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
AudioFlinger 服务启动后,其他进程可以通过 ServiceManager 来获取其代理对象 IAudioFlinger,通过 IAudioFlinger 可以向 AudioFlinger 发出各种服务请求,从而完成自己的音频业务。
音频流控制最常用的三个接口:
- AudioFlinger::PlaybackThread::Track::start:开始播放:把该 Track 置 ACTIVE 状态,然后添加到 mActiveTracks 向量中,最后调用AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情况有变
- AudioFlinger::PlaybackThread::Track::stop:停止播放:把该 Track 置 STOPPED 状态,最后调用 AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情况有变
- AudioFlinger::PlaybackThread::Track::pause:暂停播放:把该 Track 置 PAUSING 状态,最后调用 AudioFlinger::PlaybackThread::broadcast_l() 告知 PlaybackThread 情况有变
AudioFlinger 响应的服务请求主要有:
- 获取硬件设备的配置信息
- 音量调节
- 静音操作
- 音频模式切换
- 音频参数设置
- 输入输出流设备管理
- 音频流管理
- AudioTrack(写数据):AudioTrack 在 FIFO 中找到一块可用空间,把用户传入的音频数据写入到这块可用空间上,然后更新写位置(对于 AudioFinger 来说,意味 FIFO 上有更多的可读数据了);如果用户传入的数据量比可用空间要大,那么要把用户传入的数据拆分多次写入到 FIFO 中(AudioTrack 和 AudioFlinger 是不同的进程,AudioFlinger 同时也在不停地读取数据,所以 FIFO 可用空间是在不停变化的)
- AudioFlinger(读数据):AudioFlinger 在 FIFO 中找到一块可读数据块,把可读数据拷贝到目的缓冲区上,然后更新读位置(对于 AudioTrack 来说,意味着 FIFO 上有更多的可用空间了);如果FIFO 上可读数据量比预期的要小,那么要进行多次的读取,才能积累到预期的数据量(AudioTrack 和 AudioFlinger 是不同的进程,AudioTrack 同时也在不停地写入数据,所以 FIFO 可读的数据量是在不停变化的)
write方法的使用:
// 创建一个 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();
开始播放
play()->startImpl()->native_start()->android_media_AudioTrack_start.lpTrack->start()(JNI)->
mAudioTrack->start()(native)->通过IAudioTrack调用
virtual status_t start() = 0;
停止播放
停止播放音频数据,如果是STREAM模式,会等播放完最后写入buffer的数据才会停止。如果立即停止,要调用pause()方法,然后调用flush方法,会舍弃还没有播放的数据(flush()只在模式为STREAM下可用)。
stop()->native_stop()->
android_media_AudioTrack_stop中lpTrack->stop()(JNI)->
mAudioTrack->stop()->virtual void stop() = 0(IAudioTrack.h)
释放本地AudioTrack资源
release()->native_release()->android_media_AudioTrack_release
static void android_media_AudioTrack_release(JNIEnv *env, jobject thiz) {
sp<AudioTrack> lpTrack = setAudioTrack(env, thiz, 0);
if (lpTrack == NULL) {
return;
}
//ALOGV("deleting lpTrack: %x\n", (int)lpTrack);
// delete the JNI data
AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField(
thiz, javaAudioTrackFields.jniData);
// reset the native resources in the Java object so any attempt to access
// them after a call to release fails.
env->SetLongField(thiz, javaAudioTrackFields.jniData, 0);
if (pJniStorage) {
Mutex::Autolock l(sLock);
audiotrack_callback_cookie *lpCookie = &pJniStorage->mCallbackData;
//ALOGV("deleting pJniStorage: %x\n", (int)pJniStorage);
while (lpCookie->busy) {
if (lpCookie->cond.waitRelative(sLock,
milliseconds(CALLBACK_COND_WAIT_TIMEOUT_MS)) !=
NO_ERROR) {
break;
}
}
sAudioTrackCallBackCookies.remove(lpCookie);
// delete global refs created in native_setup
env->DeleteGlobalRef(lpCookie->audioTrack_class);
env->DeleteGlobalRef(lpCookie->audioTrack_ref);
delete pJniStorage;
}
}
返回当前的播放状态