最近做錄音的時(shí)候?yàn)榱撕桶沧慷私y(tǒng)一离唐,需要將pcm格式的錄音文件轉(zhuǎn)為wav格式的文件族沃。
背景知識(shí)
PCM
PCM (Pulse Code Modulation----脈沖調(diào)制錄音)摹量。所謂PCM錄音就是 將聲音等模擬信號(hào)變成符號(hào)化的脈沖列泰演,再予以記錄。PCM信號(hào)時(shí)有[1]止剖、[0]等結(jié)構(gòu)符號(hào)構(gòu)成的數(shù)字信號(hào)亮靴,而未經(jīng)過(guò)任何編碼和壓縮處理馍盟。與模擬信號(hào)比,它不易受傳送系統(tǒng)的雜波及失真的影響茧吊。動(dòng)態(tài)范圍寬贞岭,可得到音質(zhì)想到好的效果。PCM是無(wú)損的音頻格式搓侄。
WAV
WAV全稱(chēng)WAVE, .wav是其拓展名瞄桨,它是一種無(wú)損的音頻格式文件,WAV符合RIFF(Resource Interchange File Format)規(guī)范讶踪。所有的WAV都有一個(gè)文件頭芯侥,這個(gè)文件頭是音頻流的編碼參數(shù)。WAV對(duì)音頻的編碼沒(méi)有硬性規(guī)定乳讥,除了PCM之外柱查,還有幾乎所有支持ACM規(guī)范的編碼都可以為WAV音頻流進(jìn)行編碼。
PCM和WAV的關(guān)系
PCM是無(wú)損WAV文件中音頻數(shù)據(jù)的一種編碼方式云石,pcm加上文件頭就可以轉(zhuǎn)為wav格式唉工,但wav還可以用其他方式編碼。
Swift代碼如下:
class ConvertPcmToWaveTool {
// 緩存的音頻大小
private var mBufferSize = 44100 * 1 * 2
// 采樣率
private var mSampleRate = 44100
// 聲道
private var mChannel = 1
func pcmToWav(inFileName: String, outFileName: String) {
guard let input: URL = URL(string: inFileName) else { return }
guard let output: URL = URL(string: outFileName) else { return }
var totalAudioLen: Int64
var totalDataLen: Int64
let longSampleRate: Int64 = Int64(mSampleRate)
let channels = 1
let byteRate: Int64 = Int64(16 * mSampleRate * channels / 8)
totalAudioLen = Int64(inFileName.getFileSize())
print("---------totalAudioLen---------\(totalAudioLen)")
totalDataLen = totalAudioLen + 36
writewaveFileHeader(output: output, totalAudioLen: totalAudioLen, totalDataLen: totalDataLen, longSampleRate: longSampleRate, channels: channels, byteRate: byteRate)
guard let readHandler = try? FileHandle(forReadingFrom: input) else { return }
guard let writeHandler = try? FileHandle(forWritingTo: output) else { return }
// 文件頭占44字節(jié)留晚,偏移后才寫(xiě)入pcm數(shù)據(jù)
writeHandler.seek(toFileOffset: 44)
let data = readHandler.readDataToEndOfFile()
writeHandler.write(data)
print("---getFileSize----\(outFileName.getFileSize())---")
}
func writewaveFileHeader(output: URL, totalAudioLen: Int64, totalDataLen: Int64, longSampleRate: Int64, channels: Int, byteRate: Int64) {
var header: [UInt8] = Array(repeating: 0, count: 44)
// RIFF/WAVE header
header[0] = UInt8(ascii: "R")
header[1] = UInt8(ascii: "I")
header[2] = UInt8(ascii: "F")
header[3] = UInt8(ascii: "F")
header[4] = (UInt8)(totalDataLen & 0xff)
header[5] = (UInt8)((totalDataLen >> 8) & 0xff)
header[6] = (UInt8)((totalDataLen >> 16) & 0xff)
header[7] = (UInt8)((totalDataLen >> 24) & 0xff)
//WAVE
header[8] = UInt8(ascii: "W")
header[9] = UInt8(ascii: "A")
header[10] = UInt8(ascii: "V")
header[11] = UInt8(ascii: "E")
// 'fmt' chunk
header[12] = UInt8(ascii: "f")
header[13] = UInt8(ascii: "m")
header[14] = UInt8(ascii: "t")
header[15] = UInt8(ascii: " ")
// 4 bytes: size of 'fmt ' chunk
header[16] = 16
header[17] = 0
header[18] = 0
header[19] = 0
// format = 1
header[20] = 1
header[21] = 0
header[22] = UInt8(channels)
header[23] = 0
header[24] = (UInt8)(longSampleRate & 0xff)
header[25] = (UInt8)((longSampleRate >> 8) & 0xff)
header[26] = (UInt8)((longSampleRate >> 16) & 0xff)
header[27] = (UInt8)((longSampleRate >> 24) & 0xff)
header[28] = (UInt8)(byteRate & 0xff)
header[29] = (UInt8)((byteRate >> 8) & 0xff)
header[30] = (UInt8)((byteRate >> 16) & 0xff)
header[31] = (UInt8)((byteRate >> 24) & 0xff)
// block align
header[32] = UInt8(2 * 16 / 8)
header[33] = 0
// bits per sample
header[34] = 16
header[35] = 0
//data
header[36] = UInt8(ascii: "d")
header[37] = UInt8(ascii: "a")
header[38] = UInt8(ascii: "t")
header[39] = UInt8(ascii: "a")
header[40] = UInt8(totalAudioLen & 0xff)
header[41] = UInt8((totalAudioLen >> 8) & 0xff)
header[42] = UInt8((totalAudioLen >> 16) & 0xff)
header[43] = UInt8((totalAudioLen >> 24) & 0xff)
guard let writeHandler = try? FileHandle(forWritingTo: output) else { return }
let data = Data(bytes: header)
writeHandler.write(data)
}
}
extension String {
/// 計(jì)算文件夾大小(有單文件計(jì)算)
func getFileSize() -> UInt64 {
var size: UInt64 = 0
let fileManager = FileManager.default
var isDir: ObjCBool = false
let isExists = fileManager.fileExists(atPath: self, isDirectory: &isDir)
// 判斷文件存在
if isExists {
// 是否為文件夾
if isDir.boolValue {
// 迭代器 存放文件夾下的所有文件名
let enumerator = fileManager.enumerator(atPath: self)
for subPath in enumerator! {
// 獲得全路徑
let fullPath = self.appending("/\(subPath)")
do {
let attr = try fileManager.attributesOfItem(atPath: fullPath)
size += attr[FileAttributeKey.size] as! UInt64
} catch {
print("error :\(error)")
}
}
} else { // 單文件
do {
let attr = try fileManager.attributesOfItem(atPath: self)
size += attr[FileAttributeKey.size] as! UInt64
} catch {
print("error :\(error)")
}
}
}
return size
}
}
如果處理正常酵紫,打印出來(lái)最終輸出的wav文件大小比pcm文件大44字節(jié)