如何混合PCM音频源(Java)?

4

以下是我目前正在使用的内容:

for (int i = 0, numSamples = soundBytes.length / 2; i < numSamples; i += 2)
{
    // Get the samples.
    int sample1 = ((soundBytes[i] & 0xFF) << 8) | (soundBytes[i + 1] & 0xFF);   // Automatically converts to unsigned int 0...65535                                 
    int sample2 = ((outputBytes[i] & 0xFF) << 8) | (outputBytes[i + 1] & 0xFF); // Automatically converts to unsigned int 0...65535

    // Normalize for simplicity.
    float normalizedSample1 = sample1 / 65535.0f;
    float normalizedSample2 = sample2 / 65535.0f;

    float normalizedMixedSample = 0.0f;

    // Apply the algorithm.
    if (normalizedSample1 < 0.5f && normalizedSample2 < 0.5f)
        normalizedMixedSample = 2.0f * normalizedSample1 * normalizedSample2;
    else
        normalizedMixedSample = 2.0f * (normalizedSample1 + normalizedSample2) - (2.0f * normalizedSample1 * normalizedSample2) - 1.0f;

    int mixedSample = (int)(normalizedMixedSample * 65535);

    // Replace the sample in soundBytes array with this mixed sample.
    soundBytes[i] = (byte)((mixedSample >> 8) & 0xFF);
    soundBytes[i + 1] = (byte)(mixedSample & 0xFF);
}

据我所知,这是对该页面定义的算法的准确描述:http://www.vttoth.com/CMS/index.php/technical-notes/68。但是,仅仅将声音和静音(全0)混合在一起会导致听起来非常不正确,也许最好将其描述为更高频和更响亮。请帮我确定是否正确实现了算法,或者是否需要使用不同的算法/方法?请注意不要删除HTML标签。
1个回答

3
在这篇文章中,作者假设AB代表整个音频流。更具体地说,X表示流X中所有样本的最大绝对值,其中XAB。因此,他的算法会扫描两个流的全部内容,计算出每个流的最大绝对值样本,然后进行缩放,使输出理论上峰值为1.0。要实现这个算法,您需要多次通过数据,如果您的数据正在流式传输,则无法使用该算法。
以下是我认为该算法如何工作的示例。它假定样本已经转换为浮点数,以避免转换代码错误的问题。稍后我将解释其中的问题:
 double[] samplesA = ConvertToDoubles(samples1);
 double[] samplesB = ConvertToDoubles(samples2);
 double A = ComputeMax(samplesA);
 double B = ComputeMax(samplesB);

 // Z always equals 1 which is an un-useful bit of information.
 double Z = A+B-A*B;

 // really need to find a value x such that xA+xB=1, which I think is:
 double x = 1 / (Math.sqrt(A) * Math.sqrt(B));

 // Now mix and scale the samples
 double[] samples = MixAndScale(samplesA, samplesB, x);

混合和缩放:
 double[] MixAndScale(double[] samplesA, double[] samplesB, double scalingFactor)
 {
     double[] result = new double[samplesA.length];
     for (int i = 0; i < samplesA.length; i++)
         result[i] = scalingFactor * (samplesA[i] + samplesB[i]);
 }

计算最高峰值:

double ComputeMaxPeak(double[] samples)
{
    double max = 0;
    for (int i = 0; i < samples.length; i++)
    {
        double x = Math.abs(samples[i]);
        if (x > max)
            max = x;
    }
    return max;
}

转换。注意我使用的是“short”以确保符号位正确保留:

double[] ConvertToDouble(byte[] bytes)
{
    double[] samples = new double[bytes.length/2];
    for (int i = 0; i < samples.length; i++)
    {
        short tmp = ((short)bytes[i*2])<<8 + ((short)(bytes[i*2+1]);
        samples[i] = tmp / 32767.0;
    }
    return samples;
}

尝试了这段代码。经过几次编译和缺少括号的错误后,当两个音频源混合时仍然存在白噪声。还有其他遗漏的地方吗? - Raziza O
经过长时间处理这些问题后,我决定不再使用这种转换方式,而是使用 ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts); 对 shorts 进行操作,然后再将其转换回 bytes ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(shorts);。这样做非常完美。 - Raziza O

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接