草庐IT

java - 在JVM中长时间记录音频时出现突然的延迟

coder 2023-05-08 原文

我正在实现一个使用JDK Version 8 Update 201实时(或至少尽可能接近实时)记录和分析音频的应用程序。在执行模拟该应用程序典型用例的测试时,我注意到在连续录制了几个小时的音频后,突然延迟了一到两秒。到目前为止,还没有明显的延迟。仅在此关键记录点之后的几个小时才开始出现此延迟。

到目前为止我尝试过的

为了检查用于计时音频样本的代码是否错误,我注释掉了与计时有关的所有内容。这基本上使我离开了这个更新循环,该循环会在准备好音频样本后立即获取它们(请注意:Kotlin代码):

while (!isInterrupted) {
    val audioData = read(sampleSize, false)
    listener.audioFrameCaptured(audioData)
}

这是我的读取方法:
fun read(samples: Int, buffered: Boolean = true): AudioData {
    //Allocate a byte array in which the read audio samples will be stored.
    val bytesToRead = samples * format.frameSize
    val data = ByteArray(bytesToRead)

    //Calculate the maximum amount of bytes to read during each iteration.
    val bufferSize = (line.bufferSize / BUFFER_SIZE_DIVIDEND / format.frameSize).roundToInt() * format.frameSize
    val maxBytesPerCycle = if (buffered) bufferSize else bytesToRead

    //Read the audio data in one or multiple iterations.
    var bytesRead = 0
    while (bytesRead < bytesToRead) {
        bytesRead += (line as TargetDataLine).read(data, bytesRead, min(maxBytesPerCycle, bytesToRead - bytesRead))
    }

    return AudioData(data, format)
}

但是,即使没有我的帮助,问题也没有得到解决。因此,我继续进行一些实验,并让应用程序使用不同的音频格式运行,这导致了非常困惑的结果(我将使用PCM签名的16位立体声音频格式,其字节序少,采样率44100.0 Hz默认情况下,除非另有说明):
  • 根据所使用的机器,延迟出现之前必须经过的关键时间似乎有所不同。在我的Windows 10台式电脑上,它大约需要6.5到7个小时。但是,在我的笔记本电脑(也使用Windows 10)上,相同的音频格式大约需要4到5个小时。
  • 使用的音频 channel 数量似乎有影响。如果我将声道数量从立体声更改为单声道,则延迟出现之前的时间会在台式机上加倍,介于13到13.5小时之间。
  • 将样本大小从16位减小到8位也会导致延迟开始出现之前的时间加倍。在我的桌面上的13到13.5小时之间的某个时间。
  • 将字节顺序从小字节序更改为大字节序无效。
  • 从立体声混音切换到物理麦克风也不起作用。
  • 我尝试使用不同的缓冲区大小(1024、2048和3072样本帧)及其默认缓冲区大小打开该行。这也没有改变任何东西。
  • 在延迟开始发生后刷新TargetDataLine会导致所有字节为零,持续大约一到两秒钟。此后,我再次获得非零值。但是,延迟仍然存在。如果在临界点之前刷新该行,则不会得到那些零字节。
  • 在出现延迟后停止并重新启动TargetDataLine也不会更改任何内容。
  • 关闭并重新打开TargetDataLine确实可以避免延迟,直到它在几个小时后重新出现为止。
  • 每十分钟自动刷新一次TargetDataLines内部缓冲区无助于解决问题。因此,内部缓冲区中的缓冲区溢出似乎不是原因。
  • 使用并行垃圾收集器来避免应用程序冻结也无济于事。
  • 使用的采样率似乎很重要。如果我将采样率提高一倍至88200 Hertz,则延迟将在3到3.5个小时的运行时间之间开始。
  • 如果我让它使用我的“默认”音频格式在Linux下运行,则在经过大约9个小时的运行时间后,它仍然可以正常运行。

  • 我得出的结论:

    这些结果使我得出的结论是,在此问题开始发生之前,我可以记录音频的时间取决于运行应用程序的计算机,并且取决于音频的字节率(即帧大小和采样率)。音频格式。这似乎是正确的(尽管到目前为止我还不能完全确认这一点),因为如果我结合2和3中所做的更改,我会假设我可以录制四倍的音频采样时间(介于26和27小时),就像在延迟开始出现之前使用“默认”音频格式时一样。由于我还没有足够的时间让应用程序运行这么长时间,我只能说由于时间限制,在不得不停止它之前,它可以正常运行约15个小时。因此,该假设仍有待确认或否定。

    根据项目符号13的结果,似乎整个问题仅在使用Windows时出现。因此,我认为这可能是javax.sound.sampled API的特定于平台的部分中的错误。

    即使我认为当此问题开始发生时我可能已经找到了改变的方法,但我对结果并不满意。我可以定期关闭并重新打开该行,以免该问题开始出现。但是,这样做会导致一些任意的少量时间,而我将无法捕获音频样本。此外,Javadoc指出,某些行在关闭后根本无法重新打开。因此,对于我而言,这不是一个好的解决方案。

    理想情况下,整个问题都不应该发生。我是否完全缺少某些东西,或者我在使用javax.sound.sampled API可能遇到的限制?我该如何摆脱这个问题?

    编辑:通过Xtreme Biker和gidds的建议,我创建了一个小示例应用程序。您可以在Github repository中找到它。

    最佳答案

    我在Java音频接口(interface)方面有(相当)丰富的经验。
    以下几点可能有助于指导您找到正确的解决方案:

  • 这与JVM版本无关-自Java 1.3或1.5以来,Java音频系统几乎没有升级
  • Java音频系统是操作系统必须提供的任何音频接口(interface)API的穷人包装。在linux中,它是Pulseaudio库,对于Windows,则是直接显示音频API(如果我没记错的话)。
  • 同样,音频系统API属于传统API-有些功能无法使用或未实现,其他行为则很奇怪,因为它们取决于过时的设计(如果需要,我可以提供示例)。
  • 这不是垃圾收集的问题-如果您对“延迟”的定义符合我的理解(音频数据会延迟1-2秒,这意味着您会在1-2秒后开始听东西),那么,垃圾收集器无法使空白数据神奇地被目标数据行捕获,然后像往常一样在2秒钟的字节偏移后附加数据。
  • 这里最有可能发生的事情是硬件或驱动程序在某个时候为您提供了2秒的乱码数据,然后像往常一样流送其余数据,导致您遇到“延迟”。
  • 它可以在linux上完美运行的事实意味着这不是硬件问题,而是与驱动程序相关的问题。
  • 要确认这一怀疑,您可以尝试通过FFmpeg捕获音频达相同的持续时间,然后查看问题是否重现。
  • 如果您使用的是专用音频捕获硬件,则最好与硬件制造商联系,并向他询问您在Windows上面临的问题。
  • 无论如何,当从头开始编写音频捕获应用程序时,如果可能的话,我强烈建议您远离Java音频系统。对POC很好,但它是未维护的旧版API。 JNA始终是一个可行的选择(我在Linux中将其与ALSA/Pulse-audio结合使用来控制Java音频系统无法更改的音频硬件属性),因此您可以在Windows中使用C++查找音频捕获示例并将其转换为Java。它可以为您提供对音频捕获设备的精细控制,远远超过JVM提供的OOTB。如果您想看看一个生活/呼吸可用的JNA示例,请查看我的JNA AAC encoder项目。
  • 同样,如果您使用特殊的捕获技巧,那么制造商很有可能已经提供了自己的低级C api来与硬件接口(interface),并且您也应该考虑一下。
  • 如果不是这种情况,也许您和您的公司/客户应该
    考虑使用专门的捕获硬件(不必
    这么贵)。
  • 关于java - 在JVM中长时间记录音频时出现突然的延迟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55482552/

    有关java - 在JVM中长时间记录音频时出现突然的延迟的更多相关文章

    1. ruby - ECONNRESET (Whois::ConnectionError) - 尝试在 Ruby 中查询 Whois 时出错 - 2

      我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.

    2. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

      我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

    3. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

      Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

    4. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    5. ruby-on-rails - Ruby 检查日期时间是否为 iso8601 并保存 - 2

      我需要检查DateTime是否采用有效的ISO8601格式。喜欢:#iso8601?我检查了ruby​​是否有特定方法,但没有找到。目前我正在使用date.iso8601==date来检查这个。有什么好的方法吗?编辑解释我的环境,并改变问题的范围。因此,我的项目将使用jsapiFullCalendar,这就是我需要iso8601字符串格式的原因。我想知道更好或正确的方法是什么,以正确的格式将日期保存在数据库中,或者让ActiveRecord完成它们的工作并在我需要时间信息时对其进行操作。 最佳答案 我不太明白你的问题。我假设您想检查

    6. 使用 ACL 调用 upload_file 时出现 Ruby S3 "Access Denied"错误 - 2

      我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file

    7. ruby-on-rails - Rails 5 Active Record 记录无效错误 - 2

      我有两个Rails模型,即Invoice和Invoice_details。一个Invoice_details属于Invoice,一个Invoice有多个Invoice_details。我无法使用accepts_nested_attributes_forinInvoice通过Invoice模型保存Invoice_details。我收到以下错误:(0.2ms)BEGIN(0.2ms)ROLLBACKCompleted422UnprocessableEntityin25ms(ActiveRecord:4.0ms)ActiveRecord::RecordInvalid(Validationfa

    8. ruby-on-rails - 将 Ruby 中的日期/时间格式化为 YYYY-MM-DD HH :MM:SS - 2

      这个问题在这里已经有了答案:Railsformattingdate(4个答案)关闭4年前。我想格式化Time.Now函数以显示YYYY-MM-DDHH:MM:SS而不是:“2018-03-0909:47:19+0000”该函数需要放在时间中.现在功能。require‘roo’require‘roo-xls’require‘byebug’file_name=ARGV.first||“Template.xlsx”excel_file=Roo::Spreadsheet.open(“./#{file_name}“,extension::xlsx)xml=Nokogiri::XML::Build

    9. ruby - 查找字符串中的内容类型(数字、日期、时间、字符串等) - 2

      我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s

    10. java - 从 JRuby 调用 Java 类的问题 - 2

      我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

    随机推荐