目前音频格式有很多,本文针对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));
}