<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>
"); //-->

博客專(zhuān)欄

EEPW首頁(yè) > 博客 > 多媒體文件格式(五):PCM / WAV 格式

多媒體文件格式(五):PCM / WAV 格式

發(fā)布人:電子禪石 時(shí)間:2024-08-11 來(lái)源:工程師 發(fā)布文章

本文目錄:

  • 一、名詞解析

  • 二、PCM 

  • 三、WAV

  • 三、PCM & WAV 開(kāi)發(fā)實(shí)踐

  • 參考資料:


一、名詞解析

PCM(Pulse Code Modulation)也被稱(chēng)為脈碼編碼調制,PCM中的聲音數據沒(méi)有被壓縮,它是由模擬信號經(jīng)過(guò)采樣、量化、編碼轉換成的標準的數字音頻數據。采樣轉換方式參考下圖進(jìn)行了解:

音頻采樣包含以下幾大要素:

1. 采樣率

采樣率表示音頻信號每秒的數字快照數。該速率決定了音頻文件的頻率范圍。采樣率越高,數字波形的形狀越接近原始模擬波形。低采樣率會(huì )限制可錄制的頻率范圍,這可導致錄音表現原始聲音的效果不佳。根據奈奎斯特采樣定理,為了重現給定頻率,采樣率必須至少是該頻率的兩倍。例如,一般CD唱片的采樣率為每秒 44,100 個(gè)采樣,因此可重現最高為 22,050 Hz 的頻率,此頻率剛好超過(guò)人類(lèi)的聽(tīng)力極限 20,000 Hz。

 

圖中A是低采樣率的音頻信號,其效果已經(jīng)將原始聲波進(jìn)行了扭曲,B則是完全重現原始聲波的高采樣率的音頻信號。

數字音頻常用的采樣率如下:

2. 位深度 

位深度決定動(dòng)態(tài)范圍。采樣聲波時(shí),為每個(gè)采樣指定最接近原始聲波振幅的振幅值。較高的位深度可提供更多可能的振幅值,產(chǎn)生更大的動(dòng)態(tài)范圍、更低的噪聲基準和更高的保真度。

位深度越高,提供的動(dòng)態(tài)范圍越大。 

二、PCM 

在上面的名詞解析中我們應該對PCM有了一定的理解和認識,下面我們將對PCM做更多的講解。

1. PCM音頻數據存儲方式

如果是單聲道的文件,采樣數據按時(shí)間的先后順序依次存入。如果是單聲道的音頻文件,采樣數據按時(shí)間的先后順序依次存入(也可能采用 LRLRLR 方式存儲,只是另一個(gè)聲道的數據為 0)。

如果是雙聲道的話(huà)通常按照 LRLRLR 的方式存儲,存儲的時(shí)候還和機器的大小端有關(guān)。(關(guān)于字節序大小端的相關(guān)內容可參考《字節序問(wèn)題之大小端模式講解》進(jìn)行了解)

PCM的存儲方式為小端模式,存儲Data數據排列如下圖所示:

2. PCM 音頻數據的參數

描述 PCM 音頻數據的參數的時(shí)候有如下描述方式:

44100HZ 16bit stereo: 每秒鐘有 44100 次采樣, 采樣數據用 16 位(2 字節)記錄, 雙聲道(立體聲)
22050HZ 8bit  mono: 每秒鐘有 22050 次采樣, 采樣數據用 8 位(1 字節)記錄, 單聲道
48000HZ 32bit 51ch: 每秒鐘有 48000 次采樣, 采樣數據用 32 位(4 字節浮點(diǎn)型)記錄, 5.1 聲道

44100Hz 指的是采樣率,它的意思是每秒取樣 44100 次。采樣率越大,存儲數字音頻所占的空間就越大。

16bit 指的是采樣精度,意思是原始模擬信號被采樣后,每一個(gè)采樣點(diǎn)在計算機中用 16 位(兩個(gè)字節)來(lái)表示。采樣精度越高越能精細地表示模擬信號的差異。

Stereo 指的是聲道數,也即采樣時(shí)用到的麥克風(fēng)的數量,麥克風(fēng)越多就越能還原真實(shí)的采樣環(huán)境(當然麥克風(fēng)的放置位置也是有規定的)。

三、WAV

WAV 是 Microsoft 和 IBM 為 PC 開(kāi)發(fā)的一種聲音文件格式,它符合 RIFF(Resource Interchange File Format)文件規范,用于保存 Windows 平臺的音頻信息資源,被 Windows 平臺及其應用程序所廣泛支持。WAVE 文件通常只是一個(gè)具有單個(gè) “WAVE” 塊的 RIFF 文件,該塊由兩個(gè)子塊(”fmt” 子數據塊和 ”data” 子數據塊),它的格式如下圖所示:

WAV 格式定義

該格式的實(shí)質(zhì)就是在 PCM 文件的前面加了一個(gè)文件頭,每個(gè)字段的的含義如下:

復制代碼
typedef struct {    char          ChunkID[4]; //內容為"RIFF"
    unsigned long ChunkSize;  //存儲文件的字節數(不包含ChunkID和ChunkSize這8個(gè)字節)
    char          Format[4];  //內容為"WAVE“} WAVE_HEADER;
 
typedef struct {   char           Subchunk1ID[4]; //內容為"fmt"
   unsigned long  Subchunk1Size;  //存儲該子塊的字節數(不含前面的Subchunk1ID和Subchunk1Size這8個(gè)字節)
   unsigned short AudioFormat;    //存儲音頻文件的編碼格式,例如若為PCM則其存儲值為1。
   unsigned short NumChannels;    //聲道數,單聲道(Mono)值為1,雙聲道(Stereo)值為2,等等
   unsigned long  SampleRate;     //采樣率,如8k,44.1k等
   unsigned long  ByteRate;       //每秒存儲的bit數,其值 = SampleRate * NumChannels * BitsPerSample / 8
   unsigned short BlockAlign;     //塊對齊大小,其值 = NumChannels * BitsPerSample / 8
   unsigned short BitsPerSample;  //每個(gè)采樣點(diǎn)的bit數,一般為8,16,32等。} WAVE_FMT;
 
typedef struct {   char          Subchunk2ID[4]; //內容為“data”
   unsigned long Subchunk2Size;  //接下來(lái)的正式的數據部分的字節數,其值 = NumSamples * NumChannels * BitsPerSample / 8} WAVE_DATA;
復制代碼

WAV 文件頭解析

這里是一個(gè) WAVE 文件的開(kāi)頭 72 字節,字節顯示為十六進(jìn)制數字:

復制代碼
52 49 46 46 | 24 08 00 00 | 57 41 56 4566 6d 74 20 | 10 00 00 00 | 01 00 02 00 22 56 00 00 | 88 58 01 00 | 04 00 10 0064 61 74 61 | 00 08 00 00 | 00 00 00 00 24 17 1E F3 | 3C 13 3C 14 | 16 F9 18 F934 E7 23 A6 | 3C F2 24 F2 | 11 CE 1A 0D
復制代碼

字段解析如下圖:

三、PCM & WAV 開(kāi)發(fā)實(shí)踐

1. PCM格式轉為WAV格式(基于C語(yǔ)言)
復制代碼
int simplest_pcm16le_to_wave(const char *pcmpath,int channels,int sample_rate,const char *wavepath)
{
    typedef struct WAVE_HEADER{  
        char         fccID[4];        
        unsigned   long    dwSize;            
        char         fccType[4];    
    }WAVE_HEADER;  
    typedef struct WAVE_FMT{  
        char         fccID[4];        
        unsigned   long       dwSize;            
        unsigned   short     wFormatTag;    
        unsigned   short     wChannels;  
        unsigned   long       dwSamplesPerSec;  
        unsigned   long       dwAvgBytesPerSec;  
        unsigned   short     wBlockAlign;  
        unsigned   short     uiBitsPerSample;  
    }WAVE_FMT;  
    typedef struct WAVE_DATA{  
        char       fccID[4];          
        unsigned long dwSize;              
    }WAVE_DATA;  
    if(channels==0||sample_rate==0){
    channels = 2;
    sample_rate = 44100;
    }    int bits = 16;
    WAVE_HEADER   pcmHEADER;  
    WAVE_FMT   pcmFMT;  
    WAVE_DATA   pcmDATA;  
 
    unsigned   short   m_pcmData;
    FILE   *fp,*fpout;  
    fp=fopen(pcmpath, "rb");    if(fp == NULL) {  
        printf("open pcm file error\n");        return -1;  
    }
    fpout=fopen(wavepath,   "wb+");    if(fpout == NULL) {    
        printf("create wav file error\n");  
        return -1; 
    }        
    //WAVE_HEADER
    memcpy(pcmHEADER.fccID,"RIFF",strlen("RIFF"));                    
    memcpy(pcmHEADER.fccType,"WAVE",strlen("WAVE"));  
    fseek(fpout,sizeof(WAVE_HEADER),1); 
    //WAVE_FMT
    pcmFMT.dwSamplesPerSec=sample_rate;  
    pcmFMT.dwAvgBytesPerSec=pcmFMT.dwSamplesPerSec*sizeof(m_pcmData);  
    pcmFMT.uiBitsPerSample=bits;
    memcpy(pcmFMT.fccID,"fmt ",strlen("fmt "));  
    pcmFMT.dwSize=16;  
    pcmFMT.wBlockAlign=2;  
    pcmFMT.wChannels=channels;  
    pcmFMT.wFormatTag=1;  
 
    fwrite(&pcmFMT,sizeof(WAVE_FMT),1,fpout); 
    //WAVE_DATA;
    memcpy(pcmDATA.fccID,"data",strlen("data"));  
    pcmDATA.dwSize=0;
    fseek(fpout,sizeof(WAVE_DATA),SEEK_CUR);
    fread(&m_pcmData,sizeof(unsigned short),1,fp);    while(!feof(fp)){  
        pcmDATA.dwSize+=2;
        fwrite(&m_pcmData,sizeof(unsigned short),1,fpout);
        fread(&m_pcmData,sizeof(unsigned short),1,fp);
    }  
    pcmHEADER.dwSize=44+pcmDATA.dwSize;
    rewind(fpout);
    fwrite(&pcmHEADER,sizeof(WAVE_HEADER),1,fpout);
    fseek(fpout,sizeof(WAVE_FMT),SEEK_CUR);
    fwrite(&pcmDATA,sizeof(WAVE_DATA),1,fpout);
    
    fclose(fp);
    fclose(fpout);    return 0;
}
復制代碼

 

注意:函數里聲明的數據類(lèi)型unsigned long在有些C編譯器上是64位的,這時(shí)候要改成unsigned int才可以,否則wav頭有88bytes,標準的是44bytes,改完就正常了,對C還不熟悉的人小小的心得,另外,聲道數和采樣率也要注意,一般采樣率有44100/16000/8000,要確認是哪個(gè),聲道是1還是2,這兩個(gè)參數要設置好才會(huì )有正確的轉換結果。 

2. PCM降低某個(gè)聲道的音量(基于C語(yǔ)言)

一般來(lái)說(shuō) PCM 數據中的波形幅值越大,代表音量越大,對于 PCM 音頻數據而言,它的幅值(即該采樣點(diǎn)采樣值的大?。┐硪袅康拇笮?。

如果我們需要降低某個(gè)聲道的音量,可以通過(guò)減小某個(gè)聲道的數據的值來(lái)實(shí)現降低某個(gè)聲道的音量。

復制代碼
int pcm16le_half_volume_left( char *url ) {
    FILE *fp_in = fopen( url, "rb+" );
    FILE *fp_out = fopen( "output_half_left.pcm", "wb+" );
    unsigned char *sample = ( unsigned char * )malloc(4); // 一次讀取一個(gè)sample,因為是2聲道,所以是4字節 
    while ( !feof( fp_in ) ){
        fread( sample, 1, 4, fp_in );        short* sample_num = ( short* )sample; // 轉成左右聲道兩個(gè)short數據
        *sample_num = *sample_num / 2; // 左聲道數據減半
        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 的取樣值之后,將其轉成了 C 語(yǔ)言中的一個(gè) short 類(lèi)型的變量。將該數值除以 2 之后寫(xiě)回到了 PCM 文件中。

3. 分離PCM音頻數據左右聲道的數據

因為PCM音頻數據是按照LRLRLR的方式來(lái)存儲左右聲道的音頻數據的,所以我們可以通過(guò)將它們交叉的讀出來(lái)的方式來(lái)分離左右聲道的數據:

復制代碼
int simplest_pcm16le_split(char *url) {
    FILE *fp=fopen(url,"rb+");
    FILE *fp1=fopen("output_l.pcm","wb+");
    FILE *fp2=fopen("output_r.pcm","wb+");
    unsigned char *sample=(unsigned 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;
}
復制代碼

4. 從PCM16LE單聲道音頻采樣數據中截取一部分數據

本程序中的函數可以從PCM16LE單聲道數據中截取一段數據,并輸出截取數據的樣值。函數的代碼如下所示:

復制代碼
/**
 * Cut a 16LE PCM single channel file.
 * @param url        Location of PCM file.
 * @param start_num  start point
 * @param dur_num    how much point to cut */int simplest_pcm16le_cut_singlechannel(char *url,int start_num,int dur_num){
    FILE *fp=fopen(url,"rb+");
    FILE *fp1=fopen("output_cut.pcm","wb+");
    FILE *fp_stat=fopen("output_cut.txt","wb+");
 
    unsigned char *sample=(unsigned char *)malloc(2); 
    int cnt=0;    while(!feof(fp)){
        fread(sample,1,2,fp);        if(cnt>start_num&&cnt<=(start_num+dur_num)){
            fwrite(sample,1,2,fp1); 
            short samplenum=sample[1];
            samplenum=samplenum*256;
            samplenum=samplenum+sample[0];
 
            fprintf(fp_stat,"%6d,",samplenum);            if(cnt%10==0)
                fprintf(fp_stat,"\n",samplenum);
        }
        cnt++;
    } 
    free(sample);
    fclose(fp);
    fclose(fp1);
    fclose(fp_stat);    return 0;
}
復制代碼

5. 將PCM16LE雙聲道音頻采樣數據轉換為PCM8音頻采樣數據

本程序中的函數可以通過(guò)計算的方式將PCM16LE雙聲道數據16bit的采樣位數轉換為8bit。函數的代碼如下所示:

復制代碼
/**
 * Convert PCM-16 data to PCM-8 data.
 * @param url  Location of PCM file. */int simplest_pcm16le_to_pcm8(char *url){
    FILE *fp=fopen(url,"rb+");
    FILE *fp1=fopen("output_8.pcm","wb+"); 
    int cnt=0;
 
    unsigned char *sample=(unsigned char *)malloc(4); 
    while(!feof(fp)){ 
        short *samplenum16=NULL;        char samplenum8=0;
        unsigned char samplenum8_u=0;
        fread(sample,1,4,fp);        //(-32768-32767)
        samplenum16=(short *)sample;
        samplenum8=(*samplenum16)>>8;        //(0-255)
        samplenum8_u=samplenum8+128;        //L
        fwrite(&samplenum8_u,1,1,fp1);
 
        samplenum16=(short *)(sample+2);
        samplenum8=(*samplenum16)>>8;
        samplenum8_u=samplenum8+128;        //R
        fwrite(&samplenum8_u,1,1,fp1);
        cnt++;
    }
    printf("Sample Cnt:%d\n",cnt); 
    free(sample);
    fclose(fp);
    fclose(fp1);    return 0;
}
復制代碼

PCM16LE格式的采樣數據的取值范圍是-32768到32767,而PCM8格式的采樣數據的取值范圍是0到255。所以PCM16LE轉換到PCM8需要經(jīng)過(guò)兩個(gè)步驟:第一步是將-32768到32767的16bit有符號數值轉換為-128到127的8bit有符號數值,第二步是將-128到127的8bit有符號數值轉換為0到255的8bit無(wú)符號數值。在本程序中,16bit采樣數據是通過(guò)short類(lèi)型變量存儲的,而8bit采樣數據是通過(guò)unsigned char類(lèi)型存儲的。

6. 將PCM16LE雙聲道音頻采樣數據的聲音速度提高一倍

本程序中的函數可以通過(guò)抽象的方式將PCM16LE雙聲道數據的速度提高一倍,采用采樣每個(gè)聲道奇(偶)數點(diǎn)的樣值的方式,函數的代碼如下所示:

復制代碼
/**
 * Re-sample to double the speed of 16LE PCM file
 * @param url  Location of PCM file. */int simplest_pcm16le_doublespeed(char *url){
    FILE *fp=fopen(url,"rb+");
    FILE *fp1=fopen("output_doublespeed.pcm","wb+"); 
    int cnt=0;
 
    unsigned char *sample=(unsigned char *)malloc(4); 
    while(!feof(fp)){
 
        fread(sample,1,4,fp); 
        if(cnt%2!=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;
}
復制代碼

  

參考資料:

視音頻數據處理入門(mén):PCM音頻采樣數據處理   -->  致敬雷神!


*博客內容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀(guān)點(diǎn),如有侵權請聯(lián)系工作人員刪除。



關(guān)鍵詞: pcm

相關(guān)推薦

技術(shù)專(zhuān)區

關(guān)閉
国产精品自在自线亚洲|国产精品无圣光一区二区|国产日产欧洲无码视频|久久久一本精品99久久K精品66|欧美人与动牲交片免费播放
<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>