Java 音频字节缓冲区需要不同的时间来填充

Posted

技术标签:

【中文标题】Java 音频字节缓冲区需要不同的时间来填充【英文标题】:Java Audio Byte Buffer takes varying times to fill 【发布时间】:2018-03-09 16:01:17 【问题描述】:

我正在打开一个 targetdataline 以接受给定格式的音频输入。 我开始并打开线路,我有一个填充字节的缓冲区。这在一个恒定循环上运行,直到外部参数发生更改。

现在对于固定的采样率和缓冲区大小,我希望这总是需要相同的时间来填充,即如果我的缓冲区大小是 8 位流的 48000,并且我的采样率为 48kHz,我会期望我的缓冲区总是需要 1 秒才能填满。但是我发现这变化很大。

以下是我用过的代码:

 DataLine.Info info1 = new DataLine.Info(TargetDataLine.class, format1);

     try (TargetDataLine line = (TargetDataLine) m1.getLine(info1)) 
         line.open(format1);
            line.start();

            while (!pauseInput)
            long time1 = System.currentTimeMillis();


            int numBytesRead1 = line.read(buffer1, 0, buffer1.length);

            //chan1double = deinterleaveAudio(buffer1, chan1selectedchannel, chan1totalchannels);
            long time2 = System.currentTimeMillis();
            System.out.println(threadName + " Capture time = " + (time2-time1));
            
         line.stop(); 

     

注释行是每次缓冲区满时我想运行的进程。我意识到我不能把它放在这里,因为它会中断流,所以我需要找到一种不同的方式来调用它,因此我已经注释掉了。

出于测试目的,我的缓冲区大小为 4096。我的音频格式是 48kHz 16 位,所以我希望我的字节缓冲区在 42.6 毫秒内被填充。 ((1/48000) * 2048)。 (这是乘以缓冲区大小的一半,因为每个样本是两个字节)。然而,使用 currentTimeMillies 来测量每次通过时,它会以 123 毫秒和 250 毫秒的时间返回,并且在这些时间之间变化。

这里有什么我没有做的遗漏吗?

编辑:我只将代码复制到了一个全新的应用程序中,该应用程序甚至没有 GUI 或任何附加的东西。纯粹是为了输出到控制台看看发生了什么,确保没有后台线程干扰,果然同样的事情发生了。预计填充时间为 250 毫秒的缓冲区有 95% 的时间在 255-259 毫秒内填充。但是偶尔这会下降到 127 毫秒(这在物理上是不可能的,除非发生了一些奇怪的缓冲区事情。这是 java 中的某个错误吗?

【问题讨论】:

【参考方案1】:

我认为以这种方式调整时间不是一个好主意。它取决于很多东西,例如 bufferSize、混合器等。此外,您的应用程序正在与混合器共享线路的缓冲区。如果您有实时处理,请将数据存储在 循环缓冲区 中,该缓冲区的长度足以容纳您需要的数据量。在另一个线程中,从 循环缓冲区 中读取所需的数据量,并以恒定的时间间隔进行处理。因此,有时,您可能会在两个连续处理之间重叠或丢失一些字节,但您始终拥有预期的字节数。

打开线路时,您可以使用open(format, bufferSize) 指定线路的缓冲区大小,也可以通过以下方式检查实际缓冲区大小 打电话给DataLine.getBufferSize()。然后,您需要指定通过TargetDataLine.read() 检索数据时提供的短缓冲区的大小。您的短缓冲区大小必须小于行的缓冲区大小。我会将短缓冲区大小视为线路缓冲区大小的 1/4、1/8、1/16 左右。另一个想法是在调用read() 之前检查可用字节DataLine.available()。请注意,read() 是一个阻塞调用(但它不会阻塞 line 的缓冲区),也就是说,它会一直阻塞,直到读取了请求的字节数。

对于您的应用程序和音频接口之间的低延迟直接通信,您可以考虑ASIO。

【讨论】:

感谢您的回复。但我很困惑。我不是在考虑调整时间。我对为什么填充缓冲区所花费的时间不同感到困惑。填充相同大小的缓冲区应该花费相同的毫秒数,如果时间变化会导致抖动问题或音调变化。我非常想按照建议实现循环缓冲区。但是,如果音频流容易受到使用 java 声音的时序变化的影响,那么我最好使用不同的硬件接口。我已经阅读了一些使用 JNI 包装的 C 实现。【参考方案2】:

对于任何关注相同问题的人,我得到了一个答案,其中一半解释了正在发生的事情。

线程调度程序决定代码何时可以运行,这可能会导致其变化 10-20 毫秒。在早期,这高达 70 毫秒。 这并不意味着流缺少样本,而只是这个缓冲区不会提供连续的流。因此,任何考虑实时处理这些数据并将其传递到音频输出流的应用程序都需要了解这种额外的潜在延迟。

我仍在寻找缓冲区填充时间短的原因,每四五次通过。有人告诉我,这可能与 targetDataLine 缓冲区大小与我的缓冲区大小不同有关,并且该缓冲区的其余部分被写入该通道,但是我已将其更改为完全相同,但仍然没有运气。

【讨论】:

以上是关于Java 音频字节缓冲区需要不同的时间来填充的主要内容,如果未能解决你的问题,请参考以下文章

从字节数组填充音频缓冲区并使用渲染回调播放

是否可以直接从 OpenSL ES(适用于 Android)中的音频资产获取字节缓冲区?

音频数据字节的含义是啥?安卓

Java ByteBuffer 放置与包装

OpenCL 缓冲区大小填充

合并多个音频缓冲源