1、最近在項目遇到上傳音頻到服務(wù)端處理錯誤問題;當(dāng)然一般情況下如果雙端商量好格式,通過iOS系統(tǒng)的錄音框架,上傳AVAudioRecorder錄取的音頻,沒什么問題。但是工作涉及的后端比較多,格式要求必須是WAV。這時候需要將原始錄制的PCM數(shù)據(jù),轉(zhuǎn)換為WAV格式。
2、首先來看看WAV和PCM分別是什么
WAV:wav是一種無損的音頻文件格式,WAV符合 PIFF(Resource Interchange File Format)規(guī)范。所有的WAV都有一個文件頭,這個文件頭音頻流的編碼參數(shù)。WAV對音頻流的編碼沒有硬性規(guī)定,除了PCM之外,還有幾乎所有支持ACM規(guī)范的編碼都可以為WAV的音頻流進(jìn)行編碼。
PCM:PCM(Pulse Code Modulation—-脈碼調(diào)制錄音)。所謂PCM錄音就是將聲音等模擬信號變成符號化的脈沖列,再予以記錄。PCM信號是由[1]、[0]等符號構(gòu)成的數(shù)字信號,而未經(jīng)過任何編碼和壓縮處理。與模擬信號比,它不易受傳送系統(tǒng)的雜波及失真的影響。動態(tài)范圍寬,可得到音質(zhì)相當(dāng)好的影響效果。
簡單來說:wav是一種無損的音頻文件格式,pcm是沒有壓縮的編碼方式。
3、WAV和PCM的關(guān)系
WAV可以使用多種音頻編碼來壓縮其音頻流,不過我們常見的都是音頻流被PCM編碼處理的WAV,但這不表示W(wǎng)AV只能使用PCM編碼,MP3編碼同樣也可以運(yùn)用在WAV中,和AVI一樣,只要安裝好了相應(yīng)的Decode,就可以欣賞這些WAV了。在Windows平臺下,基于PCM編碼的WAV是被支持得最好的音頻格式,所有音頻軟件都能完美支持,由于本身可以達(dá)到較高的音質(zhì)的要求,因此,WAV也是音樂編輯創(chuàng)作的首選格式,適合保存音樂素材。因此,基于PCM編碼的WAV被作為了一種中介的格式,常常使用在其他編碼的相互轉(zhuǎn)換之中,例如MP3轉(zhuǎn)換成WMA。
簡單來說:pcm是無損wav文件中音頻數(shù)據(jù)的一種編碼方式,但wav還可以用其它方式編碼。
4、既然知道WAV與PCM關(guān)系了,下一步就到了轉(zhuǎn)換成wav格式,這需要手動填充wav的文件頭信息。
簡單來講:從PCM轉(zhuǎn)成WAV格式,就是在錄音后得到的PCM數(shù)據(jù)加上文件頭信息
5、頭文件的格式,我們通過一張圖表來觀察
頭文件總共44個字節(jié),這里面的信息反映的也是錄制音頻的相關(guān)信息,參數(shù)是在初始化錄音時設(shè)置的。有幾個參數(shù)需要注意:
ChunkSize: SubChunk2Size + 36 (頭文件44個字節(jié),除去RIFF占4字節(jié)和當(dāng)前占的4字節(jié))
ByteRate = ?(Sample Rate * BitsPerSample * Channels) / 8.
BlckAlign = ?(BitsPerSample * Channels) / 8
SubChunk2Size = 音頻數(shù)據(jù)大小,也就是PCMData數(shù)據(jù)大小
我這里的設(shè)置,BitsPerSample = 16 , SampleRate = 16000, channels = 1
這些參數(shù)是錄音時設(shè)置的:
NSMutableDictionary*setting = [NSMutableDictionarydictionary];
//音頻格式
setting[AVFormatIDKey] =@(kAudioFormatLinearPCM);
//錄音采樣率(Hz)(影響音頻的質(zhì)量)
setting[AVSampleRateKey] =@(16000);
//音頻通道數(shù)1或2
setting[AVNumberOfChannelsKey] =@(1);
//線性音頻的位深度8、16、24、32,每次采樣16位,2個字節(jié) 既是BitsPerSample
setting[AVLinearPCMBitDepthKey] =@(16);?
//錄音的質(zhì)量
setting[AVEncoderAudioQualityKey] = [NSNumbernumberWithInt:AVAudioQualityHigh];
6、具體實現(xiàn):
```
NSData* WriteWavFileHeader(long totalAudioLen, long totalDataLen, long longSampleRate,int channels, long byteRate) {
Byte? header[44];
header[0] = 'R';? // RIFF/WAVE header
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (Byte) (totalDataLen & 0xff);? //file-size (equals file-size - 8)
header[5] = (Byte) ((totalDataLen >> 8) & 0xff);
header[6] = (Byte) ((totalDataLen >> 16) & 0xff);
header[7] = (Byte) ((totalDataLen >> 24) & 0xff);
header[8] = 'W';? // Mark it as type "WAVE"
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
header[12] = 'f';? // Mark the format section 'fmt ' chunk
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = 16;? // 4 bytes: size of 'fmt ' chunk, Length of format data.? Always 16
header[17] = 0;
header[18] = 0;
header[19] = 0;
header[20] = 1;? // format = 1 ,Wave type PCM
header[21] = 0;
header[22] = (Byte) channels;? // channels
header[23] = 0;
header[24] = (Byte) (longSampleRate & 0xff);
header[25] = (Byte) ((longSampleRate >> 8) & 0xff);
header[26] = (Byte) ((longSampleRate >> 16) & 0xff);
header[27] = (Byte) ((longSampleRate >> 24) & 0xff);
header[28] = (Byte) (byteRate & 0xff);
header[29] = (Byte) ((byteRate >> 8) & 0xff);
header[30] = (Byte) ((byteRate >> 16) & 0xff);
header[31] = (Byte) ((byteRate >> 24) & 0xff);
header[32] = (Byte) (channels * 16 / 8); // block align
header[33] = 0;
header[34] = 16; // bits per sample
header[35] = 0;
header[36] = 'd'; //"data" marker
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (Byte) (totalAudioLen & 0xff);? //data-size (equals file-size - 44).
header[41] = (Byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (Byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (Byte) ((totalAudioLen >> 24) & 0xff);
return [[NSData alloc] initWithBytes:header length:44];;
}
```