添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
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方式呢,主要原因我认为有几点:

  1. 使用callback方式,aaudio内部会通过一个高优先级的优化后的专属线程处理回调,会避免因线程抢占等问题出现杂音。
  2. 使用callback方式,延迟更低。
  3. 使用直接向流里读写数据,需要自己维护一个播放线程,成本高,且有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 上推出改变游戏规则的创新
  •