一文帶你深入了解C++中音頻PCM數(shù)據(jù)
PCM(Pulse Code Modulation)也被稱為脈沖編碼調(diào)制,是數(shù)字通信的編碼方式之一。PCM中的聲音數(shù)據(jù)沒有被壓縮,它將輸入的模擬信號進行采樣、量化和編碼,用二進制進行編碼的數(shù)來代表模擬信號的幅度,即標(biāo)準(zhǔn)的數(shù)字音頻數(shù)據(jù)。
采樣轉(zhuǎn)換方式參考下圖進行了解:
采樣率
采樣率表示音頻信號每秒的數(shù)字快照數(shù)。該速率決定了音頻文件的頻率范圍。采樣率越高,數(shù)字波形的形狀越接近原始模擬波形。低采樣率會限制可錄制的頻率范圍,這可導(dǎo)致錄音表現(xiàn)原始聲音的效果不佳。一般數(shù)字音頻常用的采樣率電話頻率8kHz、CD頻率44.1kHz、DVD頻率48kHz。
位深度
位深度決定動態(tài)范圍。采樣聲波時,為每個采樣指定最接近原始聲波振幅的振幅值。較高的位深度可提供更多可能的振幅值,產(chǎn)生更大的動態(tài)范圍、更低的噪聲基準(zhǔn)和更高的保真度。普通的CD是16-bit。
通道
通道個數(shù)。常見的音頻有立體聲(stereo)和單聲道(mono)兩種類型,立體聲包含左聲道和右聲道。另外還有環(huán)繞立體聲等其它不太常用的類型。
Sign
表示樣本數(shù)據(jù)是否是有符號位,比如用一字節(jié)表示的樣本數(shù)據(jù),有符號的話表示范圍為-128 ~ 127,無符號是0 ~ 255。
字節(jié)序
字節(jié)序是little-endian還是big-endian。通常均為little-endian
PCM信號的兩個重要指標(biāo)是采樣頻率和量化精度,當(dāng)在播放音樂時,應(yīng)用程序從存儲介質(zhì)中讀取音頻數(shù)據(jù)(MP3、WMA、AAC等),經(jīng)過解碼后,最終送到音頻驅(qū)動程序中的就是PCM數(shù)據(jù),反過來,在錄音時,音頻驅(qū)動不停地把采樣所得的PCM數(shù)據(jù)送回給應(yīng)用程序,由應(yīng)用程序完成壓縮、存儲等任務(wù)。下面我們展開介紹下PCM音頻的存儲及操作
PCM音頻數(shù)據(jù)存儲方式
如果是單聲道的音頻文件,采樣數(shù)據(jù)按時間的先后順序依次存入(有的時候也會采用LRLRLR方式存儲,只是另一個聲道的數(shù)據(jù)為0),如果是雙聲道的話就按照LRLRLR的方式存儲,存儲的時候與字節(jié)序有關(guān)。
big-endian模式如下圖所示:
PCM開發(fā)實戰(zhàn)
分離雙聲道PCM音頻數(shù)據(jù)左右聲道的數(shù)據(jù)
按照雙聲道的LRLRLR的PCM音頻數(shù)據(jù)可以通過將它們交叉的讀出來的方式來分離左右聲道的數(shù)據(jù)。
int pcm_s16le_split(const char* file, const char* out_lfile, const char* out_rfile) { FILE *fp = fopen(file, "rb+"); if (fp == NULL) { printf("open %s failed\n", file); return -1; } FILE *fp1 = fopen(out_lfile, "wb+"); if (fp1 == NULL) { printf("open %s failed\n", out_lfile); return -1; } FILE *fp2 = fopen(out_rfile, "wb+"); if (fp2 == NULL) { printf("open %s failed\n", out_rfile); return -1; } char * sample = (char *)malloc(4); while(!feof(fp)) { fread(sample, 1, 4, fp); //L fwrite(sample, 1, 2, fp1); //R fwrite(sample + 2, 1, 2, fp2); } free(sample); fclose(fp); fclose(fp1); fclose(fp2); return 0; }
PCM降低某個聲道的音量
一般來說 PCM 數(shù)據(jù)中的波形幅值越大,代表音量越大,對于 PCM 音頻數(shù)據(jù)而言,它的幅值(即該采樣點采樣值的大?。┐硪袅康拇笮?。如果我們需要降低某個聲道的音量,可以通過減小某個聲道的數(shù)據(jù)的值來實現(xiàn)降低某個聲道的音量。
int pcm16le_half_volume_left( char *url ) { FILE *fp_in = fopen( url, "rb+" ); if (fp_in == NULL) { printf("open %s failed\n", infile); return -1; } FILE *fp_out = fopen( "output_half_left.pcm", "wb+" ); if (fp_out == NULL) { printf("open %s failed\n", outfile); return -1; } unsigned char *sample = ( unsigned char * )malloc(4); // 一次讀取一個sample,因為是2聲道,所以是4字節(jié) while ( !feof( fp_in ) ){ fread( sample, 1, 4, fp_in ); short* sample_num = ( short* )sample; // 轉(zhuǎn)成左右聲道兩個short數(shù)據(jù) *sample_num = *sample_num / 2; // 左聲道數(shù)據(jù)減半 fwrite( sample, 1, 2, fp_out ); // L fwrite( sample + 2, 1, 2, fp_out ); // R } free( sample ); fclose( fp_in ); fclose( fp_out ); return 0; }
上述程序做的事情是:在讀出左聲道的 2 Byte 的取樣值之后,將其轉(zhuǎn)成了 C 語言中的一個 short 類型的變量。將該數(shù)值除以 2 之后寫回到了 PCM 文件中。
將PCM16LE雙聲道音頻采樣數(shù)據(jù)的聲音速度提高一倍
下面函數(shù)可以通過抽象的方式將PCM16LE雙聲道數(shù)據(jù)的速度提高一倍,采樣左右聲道按奇(偶)數(shù)點的樣值的方式,函數(shù)的代碼如下所示:
int simplest_pcm16le_doublespeed(char *url){ FILE *fp=fopen(url,"rb+"); if (fp == NULL) { printf("open %s failed\n", file); return -1; } FILE *fp1=fopen("output_doublespeed.pcm","wb+"); if (fp1 == NULL) { printf("open %s failed\n", outFile); return -1; } int cnt=0; unsigned char *sample=(unsigned char *)malloc(4); while(!feof(fp)){ fread(sample,1,4,fp); if(cnt&1!=0){ //L fwrite(sample,1,2,fp1); //R fwrite(sample+2,1,2,fp1); } cnt++; } printf("Sample Cnt:%d\n",cnt); free(sample); fclose(fp); fclose(fp1); return 0; }
到此這篇關(guān)于一文帶你深入了解C++中音頻PCM數(shù)據(jù)的文章就介紹到這了,更多相關(guān)C++音頻PCM內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實現(xiàn)“隱藏實現(xiàn),開放接口”的方案
本文從一個實例講解了C++實現(xiàn)“隱藏實現(xiàn),開放接口”的方案,文章條理清新,內(nèi)容充實,需要的朋友可以參考下2015-07-07C++實現(xiàn)LeetCode(158.用Read4來讀取N個字符之二 - 多次調(diào)用)
這篇文章主要介紹了C++實現(xiàn)LeetCode(158.用Read4來讀取N個字符之二 - 多次調(diào)用),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07數(shù)據(jù)結(jié)構(gòu)之AVL樹詳解
這篇文章主要介紹了數(shù)據(jù)結(jié)構(gòu)之AVL樹詳解,本文非常細(xì)致的講解了AVL樹的基礎(chǔ)知識、AVL樹的旋轉(zhuǎn)操作、AVL數(shù)的插入和刪除操作等,需要的朋友可以參考下2014-08-08