当前位置: 首页 > 知识库问答 >
问题:

混合两个16位编码的立体声PCM样本,导致产生的音频中出现噪声和失真

刘成礼
2023-03-14

我从两个来源获得了两个不同的音频样本。

>

  • 对于麦克风声音:

    audioRecord =
             new AudioRecord(MediaRecorder.AudioSource.DEFAULT, 44100, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT,
                     (AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT)*5));
    

    对于内部声音:

    audioRecord = new AudioRecord.Builder()
                     .setAudioPlaybackCaptureConfig(config)
                     .setAudioFormat(new AudioFormat.Builder()
                             .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                             .setSampleRate(44100)
                             .setChannelMask(AudioFormat.CHANNEL_IN_STEREO)
                             .build())
                     .setBufferSizeInBytes((AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT)*5))
                     .build();
    

    对于从audioBook对象读取,我们创建单独的帧对象(自定义对象称为帧)-

    private ByteBuffer pcmBuffer = ByteBuffer.allocateDirect(4096);
    private Frame read() {
      pcmBuffer.rewind();
      int size = audioRecord.read(pcmBuffer, pcmBuffer.remaining());
      if (size <= 0) {
       return null;
      }
        return new Frame(pcmBuffer.array(),
               pcmBuffer.arrayOffset(), size);
    }
    

    我们创建了两个单独的LL(链接列表)来添加我们从读函数中获得的这些帧。

    private LinkedList internalAudioQueue=新建LinkedList

    public void onFrameReceived(Frame frame, boolean isInternalAudio) {
        if (isInternalAudio) {
            internalAudioQueue.add(frame);
        } else {
            microphoneAudioQueue.add(frame);
        }
        checkAndPoll();
    }
    

    每次我们在各自的LL中添加一个帧时,我们调用下面的checkAndPoll()函数,并根据具体情况将帧传递给audioEncoder。

    public void checkAndPoll() {
        Frame frame1 = internalAudioQueue.poll();
        Frame frame2 = microphoneAudioQueue.poll();
        if (frame1 == null && frame2 != null) {
            audioEncoder.inputPCMData(frame2);
        } else if (frame1 != null && frame2 == null) {
            audioEncoder.inputPCMData(frame1);
        } else if (frame1 != null && frame2 != null) {
            Frame frame = new Frame(PCMUtil.mix(frame1.getBuffer(), frame2.getBuffer(), frame1.getSize(), frame2.getSize(), false), frame1.getOrientation(), frame1.getSize());
            audioEncoder.inputPCMData(frame);
        }
    }
    

    现在,我们以ByteBuffer的形式混合来自两个源的音频样本,以这种方式从这个链接中获得Hendrik的帮助。

    public static byte[] mix(final byte[] a, final byte[] b, final boolean bigEndian) {
        final byte[] aa;
        final byte[] bb;
    
        final int length = Math.max(a.length, b.length);
        // ensure same lengths
        if (a.length != b.length) {
            aa = new byte[length];
            bb = new byte[length];
            System.arraycopy(a, 0, aa, 0, a.length);
            System.arraycopy(b, 0, bb, 0, b.length);
        } else {
            aa = a;
            bb = b;
        }
    
        // convert to samples
        final int[] aSamples = toSamples(aa, bigEndian);
        final int[] bSamples = toSamples(bb, bigEndian);
    
        // mix by adding
        final int[] mix = new int[aSamples.length];
        for (int i=0; i<mix.length; i++) {
            mix[i] = aSamples[i] + bSamples[i];
            // enforce min and max (may introduce clipping)
            mix[i] = Math.min(Short.MAX_VALUE, mix[i]);
            mix[i] = Math.max(Short.MIN_VALUE, mix[i]);
        }
    
        // convert back to bytes
        return toBytes(mix, bigEndian);
    }
    
    private static int[] toSamples(final byte[] byteSamples, final boolean bigEndian) {
        final int bytesPerChannel = 2;
        final int length = byteSamples.length / bytesPerChannel;
        if ((length % 2) != 0) throw new IllegalArgumentException("For 16 bit audio, length must be even: " + length);
        final int[] samples = new int[length];
        for (int sampleNumber = 0; sampleNumber < length; sampleNumber++) {
            final int sampleOffset = sampleNumber * bytesPerChannel;
            final int sample = bigEndian
                    ? byteToIntBigEndian(byteSamples, sampleOffset, bytesPerChannel)
                    : byteToIntLittleEndian(byteSamples, sampleOffset, bytesPerChannel);
            samples[sampleNumber] = sample;
        }
        return samples;
    }
    
    private static byte[] toBytes(final int[] intSamples, final boolean bigEndian) {
        final int bytesPerChannel = 2;
        final int length = intSamples.length * bytesPerChannel;
        final byte[] bytes = new byte[length];
        for (int sampleNumber = 0; sampleNumber < intSamples.length; sampleNumber++) {
            final byte[] b = bigEndian
                    ? intToByteBigEndian(intSamples[sampleNumber], bytesPerChannel)
                    : intToByteLittleEndian(intSamples[sampleNumber], bytesPerChannel);
            System.arraycopy(b, 0, bytes, sampleNumber * bytesPerChannel, bytesPerChannel);
        }
        return bytes;
    }
    
    // from https://github.com/hendriks73/jipes/blob/master/src/main/java/com/tagtraum/jipes/audio/AudioSignalSource.java#L238
    private static int byteToIntLittleEndian(final byte[] buf, final int offset, final int bytesPerSample) {
        int sample = 0;
        for (int byteIndex = 0; byteIndex < bytesPerSample; byteIndex++) {
            final int aByte = buf[offset + byteIndex] & 0xff;
            sample += aByte << 8 * (byteIndex);
        }
        return (short)sample;
    }
    
    // from https://github.com/hendriks73/jipes/blob/master/src/main/java/com/tagtraum/jipes/audio/AudioSignalSource.java#L247
    private static int byteToIntBigEndian(final byte[] buf, final int offset, final int bytesPerSample) {
        int sample = 0;
        for (int byteIndex = 0; byteIndex < bytesPerSample; byteIndex++) {
            final int aByte = buf[offset + byteIndex] & 0xff;
            sample += aByte << (8 * (bytesPerSample - byteIndex - 1));
        }
        return (short)sample;
    }
    
    private static byte[] intToByteLittleEndian(final int sample, final int bytesPerSample) {
        byte[] buf = new byte[bytesPerSample];
        for (int byteIndex = 0; byteIndex < bytesPerSample; byteIndex++) {
            buf[byteIndex] = (byte)((sample >>> (8 * byteIndex)) & 0xFF);
        }
        return buf;
    }
    
    private static byte[] intToByteBigEndian(final int sample, final int bytesPerSample) {
        byte[] buf = new byte[bytesPerSample];
        for (int byteIndex = 0; byteIndex < bytesPerSample; byteIndex++) {
            buf[byteIndex] = (byte)((sample >>> (8 * (bytesPerSample - byteIndex - 1))) & 0xFF);
        }
        return buf;
    }
    

    我得到的混合样本既有失真又有噪音。无法弄清楚需要做些什么来去除它。非常感谢这里的任何帮助。提前谢谢!

  • 共有1个答案

    樊宏邈
    2023-03-14

    我认为如果你在混合,你应该取两者的(加权)平均值。

    如果您有一个128和128的样本,结果仍然是128,而不是256,这可能超出范围。

    所以只需将代码更改为:

    // mix by adding
    final int[] mix = new int[aSamples.length];
    for (int i=0; i<mix.length; i++) {
        // calculating the average
        mix[i] = (aSamples[i] + bSamples[i]) >> 1;
    }
    

    这对你有用吗?

     类似资料:
    • 我正在构建一个相当简单的Android应用程序(sdk修订版14:ICS),它允许用户一次选择两个音频片段(都是RIFF/WAV格式、little endian、签名PCM-16位编码),并以各种方式组合它们以创建新的声音。我对这种组合使用的最基本方法如下: 然后可以通过AudioTrack类播放返回的字节数组: 使用上面代码的组合和回放的结果接近我想要的(两个样本在产生的杂交声音中仍然可以辨别)

    • 我正在开发webRTC,我正在本地网络上的两个Android设备之间进行实时流,它对我来说工作得很好,除了音质问题,声音中有噪音和回声。如果我在一端使用免提,它会变得更好,但我不想使用免提。 那么我该如何提高音质,有什么技术可以提高音质。它还表示,webRTC内置了回声消除功能,如果这是回声仍然存在的原因。

    • 我正在通过电线接收一个交错的16位PCM样本。每个样本都有签名 我把它读作Int16bit数组,让我们调用这个all_data。因此每个数组条目都是一个16位的样本。 因为它是交错的,所以我将它提取成2个通道,R-L-R-L,最后得到2个(16位)数组,大小是ALL_DATA数组的一半。 之后,我查看每个示例,并将其规范化为Float32Array,因为这是web audio API所使用的。 v

    • 问题内容: 我创建了一个pong克隆,当发生碰撞时,我想添加一些声音效果。我的问题是,考虑到整个应用程序只有90行代码,我发现的每个有关合成声音的示例都需要约30行代码。我正在寻找一种更简单的方法。有没有简单的方法来创建不同音调的提示音?持续时间无所谓。我只想要一系列不同音调的蜂鸣声。 问题答案: 这是从Java Sound 提取(并简化)的一个小示例-示例:生成音频的代码

    • 16 音频编码器 介绍当前可用的音频编码器 aac AAC(Advanced Audio Coding )编码器 当前原生(内置)编码器还处于实验阶段,而且只能支持AAC-LC(低复杂度AAC)。要使用这个编码器,必须选择 ‘experimental’或者’lower’ 因为当前还处于实验期,所以很多意外可能发生。如果需要一个更稳定的AAC编码器,参考libvo-aacenc,然而它也有一些负面报

    • 我有一个包含16个音频单声道流的MXF文件,我需要将其重新编码到一个mp4文件中,该文件包含2<=n<=16个合并输入流的声道,例如,输出声道1上的输入声道1和2,输出声道2上的输入声道9和10。这项工作将用FFMPEG完成。我阅读了文档,发现amerge和amix滤波器会很好,但它们只输出一个通道。这个问题有什么解决办法吗? 谢谢,劳拉