在Unity上面做音游,当在移动端实机运行起来,会发现,音频的发出会有一定的延迟,无论是长音效还是短音效,Unity内置的Audio内部使用的是FMOD,有以下手段改善

通过设置稍微改善其延迟的问题

Edit → Project Settings → Audio → 设置DSP Buffer size为Best latency(设置 dsp 缓冲区大小以优化延迟或性能,设置一个不合适的值会导致安卓设备的电流音)

音频文件的Load Type为Decompress On Load(让音频提前读取到缓存中)

让音频文件越小越好

代码来获取准确的音轨采样时间

糟糕的方式

AudioSource audioSource;

//100ms延迟

float GetTrackTime()

{

return audioSource.time;

}

好的方式

//20ms延迟

float GetTrackTime()

{

return 1f * audioSource.timeSamples / audioSource.clip.frequency;

}

更好的方式

double trackStartTime;

void StartMusic()

{

trackStartTime = AudioSettings.dspTime + 1;

audioSource.PlayScheduled(trackStartTime);

}

double GetTrackTime()

{

return AudioSettings.dspTime - trackStartTime;

}

最好的方式

Stopwatch stopwatch = new Stopwatch();

void StartMusic()

{

audioSource.Play();

stopwatch.Start();

}

double GetTrackTime()

{

return stopwatch.ElapsedMilliseconds/1000f;

}

//timeSample的Get方法受限与音频异步的问题是有20ms的延迟的,但是Set方法几乎没有延迟

void SetTrackTime(float time)

{

audioSource.timeSample = (int)(time * audioSource.clip.frequency);

}

但是结果往往还是无法让人满意,经过测试,iOS大多数设备的延迟降到10ms以内,误差范围外,相当于没有了,但是安卓设备的延迟根据机型的不同有不同的延迟,大约在100ms~500ms:

image.png

硬件和软件的原因造成了Android设备的延迟偏高以及不统一

音频播放的不同阶段

模拟音频输入

可能有几种不同的模拟组件,例如内置麦克风的前置放大器。在这种情况下,这些模拟组件可被视为“零延迟”,因为它们的真实延迟通常低于1 ms。

延迟:0

模数转换(ADC)

音频芯片以预定义的间隔测量输入的音频流,并将每个测量值转换为数字。此预定义间隔称为采样率,以Hz为单位。我们的移动音频普查和延迟测试应用程序显示,48000 Hz是Android和iOS设备上大多数音频芯片的原生采样率,这意味着音频流每秒采样48000次。

由于ADC实现通常在内部包含过采样滤波器,因此经验法则是将ADC步长归因于1 ms延迟。

现在音频流已经数字化,从这一点开始,音频流现在是数字音频。数字音频几乎不会一个接一个地传播,而是以块状称,称为“缓冲区”或“周期”。

延迟:1毫秒

总线从音频芯片传输到音频驱动器

音频芯片有几个任务。它处理ADC和DAC,在多个输入和输出之间切换或混合,应用音量等。它还将离散数字音频样本“分组”到缓冲区中,并处理这些缓冲区到操作系统的传输。

音频芯片通过总线连接到CPU,例如USB,PCI,Firewire等。每个总线都有自己的传输延迟,具体取决于其内部缓冲区大小和缓冲区计数。此处的延迟通常为1 ms(内部系统总线上的音频芯片)至6 ms(具有保守USB总线设置的USB声卡)。

延迟:1-6毫秒

音频驱动程序(ALSA,OSS等)

音频驱动器使用音频芯片的本机采样率(大多数情况下为48000 Hz)以“总线缓冲区大小”步骤将输入音频接收到环形缓冲器中。

此环形缓冲区在平滑总线传输抖动(“粗糙度”)中起着重要作用,并将总线传输缓冲区大小“连接”到操作系统音频堆栈的缓冲区大小。从环形缓冲区消耗数据发生在操作系统音频堆栈的缓冲区大小中,因此它自然会增加一些延迟。

Android运行在Linux的“顶部”,大多数Android设备使用最流行的Linux音频驱动程序系统ALSA(高级Linux声音架构)。ALSA像这样处理环形缓冲区:

音频以“周期大小”步骤从环形缓冲器中消耗。

环形缓冲区的大小是“周期大小”的倍数。

例如:

周期大小= 480个样本。

期间数= 2。

环形缓冲区的大小为480x2 = 960个样本。

音频输入接收到一个周期(480个样本),而音频堆栈读取/处理另一个周期(480个样本)。

延迟= 1个周期,480个样本。它等于48000 Hz时的10 ms。

环形缓冲液(960个样品)

期间(480个样本) 期间(480个样本)

常见的周期数为2,但有些系统可能会更高。

延迟:一个或多个时期

Android音频硬件抽象层(HAL)

HAL充当Android媒体服务器和Linux音频驱动程序之间的中间人。在将Android“移植”到设备上时,移动设备的制造商提供HAL实现。

实现是开放的,供应商可以自由创建任何类型的HAL代码。使用预定义的结构进行与媒体服务器的通信。媒体服务器加载HAL并要求创建具有可选首选参数的输入或输出流,例如采样率,缓冲区大小或音频效果。

注意:HAL可能会也可能不会根据参数执行,并且媒体服务器必须“适应”HAL。

典型的HAL实现是tinyALSA,用于与ALSA音频驱动程序通信。一些供应商在这里提供了封闭的源代码来实现他们认为重要的音频功

在分析Android源存储库中的许多开源HAL实现的代码之后,我们发现了一些怪癖,由于奇怪的配置和糟糕的编码而不必要地增加了音频路径的大量延迟和CPU负载。

一个好的HAL实现不应该添加任何延迟。

延迟:0个或更多样本

Audio Flinger

Android媒体服务器包含两项服务:

AudioPolicy服务处理音频会话和权限处理,例如启用麦克风访问或呼叫中断。它与iOS的音频会话处理非常相似。

Audio Flinger服务处理数字音频流。

Audio Flinger创建了一个RecordThread,它充当应用程序和音频驱动程序之间的中间人。它的基本工作是:

使用Android HAL从驱动程序的环形缓冲区中获取下一个输入音频缓冲区。

如果应用程序请求的采样率与本机采样率不同,则重新采样缓冲区。

如果应用程序请求的缓冲区大小不同于本机周期大小,则执行其他缓冲。

如果按照这种方式配置Android,音频Flinger有一个“快速混音器”路径。如果用户应用程序使用本机(Android NDK)代码并设置具有本机硬件采样率和周期大小的音频缓冲区队列,则不会在此步骤中进行重新采样,额外缓冲或混合(“MixerThread”)。

RecordThread使用“推”方法,没有与音频驱动程序的任何严格同步。它试图在醒来和跑步时进行“有根据的猜测”,但“推”方法对辍学者更敏感。低延迟系统始终使用“拉”方法,其中音频驱动程序通过整个音频链“指示”音频i / o。很明显,当最初构思,设计和开发Android OS时,低延迟音频不是优先考虑的事情。

延迟:1个周期(最佳情况)

Binder

Android主进程间通信系统中的共享内存用于在Audio Flinger和用户应用程序之间传输音频缓冲区。它是Android的核心,在Android内部随处使用。

延迟:0

AudioRecord

我们现在处于用户应用程序的过程中。AudioRecord实现音频输入的应用程序端。这是一个可通过OpenSL ES访问的客户端库功能。

AudioRecord运行一个线程,定期从Audio Flinger获取一个新缓冲区,其音频Flinger描述了“推送”理念。如果开发人员将其设置为仅使用一个缓冲区,则不会为音频路径添加延迟。

延迟:0+样本

用户应用程序

最后,音频输入到达其目的地,即用户应用程序。

由于输入和输出线程不相同,因此用户应用程序必须在线程之间实现环形缓冲区。它的大小最小为2个周期(1个用于音频输入,1个用于音频输出),但写得不好的应用程序通常使用暴力并使用更多周期来解决CPU瓶颈。

从这一点开始,我们开始带着一些音频输出返回。

延迟:超过1个周期,通常接近2个(最佳情况)

AudioTrack

AudioTrack实现音频输出的用户应用程序。这是一个可通过OpenSL ES访问的客户端库功能。它运行一个线程,定期将下一个音频缓冲区发送到Audio Flinger。在Android 4.4.4之后,AudioTrack不会为音频路径添加延迟,因为它可以设置为仅使用一个缓冲区。

延迟:0+样本

Audio Flinger

创建一个PlaybackThread,它作为音频输入中描述的RecordThread的反转。

延迟:1期(最佳情况)

Android音频HAL

与音频输入相同。

延迟:0个或更多样本

音频驱动程序(ALSA,OSS等)

音频驱动器中的音频输出与音频输入的工作方式相同,也使用环形缓冲器。

延迟:一个或多个时期

总线从音频驱动器传输到音频芯片

与音频输入的总线传输类似,此处的延迟通常为1 ms至6 ms。

延迟:1-6毫秒

数模转换(DAC)

在这一点上,ADC的反转,数字音频被“转换”回模拟。出于与ADC相同的原因,经验法则是假设DAC有1 ms的延迟。

延迟:1毫秒

模拟音频输出

DAC的输出信号是模拟音频,但它需要额外的组件来驱动连接的设备,如耳机。与模拟音频输入类似,模拟组件可被视为“零延迟”。

延迟:0

解决方案

在所有阶段中,除非重写安卓底层音频系统,否则我们开发者能够操作的部分只有音频的播放方式,目前安卓原生的播放方式有三种:

MediaPlayer

SoundPool

AudioTrack(OpenSL)

AAudio

第一种用于长音频播放,实际测试结果为音频延迟依然十分大100ms~500ms之间

第二种和第三种用于短音频播放,短音频的播放延迟得到了很大的改善,基本徘徊在50ms之间

但是由于无法应用于长音频的播放,问题依旧还是没得到解决

image.png

现有的解决方案推荐

superpowered

致力于安卓低延迟做底层开发的C++ API

NativeAudio

Unity插件,泰国音频大佬

Unity2019

听说2019优化了底层音频的播放机制

关于长音频的延迟在各个机型上的不同而无法自动修正的解决方案

收集各种机型预设一个延迟的值

设计一个体验良好的界面可以帮助用户设置这个延迟的值

Unity2017默认音频、NativeAudio(OpenSL)、Criware(OpenSL)、Unity2019(OpenSL)默认音频延迟比较

短音效

单位是10ms

短音效延迟上NativeAudio和Criware是最低的,也是差不多的。

长音效

误差范围内的差距

效率待实验。

audio unity 加速_浅谈Unity中Android、iOS音频延迟相关推荐

  1. python sys模块作用_浅谈Python中的模块

    模块 为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式.在Python中,一个.py文件就称之为一个模块(Mod ...

  2. python的re2和re区别_浅谈Python中re.match()和re.search()的使用及区别

    1.re.match()fvk免费资源网 re.match()的概念是从头匹配一个符合规则的字符串,从起始位置开始匹配,匹配成功返回一个对象,未匹配成功返回None.fvk免费资源网 包含的参数如下: ...

  3. python读取图像数据流_浅谈TensorFlow中读取图像数据的三种方式

    本文面对三种常常遇到的情况,总结三种读取数据的方式,分别用于处理单张图片.大量图片,和TFRecorder读取方式.并且还补充了功能相近的tf函数. 1.处理单张图片 我们训练完模型之后,常常要用图片 ...

  4. swift 引用其他类_浅谈swift中闭包修饰符 weak?unowned? 或什么都不用

    浅谈swift中闭包修饰符 weak?unowned? 或什么都不用 平常的开发中,clourse是我们iOSr绕不过去的坎儿. 苹果本身也很重视闭包,像之前的一些老的target-action类型的 ...

  5. python命名规则数字开头的成语_浅谈Python中带_的变量或函数命名

    搜索热词 Python 的代码风格由 PEP 8 描述.这个文档描述了 Python 编程风格的方方面面.在遵守这个文档的条件下,不同程序员编写的 Python 代码可以保持最大程度的相似风格.这样就 ...

  6. java 中的单元测试_浅谈Java 中的单元测试

    单元测试编写 Junit 单元测试框架 对于Java语言而言,其单元测试框架,有Junit和TestNG这两种, 下面是一个典型的JUnit测试类的结构 package com.example.dem ...

  7. python生成器和迭代器作用_浅谈Python中的生成器和迭代器

    迭代器 迭代器协议 对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么返回一个异常来终止本次迭代.(只能往前走,不能往后退!) 迭代器对象 遵循了(实现了)迭代器协议的对象.(对象内 ...

  8. python中 是什么类型_浅谈python中的变量默认是什么类型

    浅谈python中的变量默认是什么类型 1.type(变量名),输出的结果就是变量的类型: 例如 >>> type(6) 2.在Python里面变量在声明时,不需要指定变量的类型,变 ...

  9. java中修饰常量的事_浅谈java中的声明常量为什么要用static修饰

    今天定义一个类常量,想着也只有这个类可以用到,就没用static关键字修饰.结果sonar代码检查提示: Rename this field "PERSON_TYPE_USER" ...

最新文章

  1. Windows7 libsvm库中grid.py的使用步骤
  2. python3.5升级_python升级 (2.6升级到3.5)
  3. 华为手机业务网络推广外包持续受限,在当前市场下还能做些什么?
  4. 直流电动机matlab仿真实验,直流电动机的MATLAB仿真.doc
  5. 计算机操作系统(6):练习题
  6. jQuery中each的用法之退出循环和结束本次循环
  7. (1)Deep Learning之感知器
  8. 老手萌新学习composer的使用
  9. uiscrollview 图片放大缩小
  10. html5 显示k线图,canvas绘图,html5 k线图,股票行情图
  11. php 中%3cspan%3e,vue实战(4)——网站统计之——友盟百度统计
  12. dy极速版-艳云脚本云控系统
  13. 2020福州大学计算机录取名单,福州大学数学与计算机科学/软件学院2020年硕士研究生招生复试结果(第二批非全日制公示)...
  14. Android中wifi认证的实现
  15. [转]深度学习在目标跟踪中的应用
  16. 完美结合,10款提升编程能力的游戏项目
  17. 集团公司预算控制与网上费用报销系统
  18. RT_Thread_空闲线程
  19. 手机chrome没有声音_Chrome浏览器没有声音,解决windows10Chrome浏览器没有声音的问题...
  20. p2p打洞stun的原理

热门文章

  1. python 基础知识点整理 和具体应用
  2. 人生感悟:人生像吃自助餐
  3. Intel硬件加速 VS CUDA完胜 视频转码感受
  4. 关于C编程的一点感受
  5. 无符号哥伦布指数编码
  6. Java中Web程序修改配置文件不重启服务器的方法
  7. [Swift]LeetCode916.单词子集 | Word Subsets
  8. Mysql 替换字段的一部分内容
  9. Python基础-time and datetime
  10. Cadence 电源完整性仿真实践(二)