【Android 高性能音频】AAudio 音频流 数据回调细节 ( 数据回调函数优先级 | 数据回调函数 | 采样率 | 采样数 | 缓冲区调整 | 线程不安全 )

2023-03-27 17:42:47 浏览数 (1)

文章目录

代码语言:txt复制
        - [I . 数据回调函数优先级](https://cloud.tencent.com/developer)
        - [II . 数据回调函数 相关内容](https://cloud.tencent.com/developer)
        - [III . 采样率 处理细节](https://cloud.tencent.com/developer)
        - [IV . 数据回调函数 每次 采样个数 numFrames](https://cloud.tencent.com/developer)
        - [V . 数据回调函数 缓冲区 ( AAudio 内部缓冲区 ) 调整](https://cloud.tencent.com/developer)
        - [VI . AAudio 音频系统的线程安全性分析](https://cloud.tencent.com/developer)
I . 数据回调函数优先级

1 . 普通线程操作 : 从普通线程中读写 AAudio 音频流的 音频数据 , 普通线程的优先级比较低 , 容易被抢占 , 或者遇到资源抖动 , 对需要连续性能的音频流操作造成干扰 , 出现卡顿 电流 等情况 ;

2 . 增加 AAudio 内部缓冲区 : 解决上述音频干扰的方案就是 增加 AAudio 音频流的内部缓冲区 , 这个缓冲区在上一篇博客中有详细介绍 , 该缓冲区是维护在音频设备 , 增加该缓冲区大小会提高整体 AAudio 系统采样播放的容错率 , 采样足够多 , 即使某一时刻出现了采样不足的情况 , 也能掩盖过去 , 不会出现卡顿电流等情况 , 让用户无法发现 , 但是这样音频的延迟会增大 ;

缓冲区相关细节 : 【Android 高性能音频】AAudio 音频流 缓冲区 简介 ( AAudio 音频流内部缓冲区 | 缓冲区帧容量 | 缓冲区帧大小 | 音频数据读写缓冲区 )

3 . 低延迟推荐方案 : AAudio 音频流 提供了一个 异步的 数据回调函数 AAudioStream_dataCallback , 该函数运行在优先级很高的线程中 , 该线程的资源不容易被抢占 , 可以提供一个较稳定的性能支持 ;

AAudio 音频流开启播放后 , 会自动回调该异步数据回调函数 , 在该函数中执行采样播放的过程 , 将采样数据写入缓冲区 , 这组数据消费完毕后 , 又会调用回调函数 , 申请新的数据 ; 数据回调函数基本工作流程 : 【Android 高性能音频】AAudio 音频流 PCM 采样 的 采样 缓冲 播放 的 连续机制 ( 数据回调机制 | 数据回调函数指针 | 实现数据回调函数 | 设置数据回调函数 )

II . 数据回调函数 相关内容

1 . 数据回调函数原型 : 由开发者实现 , 返回值 aaudio_data_callback_result_t 类型 , 参数 按照如下参数顺序实现 ;

代码语言:javascript复制
typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames);

2 . 数据回调函数注册 : 开发者实现了 AAudioStream_dataCallback 类型的数据回调函数后 , 需要 调用 AAudioStreamBuilder_setDataCallback 函数 设置给 AAudio 音频流 , 然后 AAudio 处于 Started 状态后 , 就会立刻第一次回调该数据回调函数 ;

代码语言:javascript复制
AAudioStreamBuilder_setDataCallback(builder, myCallback, myUserData);

数据回调函数基本工作流程 : 【Android 高性能音频】AAudio 音频流 PCM 采样 的 采样 缓冲 播放 的 连续机制 ( 数据回调机制 | 数据回调函数指针 | 实现数据回调函数 | 设置数据回调函数 )

III . 采样率 处理细节

1 . 采样率 : 每秒钟的采样个数 , 单位是 赫兹 ( Hz ) , 一般是 44100 Hz , 或 48000 Hz ;

在 Android 手机中 , 一般是 48000 Hz , 即每秒需要处理 48000 个采样 ;

2 . AAudio 中采样率处理 : 在 AAudio 音频流中 不建议设置采样率 , 一般使用默认采样率即可 , 每个音频设备都有一个最佳采样率 , 如果不设置 , 默认就按照该最佳采样率进行工作 , 如果设置错了 , 那么音频流在打开时就会失败 ;

3 . 采样率获取 : 如果不设置采样率 , 那么使用默认的采样率 , 该默认采样率通过调用 AAudioStream_getSampleRate () 方法获得 ;

4 . 采样率使用 : 获取采样率后 , 需要准备样本 , 这些样本的采样率需要转换成指定的采样率 , 才能向 AAudio 音频流中读写 , 如果采样率不对 , 播出来的声音就会出问题 ;

Android 的音频设备采样率一般是 48000 Hz , 需要将准备的读写缓冲区的音频样本数据采样率转为 48000Hz 后才能向 AAudio 音频流中读写 ;

IV . 数据回调函数 每次 采样个数 numFrames

1 . 采样个数 : 数据回调函数中有如下细节 , stream , userData , audioData 是指针类型 , 需要从外部传入到 函数中使用这些数据 , 但是唯独 numFrames 参数不是由用户指定的 , 每次的采样个数是由 AAudio 系统指定的 ;

代码语言:javascript复制
typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
        AAudioStream *stream,
        void *userData,
        void *audioData,
        int32_t numFrames);

2 . 采样个数实际测试值 : 这个值在不同系统 , 版本 , 硬件手机上可能不同 , 但是我测试的 三星 小米 华为等手机 , 该值是 192 , 意味着 每次采集 192 帧的数据 , 每帧的样本数是 通道数 ;

3 . 采集的样本缓冲区大小 :

  • ① 帧 : numFrames 单位是帧 ;
  • ② 样本数 : 每帧的样本数 等于 通道数 , 如果是单声道 每帧有 1 个样本 , 如果是立体声 , 每帧有 2 个样本 ;
  • ③ 每个样本字节数 : AAUDIO_FORMAT_PCM_I16 每个样本 2 字节 , AAUDIO_FORMAT_PCM_FLOAT 每个样本 4 字节 ;
  • ④ 立体声 16 位整形采样 : AAudio 中每个样本都有指定的个数 , 16 位整形样本 AAUDIO_FORMAT_PCM_I16 代表 16 位采样 , 每个样本有 两个字节 , 那么需要采集的样本缓冲区大小为
numFrames times 2 times 2

个样本 ;

V . 数据回调函数 缓冲区 ( AAudio 内部缓冲区 ) 调整

1 . 降低延迟 : 如果要求 AAudio 延迟尽可能低 , 需要将其内部缓冲区大小降到最低 ;

2 . 增加容错 : 缓冲区太小 , 容错空间也跟着变小 , 稍有风吹草动 , 就会出现卡顿 电流等播放异常的情况 , 这就需要增加缓冲区 ;

3 . 动态修改 : 上述两个需求相互冲突 , 就必须在二者之间找到平衡 , 在不出现播放异常的情况下 , 找到能够在当前性能下容错的最小缓冲区 , 该值要随着系统环境变化而动态修改 ;

4 . 调整缓冲区方法 : 在下面两篇博客中有调整缓冲区的细节 ;

  • 【Android 高性能音频】AAudio 音频流 缓冲区 简介 ( AAudio 音频流内部缓冲区 | 缓冲区帧容量 | 缓冲区帧大小 | 音频数据读写缓冲区 )
  • 【Android 高性能音频】AAudio 缓冲区控制 ( XRun | 欠载 UnderRun | 超限 OverRun | 获取缓冲区大小 | 设置缓冲区大小 )
VI . AAudio 音频系统的线程安全性分析

1 . 线程不安全 : AAudio 的 API 大部分都是线程不安全的 ;

2 . 线程不安全原理 : 线程安全就意味着存在线程同步机制 , 线程同步就涉及到了线程的阻塞等待机制 , 在 AAudio 系统中显然不能出现线程的阻塞 , 每秒钟回调几千次 , 一旦阻塞1毫秒 , 整个系统都无法正常运行 ; 此外线程阻塞后 , 其会被抢占甚至干扰 , 导致后续无法以高效率运行 ;

3 . 避免多线程操作 : 在调用 AAudio 时 , 尽量避免多线程操作 AAudio ;

  • ① 等待状态改变操作 : AAudioStream_waitForStateChange() 操作会造成线程阻塞 , 禁止在不同线程中调用该方法 ;
  • ② 读写操作 : 禁止在 不同线程中 读写同一个 AAudio 音频流 ;

4 . 线程安全的操作 :

  • ① 获取 AAudio 配置的操作 : 除 AAudioStream_getTimestamp 方法是线程不安全的之外 , 其它的 AAudioStream_get*() 类的方法 都是线程安全的 ;
  • ② 创建 AAudio 音频流构建器 : AAudio_createStreamBuilder() 方法是线程安全的 ;
  • ③ 输出 AAudio 文本 : AAudio_convert*ToText() 类的方法也是线程安全的 ;

0 人点赞