1

为了好玩,我正在以更快的速度连续循环播放声音片段,并且偶然发现了这个问题,我猜它很好地解决了这个问题。当您高速运行时,它确实会出现问题,因为它会丢弃介于两者之间的任何内容,并且每隔一段时间只占用一个字节。所以我想改变它来取数组中所有字节的平均值。问题是,字节不适合被整数除,而且在从字节更改为整数时我有点愚蠢。我的解决方案是这样做(再次补充提到的问题。

 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?");
     }
}

我确实意识到这可能是错误的方法,但我不确定我还能做什么,有什么想法吗?

最好的,亚历克斯。

4

1 回答 1

1

由于引用了我之前的“解决方案”,我将更详细地列出我用于变速播放的内容。我承认,我并不完全理解这个问题中使用的方法,因此不会尝试改进代码。我这样做是冒着“不回答问题”的风险,但也许关于使用线性插值的更多细节将表明这可能是制作您想要的更高速循环的充分方法。

我并不是说我想出的方法是最好的。我不是音响工程师。但它似乎工作。(我总是感谢任何建议的改进。)

这是我为自己的游戏制作的声音库。它基于 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;
}

也许返回一个浮点数组是可以接受的,而不是双[]?

这是用于变速的增强 get() 方法。它使用线性插值来解释用作 clipData 索引的 double 的小数部分:

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(playing) 循环(用于将数据加载到播放 SourceDataLine 中)有一个与 clipData 关联的变量,我称之为“光标”,它遍历声音数据数组。对于正常播放,'cursor' 增加 1,并测试以确保它在到达 clipData 末尾时回到零。

您可以编写如下内容:audioData = clipData.get(cursor++)读取数据的连续帧。

对于变速,上面会更像这样:

audioData = clipData.get(cursor += speedIncrement);

'speedIncrement' 是双倍的。如果设置为 2.0,则播放速度提高一倍。如果设置为 0.5,则速度减半。如果您进行正确的检查,您甚至可以使 speedIncrement 等于反向播放的负值。

只要速度不超过奈奎斯特值(至少在理论上),这种方法就可以工作。再次,您必须进行测试以确保“光标”没有离开剪辑数据的边缘,而是在声音数据数组另一端的适当位置重新开始。

希望这可以帮助!

另一个注意事项:您可能希望重写上述 get() 方法以发送缓冲区的读取值而不是单个帧。我目前正在尝试在每帧的基础上做事。我认为它使代码更容易理解,并有助于每帧处理和响应,但它肯定会减慢速度。

于 2013-05-05T20:36:25.273 回答