我一直在玩着以更快的速度循环播放一段声音的过程,这很有趣,偶然发现this question,很好地解决了这个问题,我想。当您进入高速状态时,它的确会出现 buggy 的问题,因为它会掉落介于两者之间的任何东西,而且每隔一个字节就占用一个字节。所以我想将其更改为采用数组中所有字节的平均值。问题是,字节不适合用int进行划分,而从字节更改为int时,我有点愚蠢。我的解决方案是这样做(再次补充上述问题。
import javax.swing.JOptionPane;
import javax.swing.JFileChooser;
import javax.sound.sampled.*;
import java.net.URL;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.util.Date;
import java.io.File;
class AcceleratePlayback {
public static void main(String[] args) throws Exception {
int playBackSpeed = 3;
File soundFile;
if (args.length>0) {
try {
playBackSpeed = Integer.parseInt(args[0]);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
System.out.println("Playback Rate: " + playBackSpeed);
JFileChooser chooser = new JFileChooser();
chooser.showOpenDialog(null);
soundFile = chooser.getSelectedFile();
System.out.println("FILE: " + soundFile);
AudioInputStream ais = AudioSystem.getAudioInputStream(soundFile);
AudioFormat af = ais.getFormat();
int frameSize = af.getFrameSize();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[2^16];
int read = 1;
while( read>-1 ) {
read = ais.read(b);
if (read>0) {
baos.write(b, 0, read);
}
}
System.out.println("End entire: \t" + new Date());
//This is the important bit
byte[] b1 = baos.toByteArray();
byte[] b2 = new byte[b1.length/playBackSpeed];
for (int ii=0; ii<b2.length/frameSize; ii++) {
for (int jj=0; jj<frameSize; jj++) {
int b3=0;
for (int kk = 0; kk < playBackSpeed; kk++){
b3 = b3+(int)b1[(ii*frameSize*playBackSpeed)+jj+kk];
}
b3 = b3/playBackSpeed;
b2[(ii*frameSize)+jj] = (byte)b3;
}
}
//ends here
System.out.println("End sub-sample: \t" + new Date());
ByteArrayInputStream bais = new ByteArrayInputStream(b2);
AudioInputStream aisAccelerated = new AudioInputStream(bais, af, b2.length);
Clip clip = AudioSystem.getClip();
clip.open(aisAccelerated);
clip.loop(2*playBackSpeed);
clip.start();
JOptionPane.showMessageDialog(null, "Exit?");
}
}
我确实意识到这可能是错误的方法,但是我不确定我还能做什么,有什么想法吗?
最好,亚历克斯。
最佳答案
自从引用了我先前的“解决方案”以来,我将详细介绍变速播放中使用的内容。我承认,我不完全理解此问题中使用的方法,因此也不会尝试对代码进行改进。在这样做时,我冒着“不回答问题”的风险,但是也许有关使用线性插值的更多细节将表明,这可能是制作所需高速循环的足够方法。
我并不是说我想出的方法是最好的。我不是音响工程师。但这似乎有效。 (对于任何建议的改进,我总是感激不尽。)
这是我为自己的游戏制作的声音库。它基于Java Clip的思想,但具有一些额外的功能。在我的图书馆中,有一个存储数据的地方,另一个用于回放的结构,一个用于并发单播放,另一个用于循环。两者都允许变速,甚至可以反向播放声音。
为了加载和保存“剪辑”数据,我只使用了一个名为“clipData”的int [],但是我在L和R上都使用了它,所以奇数和偶数均适用于任何一只耳朵。
最初加载“clipData”:
while((bytesRead = ais.read(buffer, 0, 1024)) != -1)
{
bufferIdx = 0;
for (int i = 0, n = bytesRead / 2; i < n; i ++)
{
clipData[(int)clipIdx++] =
( buffer[(int)bufferIdx++] & 0xff )
| ( buffer[(int)bufferIdx++] << 8 ) ;
}
}
为了回放,保存此数据数组的对象有两个get()方法。首先是正常速度。一个int用来索引clipData数组(对于较大的音频文件,可能应该为'long'!):
public double[] get(int idx) throws ArrayIndexOutOfBoundsException
{
idx *= 2; // assumed: stereo data
double[] audioVals = new double[2];
audioVals[0] = clipData[idx++];
audioVals[1] = clipData[idx];
return audioVals;
}
也许返回一个float数组代替double []是可以接受的?
这是用于变速的增强型get()方法。它使用线性插值法来计算double的小数部分,该小数部分用作clipData的索引:
public double[] get(double idx) throws ArrayIndexOutOfBoundsException
{
int intPart = (int)idx * 2;
double fractionalPart = idx * 2 - intPart;
int valR1 = clipData[intPart++];
int valL1 = clipData[intPart++];
int valR2 = clipData[intPart++];
int valL2 = clipData[intPart];
double[] audioVals = new double[2];
audioVals[0] = (valR1 * (1 - fractionalPart)
+ valR2 * fractionalPart);
audioVals[1] = (valL1 * (1 - fractionalPart)
+ valL2 * fractionalPart);
return audioVals;
}
while(播放)循环(用于将数据加载到播放SourceDataLine中)具有与clipData相关联的变量,我称之为“游标”,该变量在声音数据数组中进行迭代。对于正常播放,“光标”以1递增,并经过测试以确保在到达clipData的末尾时它返回零。
您可以编写类似以下内容的代码:
audioData = clipData.get(cursor++)
以读取连续的数据帧。对于变速而言,以上内容将更像这样:
audioData = clipData.get(cursor += speedIncrement);
“speedIncrement”是 double 值。如果将其设置为2.0,则播放速度快两倍。如果将其设置为0.5,则速度快一半。如果您进行了正确的检查,您甚至可以使speedIncrement等于负值以进行反向播放。
只要速度不超过奈奎斯特值(至少在理论上),此方法就起作用。再次,您必须进行测试以确保“游标”没有脱离clipData的边缘,而是在声音数据数组另一端的适当位置重新开始。
希望这可以帮助!
另一个注意事项:您可能想重写上面的get()方法,以发送缓冲区的读取值,而不是发送单个帧。我目前正在尝试按帧进行操作。我认为它使代码更易于理解,并有助于逐帧处理和响应,但肯定会使速度变慢。