AudioTrack 是应用用于播放音频数据的类,可以使用 set 方法设置音频参数,然后使用 start 来启动播放。而 pause 和 stop 则是用于控制播放过程的方法。不管 APP 使用什么接口播放音频文件,最终都是由 AudioTrack 实现。
构造函数:创建 AudioTrack 时调用。
play():播放音频文件。
pause():暂停播放音频文件。
stop():停止播放音频文件。
flush():释放音频缓存。
finalize():系统垃圾回收回调。
release():释放 AudioTrack 资源。
下面分析以上方法。
源码位置:/frameworks/base/media/java/android/media/AudioTrack.java
构造函数
private AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
int mode, int sessionId, boolean offload, int encapsulationMode,
@Nullable TunerConfiguration tunerConfiguration) throws IllegalArgumentException {
……
// 本机的初始化
int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,
mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/,
offload, encapsulationMode, tunerConfiguration,
getCurrentOpPackageName());
if (initResult != SUCCESS) {
loge("Error code "+initResult+" when initializing AudioTrack.");
return; // with mState == STATE_UNINITIALIZED
}
……
}
这里我们只关注调用的 native 方法和创建失败时的相关 Log。
播放音频
/**
* 开始播放AudioTrack
* 如果track的创建模式MODE_STATIC,必须先调用一次方法write()。
* 如果模式是MODE_STREAM,你可以在调用play()之前选择数据路径,通过写入bufferSizeInBytes(从构造函数)。
* 如果不先调用write(),或者调用write()但数据量不足,则在play()时轨道将处于欠运行状态。在这种情况下,播放将不会真正开始播放,直到track被填充到特定于设备的最小数据量。在调用stop()后恢复音频播放时,将路径填充到最小数据量的要求也适用。
* 类似地,由于没有及时调用write()并获取足够的数据,导致轨道运行不足,缓冲区将需要再次填充。
* 为了可移植性,应用程序应该通过写入数据来使数据路径达到允许的最大值,直到write()方法返回一个短的传输计数。
* 这允许play()立即开始,并减少运行不足的机会
*/
public void play() throws IllegalStateException {
if (mState != STATE_INITIALIZED) {
throw new IllegalStateException("play() called on uninitialized AudioTrack.");
}
final int delay = getStartDelayMs();
if (delay == 0) {
startImpl();
} else {
new Thread() {
public void run() {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
baseSetStartDelayMs(0);
try {
startImpl();
} catch (IllegalStateException e) {
}
}
}.start();
}
}
这里判断了是否延迟播放,但最终都调用了 startImpl() 方法。
private void startImpl() {
synchronized(mPlayStateLock) {
baseStart();
native_start();
if (mPlayState == PLAYSTATE_PAUSED_STOPPING) {
mPlayState = PLAYSTATE_STOPPING;
} else {
mPlayState = PLAYSTATE_PLAYING;
mOffloadEosPending = false;
}
}
}
暂停播放
/**
* 暂停音频数据的播放,未回放的数据不会被丢弃。
* 随后对play的调用将继续播放这些数据。
* 调用flush()丢弃这些数据。
*/
public void pause() throws IllegalStateException {
if (mState != STATE_INITIALIZED) {
throw new IllegalStateException("pause() called on uninitialized AudioTrack.");
}
// pause playback
synchronized(mPlayStateLock) {
native_pause();
basePause();
if (mPlayState == PLAYSTATE_STOPPING) {
mPlayState = PLAYSTATE_PAUSED_STOPPING;
} else {
mPlayState = PLAYSTATE_PAUSED;
}
}
}
调用 pause 方法可以暂停播放,再次调用 play 方法可以从暂停的位置继续播放。
停止播放
/**
* 停止播放音频数据
* 当在MODE_STREAM模式下创建的实例上使用时,音频将在播放完写入缓冲区数据后停止。
* 对于立即停止,使用pause(),然后使用flush()来丢弃尚未播放的音频数据。
*/
public void stop() throws IllegalStateException {
if (mState != STATE_INITIALIZED) {
throw new IllegalStateException("stop() called on uninitialized AudioTrack.");
}
// stop playing
synchronized(mPlayStateLock) {
native_stop();
baseStop();
if (mOffloaded && mPlayState != PLAYSTATE_PAUSED_STOPPING) {
mPlayState = PLAYSTATE_STOPPING;
} else {
mPlayState = PLAYSTATE_STOPPED;
mOffloadEosPending = false;
mAvSyncHeader = null;
mAvSyncBytesRemaining = 0;
mPlayStateLock.notify();
}
}
}
在调用 stop 方法后,AudioTrack 会重置播放指针到起始位置,但是不会清除之前设置的音频数据,在调用 start 方法时会从重置后的起始位置开始播放之前设置的音频数据。
因此,如果需要重新设置音频数据,要在调用 stop 方法后调用 release 方法释放 AudioTrack 资源,然后调用 set 方法设置音频数据。否则可以直接调用 start 方法继续播放之前设置的音频数据。
清理数据
对于下面的三个方法都是用于释放 AudioTrack 资源的方法,不同点在于释放资源的时机和方式。
flush
/**
* 清空当前排队等待播放的音频数据。任何已写入但尚未播放的数据将被丢弃。
* 如果没有停止或暂停,或者如果曲目的创建模式不是MODE_STREAM,则不进行操作。
*/
public void flush() {
if (mState == STATE_INITIALIZED) {
// 刷新本机层中的数据
native_flush();
mAvSyncHeader = null;
mAvSyncBytesRemaining = 0;
}
}
flush 方法是用于清空 AudioTrack 中的播放缓存,等待缓存中的数据播放完毕后便可以停止播放。这个方法并不会释放 AudioTrack 资源,而是尝试清空播放缓存的数据。不过,由于这个方法会清空缓存,因此可以用于中断正在播放中的音频,并提前停止播放。
finalize
@Override
protected void finalize() {
baseRelease();
native_finalize();
}
finalize 方法是 Java 中的一个方法,它在对象被垃圾回收时自动调用,这个方法可以用于释放未被释放的资源。在 AudioTrack 的实现中,finalize 方法会自动调用 release 方法来释放 AudioTrack 资源。
/**
* 释放原生AudioTrack资源
*/
public void release() {
synchronized (mStreamEventCbLock){
endStreamEventHandling();
}
// 即使native_release()停止了原生AudioTrack,我们也需要停止AudioTrack的子类。
try {
stop();
} catch(IllegalStateException ise) {
// 不要引发异常,我们正在释放资源
}
baseRelease();
native_release();
synchronized (mPlayStateLock) {
mState = STATE_UNINITIALIZED;
mPlayState = PLAYSTATE_STOPPED;
mPlayStateLock.notify();
}
}
release 方法是最终释放 AudioTrack 资源的方法,释放调用该方法的 AudioTrack 对象所占用的所有资源,包括缓冲区、音频硬件等。
一般来说,使用 AudioTrack 时都应该在不再使用时调用 release方法来释放资源。使用 flush 方法可以在播放音频时,快速中断播放并清空数据,而使用 finalize 方法依赖于系统自动垃圾回收的时机,不太可靠。
源码位置:/frameworks/base/core/jni/android_media_AudioTrack.cpp
对于上面的方法最终都是调用到对应的 native 层方法:native_setup、native_start、native_pause、native_stop、native_flush、native_finalize 和 native_release。
//创建AudioTrack
static const JNINativeMethod gMethods[] = {
{"native_start", "()V", (void *)android_media_AudioTrack_start},
{"native_stop", "()V", (void *)android_media_AudioTrack_stop},
{"native_pause", "()V", (void *)android_media_AudioTrack_pause},
{"native_flush", "()V", (void *)android_media_AudioTrack_flush},
{"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;[IIIIII[IJZILjava/lang/Object;Ljava/lang/String;)I", (void *)android_media_AudioTrack_setup},
{"native_finalize", "()V", (void *)android_media_AudioTrack_finalize},
{"native_release", "()V", (void *)android_media_AudioTrack_release},
};
static jint android_media_AudioTrack_setup(……) {
……
// if we pass in an existing *Native* AudioTrack, we don't need to create/initialize one.
sp<AudioTrack> lpTrack;
if (nativeAudioTrack == 0) {
……
// 初始化原生AudioTrack对象
status_t status = NO_ERROR;
switch (memoryMode) {
case MODE_STREAM:
status = lpTrack->set(AUDIO_STREAM_DEFAULT, // 流类型
sampleRateInHertz, format, // word length, PCM
nativeChannelMask, offload ? 0 : frameCount,
offload ? AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD : AUDIO_OUTPUT_FLAG_NONE,
audioCallback, &(lpJniStorage->mCallbackData), // 回调数据(用户)
0, // notificationFrames == 0 因为没有使用EVENT_MORE_DATA来反馈AudioTrack
0, // 共享mem
true, // 线程可以调用Java
sessionId, // audio session ID
offload ? AudioTrack::TRANSFER_SYNC_NOTIF_CALLBACK : AudioTrack::TRANSFER_SYNC,
offload ? &offloadInfo : NULL, -1, -1, // 默认uid
paa.get());
break;
case MODE_STATIC:
// AudioTrack正在使用共享内存
……
status = lpTrack->set(AUDIO_STREAM_DEFAULT, // 流类型
sampleRateInHertz, format, // word length, PCM
nativeChannelMask, frameCount, AUDIO_OUTPUT_FLAG_NONE,
audioCallback, &(lpJniStorage->mCallbackData), // 回调数据
0, // notificationFrames == 0 因为没有使用EVENT_MORE_DATA来反馈AudioTrack
lpJniStorage->mMemBase, // 共享mem
true, // 线程可以调用Java
sessionId, // audio session ID
AudioTrack::TRANSFER_SHARED,
NULL, -1, -1, // 默认uid
paa.get());
break;
……
} else { // end if (nativeAudioTrack == 0)
……
}
……
}
//播放AudioTrack
static void android_media_AudioTrack_start(JNIEnv *env, jobject thiz)
{
sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
……
lpTrack->start();
}
//暂停播放AudioTrack
static void android_media_AudioTrack_pause(JNIEnv *env, jobject thiz)
{
sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
……
lpTrack->pause();
}
//停止播放AudioTrack
static void android_media_AudioTrack_stop(JNIEnv *env, jobject thiz)
{
sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
……
lpTrack->stop();
}
//清理AudioTrack缓存
static void android_media_AudioTrack_flush(JNIEnv *env, jobject thiz)
{
sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
……
lpTrack->flush();
}
//系统内存回收
static void android_media_AudioTrack_finalize(JNIEnv *env, jobject thiz) {
android_media_AudioTrack_release(env, thiz);
}
//释放AudioTrack数据
#define CALLBACK_COND_WAIT_TIMEOUT_MS 1000
static void android_media_AudioTrack_release(JNIEnv *env, jobject thiz) {
sp<AudioTrack> lpTrack = setAudioTrack(env, thiz, 0);
……
// 删除JNI数据
AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField(thiz, javaAudioTrackFields.jniData);
// 重置Java对象中的本机资源,以便在调用release后访问它们的任何尝试都失败。
env->SetLongField(thiz, javaAudioTrackFields.jniData, 0);
if (pJniStorage) {
Mutex::Autolock l(sLock);
audiotrack_callback_cookie *lpCookie = &pJniStorage->mCallbackData;
while (lpCookie->busy) {
if (lpCookie->cond.waitRelative(sLock, milliseconds(CALLBACK_COND_WAIT_TIMEOUT_MS)) != NO_ERROR) {
break;
}
}
sAudioTrackCallBackCookies.remove(lpCookie);
// 删除在native_setup中创建的全局引用
env->DeleteGlobalRef(lpCookie->audioTrack_class);
env->DeleteGlobalRef(lpCookie->audioTrack_ref);
delete pJniStorage;
}
}
可以看到系统内存的回收也是调用释放 AudioTrack 的代码。
源码位置:android/frameworks/av/media/libaudioclient/AudioTrack.cpp
接下来代码调用到 AudioTrack.cpp 中。我们来看看对应方法:set、start、pause、stop 和 flush。
status_t AudioTrack::set(……)
{
……
//Android 9.0
ALOGV("set(): streamType %d, sampleRate %u, format %#x, channelMask %#x, frameCount %zu, "
"flags #%x, notificationFrames %d, sessionId %d, transferType %d, uid %d, pid %d",
streamType, sampleRate, format, channelMask, frameCount, flags, notificationFrames,
sessionId, transferType, uid, pid);
//Android 11
ALOGV("%s(): streamType %d, sampleRate %u, format %#x, channelMask %#x, frameCount %zu, "
"flags #%x, notificationFrames %d, sessionId %d, transferType %d, uid %d, pid %d",
__func__,
streamType, sampleRate, format, channelMask, frameCount, flags, notificationFrames,
sessionId, transferType, uid, pid);
……
}
status_t AudioTrack::start()
{
//Android 11
ALOGV("%s(%d): prior state:%s", __func__, mPortId, stateToString(mState));
}
void AudioTrack::pause()
{
//Android 11
ALOGV("%s(%d): prior state:%s", __func__, mPortId, stateToString(mState));
}
void AudioTrack::stop()
{
//Android 11
ALOGV("%s(%d): prior state:%s", __func__, mPortId, stateToString(mState));
}
void AudioTrack::flush()
{
//Android 11
ALOGV("%s(%d): prior state:%s", __func__, mPortId, stateToString(mState));
}
通过上面的代码,我们可以看到 AudioTrack.cpp 打印的相关 Log 来分析 AudioTrack 的执行过程,从而分析出现的问题。
Track创建
AudioTrack: set(): streamType -1, sampleRate 16000, format 0x1, channelMask 0x1, frameCount 1552, flags #0, notificationFrames 0, sessionId 0, transferType 3, uid -1, pid -1
streamType:音频流类型。-1表示未指定特定的流类型。
sampleRate:音频采样率,即每秒钟采集的样本数。这里是16000。
format:音频格式。0x1代表PCM 16-bit格式。
channelMask:音频通道配置。0x1表示单声道。
frameCount:音频缓冲区大小,以frame(帧)为单位。这里是1552。
flags:其他标志位,这里是#0,表示没有附加标志。
notificationFrames:通知间隔的帧数。这里是0,表示没有通知间隔。
sessionId:在Android音频系统中使用的会话ID。这里是0,表示没有指定特定的会话ID。
transferType:音频传输类型。3代表STREAM类型。
uid和pid:用户ID和进程ID。-1表示未指定。
对于不同的 Android 版本打印的信息可能有所不同,也可以自定义打印自己需要的内容,更直观的打印对因信息。
Track操作
开始/暂停/停止播放音源时,分别调用 AudioTrack.cpp 中的 start()、pause() 和 stop() 方法,打印如下 Log,对于 Android 9.0 没有打印 Log,Android 11 的 Log 也不够直观,我们可以进行如下修改:
ALOGI(“start(), streamType=%d”, mStreamType);
ALOGI(“pause(), streamType=%d”, mStreamType);
ALOGI(“stop(), streamType=%d”, mStreamType);
//开始播放
I AudioTrack: start(), usage=1
//暂停播放
I AudioTrack: pause(), usage=1
//停止播放
I AudioTrack: stop(), usage=1
这样可以直观的通过 usage 用来识别播放的音源类型。
audiotrack的使用方法和性质可以参考官方文档!后续有空再补充。