PCM音频格式理解

什么是PCM

PCM(Pulse Code Modulation)是模拟信号以固定的采样频率转换成数字信号后的表现形式。
PCM相关的5个概念:

  • 采样率(Sample Rate)
    一秒钟内对声音信号的采样次数,单位:Hz,采样频率越高,音频质量越好,占用空间也越大。采样频率一般共分为11025Hz、22050Hz、24000Hz、44100Hz、48000Hz五个等级,11025Hz能达到AM调幅广播的声音品质,而22050Hz和24000HZ能达到FM调频广播的声音品质,44100Hz则是理论上的CD音质界限,48000Hz则更加精确一些。
  • 音频数据是否是有符号的
    通常情况下音频数据都是有符号的,可以是负值。
  • 采样位数(Sample Size)
    表示每一个采样数据的大小,有8、16位之分,用来衡量声音波动变化的一个参数,也可以说是声卡的分辨率。它的数值越大,分辨率也就越高,所发出声音的能力越强。
  • 字节序
    音频数据存储字节序,有little-endian和big-endian,音频通常使用little-endian字节序存储。
  • 声道数
    标识音频是单声道(mono, 1 channel)还是双声道立体声(stereo, 2 channels)。

PCM数据排列格式

PCM数据排列格式
注:该图来自https://blog.csdn.net/lifei092/article/details/80990813

  • 16位单声道PCM
    各个采样点依次排列,每一个采样点占2个字节(16bit),小端字节序,低字节在低地址,高字节在高地址;
  • 16位双通道(立体声)PCM
    左右声道采样点依次交叉排列,每个采样点占2个字节(16-bit),小端字节序,低字节在低地址,高字节在高地址;
    |----------------------------------------------------------------------------------------------------------------------|
    | LFrame1 | RFrame1 | LFrame2 | RFrame2 | LFrame3 | RFrame3 | LFrame4 | RFrame4 |
    |----------------------------------------------------------------------------------------------------------------------|

PCM音频数据可以使用音频编辑软件导入查看,如:开源的音频编辑软件Audacity。

从pcm16双通道数据中分离左右通道数据

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
    从pcm16双通道数据中分离左右通道数据
    pcm16双通道格式中,左右通道采样点数据交叉排列,每个采样点16bit
    |-------|-------|-------|-------|-------|-------|
    |L1Frame|RlFrame|L2Frame|R2Frame|L3Frame|R3Frame|
    |-------|-------|-------|-------|-------|-------|
    | 16bit | 16bit | 16bit | 16bit | 16bit | 16bit |
    |-------|-------|-------|-------|-------|-------|
*/
int pcm16le_split(const char *file_pcm16le, const char *file_pcm16le_l, const char *file_pcm16le_r)
{
    FILE *fp_pcm16le = fopen(file_pcm16le, "rb+");
    if (fp_pcm16le == NULL)
    {
        printf("fopen error: cannot open %s\n", file_pcm16le);
        return -1;
    }

    FILE *fp_pcm16le_l = fopen(file_pcm16le_l, "wb+");
    if (fp_pcm16le_l == NULL)
    {
        printf("fopen error: cannot open %s\n", file_pcm16le_l);
        fclose(fp_pcm16le);
        return -1;
    }

    FILE *fp_pcm16le_r = fopen(file_pcm16le_r, "wb+");
    if (fp_pcm16le_r == NULL)
    {
        printf("fopen error: cannot open %s\n", file_pcm16le_r);
        fclose(fp_pcm16le);
        fclose(fp_pcm16le_l);
        return -1;
    }

    unsigned sampleSize = 4;  // 左右两个声道,各一个16-bit采样点,16 * 2 = 32bit,4字节
    unsigned char *sampleBuf = (unsigned char *)malloc(sampleSize);

    while (!feof(fp_pcm16le))
    {
        // 同时读取左右声道各一个采样点值
        fread(sampleBuf, 1, 4, fp_pcm16le);
        // 写左声道采样点值
        fwrite(sampleBuf, 1, 2, fp_pcm16le_l);
        // 写右声道采样点值
        fwrite(sampleBuf + 2, 1, 2, fp_pcm16le_r);
    }

    free(sampleBuf);
    fclose(fp_pcm16le);
    fclose(fp_pcm16le_l);
    fclose(fp_pcm16le_r);

    return 0;
}

int main(void)
{
    const char *file_pcm16le = "NocturneNo2inEflat_44.1k_s16le.pcm";
    const char *file_pcm16le_l = "output_pcm16le_l.pcm";
    const char *file_pcm16le_r = "output_pcm16le_r.pcm";

    pcm16le_split(file_pcm16le, file_pcm16le_l, file_pcm16le_r);

    return 0;
}

PCM音量

使用Audacity工具打开一个PCM文件,将波形放大,音频就是正弦波
在这里插入图片描述增加每个一个波形的振幅就可以增加音量。
所以如果要增加PCM数据的音量,只需要将每一个采样的数据乘以一个系数,降低PCM数据的音量就除以一个系数。

将pcm16双通道中左通道音量减半

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
    将pcm16双通道中左通道音量减半
    pcm16双通道格式中,左右通道采样点数据交叉排列,每个采样点16bit
    |-------|-------|-------|-------|-------|-------|
    |L1Frame|RlFrame|L2Frame|R2Frame|L3Frame|R3Frame|
    |-------|-------|-------|-------|-------|-------|
    | 16bit | 16bit | 16bit | 16bit | 16bit | 16bit |
    |-------|-------|-------|-------|-------|-------|
    PCM采音频呈现正弦波,增加每个波形的振幅就可以增加音量
    所以如果要增加PCM数据的音量,只需要将每一个采样的数据乘以一个系数,
    降低PCM数据的量就除以一个系数。
*/

int pcm16le_half_volume_left(const char *file_pcm16le, const char *file_pcm16le_half_left)
{
    int cnt = 0;
    short leftFrame = 0;
    FILE *fp_pcm16e = fopen(file_pcm16le, "rb+");
    FILE *fp_pcm16le_half_left = fopen(file_pcm16le_half_left, "wb+");

    unsigned sampleSize = 4; // 左右两个声道,各一个16-bit采样点,16 * 2 = 32bit,4字节
    unsigned char *sampleBuf = (unsigned char *)malloc(sampleSize);

    while (!feof(fp_pcm16e))
    {
        // 同时读取左右声道各一个采样点值
        fread(sampleBuf, 1, 4, fp_pcm16e);
        // 拷贝左声道的采样点
        memcpy(&leftFrame, sampleBuf, 2);
        // 左声道采样点值减半
        leftFrame /= 2;
        // 写左声道采样点
        fwrite(&leftFrame, 1, 2, fp_pcm16le_half_left);
        // 写右声道采样点
        fwrite(sampleBuf + 2, 1, 2, fp_pcm16le_half_left);
        cnt++;
    }

    printf("sample count: %d\n", cnt);

    free(sampleBuf);
    fclose(fp_pcm16e);
    fclose(fp_pcm16le_half_left);

    return 0;
}

int main(void)
{
    const char *file_pcm16le = "NocturneNo2inEflat_44.1k_s16le.pcm";
    const char *file_pcm16le_half_left = "output_half_left.pcm";

    pcm16le_half_volume_left(file_pcm16le, file_pcm16le_half_left);

    return 0;
}

参考

https://blog.csdn.net/leixiaohua1020/article/details/50534316
https://blog.csdn.net/lifei092/article/details/80990813
https://blog.csdn.net/ljxt523/article/details/52068241

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值