Android Audio基础(20)——AudioTrack音频控制

AudioTrack 是应用用于播放音频数据的类,可以使用 set 方法设置音频参数,然后使用 start 来启动播放。而 pause 和 stop 则是用于控制播放过程的方法。不管 APP 使用什么接口播放音频文件,最终都是由 AudioTrack 实现。

一、源码分析

1、主要方法

构造函数:创建 AudioTrack 时调用。

play():播放音频文件。

pause():暂停播放音频文件。

stop():停止播放音频文件。

flush():释放音频缓存。

finalize():系统垃圾回收回调。

release():释放 AudioTrack 资源。

下面分析以上方法。

2、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 方法依赖于系统自动垃圾回收的时机,不太可靠。

3、android_media_AudioTrack.cpp

源码位置:/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 的代码。

4、AudioTrack.cpp

源码位置: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的使用方法和性质可以参考官方文档!后续有空再补充。

你可能感兴趣的:(音频,android,音视频,驱动开发)