多媒體基礎知識之PCM數據


1.什么是PCM音頻數據

PCM(Pulse Code Modulation)也被稱為脈沖編碼調制。PCM音頻數據是未經壓縮的音頻采樣數據裸流,它是由模擬信號經過采樣、量化、編碼轉換成的標准的數字音頻數據。

2.PCM音頻數據是如何存儲的

如果是單聲道的音頻文件,采樣數據按時間的先后順序依次存入(有的時候也會采用LRLRLR方式存儲,只是另一個聲道的數據為0),如果是雙聲道的話就按照LRLRLR的方式存儲,存儲的時候還和機器的大小端有關。大端模式如下圖所示:

3.PCM音頻數據中常用的專業術語

一般我們描述PCM音頻數據的參數的時候有如下描述方式

44100HZ 16bit stereo: 每秒鍾有 44100 次采樣, 采樣數據用 16 位(2字節)記錄, 雙聲道(立體聲);
22050HZ 8bit  mono: 每秒鍾有 22050 次采樣, 采樣數據用 8 位(1字節)記錄, 單聲道;

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

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

一般來說PCM數據中的波形幅值越大,代表音量越大。

4.PCM音頻數據的處理

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

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

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.2.降低某個聲道的音量

因為對於PCM音頻數據而言,它的幅值(即該采樣點采樣值的大小)代表音量的大小,所以我們可以通過減小某個聲道的數據的值來實現降低某個聲道的音量

int simplest_pcm16le_halfvolumeleft(char *url){
    FILE *fp=fopen(url,"rb+");
    FILE *fp1=fopen("output_halfleft.pcm","wb+");
    int cnt=0;
    unsigned char *sample=(unsigned char *)malloc(4);
    while(!feof(fp)){
        short *samplenum=NULL;
        fread(sample,1,4,fp);
        samplenum=(short *)sample;
        *samplenum=*samplenum/2;
        //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;
}

4.3.將PCM音頻數據轉換成WAV格式

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

該格式的實質就是在PCM文件的前面加了一個文件頭,每個字段的的含義為

typedef struct{
       char          ChunkID[4];//內容為"RIFF"
       unsigned long ChunkSize;//存儲文件的字節數(不包含ChunkID和ChunkSize這8個字節)
       char          Format[4];//內容為"WAVE"
   }WAVE_HEADER;
   typedef struct{
        char          Subchunk1ID[4];//內容為"fmt"
        unsigned long  Subchunk1Size;//存儲該子塊的字節數(不含前面的Subchunk1ID和Subchunk1Size這8個字節)
        unsigned short AudioFormat;//存儲音頻文件的編碼格式,例如若為PCM則其存儲值為1,若為其他非PCM格式的則有一定的壓縮。
        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;//每個采樣點的bit數,一般為8,16,32等。
   }WAVE_FMT;
   typedef struct{
        char          Subchunk2ID[4];//內容為“data”
        unsigned long Subchunk2Size;//內容為接下來的正式的數據部分的字節數,其值=NumSamples * NumChannels * BitsPerSample/8
   }WAVE_DATA;

比如下面的例子

這里是一個WAVE文件的開頭72字節,字節顯示為十六進制數字: 
52 49 46 46 24 08 00 00 57 41 56 45 66 6d 74 20 10 00 00 00 01 00 02 00 
22 56 00 00 88 58 01 00 04 00 10 00 64 61 74 61 00 08 00 00 00 00 00 00 
24 17 1e f3 3c 13 3c 14 16 f9 18 f9 34 e7 23 a6 3c f2 24 f2 11 ce 1a 0d 
字段解析:

代碼實現為

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;
}

參考文章:

http://blog.csdn.net/leixiaohua1020/article/details/50534316

http://blog.csdn.net/u010011236/article/details/53026127

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM