繁体   English   中英

Java - 将16位带符号的pcm音频数据数组转换为双数组

[英]Java - Convert 16-bit signed pcm audio data array to double array

我正在从事涉及音频处理的项目。

我从一个文件中取出一段音频,然后想对它进行一些处理。 问题是我将音频数据作为字节数组,而我的处理是在双数组上(稍后在复杂数组上......)。

我的问题是,如何正确地将我收到的字节数组转换为双数组继续?

这是我的输入代码:

AudioFormat format = new AudioFormat(8000, 16, 1, true, true);
AudioInputStream in = AudioSystem.getAudioInputStream(WAVfile);
AudioInputStream din = null;
AudioFormat decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 
                        8000,
                        16,
                        1,
                        2,
                        8000,
                        true);
din = AudioSystem.getAudioInputStream(decodedFormat, in);
TargetDataLine fileLine = AudioSystem.getTargetDataLine(decodedFormat);
fileLine .open(format);
fileLine .start();

int numBytesRead;
byte[] targetData = new byte[256]; // (samplingRate / 1000) * 32ms

while (true) {
    numBytesRead = din.read(targetData, 0, targetData.length);

    if (numBytesRead == -1) {
        break;
    }

    double[] convertedData;
    // Conversion code goes here...

    processAudio(convertedData);
}

到目前为止,我已经在本网站和其他网站上查看了不同问题的不同答案。 我试过使用ByteBuffer和位转换,但是他们两个都没有给我看起来正确的结果(我的另一个成员在Python中的同一个文件上做了同样的事情所以我有一个参考结果应该是什么差不多......

我错过了什么? 如何正确地将字节转换为双精度? 如果我想在targetData中只捕获32ms的文件,targerData的长度应该是多少? 那么convertData的长度是多少?

提前致谢。

使用NIO缓冲区的转换不应该那么难。 您所要做的就是应用一个因子来从16位范围标准化到[-1.0…1.0]范围。

嗯, 这不是那么容易 ,但对于大多数实际目的,决定一个因素就足够了:

AudioFormat decodedFormat = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 
                                            8000, 16, 1, 2, 8000, true);
try(AudioInputStream in  = AudioSystem.getAudioInputStream(WAVfile);
    AudioInputStream din = AudioSystem.getAudioInputStream(decodedFormat, in);
    ReadableByteChannel inCh = Channels.newChannel(din)) {

    ByteBuffer inBuf=ByteBuffer.allocate(256);
    final double factor=2.0/(1<<16);
    while(inCh.read(inBuf) != -1) {
        inBuf.flip();
        double[] convertedData=new double[inBuf.remaining()/2];
        DoubleBuffer outBuf=DoubleBuffer.wrap(convertedData);
        while(inBuf.remaining()>=2) {
            outBuf.put(inBuf.getShort()*factor);
        }
        assert !outBuf.hasRemaining();
        inBuf.compact();
        processAudio(convertedData);
    }
}

上面的解决方案有效地使用了…/(double)0x8000变体。 由于我不知道processAudio对提供的缓冲区做了什么,例如它是否保留对它的引用,循环在每次迭代中分配一个新的缓冲区,但是应该很容易将它更改为可重用的缓冲区。 使用预先分配的缓冲区时,您只需要注意读取/转换的双精度数。

首先,阅读有关样本AudioFormat.Encoding.PCM_SIGNEDBigEndian的格式,然后了解java int(此数字的格式)。 然后使用二进制移位运算符>><<正确移动字节(将其中一个字节向左移8位 - 这样它将表示整数的高位字节 - 需要移位的字节取决于是否为Little或Big Endian,Big Endian意味着包含更重要部分的字节位于字节数组数组的末尾 - 所以你应该将第二个字节从数组8位移到左边),然后用+|求和结果| 运算符转换为一个int变量,然后你需要将int除以你想要的双精度范围。 假设您想要范围-1 ... + 1,那么您应该将整数除以等于32768的整数。

我会在这里发布代码,但我现在没有它。 这是我遵循的指示。

例如,我使用以下方法成功获取立体声音频数据:

AudioFormat format = new AudioFormat(8000, 16, 2, true, false);

然后转换它们:

   int l = (short) ((readedData[i*4+1]<<8)|readedData[i*4+0]);
   int r = (short) ((readedData[i*4+3]<<8)|readedData[i*4+2]);

所以你的缩放应该是:

   double scaledL = l/32768d;
   double scaledR = r/32768d;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM