python已經支援WAV格式的書寫,而實時的聲音輸入輸出需要安裝pyAudio(http://people.csail.mit.edu/hubert/pyaudio)。最後我們還將使用pyMedia(http://pymedia.org)進行Mp3的解碼和播放。
音訊訊號是模擬訊號,我們需要將其儲存為數位訊號,才能對語音進行演演算法操作,WAV是Microsoft開發的一種聲音檔案格式,通常被用來儲存未壓縮的聲音資料。
語音訊號有四個重要的引數:聲道數、取樣頻率、量化位數(位深)和位元率。
如果你需要自己錄製和編輯聲音檔案,推薦使用Audacity,它是一款開源的、跨平臺、多聲道的錄音編輯軟體。在我的工作中經常使用Audacity進行聲音訊號的錄製,然後再輸出成WAV檔案供Python程式處理。
如果想要快速看語音波形和語譜圖,推薦使用Adobe Audition,他是Adobe公司開發專門處理音訊的專業軟體,微博關注vposy,下載地址見置頂。他破解了很多adobe公司的軟體,包括PS、PR...
WAV格式是微軟公司開發的一種無失真聲音檔案格式,也稱為波形聲音檔案,WAV格式支援多種壓縮演演算法、音訊位數、取樣頻率和聲道。
WAV 符合 RIFF(Resource Interchange File Format) 規範,所有的WAV都由 44位元組 標頭檔案 和 PCM檔案 組成,這個檔案頭包含語音訊號的所有引數資訊(聲道數、取樣率、量化位數、位元率....)
44個位元組的 標頭檔案由 3個區塊組成:
相反的,在PCM檔案頭部新增44個位元組的WAV檔案頭,就可以生成WAV格式檔案
規範的WAVE格式遵循RIFF頭
名稱 | 位元組數 | 內容 |
ChunkID | 4 | "RIFF" 識別符號 |
ChunkSize | 4 |
表示從下個地址開始到檔案尾的總位元組數 更準確的說:等於整個wav檔案大小-8 |
Format | 4 | "WAVE" 識別符號 |
描述聲音資料的格式
名稱 | 位元組數 | 內容 |
Subchunk1ID | 4 | "fmt " 識別符號,最後一位是空格 |
Subchunk1Size | 4 | 該區塊資料的長度(不包含該區塊ID和Size的長度) |
AudioFormat | 2 | 音訊格式,PCM音訊資料的值為1 |
NumChannels | 2 | 通道數 |
SampleRate | 4 | 取樣率 |
ByteRate | 4 | 每秒資料位元組數 = SampleRate * NumChannels * BitsPerSample / 8 |
BlockAlign | 2 | 每個取樣點所需的位元組數 = NumChannels * BitsPerSample / 8 |
BitsPerSample | 2 | 量化位數(bit) |
包含資料的大小和實際聲音
名稱 | 位元組數 | 內容 |
Subchunk2ID | 4 | "data" 識別符號 |
Subchunk2Size | 4 | 該區塊資料的長度,(不包含該區塊ID和Size的長度),也就是PCM位元組數 |
Data | * | 音訊資料 |
檔案範例:
RIFF區塊
FORMAT區塊
DATA區塊
因為wav比pcm多44個位元組的檔案頭,也就是說44位元組後的資訊,就是pcm資料
#include <stdio.h> /** * wav2pcm ***.wav **.pcm * @param argc 命令列引數的長度 * @param argv 命令列引數,argv[0]是程式名稱 * @return */ int main(int argc, char *argv[]) { FILE *wavfile; FILE *pcmfile; char buf[1024]; int read_len; if (argc != 3) { printf("usage:\n" "\t wav2pcm ***.wav **.pcm\n"); } wavfile = fopen(argv[1], "rb"); if (wavfile == NULL) { printf("!Error: Can't open wavfile.\n"); return 1; } pcmfile = fopen(argv[2], "wb"); if (pcmfile == NULL) { printf("!Error: Can't open pcmfile.\n"); return 1; } fseek(wavfile, 44, SEEK_SET); // 將檔案指標移動到檔案開頭,後移44位元組 while ((read_len = fread(buf, 1, sizeof(buf), wavfile)) != 0) { fwrite(buf, 1, read_len, pcmfile); } fclose(pcmfile); fclose(wavfile); return 0; }
dd if=1.wav of=1.pcm bs=1 skip=44
def wav2pcm(wavfile, pcmfile, data_type=np.int16): f = open(wavfile, "rb") f.seek(0) f.read(44) data = np.fromfile(f, dtype=data_type) data.tofile(pcmfile)
還有一個github開原始碼:wavutils
當我們讀取pcm資料的時候,我們需要弄清楚語音每個取樣點的位深是多少bit,一般來說是16bit,那麼我們去pcm資料的時候就應該2個位元組的去取,應該建立short的buf。
#include <stdio.h> int main() { FILE *pcmfile; int frame_len = 480; // 幀長 short buf[frame_len]; // 每個取樣點2位元組 int read_len; char pcmpath[]="../p225_001.pcm"; pcmfile = fopen(pcmpath, "rb"); if (pcmfile == NULL) { printf("!Error: Can't open wavfile.\n"); return 1; } while (feof(pcmfile)==0){ read_len = fread(buf, sizeof(short), frame_len, pcmfile); for (int i = 0; i < read_len; i++) { printf("%d ", buf[i]); } } fclose(pcmfile); return 0; }
RAW、PCM(Pulse Code Modulation)、SAM 都是一種儲存 原始資料 的音訊檔格式,未經過任何編碼和壓縮處理,他們的本質一樣,只是副檔名不同,也可以沒有擴充套件名。與WAV或AIFF的大小相比,這音訊檔不包含任何標題資訊(取樣率、位深度、通道數)。
如果在PCM檔案的前面新增WAV檔案頭,就可以生成WAV格式檔案。
如果是16位元的話,pcm每個取樣點的值在0~$2^{15}-1$,因為第一位是符號位。所以我們有時候用librosa讀取的音訊每個取樣點都是0~1之間的,如果該音訊是16bit的,如果想將他換成short型應該乘以$2^{15}$。
版本一:程式碼參考自:https://github.com/pliu6/pcm2wav
/** * https://github.com/pliu6/pcm2wav */ #include <stdlib.h> #include <string.h> #include <stdio.h> typedef struct { unsigned char chunk_id[4]; /*{'R', 'I', 'F', 'F'}*/ unsigned int chunk_size; unsigned char format[4]; } FIFFChunk; typedef struct { unsigned char chunk_id[4]; /* {'f', 'm', 't', ' '} */ unsigned int chunk_size; unsigned short audio_format; // 2位元組 unsigned short channels; // 4位元組 unsigned int sample_rate; // 4位元組 unsigned int byte_rate; // 4位元組 unsigned short block_align; // 2位元組 unsigned short bits_per_sample; // 2位元組 } FormatChunk; typedef struct { unsigned char chunk_id[4]; /* {'d', 'a', 't', 'a'} */ unsigned int chunk_size; } DataChunk; // pcm2wav ***.pcm ***.wav 通道 取樣率 量化位數 int main(int argc, char *argv[]) { FILE *pcmfile, *wavfile; long pcmfile_size; FIFFChunk fiffchunk; FormatChunk formatchunk; DataChunk datachunk; int read_len; char buf[1024]; if (argc != 6) { printf("usage:\n" "\t%s pcmfile wavfile channel samplerate bitspersample\n", argv[0]); return 1; } pcmfile = fopen(argv[1], "rb"); if (pcmfile == NULL) { printf("!Error: Can't open pcmfile.\n"); return 1; } fseek(pcmfile, 0, SEEK_END); // 將檔案指標移動到檔案最後 pcmfile_size = ftell(pcmfile); // 返回給定流 stream 的當前檔案位置(位元組) fseek(pcmfile, 0, SEEK_SET); // 將檔案指標移動到檔案開頭 wavfile = fopen(argv[2], "wb"); if (wavfile == NULL) { printf("!Error: Can't create wavfile.\n"); return 1; } /* *********** RIFF區塊 ********************* */ strncpy(fiffchunk.chunk_id,"RIFF", 4); fiffchunk.chunk_size = pcmfile_size+36; strncpy(fiffchunk.format,"WAVE",4); fwrite(&fiffchunk, sizeof(fiffchunk), 1, wavfile); /* *********** FORMAT區塊 ********************* */ strncpy(formatchunk.chunk_id,"fmt ", 4); formatchunk.chunk_size = sizeof(FormatChunk) - 8; // 不包含該區塊ID和Size的長度 formatchunk.audio_format = 1; /* 未壓縮的 */ formatchunk.channels = atoi(argv[3]); // 通道數,字串轉換成整型 formatchunk.sample_rate = atoi(argv[4]); // 取樣率 formatchunk.bits_per_sample = atoi(argv[5]); // 量化位數 formatchunk.byte_rate = formatchunk.sample_rate * formatchunk.channels * (formatchunk.bits_per_sample >> 3); // 每秒資料位元組數=SampleRate * NumChannels * BitsPerSample/8 formatchunk.block_align = formatchunk.channels * (formatchunk.bits_per_sample >> 3); // 每個樣本需要的位元組數 fwrite(&formatchunk, 1, sizeof(formatchunk), wavfile); /* *********** DATA區塊 ********************* */ strncpy(datachunk.chunk_id, "data",4); datachunk.chunk_size = pcmfile_size; fwrite(&datachunk, 1, sizeof(datachunk.chunk_id) + sizeof(datachunk.chunk_size), wavfile); while ((read_len = fread(buf, 1, sizeof(buf), pcmfile)) != 0) { fwrite(buf, 1, read_len, wavfile); } fclose(pcmfile); fclose(wavfile); }
版本二:程式碼參考自:https://github.com/jwhu1024/pcm-to-wav
/** * https://github.com/jwhu1024/pcm-to-wav */ #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { unsigned char chunk_id[4]; // RIFF string unsigned int chunk_size; // overall size of file in bytes (36 + data_size) unsigned char sub_chunk1_id[8]; // WAVEfmt string with trailing null char unsigned int sub_chunk1_size; // 16 for PCM. This is the size of the rest of the Subchunk which follows this number. unsigned short audio_format; // format type. 1-PCM, 3- IEEE float, 6 - 8bit A law, 7 - 8bit mu law unsigned short num_channels; // Mono = 1, Stereo = 2 unsigned int sample_rate; // 8000, 16000, 44100, etc. (blocks per second) unsigned int byte_rate; // SampleRate * NumChannels * BitsPerSample/8 unsigned short block_align; // NumChannels * BitsPerSample/8 unsigned short bits_per_sample; // bits per sample, 8- 8bits, 16- 16 bits etc unsigned char sub_chunk2_id[4]; // Contains the letters "data" unsigned int sub_chunk2_size; // NumSamples * NumChannels * BitsPerSample/8 - size of the next chunk that will be read } wav_header_t; char *dummy_get_raw_pcm(char *p, int *bytes_read) { long lSize; char *pcm_buf; size_t result; FILE *fp_pcm; fp_pcm = fopen(p, "rb"); if (fp_pcm == NULL) { printf("File error"); exit(1); } // obtain file size: fseek(fp_pcm, 0, SEEK_END); // 將檔案指標移動到檔案最後 lSize = ftell(fp_pcm); // 返回給定流 stream 的當前檔案位置(位元組) rewind(fp_pcm); // 將檔案指標移動到檔案開頭 // 分配記憶體來包含整個檔案 pcm_buf = (char *) malloc(sizeof(char) * lSize); if (pcm_buf == NULL) { printf("Memory error"); exit(2); } // 將檔案複製到pcm_buf中: result = fread(pcm_buf, 1, lSize, fp_pcm); if (result != lSize) { printf("Reading error"); exit(3); } *bytes_read = (int) lSize; return pcm_buf; } void get_wav_header(int raw_sz, wav_header_t *wh) { // RIFF chunk strcpy(wh->chunk_id, "RIFF"); wh->chunk_size = 36 + raw_sz; // fmt sub-chunk (to be optimized) strncpy(wh->sub_chunk1_id, "WAVEfmt ", strlen("WAVEfmt ")); wh->sub_chunk1_size = 16; wh->audio_format = 1; wh->num_channels = 1; wh->sample_rate = 16000; wh->bits_per_sample = 16; wh->block_align = wh->num_channels * wh->bits_per_sample / 8; wh->byte_rate = wh->sample_rate * wh->num_channels * wh->bits_per_sample / 8; // data sub-chunk strncpy(wh->sub_chunk2_id, "data", strlen("data")); wh->sub_chunk2_size = raw_sz; } void dump_wav_header(wav_header_t *wh) { printf("=========================================\n"); printf("chunk_id:\t\t\t%s\n", wh->chunk_id); printf("chunk_size:\t\t\t%d\n", wh->chunk_size); printf("sub_chunk1_id:\t\t\t%s\n", wh->sub_chunk1_id); printf("sub_chunk1_size:\t\t%d\n", wh->sub_chunk1_size); printf("audio_format:\t\t\t%d\n", wh->audio_format); printf("num_channels:\t\t\t%d\n", wh->num_channels); printf("sample_rate:\t\t\t%d\n", wh->sample_rate); printf("bits_per_sample:\t\t%d\n", wh->bits_per_sample); printf("block_align:\t\t\t%d\n", wh->block_align); printf("byte_rate:\t\t\t%d\n", wh->byte_rate); printf("sub_chunk2_id:\t\t\t%s\n", wh->sub_chunk2_id); printf("sub_chunk2_size:\t\t%d\n", wh->sub_chunk2_size); printf("=========================================\n"); } // pcm-to-wav ./time.pcm ./***.wav int main(int argc, char *argv[]) { int raw_sz = 0; FILE *fwav; wav_header_t wheader; // 檔案頭 結構體變數宣告 memset(&wheader, '\0', sizeof(wav_header_t)); // 清除記憶體位置 // check argument if (argc != 2) return -1; // dummy raw pcm data char *pcm_buf = dummy_get_raw_pcm("./time.pcm", &raw_sz); // construct wav header get_wav_header(raw_sz, &wheader); // 給檔案頭賦 初值 dump_wav_header(&wheader); // 列印檔案頭 資訊 // write out the .wav file fwav = fopen(argv[1], "wb"); fwrite(&wheader, 1, sizeof(wheader), fwav); fwrite(pcm_buf, 1, raw_sz, fwav); fclose(fwav); if (pcm_buf) free(pcm_buf); return 0; }
版本三:使用python的wave庫
def pcm2wav(pcm_file, wav_file, channels=1, bits=16, sample_rate=16000): pcmf = open(pcm_file, 'rb') pcmdata = pcmf.read() pcmf.close() if bits % 8 != 0: raise ValueError("bits % 8 must == 0. now bits:" + str(bits)) wavfile = wave.open(wav_file, 'wb') wavfile.setnchannels(channels) wavfile.setsampwidth(bits // 8) wavfile.setframerate(sample_rate) wavfile.writeframes(pcmdata) wavfile.close()
還有一個github開原始碼:wavutils
MP3
MP3利用MPEG Audio Layer3 壓縮方式進行壓縮,所以簡稱為MP3,是一種有失真壓縮格式。 MPEG Audio Layer 3 壓縮技術可以將音樂以1:10 甚至 1:12 的壓縮率,能夠在音質丟失很小的情況下把檔案壓縮到更小的程度。由於MP3體積小,音質高網際網路上音樂幾乎都是這種格式。但Mp3最高位元率320K,高頻部分一刀切是他的缺點,對音質要求高的話還是建議wav格式。
ARM格式全稱Adaptive Multi-Rate 和 Adaptive Multi-Rate Wideband,主要用於移動裝置的音訊,壓縮比比較大,但相對其他的壓縮格式質量比較差,多用於人聲,通話,是一種有失真壓縮格式。
Ogg全稱應該是OGG Vobis(ogg Vorbis) 是一種新的聲頻壓縮格式,類似於MP3等現有的音樂格式。相對於MP3壓縮技術它是完全免費、開放和沒有專利限制的,是一種有失真壓縮格式。
AAC(Advanced Audio Coding),中文稱為「高階音訊編碼」,出現於1997年,基於 MPEG-2的音訊編碼技術,是一種有失真壓縮技術。
LAC即是Free Lossless Audio Codec的縮寫,為無失真聲頻壓縮編碼,由於不會丟失任何音訊資訊可以利用演演算法恢復原始編碼,前景廣闊。