Android 之 audiotrack

一、成员

mReleased

mReleasedAudioTrack 中的一个关键状态变量,类型为 Modulo<uint32_t>(用于处理环形缓冲区的位置回绕)。它记录了 客户端已释放给音频服务器处理的音频帧数量,在流式传输(非静态缓冲区)模式下尤为重要。以下是其核心作用及变化点:

1. 初始化
  • 构造函数mReleased 初始化为 0

    cpp 复制代码
    mReleased = 0; // 初始无帧释放
2. 写入数据时更新
  • write() 方法
    当应用通过 write() 写入音频数据时,每次成功写入后调用 releaseBuffer(),增加 mReleased
3. 播放控制时的重置
  • stop()
    停止播放时,若非 offloaded 模式,重置 mReleased = 0(清空进度):

    cpp 复制代码
    if (!isOffloaded_l()) {
        mState = STATE_STOPPED;
        mReleased = 0;  // 重置释放计数器
    }
  • flush()
    清空缓冲区时,调用 flush_l() 重置状态:

    cpp 复制代码
    void flush_l() {
        mState = STATE_FLUSHED;
        mReleased = 0;  // 释放帧数归零
    }
4. 位置查询与同步
  • getPosition()
    通过 updateAndGetPosition_l() 计算服务端已消费的帧数时,会参考 mReleased

    cpp 复制代码
    Modulo<uint32_t> position = updateAndGetPosition_l(); // 依赖 mReleased
  • 位置恢复(如轨道重建)
    restoreTrack_l() 中恢复播放位置时,会从之前的 mReleased 恢复:

    cpp 复制代码
    if (mStaticProxy == 0) { // 流模式
        mPosition = mReleased; // 从释放位置恢复客户端位置
    }
5. 状态跟踪与调试
  • 日志输出
    stop() 时记录当前释放帧数(用于调试卡顿问题):

    cpp 复制代码
    ALOGD_IF(mSharedBuffer == nullptr,
            "%s(%d): called with %u frames delivered", 
            __func__, mPortId, mReleased.value());
6.关键场景总结
操作 mReleased 变化 说明
初始化 设为 0 初始状态
成功写入数据 (write()) 增加写入帧数 表示数据已提交给服务端
停止 (stop()) 非 offloaded 模式时重置为 0 播放停止,进度清零
清空 (flush()) 重置为 0 缓冲区清空,丢弃所有数据
轨道恢复 从之前保存的值恢复 服务崩溃后恢复播放位置
7.重点
  1. 进度同步
    mReleased 是客户端与服务端之间的"数据移交点",确保双方对已处理数据量达成一致。
  2. 流控基础
    结合 mFrameCount(缓冲区大小)和 mPosition(服务端消费位置),实现写入阻塞(避免溢出)。
  3. 状态恢复
    在音频服务重启或轨道重建时,通过 mReleased 恢复播放进度,保证连续性。

:在静态缓冲区模式(mSharedBuffer != 0)下,mReleased 作用较弱,因为数据一次性加载,无需流式更新。

二、成员函数

releaseBuffer()

是 Android AudioTrack 中管理共享内存缓冲区的核心方法,主要用于更新客户端写入位置通知服务端有新数据可读

一、核心作用
  1. 更新环形缓冲区写指针

    • MODE_STREAM 模式下,AudioTrackAudioFlinger 通过环形缓冲区传递数据。
    • 当应用通过 write() 写入数据后,需调用 releaseBuffer() 更新缓冲区的写指针位置mReleased 变量),标记已写入的数据范围。
  2. 触发服务端数据消费

    • releaseBuffer() 内部通过 mProxy->releaseBuffer() 修改共享内存控制块(audio_track_cblk_t)的状态,通知 AudioFlingerPlaybackThread 有新数据待读取。
二、内部工作流程

以下为 releaseBuffer() 的典型执行流程(以 Android 源码为例):

cpp 复制代码
void AudioTrack::releaseBuffer(const Buffer* audioBuffer) {
    // 1. 计算本次写入的帧数
    size_t stepCount = audioBuffer->size / mFrameSize;  
    
    // 2. 封装缓冲区信息
    Proxy::Buffer buffer;
    buffer.mFrameCount = stepCount;
    buffer.mRaw = audioBuffer->raw;
    
    // 3. 通过代理更新共享内存控制块
    mProxy->releaseBuffer(&buffer);
}
  1. 帧数计算
    stepCount = size / mFrameSize 确定本次写入的音频帧数量mFrameSize 由采样位宽和声道数决定)[citation:4][citation:6]。

  2. 代理操作

    • mProxyAudioTrackClientProxy(流模式)或 StaticAudioTrackClientProxy(静态模式)的实例。
    • 调用其 releaseBuffer() 会更新共享内存中的 u.mStreaming.mRear 指针 (环形缓冲区的写位置),并清除 UNDERRUN 标志[citation:8]。
  3. 服务端唤醒

    AudioFlinger 的播放线程因缓冲区无数据而阻塞,写指针更新会触发其唤醒并读取新数据[citation:2][citation:5]。

三、关键参数解析
参数 作用
audioBuffer->size 本次写入的字节数,需为帧大小的整数倍(否则截断处理)[citation:6]。
mFrameSize 单帧大小(字节)= 采样位宽(如16bit=2字节) × 声道数(如双声道=2)[citation:6]。
mProxy 代理对象,封装了共享内存操作(如指针更新、状态同步)[citation:8]。
四、使用场景与注意事项
  1. 流模式(MODE_STREAM

    • 必须与 obtainBuffer() 配对使用

      cpp 复制代码
      AudioTrack::Buffer buffer;
      obtainBuffer(&buffer, ...);  // 获取可写空间
      memcpy(buffer.i8, data, size); // 填充数据
      releaseBuffer(&buffer);       // 提交数据
    • 未调用 releaseBuffer() 会导致服务端无法读取新数据,引发播放卡顿或中断

  2. 静态模式(MODE_STATIC

    • 数据一次性写入,无需多次调用 `releaseBuffer()。
    • 仅在初始化时通过 write() 提交全部数据,后续直接 play() 即可。
  3. 线程安全

    • 共享内存控制块通过原子操作和内存屏障保证多线程安全,无需额外锁。
    • 若跨线程调用,需确保 AudioTrack 状态一致(如不在 stop() 后调用。
五、与 obtainBuffer() 的关系
  1. 数据流协作

获取空闲缓冲区 更新写指针 obtainBuffer write数据 releaseBuffer AudioFlinger消费数据

  • obtainBuffer() 获取可写空间,返回 Buffer 结构体(含起始地址和最大容量)。
  • releaseBuffer() 提交实际写入量(若写入量小于申请量,会调整剩余空间。
  1. 环形缓冲区管理
    共享内存控制块(audio_track_cblk_t)通过以下字段协调读写:
    • mFront:服务端读位置(由 AudioFlinger 更新)。
    • mRear:客户端写位置(由 releaseBuffer() 更新)。
    • mUnderrun:标志位,缓冲区无数据时置位。
六、总结
  • 核心作用releaseBuffer() 是流模式下数据提交的终点,通过更新环形缓冲区写指针驱动播放流程。
  • 性能影响 :延迟调用会导致播放卡顿;过早调用可能覆盖未读取的数据(需结合 obtainBuffer() 精确控制)。
  • 最佳实践
    write() 后立即调用 releaseBuffer()
    静态模式无需主动调用;
    避免跨线程操作时状态冲突。
相关推荐
恋猫de小郭1 小时前
Flutter 3.35 发布,快来看看有什么更新吧
android·前端·flutter
雨白4 小时前
加密、签名与编码
android
李新_5 小时前
【Android Bug Fix】UI不响应、异位异常排查
android·程序员
帅得不敢出门6 小时前
Android Framework定制长按电源键关机的窗口
android·java·framework
爬山算法7 小时前
MySQL(191) 如何优化MySQL的磁盘I/O?
android·数据库·mysql
深盾安全9 小时前
Kotlin Data Classes 快速上手
android
一枚小小程序员哈11 小时前
安卓\android程序开发之基于 Android 的校园报修系统的设计与实现
android
用户20187928316712 小时前
🌟 一场失败的加密舞会:SSL握手失败的奇幻冒险
android
tangweiguo0305198713 小时前
面向对象编程三剑客:Dart、Java 和 Kotlin 的核心区别
android·flutter·kotlin
幼稚园的山代王13 小时前
Kotlin数据类型
android·开发语言·kotlin