gpt4 book ai didi

java - 如何在Java中同步TargetDataLine和SourceDataLine(同步音频记录和播放)

转载 作者:行者123 更新时间:2023-12-02 23:28:52 31 4
gpt4 key购买 nike

我正在尝试创建一个Java应用程序,该程序能够播放音频,记录用户语音并告诉用户是否在正确的时间唱歌。

目前,我只专注于录制和播放音频(音调识别超出范围)。

为此,我使用了Java音频API中的TargetDataLine和SourceDataLine。首先,我开始录音,然后开始音频播放。由于我想确保用户在正确的时间唱歌,因此我需要在录制的音频和播放的音频之间保持同步。

例如,如果在音频录制后1秒钟开始音频播放,我知道我将忽略记录缓冲区中的第一秒数据。

我在测试中使用以下代码(代码远非完美,但仅用于测试目的)。

import javax.sound.sampled.*;
import java.io.File;
import java.io.IOException;

class AudioSynchro {

private TargetDataLine targetDataLine;
private SourceDataLine sourceDataLine;
private AudioInputStream ais;
private AudioFormat recordAudioFormat;
private AudioFormat playAudioFormat;

public AudioSynchro(String sourceFile) throws IOException, UnsupportedAudioFileException {
ais = AudioSystem.getAudioInputStream(new File(sourceFile));

recordAudioFormat = new AudioFormat(44100f, 16, 1, true, false);
playAudioFormat = ais.getFormat();
}

//Enumerate the mixers
public void enumerate() {
try {
Mixer.Info[] mixerInfo =
AudioSystem.getMixerInfo();
System.out.println("Available mixers:");
for(int cnt = 0; cnt < mixerInfo.length;
cnt++){
System.out.println(mixerInfo[cnt].
getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}

//Init datalines
public void initDataLines() throws LineUnavailableException {
Mixer.Info[] mixerInfo =
AudioSystem.getMixerInfo();

DataLine.Info targetDataLineInfo = new DataLine.Info(TargetDataLine.class, recordAudioFormat);

Mixer targetMixer = AudioSystem.getMixer(mixerInfo[5]);

targetDataLine = (TargetDataLine)targetMixer.getLine(targetDataLineInfo);

DataLine.Info sourceDataLineInfo = new DataLine.Info(SourceDataLine.class, playAudioFormat);

Mixer sourceMixer = AudioSystem.getMixer(mixerInfo[3]);

sourceDataLine = (SourceDataLine)sourceMixer.getLine(sourceDataLineInfo);
}

public void startRecord() throws LineUnavailableException {
AudioInputStream stream = new AudioInputStream(targetDataLine);

targetDataLine.open(recordAudioFormat);

byte currentByteBuffer[] = new byte[512];

Runnable readAudioStream = new Runnable() {
@Override
public void run() {
int count = 0;
try {
targetDataLine.start();
while ((count = stream.read(currentByteBuffer)) != -1) {
//Do something
}
}
catch(Exception e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(readAudioStream);
thread.start();
}

public void startPlay() throws LineUnavailableException {
sourceDataLine.open(playAudioFormat);
sourceDataLine.start();

Runnable playAudio = new Runnable() {
@Override
public void run() {
try {
int nBytesRead = 0;
byte[] abData = new byte[8192];
while (nBytesRead != -1) {
nBytesRead = ais.read(abData, 0, abData.length);
if (nBytesRead >= 0) {
int nBytesWritten = sourceDataLine.write(abData, 0, nBytesRead);
}
}

sourceDataLine.drain();
sourceDataLine.close();
}
catch(Exception e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(playAudio);
thread.start();
}

public void printStats() {
Runnable stats = new Runnable() {

@Override
public void run() {
while(true) {
long targetDataLinePosition = targetDataLine.getMicrosecondPosition();
long sourceDataLinePosition = sourceDataLine.getMicrosecondPosition();
long delay = targetDataLinePosition - sourceDataLinePosition;
System.out.println(targetDataLinePosition+"\t"+sourceDataLinePosition+"\t"+delay);

try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};

Thread thread = new Thread(stats);
thread.start();
}

public static void main(String[] args) {
try {
AudioSynchro audio = new AudioSynchro("C:\\dev\\intellij-ws\\guitar-challenge\\src\\main\\resources\\com\\ouestdev\\guitarchallenge\\al_adagi.mid");
audio.enumerate();
audio.initDataLines();
audio.startRecord();
audio.startPlay();
audio.printStats();
} catch (IOException | LineUnavailableException | UnsupportedAudioFileException e) {
e.printStackTrace();
}
}

}

该代码初始化2条数据线,开始音频记录,开始音频播放并显示统计信息。 enumerate()方法用于显示系统上可用的混合器。您必须根据系统来更改initDataLines()方法中使用的混合器,以进行自己的测试。
printStats方法()启动一个线程,该线程以微秒为单位询问两条数据线的位置。这是我尝试用来跟踪同步的数据。我观察到的是,这两个数据线并非一直保持同步。这是我的输出控制台的简短摘录:

130000 0 130000

150000 748 149252

170000 20748 149252

190000 40748 149252

210000 60748 149252

230000 80748 149252

250000 100748 149252

270000 120748 149252

290000 140748 149252

310000 160748 149252

330000 180748 149252

350000 190748 159252

370000 210748 159252

390000 240748 149252

410000 260748 149252

430000 280748 149252

450000 300748 149252

470000 310748 159252

490000 340748 149252

510000 350748 159252

530000 370748 159252

正如我们所看到的,延迟可能会定期变化10毫秒,因此我无法精确地确定记录缓冲区中的哪个位置与播放缓冲区的开头相匹配。特别是,在前面的示例中,我不知道应该从位置149252还是159252开始。
对于音频处理,10毫秒很重要,我想更准确一些(可接受1或2毫秒)。
而且,听起来很奇怪,当两个度量之间存在差异时,仍然相差10毫秒。

然后,我尝试进一步 push 测试,但没有得到更好的结果:
-尝试使用更大或更小的缓冲区
-尝试将缓冲区增大两倍以进行播放。由于音频文件是立体声文件,因此会占用更多字节(用于录制的每个字节2个字节,用于播放的每个字节4个字节)
-尝试在同一音频设备上录制和播放

我认为,有两种同步两个缓冲区的策略:
-我想做的。精确确定回放开始时在记录缓冲区中的位置。
-同步记录的开始和播放。

在这两种策略中,我都需要保证保持同步。

你们有没有遇到过这类问题?

目前,我将Java 12和JavaFx用于我的应用程序,但我准备使用其他框架。我没有尝试过,但是可以使用框架lwjgl( https://www.lwjgl.org/基于OpenAl)或珠子(http:// www.beadsproject.net/)获得更好的结果和更多控制。如果你们中的任何人知道他的框架并且可以给我返回,我很感兴趣。

最后,最后可接受的解决方案是更改编程语言。

最佳答案

我对TargetDataLines的工作还不多,但是我认为我可以提供有用的观察和建议。

首先,您编写的测试可能是在多线程算法中测量方差,而不是文件时间上的延误。 JVM在处理线程之间来回反弹的方式可能是无法预测的。您可以阅读good article on real time, low-latency coding in Java以获取背景信息。

其次,Java使用带有音频IO的阻塞队列的方式提供了很多稳定性。如果没有,我们将在播放或录音时听到各种音频失真。

您可以尝试以下方法:创建一个具有runnable循环的单个while,该循环在同一迭代中处理TargetDataLineSourceDataLine中相同数量的帧。此runnable可以松耦合(使用 bool(boolean) 值打开/关闭行)。

主要好处是您知道每次循环迭代都将产生协调的数据。

编辑:这是我用帧计数所做的几个示例:
(1)我有一个音频循环,在处理过程中对帧进行计数。所有时序均严格由处理的帧数确定。我从不理会从SDL的位置读取数据。我已经编写了一个节拍器,它每N帧会发起一个合成的点击(其中N是基于速度的)。在第N帧,用于合成点击的数据被混合到从SDL发送出去的音频数据中。通过这种方法获得的计时精度非常出色。

在第N个框架上的另一个应用程序,我启动了视觉/图形事件。图形循环通常设置为60fps,音频设置为44100fps。初始化是通过松散耦合来处理的:事件的 bool(boolean) 值被音频线程翻转(仅此而已,由于多余的 Activity 而使音频线程杂乱是危险的,可能导致结结和辍学)。图形处理循环(也称为“游戏循环”)获取 bool(boolean) 值更改并在自己的时间(60 fps)中进行处理。我通过这种方式发生了一些不错的视觉+听觉同步,包括使对象的亮度随所播放声音的音量而变化。这类似于许多人使用Java编写的数字VU表。

根据您希望的准确性水平,我认为帧计数就足够了。我不知道使用Java可以提供同样多的准确性。

关于java - 如何在Java中同步TargetDataLine和SourceDataLine(同步音频记录和播放),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57699417/

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