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

如何解决混合两个 16 位编码立体声 PCM 样本,导致生成的音频出现噪音和失真

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

  1. 对于麦克风声音:

    audioRecord =
             new AudioRecord(MediaRecorder.AudioSource.DEFAULT,44100,AudioFormat.CHANNEL_IN_STEREO,AudioFormat.ENCODING_PCM_16BIT,(AudioRecord.getMinBufferSize(44100,AudioFormat.ENCODING_PCM_16BIT)*5));
    
  2. 对于内部声音:

    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.ENCODING_PCM_16BIT)*5))
                     .build();
    

为了从 audioRecord 对象中读取,我们创建了单独的框架对象(称为框架的自定义对象)-

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(Linked List)来添加我们从 read 函数中获得的这些帧。

private LinkedList internalAudioQueue = new LinkedList(); private LinkedListmicAudioQueue = new 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);
    }
}

现在我们在 Hendrik 的帮助下以这种方式混合来自两个源的 ByteBuffer 形式的音频样本。

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,aa,a.length);
        System.arraycopy(b,bb,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,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,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 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;
}

我得到的混合样本既有失真又有噪声。无法弄清楚需要做什么才能删除它。任何帮助在这里表示赞赏。 提前致谢!

解决方法

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

如果您有样本 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;
}

这对你有用吗?

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-