音视频流媒体基础知识之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”子数据块),它的格式如下图所示

 CSDN站内私信我,领取最新最全C++音视频学习提升资料,内容包括(C/C++Linux 服务器开发,FFmpeg webRTC rtmp hls rtsp ffplay srs

该格式的实质就是在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 

字段解析如下图:

PCM → WAV 代码

int simplest_pcm16le_to_wave( const char *pcmpath, int channels, int sample_rate, const char *wavepath )
{ // 省去错误判断
    short pcmData;
    FILE* fp = fopen( pcmpath, "rb" );
    FILE* fpout = fopen( wavepath, "wb+" );
    
    // 填充 WAVE_HEADER
    WAVE_HEADER pcmHEADER;
    memcpy( pcmHEADER.ChunkID, "RIFF", strlen( "RIFF" ) );
    memcpy( pcmHEADER.Format, "WAVE", strlen( "WAVE" ) );
    fseek( fpout, sizeof( WAVE_HEADER ), 1 );
    
    //填充 WAVE_FMT 
    WAVE_FMT pcmFMT;
    pcmFMT.SampleRate = sample_rate;
    pcmFMT.ByteRate = sample_rate * sizeof( pcmData );
    pcmFMT.BitsPerSample = 8 * sizeof( pcmData );
    memcpy( pcmFMT.Subchunk1ID, "fmt ", strlen( "fmt " ) );
    pcmFMT.Subchunk1Size = 16;
    pcmFMT.BlockAlign = channels * sizeof( pcmData );
    pcmFMT.NumChannels = channels;
    pcmFMT.AudioFormat = 1;
    fwrite( &pcmFMT, sizeof( WAVE_FMT ), 1, fpout );

    //填充 WAVE_DATA;
    WAVE_DATA pcmDATA;
    memcpy( pcmDATA.Subchunk2ID, "data", strlen( "data" ) );
    pcmDATA.Subchunk2Size = 0;
    fseek( fpout, sizeof( WAVE_DATA ), SEEK_CUR );
    fread( &m_pcmData, sizeof( short ), 1, fp );
    while ( !feof( fp ) ) {
         pcmDATA.dwSize += 2;
         fwrite( &m_pcmData, sizeof( short ), 1, fpout );
         fread( &m_pcmData, sizeof( short ), 1, fp );
    }
    
    int headerSize = sizeof( pcmHEADER.Format ) + sizeof( WAVE_FMT ) + sizeof( WAVE_DATA ); // 36
    pcmHEADER.ChunkSize = headerSize + pcmDATA.Subchunk2Size;

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

猜你喜欢

转载自blog.csdn.net/m0_60259116/article/details/124871532