AAudioStreamBuilder_setDeviceId(builder, AAUDIO_UNSPECIFIED); AAudioStreamBuilder_setFormat(builder, mFormat); AAudioStreamBuilder_setChannelCount(builder, mChannel); AAudioStreamBuilder_setSampleRate(builder, mSampleRate); // We request EXCLUSIVE mode since this will give us the lowest possible latency. // If EXCLUSIVE mode isn't available the builder will fall back to SHARED mode. AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_EXCLUSIVE); AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY); AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT); // AAudioStreamBuilder_setDataCallback(builder, aaudiodemo::dataCallback, this); // AAudioStreamBuilder_setErrorCallback(builder, aaudiodemo::errorCallback, this);
简述一下上面的函数,具体大家可以看源码里面的注释。
DeviceId—-指定物理音频设备,例如内建扬声器,麦克风,有线耳机等等。这里AAUDIO_UNSPECIFIED表示有AAudio根据上下文自行决定,如果是播放音频的话,一般它会选择扬声器。
format,channel,sample就不细说了,很容易理解,这里提一嘴的是,测试下来发现AAudio只支持单声道和双声道音频,5.1、7.1这种多声道音频,不支持播放。opensles也是,虽然提供了多声道的枚举值,但是当真正设置进去的时候,会提示说不支持。
SharingMode 分为:
AAUDIO_SHARING_MODE_EXCLUSIVE
和
AAUDIO_SHARING_MODE_SHARED
,因为AAudioStream是要和device绑定的,独占模式就是独占这个audio device,别的audio stream不能访问,独占模式下延迟会更小,但是要注意不用的时候及时关闭释放,不然别的流没法访问该audio device了。共享模式就是多个音频流可以共享一个audio device,官方说共享模式下,同一个audio device所有音频流可以实现混音,这个混音我还没试,后面抽空试试。到时候会在文末补充结论。
PerformanceMode:
AAUDIO_PERFORMANCE_MODE_NONE
默认模式,在延迟和省电间,自己平衡
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
更注重延迟
AAUDIO_PERFORMANCE_MODE_POWER_SAVING
更注重省电
Direction
AAUDIO_DIRECTION_INPUT
录音的时候用
AAUDIO_DIRECTION_OUTPUT
播放的时候用
最后两行被注释的回调,我们后面再说。
3.创建AAudioStream
aaudio_result_t AAudioStreamBuilder_openStream(AAudioStreamBuilder* builder, AAudioStream** stream)
通过openStream函数,获取到指定配置的AAudioStream对象,接着就可以拿着audio stream处理音频数据了。这里在成功创建玩AAudioStream之后,可以通过调用AAudioStream的相关getXXX函数,获取“
真正
” audio stream配置,之前通过audio stream builder设置的配置是我们的意向配置。
一般来说,意向配置就是最终配置,但是也会存在偏差,所以在调式阶段,最好再打印一下,真正的audio stream配置,帮助开发者获取确切信息。
AAudioStreamBuilder_setDeviceId()
AAudioStream_getDeviceId()
AAudioStreamBuilder_setDirection()
AAudioStream_getDirection()
AAudioStreamBuilder_setSharingMode()
AAudioStream_getSharingMode()
AAudioStreamBuilder_setSampleRate()
AAudioStream_getSampleRate()
AAudioStreamBuilder_setChannelCount()
AAudioStream_getChannelCount()
AAudioStreamBuilder_setFormat()
AAudioStream_getFormat()
AAudioStreamBuilder_setBufferCapacityInFrames()
AAudioStream_getBufferCapacityInFrames()
在创建完成AAudioStream后,需要释放AAudioStreamBuilder对象。
AAudioStreamBuilder_delete(builder);
4.操作AAudioStream
AAudioStream 的生命周期
Open
Started
Paused
Flushed
Stopped
Disconnected
Error
我们先说前五种状态,它们的状态变化可以用下面的流程图表示,虚线框表示瞬时状态,实线框表示稳定状态:
这个函数需要注意的是
inputState
和
nextState
参数,inputState参数代表当前的状态,可以通过
AAudioStream_getState
获取,
nextState是指状态发生变化后,新的状态值,这里新的状态值是不确定的,但有一点确定的是,一定跟inputState值不一样
。
aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_PAUSING; aaudio_stream_state_t nextState = AAUDIO_STREAM_STATE_UNINITIALIZED; int64_t timeoutNanos = 100 * AAUDIO_NANOS_PER_MILLISECOND; result = AAudioStream_requestPause(stream); result = AAudioStream_waitForStateChange(stream, inputState, &nextState, timeoutNanos);
例如上面的代码,waitForStateChange函数调用后,nextState就一定是Paused吗?不一定,有可能是其他的状态,比如:Disconnected,但是nextState一定不等于inputState。
不要在调用AAudioStream_close之后,调用waitForStateChange函数
当其他线程运行waitForStateChange函数时,不要调用AAudioStream_close函数
5.AAudioStream处理音频数据
当audio stream启动后,有两种方式来处理音频数据。
通过
AAudioStream_write
和
AAudioStream_read
函数向流里写数据和读数据,使用此方式需要自己创建线程控制数据读写。
通过callback的方式,使用此方式,会更高效,延迟更低,是官方推荐的方式
通过write、read函数直接读写数据
先看下函数原型:
aaudio_result_t AAudioStream_write(AAudioStream* stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds)
buffer: 音频原始数据
numFrames:请求处理的帧数,例如:16位双声道的数据,那么该值就是:bufferSize/(16/8)/2
timeoutNanoseconds: 最长阻塞时间,当值为0时,表示不阻塞
return value: 表示实际处理的帧数
知道函数的使用方式后,我们就可以在愉快的往里面填充数据了~
通过callback回调的方式处理数据
为什么官方说推荐使用callback方式呢,主要原因我认为有几点:
使用callback方式,aaudio内部会通过一个高优先级的优化后的专属线程处理回调,会避免因线程抢占等问题出现杂音。
使用callback方式,延迟更低。
使用直接向流里读写数据,需要自己维护一个播放线程,成本高,且有bug风险。而且如果要创建多个音频播放器,考虑出现多个线程,进而出现资源紧张的问题。
那这么说,是不是就一定得用callback方式了呢,也不是,经过作者的测试发现,关于延迟的指标,除非对延迟要求很高的产品,大多数情况下,使用直接读写数据到流的方式也是没问题的。所以选择具体方案还是要根据项目的真实情况决定。
具体怎么通过callback的方式处理数据呢?
还记得在
配置AAudioStream
这一节的时候,被注释的两行代码嘛。
// AAudioStreamBuilder_setDataCallback(builder, aaudiodemo::dataCallback, this);
// AAudioStreamBuilder_setErrorCallback(builder, aaudiodemo::errorCallback, this);
当使用callback模式处理音频数据的时候,就需要设置这两个函数。
typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)( AAudioStream *stream, ///上下文环境 void *userData, ///填充音频数据 void *audioData, ///需要填充多少帧数据,具体的换算方式:dataSize = numFrames*channels*(format == AAUDIO_FORMAT_PCM_I16 ? 2 : 1) int32_t numFrames);
该回调的返回值有两种:
AAUDIO_CALLBACK_RESULT_CONTINUE 表示继续播放
AAUDIO_CALLBACK_RESULT_STOP 表示停止播放,该回调不会再触发
因为dataCallback会频繁调用。所以最好不要在此回调中做一下耗时的,很重的任务。
errorCallback
当出现错误发生的时候或者断开连接的时候,此回调会被触发。常见的一个例子是:如果audio device disconnected时,会触发errorCallback,这个时候需要新开一个线程,重新创建AAudioStream。
typedef void (*AAudioStream_errorCallback)( AAudioStream *stream, void *userData, aaudio_result_t error);
在此回调中,下列函数不要直接调用,需要新开一个线程处理
AAudioStream_requestStop() AAudioStream_requestPause() AAudioStream_close() AAudioStream_waitForStateChange() AAudioStream_read() AAudioStream_write()
AAudioStream相关的getXXX函数可以直接调用,如:AAudioStream_get*()**
关于AAudio的使用demo,已上传至github,觉得不错的话,就给个star吧~ღ( ´・ᴗ・` )比心
[AAUDIODEMO] github.com/MRYangY/AAu…
6.销毁AAudioStream
AAudioStream_close(stream);
Extra Info
上面的章节属于必须内容,下面的为补充内容,按需了解。
underrun & overrun
underrun和overrun是音频数据的生产与消费节奏不匹配导致的。
underrun 是指在播放音频的时候,没有及时往audio stream写入的数据,系统没有可用的音频数据。
overrun 是指在录制音频的时候,没有及时的从audio stream读取数据,导致音频没人接收,就给丢了。
这两种情况都会导致音频出现问题。
AAudio是怎么解决这种问题呢?
利用动态调整缓冲区大小来降低延迟,避免underrun。涉及到的函数有:
///app一次处理音频的数据量-(帧数),返回的值是经过系统优化后的适应低延迟的值,可作为出现XRun时的StepSize int32_t AAudioStream_getFramesPerBurst(AAudioStream* stream); ///缓冲区大小设置,实现低延迟的,解决xrun的本质就是动态的调节这个size的大小 aaudio_result_t AAudioStream_setBufferSizeInFrames(AAudioStream* stream,int32_t numFrames); int32_t AAudioStream_getBufferSizeInFrames(AAudioStream* stream) ///通过该方法,可以知道是否发生underrun或者overrun,进而决定该如何调整缓冲区大小 int32_t AAudioStream_getXRunCount(AAudioStream* stream)
演示调用流程:
int32_t previousUnderrunCount = 0; int32_t framesPerBurst = AAudioStream_getFramesPerBurst(stream); ///通常在最开始的时候把framesPerBurst的值通过AAudioStream_setBufferSize设置给bufferSize,让它两一样会更容易达到低延迟 int32_t bufferSize = AAudioStream_getBufferSizeInFrames(stream); int32_t bufferCapacity = AAudioStream_getBufferCapacityInFrames(stream); while (run) { /// 向AAudioStream写数据 result = writeSomeData(); if (result < 0) break; // Are we getting underruns? if (bufferSize < bufferCapacity) { int32_t underrunCount = AAudioStream_getXRunCount(stream); if (underrunCount > previousUnderrunCount) { previousUnderrunCount = underrunCount; // Try increasing the buffer size by one burst bufferSize += framesPerBurst; bufferSize = AAudioStream_setBufferSize(stream, bufferSize); } } }
上面的代码很容易理解,就是刚开始会初始化一块小的buffer, 当发生underrun的时候,根据framesPerBurst不断的增大buffer,来实现低延迟。
Thread safety
AAudio的接口不是完全线程安全的。在使用的时候需要注意:
不要在多个线程并发调用AAudioStream_waitForStateChange()/read/write函数。
不要在一个线程关闭流,另一个线程读写流。
线程安全的有:
AAudio_convert*ToText()
AAudio_createStreamBuilder()
AAudioStream_get*()
系列函数,除了
AAudioStream_getTimestamp()
aaudio接口很简单,跟opensles的代码量相比,少多了。不过功能比opensles少一些。像是解码,控制音量等,aaudio都木有。大家看自己需求选择吧。
给个demo工程链接,配合着文章看看就懂了。
https://github.com/MRYangY/AAudioDemo
https://developer.android.com/ndk/guides/audio/aaudio/aaudio
原文链接: https://juejin.cn/post/7196636673692975162
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至[email protected] 举报,一经查实,本站将立刻删除。
赞
(0)
Netflix 将于 2025 年推出“Netflix House”沉浸式体验店
NVIDIA 虚拟形象解决方案改进客户服务并优化虚拟助手
Newsbridge 为其突破性的 MXT-1 AI 索引技术推出新的多语言功能
中国信通院联合虚拟现实与元宇宙产业联盟(XRMA)发布《元宇宙白皮书(2023年)》
5G 统一通信使移动通信竞争更加公平
IBC2023:2024 年值得关注的 5 项热门技术
SRS流媒体服务器基本流程
Meta 计划在去年以 10 亿美元收购 Kustomer 后出售它
如何使用FFmpeg实现无人直播带货
Zoom 在 Zoomtopia 2023 上推出改变游戏规则的创新