java 代码转换音频数据格式(字节数组转换)

目前音频格式有很多,本文针对PCM 音频文件进行转换

所谓pcm 就是将声音等模拟信号变成符号化的脉冲列,再予以记录。PCM信号是由[1]、[0]等符号构成的数字信号。与模拟信号比,它不易受传送系统的杂波及失真的影响。动态范围宽,可得到音质相当好的影响效果。PCM轨迹与视频轨迹不同,故也可用于后期录音。但在Hi8的摄像机中要实现PCM,必须通过其他的专业器材,仅靠摄像机是无法达到该效果的。

 

alaw 与 ulaw 都为pcm 文件编码格式

参考:

https://www.2cto.com/kf/201504/390076.html

 

8k8bit pcm 文件指的是采样点为8000,采用精度为8bit, bit可以理解为每个采样点大小

这种音频格式的录音为 8000*8*1(声道数)/8/1024 = 8kbs

 

同理8k16bit pcm,16k16bit pcm文件

 

下面介绍下 这些音频格式利用java代码相互转换的过程

由于网上有很多工具能够直接将完整的音频文件直接转换为相应的格式,这里就不多介绍了,

下面介绍的是如果音频文件并非完整的,而是一段二进制的数组

比如说实时的语音流转换

 

1 声道数的转换

 

8K8bit立体音 分离 成单声道(立体语音是8kAlaw)

立体音本身的采样就是左声道采样一个点,右声道采样一个记录在一起,知道这个原理,分离就很简单了。

 

/**
 * 立体音 分离 成单声道(立体语音是8kAlaw)
 * 
 * @param stereoBytes
 * @return
 * @throws IOException
 */
public static byte[][] stereo2MonoForAlaw(byte[] stereoBytes) throws IOException {

  byte[][] objList = new byte[2][];

  try (ByteArrayOutputStream outputStreamL = new ByteArrayOutputStream()) {
    try (ByteArrayOutputStream outputStreamR = new ByteArrayOutputStream()) {

      for (int i = 0; i < stereoBytes.length; i = i + 2) {
        outputStreamL.write(stereoBytes[i]);
        outputStreamR.write(stereoBytes[i + 1]);
      }
      outputStreamL.flush();
      outputStreamR.flush();
      objList[0] = outputStreamL.toByteArray();
      objList[1] = outputStreamR.toByteArray();
    }
  }
  return objList;
}

 

 

 

16K16bit立体音 分离 成单声道(立体语音是16k16bit)

16k16bit的录音文件,只是用两个字节16bit描述一个采样点,采样的规则和8k8bit一致

 

/**
 * 立体音 分离 成单声道(立体语音是16k16bit)
 * 
 * @param stereoBytes
 * @return
 * @throws IOException
 */
public static byte[][] stereo2MonoFor16Bit(byte[] stereoBytes) throws IOException {

  byte[][] objList = new byte[2][];

  try (ByteArrayOutputStream outputStreamL = new ByteArrayOutputStream()) {
    try (ByteArrayOutputStream outputStreamR = new ByteArrayOutputStream()) {

      for (int i = 0; i < stereoBytes.length; i = i + 4) {
        outputStreamL.write(stereoBytes[i]);
        outputStreamL.write(stereoBytes[i + 1]);
        outputStreamR.write(stereoBytes[i + 2]);
        outputStreamR.write(stereoBytes[i + 3]);
      }
      outputStreamL.flush();
      outputStreamR.flush();
      objList[0] = outputStreamL.toByteArray();
      objList[1] = outputStreamR.toByteArray();
    }
  }
  return objList;
}

 

单声道合成立体音(单声道为8k16bit 音频格式)

 

同理,将两个单声道录音按照左声道采样一个点后,右声道在采样一个排列的规则

/**
 * 单声道(8K16bit) 合成 立体音
 * 
 * @param left
 * @param right
 * @return byte数组
 */
public static byte[] line2stereo(byte[] left, byte[] right) {
  int length = Math.min(left.length, right.length) * 2;
  byte[] stereoBytes = new byte[length];
  for (int i = 0, j = 0, k = 0; i < length;) {
    stereoBytes[i++] = left[j++];
    stereoBytes[i++] = left[j++];
    stereoBytes[i++] = right[k++];
    stereoBytes[i++] = right[k++];
  }
  return stereoBytes;
}

2 采样点转换

8k16bit -->16k16bit

 

策略1:将采样点复制一份

/**
   * 将采用点复制,8k-->16k
   * @param orig
   * @return
   */
  public static byte[] convert8kTo16k(byte[] orig) {
    byte[] dest = new byte[] {};
    for (int j = 0; j < orig.length; j = j + 2) {
      byte[] byte2 = new byte[2];
      byte2[1] = orig[j + 1];
      byte2[0] = orig[j];
      dest = append(dest, byte2);
      dest = append(dest, byte2);
    }
    return dest;
  }

 

策略2:取两个采样点中间值添加到音频数据,提升转换后效果

 /**
   * 取两个采样点中间值添加到音频数据,增加转换后效果
   * 8k16bit->16k16bit
   * @param orig
   * @return
   */
  public static byte[] convert8000To16000(byte[] orig){
    byte[] dest = new byte[]{};
    for (int j = 0; j < orig.length; j = j + 2) {
      byte[] byte1 = new byte[2];
      byte1[1] = orig[j + 1];
      byte1[0] = orig[j];

      dest = append(dest, byte1);
      if (j+2>=orig.length){
        dest = append(dest,byte1);
      }else {
        short sample = toShort(byte1);
        byte[] byte2 = new byte[2];
        byte2[0] = orig[j+2];
        byte2[1] = orig[j+3];
        short sample1 = toShort(byte2);
        short sample2 = (short) ((sample+sample1)/2);
        byte[] byte3= toByte(sample2);
        dest = append(dest, byte3);
      }
    }
    return dest;
  }

 

3采样精度转换

采样精度8bit 可以理解为一个byte 描述了一个采样点,而采样精度为16bit 则可以理解16bit即2个byte 描述一个采样点

8k8bit -->8k16bit

 /**
   * 8bit ->16bit
   * @param orig
   * @return
           */
  public static byte[] convert8bitTo16bit(byte[] orig){
    byte[] dest = new byte[]{};
    for (int i=0;i<orig.length;i++){
      // 转无符号整数
//      int sample = orig[i] & 0xff;
//      sample = sample - 128;
//      int s1 = (int) (sample * 1.0 / 256 * Short.MAX_VALUE);
      int s1 = (orig[i]+128)<<8;
      byte[] byte2 = new byte[2];
      byte2[1] = (byte) ((s1 << 16) >> 24);
      byte2[0] = (byte) ((s1 << 24) >> 24);
      dest = append(dest, byte2);
    }
    return dest;
  }

 

4 采样点和采样精度同时转换

/**
   * 拼接8k8bit byte[] 转换成16k6bit byte[]
   * 
   * @param orig 原始byte[]
   */
  public static byte[] convertTo16k16Bit(byte[] orig) {
    byte[] dest = new byte[] {};
    for (int j = 0; j < orig.length; j++) {
      // 转无符号整数
      int sample = orig[j] & 0xff;
      // 转成正负值
      sample = sample - 128;

      // 等比缩放,转化成16bit
      int s1 = (int) (sample * 1.0 / 256 * Short.MAX_VALUE);
      byte[] byte2 = new byte[2];
      byte2[1] = (byte) ((s1 << 16) >> 24);
      byte2[0] = (byte) ((s1 << 24) >> 24);

      // TODO 采样点 8k->16k,复制一个采样点,可使用其他算法实现(统计学公式,计算趋势)
//      dest = append(dest, byte2);
//      dest = append(dest, byte2);

      dest = append(dest, byte2);
      // TODO 采样点 8k->16k
      int sample2 = (orig[j + 2 > orig.length ? j : j + 1] & 0xff) - 128;
      int s2 = (int) ((sample2 * 1.0 / 256 * Short.MAX_VALUE) + s1) / 2;
      byte2 = new byte[2];
      byte2[1] = (byte) ((s2 << 16) >> 24);
      byte2[0] = (byte) ((s2 << 24) >> 24);
      dest = append(dest, byte2);
    }
    return dest;
  }

 


java 代码将音频格式按照字节方式转换的代码到此也差不多了,下面补全上述方法中可能用到的一些其他方法

 

 

 

/**
 * short->byte
 * @param s
 * @return
 */
public static byte[] toByte(short s){
  byte[] byte2 = new byte[2];
  byte2[1] = (byte) ((s << 16) >> 24);
  byte2[0] = (byte) ((s << 24) >> 24);
  return byte2;
}


/**
 * 拼接byte[]
 * 
 * @param orig 原始byte[]
 * @param dest 需要拼接的数据
 * @return byte[]
 */
public static byte[] append(byte[] orig, byte[] dest) {

  byte[] newByte = new byte[orig.length + dest.length];

  System.arraycopy(orig, 0, newByte, 0, orig.length);
  System.arraycopy(dest, 0, newByte, orig.length, dest.length);

  return newByte;

}

 


  /**
   * byte->short
   * @param b
   * @return
   */
  public static short toShort(byte[] b) {
    return(short)((b[1]<<8)+(b[0]<<0));
  }

 

 

 

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
下面是一个简单的 Java 代码示例,可以使用降噪算法来处理音频文件。这个示例使用了一个名为“SpectralSubtraction”的算法,它将信号的频谱进行分析,并且从中剔除噪声。这个算法需要一些音频处理的基础知识,但是代码本身并不复杂。 首先,你需要导入 Java音频处理库: ``` import javax.sound.sampled.*; ``` 然后,你可以使用以下代码来读取你想要处理的音频文件: ``` AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(new File("input.wav")); ``` 其中,"input.wav" 是你要读取的音频文件的路径。 接下来,你需要定义一些常量。这些常量将会影响你的降噪算法的效果。例如,你可以根据需要修改采样率、窗口大小和噪声门限等参数。 ``` final int sampleRate = 16000; final int windowSize = 512; final int hopSize = 256; final double noiseThreshold = 0.15; ``` 然后,你可以使用以下代码来进行降噪处理: ``` // 读取音频数据 byte[] audioBytes = new byte[(int) (audioInputStream.getFrameLength() * audioInputStream.getFormat().getFrameSize())]; audioInputStream.read(audioBytes); // 将字节数组转换为采样数组 double[] audioSamples = new double[audioBytes.length / 2]; for (int i = 0; i < audioSamples.length; i++) { audioSamples[i] = ((short) ((audioBytes[i * 2] & 0xff) | (audioBytes[i * 2 + 1] << 8))) / 32768.0; } // 计算频谱 FFT fft = new FFT(windowSize); int numFrames = (audioSamples.length - windowSize) / hopSize + 1; double[][] frames = new double[numFrames][windowSize]; for (int i = 0; i < numFrames; i++) { for (int j = 0; j < windowSize; j++) { frames[i][j] = audioSamples[i * hopSize + j]; } fft.forward(frames[i]); } // 计算噪声门限 double[] spectrum = new double[windowSize / 2 + 1]; double[] noiseThresholds = new double[spectrum.length]; for (int i = 0; i < spectrum.length; i++) { double sum = 0.0; for (int j = 0; j < numFrames; j++) { double magnitude = Math.sqrt(frames[j][2 * i] * frames[j][2 * i] + frames[j][2 * i + 1] * frames[j][2 * i + 1]); spectrum[i] += magnitude / numFrames; if (magnitude > noiseThreshold) { sum += 1.0; } } noiseThresholds[i] = (sum / numFrames) * noiseThreshold; } // 进行降噪 for (int i = 0; i < numFrames; i++) { for (int j = 0; j < windowSize / 2 + 1; j++) { double magnitude = Math.sqrt(frames[i][2 * j] * frames[i][2 * j] + frames[i][2 * j + 1] * frames[i][2 * j + 1]); if (magnitude < noiseThresholds[j]) { frames[i][2 * j] = 0.0; frames[i][2 * j + 1] = 0.0; } } fft.inverse(frames[i]); } // 合并所有帧 double[] denoisedSamples = new double[(numFrames - 1) * hopSize + windowSize]; for (int i = 0; i < numFrames; i++) { for (int j = 0; j < windowSize; j++) { if (i == 0) { denoisedSamples[j] = frames[i][j]; } else { denoisedSamples[i * hopSize + j] += frames[i][j]; } } } // 将采样数组转换字节数组 byte[] denoisedBytes = new byte[audioBytes.length]; for (int i = 0; i < denoisedSamples.length; i++) { short sample = (short) (denoisedSamples[i] * 32768.0); denoisedBytes[i * 2] = (byte) (sample & 0xff); denoisedBytes[i * 2 + 1] = (byte) (sample >> 8); } // 将降噪后的音频数据写入文件 AudioFormat audioFormat = new AudioFormat(sampleRate, 16, 1, true, false); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(denoisedBytes); AudioInputStream denoisedAudioInputStream = new AudioInputStream(byteArrayInputStream, audioFormat, denoisedBytes.length / audioFormat.getFrameSize()); AudioSystem.write(denoisedAudioInputStream, AudioFileFormat.Type.WAVE, new File("output.wav")); ``` 其中,"output.wav" 是处理后的音频文件的路径。 这个示例中的降噪算法只是其中一种,你可以根据需要使用其他算法。请注意,音频处理是一个非常复杂的领域,需要深入的知识和经验才能取得好的效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值