Android 之 audiotrack

发布于:2025-07-20 ⋅ 阅读:(22) ⋅ 点赞:(0)

一、成员

mReleased

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

1. 初始化
  • 构造函数mReleased 初始化为 0
    mReleased = 0; // 初始无帧释放
    
2. 写入数据时更新
  • write() 方法
    当应用通过 write() 写入音频数据时,每次成功写入后调用 releaseBuffer(),增加 mReleased
3. 播放控制时的重置
  • stop()
    停止播放时,若非 offloaded 模式,重置 mReleased = 0(清空进度):
    if (!isOffloaded_l()) {
        mState = STATE_STOPPED;
        mReleased = 0;  // 重置释放计数器
    }
    
  • flush()
    清空缓冲区时,调用 flush_l() 重置状态:
    void flush_l() {
        mState = STATE_FLUSHED;
        mReleased = 0;  // 释放帧数归零
    }
    
4. 位置查询与同步
  • getPosition()
    通过 updateAndGetPosition_l() 计算服务端已消费的帧数时,会参考 mReleased
    Modulo<uint32_t> position = updateAndGetPosition_l(); // 依赖 mReleased
    
  • 位置恢复(如轨道重建)
    restoreTrack_l() 中恢复播放位置时,会从之前的 mReleased 恢复:
    if (mStaticProxy == 0) { // 流模式
        mPosition = mReleased; // 从释放位置恢复客户端位置
    }
    
5. 状态跟踪与调试
  • 日志输出
    stop() 时记录当前释放帧数(用于调试卡顿问题):
    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 源码为例):

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() 配对使用
      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()
    静态模式无需主动调用;
    避免跨线程操作时状态冲突。

网站公告

今日签到

点亮在社区的每一天
去签到