Android AAudio——C API创建AudioTrack(六)

虽然 AAudio 试图提供一种直接的硬件访问途径,但在某些场景下,如处理兼容性问题、使用系统服务(如 AudioFlinger)或者在某些设备上,使用 AudioTrack 可能是最有效或最合适的途径。这并不违背 AAudio 的初衷,因为它的目标是提供高性能的音频处理,而不是避免使用系统服务。在某些实现中,如果能通过 AudioTrack 实现这一目标,那么使用 AudioTrack 就是合理的策略。

所以,这里我们来看一下 builder_createStream() 方法中的另一个分支。

一、创建音频流

1、AudioStreamBuilder.cpp

源码位置:/frameworks/av/media/libaaudio/src/core/AudioStreamBuilder.cpp

builder_createStream

cpp 复制代码
static aaudio_result_t builder_createStream(aaudio_direction_t direction, aaudio_sharing_mode_t sharingMode,
            bool tryMMap, android::sp<AudioStream> &stream) {
    aaudio_result_t result = AAUDIO_OK;

    switch (direction) {
        case AAUDIO_DIRECTION_INPUT:
            if (tryMMap) {
                stream = new AudioStreamInternalCapture(AAudioBinderClient::getInstance(), false);
            } else {
                stream = new AudioStreamRecord();
            }
            break;
  
        case AAUDIO_DIRECTION_OUTPUT:
            if (tryMMap) {
                stream = new AudioStreamInternalPlay(AAudioBinderClient::getInstance(), false);
            } else {
                stream = new AudioStreamTrack();
            }
            break;
        ......
    }
    return result;
}

可以看到再不使用 MMAP 的情况下,这里分别创建了对应的输入(AudioStreamRecord)和输出(AudioStreamTrack)音频流,同时将新创建的实例赋值给传进来的 AudioStream 参数。

2、创建AudioTrack

根据前面的分析 audioStream->open 其实调用的就是 AudioStreamTrack 中的 open() 方法。

AudioStreamTrack

源码位置:/frameworks/av/media/libaaudio/src/legacy/AudioStreamTrack.cpp

cpp 复制代码
aaudio_result_t AudioStreamTrack::open(const AudioStreamBuilder& builder)
{
    aaudio_result_t result = AAUDIO_OK;
    // 这个方法前面也提到过,设置了AudioStream对象的属性
    result = AudioStream::open(builder);
    if (result != OK) {
        return result;
    }
    ......
    // 尝试创建一个AudioTrack
    // 如果未指定,则使用立体声。
    int32_t samplesPerFrame = (getSamplesPerFrame() == AAUDIO_UNSPECIFIED) ? 2 : getSamplesPerFrame();
    audio_channel_mask_t channelMask = samplesPerFrame <= 2 ? audio_channel_out_mask_from_count(samplesPerFrame) :
                audio_channel_mask_for_index_assignment_from_count(samplesPerFrame);

    // 根据AudioStreamBuilder设置参数。如果有回调,设置回调。
    ......

    // 创建AudioTrack对象
    mAudioTrack = new AudioTrack();
    // 设置相关属性
    mAudioTrack->set( ...... );

    // 方便在打开失败时析构函数记录它
    mAudioTrack->setCallerName(kCallerName);

    // Did we get a valid track?
    status_t status = mAudioTrack->initCheck();
    if (status != NO_ERROR) {
        releaseCloseFinal();
        ALOGE("open(), initCheck() returned %d", status);
        return AAudioConvert_androidToAAudioResult(status);
    }
    ......
    return AAUDIO_OK;
}

可以看到,这里主要功能就是初始化和创建一个 AudioTrack 对象,并将音频属性设置到 AudioTrack 中,到这里 AudioTrack 创建完成。

二、控制音频流

对于音频流的控制,这里同样包含启动音频流(requestStart)、暂停音频流(requestPause)、停止音频流(requestStop)和清空数据缓冲区(requestFlush)。

1、启动音频流

cpp 复制代码
aaudio_result_t AudioStreamTrack::requestStart() {
    ......
    // 在启动AudioTrack之前启用回调,以避免由于竞争条件而关闭
    mCallbackEnabled.store(true);
    aaudio_stream_state_t originalState = getState();
    // 在开始回调之前设置,以便在回调调用updateStateMachine()之前处于正确的状态。
    setState(AAUDIO_STREAM_STATE_STARTING);
    // 调用AudioTrack的start()方法
    err = mAudioTrack->start();
    if (err != OK) {
        // 关闭回调
        mCallbackEnabled.store(false);
        // 恢复状态
        setState(originalState);
        return AAudioConvert_androidToAAudioResult(err);
    }
    return AAUDIO_OK;
}

该方法主要用于启动一个音频轨道,确保了在启动音频轨道时的线程安全和状态一致性,即使在多线程环境下也能正确处理错误。最终调用的是 AudioTrack 的相关方法,这里就不做深入分析了。

2、暂停音频流

cpp 复制代码
aaudio_result_t AudioStreamTrack::requestPause() {
    ......
    // 设置暂停中状态
    setState(AAUDIO_STREAM_STATE_PAUSING);
    // 调用AudioTrack中的pause()方法
    mAudioTrack->pause();
    // 关闭回调
    mCallbackEnabled.store(false);
    // 获取暂停时的位置,保存在mPositionWhenPausing变量中,用于后续恢复播放时的定位
    status_t err = mAudioTrack->getPosition(&mPositionWhenPausing);
    if (err != OK) {
        return AAudioConvert_androidToAAudioResult(err);
    }
    // 检查是否有断开连接的请求
    return checkForDisconnectRequest(false);
}

这个方法用于暂停一个音频轨道,确保了在暂停音频轨道时的正确操作,包括更新状态、暂停操作、保存暂停位置以及处理可能的错误或断开连接请求。最后同样调用的是 AudioTrack 的相关方法。

3、停止音频流

cpp 复制代码
aaudio_result_t AudioStreamTrack::requestStop() {
    ......
    // 设置停止状态
    setState(AAUDIO_STREAM_STATE_STOPPING);
    // 同步读取和写入帧数
    mFramesRead.catchUpTo(getFramesWritten());
    // 更新时间戳位置,使其与写入的帧数保持同步。
    mTimestampPosition.catchUpTo(getFramesWritten());
    // 重置已读取的帧数,因为服务在停止时会重置读取位置
    mFramesRead.reset32(); 
    // 重置时间戳位置,与读取帧数的重置相对应
    mTimestampPosition.reset32();
    // 调用AudioTrack中的stop()方法
    mAudioTrack->stop();
    // 关闭回调
    mCallbackEnabled.store(false);
    // 检查是否有断开连接的请求
    return checkForDisconnectRequest(false);;
}

这个方法用于停止一个音频轨道,确保了在停止音频轨道时的正确操作,包括更新状态、同步帧数、重置计数器、停止操作、禁用回调以及处理可能的断开连接请求。最后调用了 AudioTrack 的 stop() 方法。

4、清空数据缓冲区

cpp 复制代码
aaudio_result_t AudioStreamTrack::requestFlush() {
    ......    
    // 设置刷新状态
    setState(AAUDIO_STREAM_STATE_FLUSHING);
    // 调整已读取帧数
    incrementFramesRead(getFramesWritten() - getFramesRead());
    // 执行清空操作
    mAudioTrack->flush();
    // 重置计数器
    mFramesRead.reset32(); 
    mTimestampPosition.reset32();
    return AAUDIO_OK;
}

该方法通常是为了解决音频同步问题或准备重新开始播放,确保了在清空音频流时的正确流程,包括更新状态、同步读写帧数、执行实际的清空操作以及重置相关计数器,为后续的操作做好准备。最后调用了 AudioTrack 的 flush() 方法。

同样,这里也包含了数据的读写操作,最终也是调用 AudioTrack 中的 对应方法,这里就不做介绍了。

三、音频流释放

1、AAudioAudio.cpp

源码位置:/frameworks/av/media/libaaudio/src/core/AAudioAudio.cpp

cpp 复制代码
// 释放音频流的引用计数,但不一定立即关闭流。
// 如果引用计数归零且流未处于活动状态,则流最终会被关闭和删除。
AAUDIO_API aaudio_result_t  AAudioStream_release(AAudioStream* stream) {
    ......
    if (audioStream != nullptr) {
        aaudio_stream_id_t id = audioStream->getId();
        ALOGD("%s(s#%u) called ---------------", __func__, id);
        result = audioStream->safeRelease();
        // saferrelease()只有在非法调用时才会失败,例如,从回调中调用。
        if (result != AAUDIO_OK) {
            ALOGW("%s(s#%u) failed. Release it from another thread.", __func__, id);
        }
        ALOGD("%s(s#%u) returned %d %s ---------", __func__, id, result, AAudio_convertResultToText(result));
    }
    return result;
}

// 不仅释放引用计数,还尝试关闭并清理音频流。
// 这通常意味着流将被完全关闭并释放所有相关资源。
AAUDIO_API aaudio_result_t  AAudioStream_close(AAudioStream* stream) {
    ......
    if (audioStream != nullptr) {
        aaudio_stream_id_t id = audioStream->getId();
        ALOGD("%s(s#%u) called ---------------", __func__, id);
        result = audioStream->safeReleaseClose();
        // 同样不能在回调中调用
        if (result != AAUDIO_OK) {
            ALOGW("%s(s#%u) failed. Close it from another thread.", __func__, id);
        } else {
            audioStream->unregisterPlayerBase();
            // 允许删除流
            AudioStreamBuilder::stopUsingStream(audioStream);
        }
        ALOGD("%s(s#%u) returned %d ---------", __func__, id, result);
    }
    return result;
}
  • AAudioStream_release 更倾向于释放引用,适合在你不再需要使用流但仍允许其他部分代码继续使用它的情况下调用。
  • AAudioStream_close 则是一个更加彻底的操作,除了释放引用外,还尝试关闭流并执行必要的清理工作,适用于希望完全结束流的使用场景。

两者都强调了不应该在回调中调用这些函数,因为这可能导致不稳定或崩溃,体现了对并发和资源管理的谨慎处理。

2、AudioStream.cpp

源码位置:/frameworks/av/media/libaaudio/src/core/AudioStream.cpp

cpp 复制代码
aaudio_result_t AudioStream::safeRelease() {
    // 当加入回调线程时,这些会在MMAP release()中暂时解锁。
    std::lock_guard<std::mutex> lock(mStreamLock);
    if (collidesWithCallback()) {
        ALOGE("%s cannot be called from a callback!", __func__);
        return AAUDIO_ERROR_INVALID_STATE;
    }
    if (getState() == AAUDIO_STREAM_STATE_CLOSING) { // already released?
        return AAUDIO_OK;
    }
    return release_l();
}

aaudio_result_t AudioStream::safeReleaseClose() {
    std::lock_guard<std::mutex> lock(mStreamLock);
    if (collidesWithCallback()) {
        ALOGE("%s cannot be called from a callback!", __func__);
        return AAUDIO_ERROR_INVALID_STATE;
    }
    releaseCloseFinal();
    return AAUDIO_OK;
}

这里分别调用了 release_l() 和 releaseCloseFinal() 方法,且这两个都在 AudioStream.h 中。

3、AudioStream.h

源码位置:/frameworks/av/media/libaaudio/src/core/AudioStream.h

cpp 复制代码
/**
 * 释放然后关闭流
 */
void releaseCloseFinal() {
    if (getState() != AAUDIO_STREAM_STATE_CLOSING) { // not already released?
        // Ignore result and keep closing.
        (void) release_l();
    }
    close_l();
}

/**
 * 从open()调用中释放任何硬件或系统资源
 */
virtual aaudio_result_t release_l() {
    setState(AAUDIO_STREAM_STATE_CLOSING);
    return AAUDIO_OK;
}

/**
 * 释放所有尚未被release_l()释放的资源
 */
virtual void close_l() {
    // 释放流将把状态设置为CLOSING
    assert(getState() == AAUDIO_STREAM_STATE_CLOSING);
    // setState()防止从CLOSED状态转换到CLOSED状态以外的任何状态
    // 状态由析构函数检查
    setState(AAUDIO_STREAM_STATE_CLOSED);
}

这里对数据进行了最终的清理。当然对于 AudioStream 的子类可能会对方法进行从写,以完成对数据的专项清理工作。

相关推荐
Dnelic-1 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen3 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年11 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿13 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神14 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛14 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法15 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter16 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快17 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl17 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5