我一直在玩着以更快的速度循环播放一段声音的过程,这很有趣,偶然发现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()方法,以发送缓冲区的读取值,而不是发送单个帧。我目前正在尝试按帧进行操作。我认为它使代码更易于理解,并有助于逐帧处理和响应,但肯定会使速度变慢。

10-05 23:29