java 程式碼轉換音訊資料格式(位元組陣列轉換)
阿新 • • 發佈:2019-02-07
目前音訊格式有很多,本文針對PCM 音訊檔案進行轉換
所謂pcm 就是將聲音等模擬訊號變成符號化的脈衝列,再予以記錄。PCM訊號是由[1]、[0]等符號構成的數字訊號。與模擬訊號比,它不易受傳送系統的雜波及失真的影響。動態範圍寬,可得到音質相當好的影響效果。PCM軌跡與視訊軌跡不同,故也可用於後期錄音。但在Hi8的攝像機中要實現PCM,必須通過其他的專業器材,僅靠攝像機是無法達到該效果的。
alaw 與 ulaw 都為pcm 檔案編碼格式
參考:
8k8bit pcm 檔案指的是取樣點為8000,採用精度為8bit, bit可以理解為每個取樣點大小
這種音訊格式的錄音為 8000*8*1(聲道數)/8/1024 = 8kbs
同理8k16bit pcm,16k16bit pcm檔案
下面介紹下 這些音訊格式利用java程式碼相互轉換的過程
由於網上有很多工具能夠直接將完整的音訊檔案直接轉換為相應的格式,這裡就不多介紹了,
下面介紹的是如果音訊檔案並非完整的,而是一段二進位制的陣列
比如說實時的語音流轉換
1 聲道數的轉換
8K8bit立體音 分離 成單聲道(立體語音是8kAlaw)
16K16bit立體音 分離 成單聲道(立體語音是16k16bit) 16k16bit的錄音檔案,只是用兩個位元組16bit描述一個取樣點,取樣的規則和8k8bit一致
單聲道合成立體音(單聲道為8k16bit 音訊格式) 同理,將兩個單聲道錄音按照左聲道取樣一個點後,右聲道在取樣一個排列的規則
策略2:取兩個取樣點中間值新增到音訊資料,提升轉換後效果
3取樣精度轉換 取樣精度8bit 可以理解為一個byte 描述了一個取樣點,而取樣精度為16bit 則可以理解16bit即2個byte 描述一個取樣點 8k8bit -->8k16bit
4 取樣點和取樣精度同時轉換
java 程式碼將音訊格式按照位元組方式轉換的程式碼到此也差不多了,下面補全上述方法中可能用到的一些其他方法
立體音本身的取樣就是左聲道取樣一個點,右聲道取樣一個記錄在一起,知道這個原理,分離就很簡單了。
/** * 立體音 分離 成單聲道(立體語音是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;
}