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

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

文章目录

I . 数据回调函数优先级

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

III . 采样率 处理细节

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

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

VI . AAudio 音频系统的线程安全性分析



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 类型 , 参数 按照如下参数顺序实现 ;


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 状态后 , 就会立刻第一次回调该数据回调函数 ;


AAudioStreamBuilder_setDataCallback(builder, myCallback, myUserData);

1

数据回调函数基本工作流程 : 【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 系统指定的 ;


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 位采样 , 每个样本有 两个字节 , 那么需要采集的样本缓冲区大小为 n u m F r a m e s × 2 × 2 numFrames \times 2 \times 2numFrames×2×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() 类的方法也是线程安全的 ;


目录
相关文章
|
4月前
|
消息中间件 监控 安全
服务Down机了,线程池中的数据如何保证不丢失?
在分布式系统与高并发应用开发中,服务的稳定性和数据的持久性是两个至关重要的考量点。当服务遭遇Down机时,如何确保线程池中处理的数据不丢失,是每一位开发者都需要深入思考的问题。以下,我将从几个关键方面分享如何在这种情况下保障数据的安全与完整性。
80 2
|
2月前
|
消息中间件 监控 Java
线程池关闭时未完成的任务如何保证数据的一致性?
保证线程池关闭时未完成任务的数据一致性需要综合运用多种方法和机制。通过备份与恢复、事务管理、任务状态记录与恢复、数据同步与协调、错误处理与补偿、监控与预警等手段的结合,以及结合具体业务场景进行分析和制定策略,能够最大程度地确保数据的一致性,保障系统的稳定运行和业务的顺利开展。同时,不断地优化和改进这些方法和机制,也是提高系统性能和可靠性的重要途径。
124 62
|
9天前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
9天前
|
Java 调度
【JavaEE】——线程的安全问题和解决方式
【JavaEE】——线程的安全问题和解决方式。为什么多线程运行会有安全问题,解决线程安全问题的思路,synchronized关键字的运用,加锁机制,“锁竞争”,几个变式
|
3月前
|
缓存 安全 Java
使用 Java 内存模型解决多线程中的数据竞争问题
【10月更文挑战第11天】在 Java 多线程编程中,数据竞争是一个常见问题。通过使用 `synchronized` 关键字、`volatile` 关键字、原子类、显式锁、避免共享可变数据、合理设计数据结构、遵循线程安全原则和使用线程池等方法,可以有效解决数据竞争问题,确保程序的正确性和稳定性。
65 2
|
4月前
|
消息中间件 存储 Java
服务重启了,如何保证线程池中的数据不丢失?
【8月更文挑战第30天】为确保服务重启时线程池数据不丢失,可采用数据持久化(如数据库或文件存储)、使用可靠的任务队列(如消息队列或分布式任务队列系统)、状态监测与恢复机制,以及分布式锁等方式。这些方法能有效提高系统稳定性和可靠性,需根据具体需求选择合适方案并进行测试优化。
268 5
|
5月前
|
Java
【Java集合类面试十二】、HashMap为什么线程不安全?
HashMap在并发环境下执行put操作可能导致循环链表的形成,进而引起死循环,因而它是线程不安全的。
|
5月前
|
安全 算法 Java
【Java集合类面试二】、 Java中的容器,线程安全和线程不安全的分别有哪些?
这篇文章讨论了Java集合类的线程安全性,列举了线程不安全的集合类(如HashSet、ArrayList、HashMap)和线程安全的集合类(如Vector、Hashtable),同时介绍了Java 5之后提供的java.util.concurrent包中的高效并发集合类,如ConcurrentHashMap和CopyOnWriteArrayList。
【Java集合类面试二】、 Java中的容器,线程安全和线程不安全的分别有哪些?
|
5月前
处理串口线程数据的函数
【8月更文挑战第4天】处理串口线程数据的函数。
34 4
|
5月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
74 1