gpt4 book ai didi

java - Java 上语音流的延迟

转载 作者:行者123 更新时间:2023-12-01 14:09:29 25 4
gpt4 key购买 nike

我一直在开发一个通过 USB 调制解调器进行调用的 Java 项目。该应用程序在我的计算机上运行得很好,但是当我尝试在较低规范的计算机上运行它时,从电脑上调用的人的音频流完美地消失,并且在被叫的电话上也能完美地听到。但是电脑用户应该听到的音频会被延迟(3 到 5 秒),并伴有白噪声,并且实际上无法进行对话。

需要记住的一些事情:

  • 我的电脑是 i3 4GB RAM 笔记本电脑,低配置的是 Pentium 4 1GB RAM 台式机。
  • 我测试了 CPU 和 RAM 使用情况,应用程序消耗了我计算机上 20 - 25% 的 CPU,在低规范计算机上几乎 100%,两种情况下都消耗了大约 30 - 40mb 的 RAM。
  • 该应用程序还具有通话录音功能,并且由于某种原因,输出文件写入完美(没有延迟或干扰)。

有什么线索可以说明问题所在或如何解决吗?

启动新线程后用于处理音频的类:(传入调用音频)

public class SerialVoiceReader implements Runnable{

/** The running. */
private volatile boolean running = true;

/** The in. */
DataInputStream in;

/** The af. */
AudioFormat af;

/** The samples per frame. */
private int samplesPerFrame = 160;

/** The audio buffer size. */
private int audioBufferSize = samplesPerFrame * 2 ; //20ms delay

private String tel;

private String timestamp;

public SerialVoiceReader ( DataInputStream in, AudioFormat af){
this.in = in;
this.af = af;
}

public void run (){
try
{
Info infos = new Info(SourceDataLine.class, af);
SourceDataLine dataLine = (SourceDataLine) AudioSystem.getLine(infos);
dataLine.open(dataLine.getFormat(),audioBufferSize *2);
dataLine.start();
// set the volume up
if (dataLine.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
FloatControl volume = (FloatControl) dataLine.getControl(FloatControl.Type.MASTER_GAIN);
volume.setValue(volume.getMaximum());
}
// get a field from GUI to set as part of the file name
tel = CallGUI.telField.getText();
timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(Calendar.getInstance().getTime());

// save the stream to a file to later set the header and make it .wav format
FileOutputStream fos = new FileOutputStream("Llamadas/" + timestamp + "-" + tel + "-OUT.raw");
// the audio buffer writing (this is the audio that goes out on the call)
while (running){
byte[] buffer = new byte[audioBufferSize];
int offset = 0;
int numRead = 0;
while (running && (offset < buffer.length && (numRead = this.in.read(buffer, offset, buffer.length - offset)) >= 0))
{
offset += numRead;
}
if(running && offset>=0){
dataLine.write(buffer, 0, offset);
fos.write(buffer);
}
}
dataLine.stop();
dataLine.drain();
dataLine.close();
fos.close();

}
catch ( Exception e )
{
}
}
<小时/>

启动新线程后用于处理音频的类:(去电音频)

public class SerialVoiceWriter implements Runnable{

/** The running. */
private volatile boolean running = true;

/** The out. */
DataOutputStream out;

/** The af. */
AudioFormat af;

/** The samples per frame. */
private int samplesPerFrame = 160;

/** The audio buffer size. */
private int audioBufferSize = samplesPerFrame * 2; //20ms delay

private String tel;

private String timestamp;

public SerialVoiceWriter ( DataOutputStream out, AudioFormat af, Boolean playMessage)
{
this.out = out;
this.af = af;
}

public void run ()
{
try
{
Info infos = new Info(TargetDataLine.class, af);
TargetDataLine dataLine = (TargetDataLine) AudioSystem.getLine(infos);
dataLine.open(dataLine.getFormat(),audioBufferSize*2 );
dataLine.start();

tel = CallGUI.telField.getText();
timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(Calendar.getInstance().getTime());

FileOutputStream fis = new FileOutputStream("Llamadas/" + timestamp + "-" + tel + "-IN.raw");
while (running){
byte[] audioBuffer = new byte[audioBufferSize];
int offset = 0;
int numRead = 0;
while (running && (offset < audioBuffer.length && (numRead = dataLine.read(audioBuffer, offset, audioBuffer.length - offset)) > 0))
{
offset += numRead;
}
if(running && offset>=0){
this.out.write(audioBuffer);
fis.write(audioBuffer);
}
}
dataLine.flush();
dataLine.stop();
dataLine.close();
fis.close();
dataLine = null;

}
catch (Exception e )
{
}
}

谢谢您的建议

最佳答案

您需要采取的步骤是:

  1. 分析/采样应用程序并找出时间真正花在哪里。 VisualVM 功能强大且免费,作为 JDK 的一部分提供。开始您的应用程序。启动 VisualVM。让 VisualVM 连接到您的应用程序。转到“采样器”选项卡并开始采样 CPU 使用情况。几分钟后拍一张快照。看它。如果您无法弄清楚,请在此处发布内容。
  2. 将音频缓冲区初始化移出循环。如果您的缓冲区为 20 毫秒,则每秒分配字节数组并进行垃圾收集 50 次。这是显而易见且容易做到的,但可能无法解决您的问题。
  3. 用 BufferedOutputStreams 包装 FileOutputStreams。像这样: OutputStream fos = new BufferedOutputStream( new FileOutputStream("Llamadas/"+ timestamp + "-"+ tel + "-OUT.raw")); 您将获得极大的性能提升。现在循环的每次迭代都会等待缓冲区完成写入磁盘。物理磁盘速度很慢,这会导致大量等待。
  4. 摆脱内部 while 循环。实际填充缓冲区并不重要。当内部 while 循环填满该缓冲区时,您就会失去同步。您想要做的就是尝试从输入流中读取一次,如果读取了某些内容,则将读取到的内容写入输出流。不要调用 write(byte[]) 而是调用 DataOutputStream write(byte[], off, len)
  5. 这将需要更多的工作:不要依次写入 dataLine 然后写入 fos,而是并行写入它们。它们各自需要一定的时间将数据写入各自的目的地。如果 fos 需要 X 微秒,而 dataLine 需要 Y,则当前代码需要 X + Y 微秒。如果你并行执行,你最终可能只等待 max(X, Y)。 `

    ExecutorService es = Executors.newFixedThreadPool(2);
    Callable<Void>[] calls = new Callable[2];
    //... your other code here...
    if (running && offset >= 0) {
    final int finalOffset = offset;
    Callable<Void> call1 = new Callable<Void>()
    {
    @Override
    public Void call() throws Exception
    {
    dataLine.write(buffer, 0, finalOffset);
    return null;
    }
    };

    Callable<Void> call2 = new Callable<Void>()
    {
    @Override
    public Void call() throws Exception
    {
    fos.write(buffer); // or however you need to write.
    return null;
    }
    };

    calls[0] = call1;
    calls[1] = call2;
    List<Callable<Void>> asList = Arrays.asList(calls);
    es.invokeAll(asList); // invokeAll will block until both callables have completed.
    }

    `

  6. 如果#5 中的改进还不够好,您可以将写入移至后台。一旦您读取了第一条数据,您就可以在单独的线程中开始写入 - 但不要等待写入完成。立即开始读取下一条数据。一旦获得下一位数据,您就等待第一次写入完成,然后在后台开始第二次写入。

关于java - Java 上语音流的延迟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18641040/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com