PCM

PCM(Pulse Code Modulation,脉冲编码调制)是一种将模拟信号转换为数字信号的编码技术,也是现代数字通信和音频存储的核心基础。它的核心原理是通过采样、量化和编码三个步骤,将连续的模拟信号(如声音、图像)转变为离散的数字二进制码流,从而实现高效传输、存储和处理。

PCM的基本工作原理

  1. 采样(Sampling)
    以固定时间间隔(采样率,如44.1kHz)测量模拟信号的瞬时振幅值。根据奈奎斯特采样定理,采样率至少需达到信号最高频率的2倍,才能完整还原原始信号(例如人耳可听范围20Hz-20kHz,故CD音质采用44.1kHz采样率)。

  2. 量化(Quantization)
    将采样得到的连续振幅值近似为有限个离散电平(量化精度,如16bit、24bit)。量化位数越高,动态范围越广(16bit可表示65,536个电平),信号保真度越好,但数据量也越大。

  3. 编码(Encoding)
    将量化后的数值转换为二进制码(如16bit线性PCM),形成数字信号序列,便于存储或传输。

PCM的关键参数

  • 采样率(Sample Rate):每秒采样次数(如8kHz电话语音、48kHz高清音频)。
  • 位深度(Bit Depth):单个采样的量化精度(如16bit CD音质、24bit专业录音)。
  • 声道数(Channels):单声道(Mono)、立体声(Stereo)或多声道(如5.1环绕声)。

PCM的应用场景

  1. 数字音频

    • CD音质(44.1kHz/16bit立体声PCM)
    • WAV、AIFF等无损音频格式直接存储PCM数据。
    • 蓝牙音频传输(如SBC编解码器先解码为PCM再播放)。
  2. 通信系统

    • 传统电话语音(G.711标准采用8kHz/8bit PCM)。
    • VoIP、视频会议中的音频底层编码。
  3. 专业影音制作

    • 影视录音、音乐制作中常用高精度PCM(如96kHz/24bit)保证后期处理空间。
  4. 存储与传输

    • 多数有损压缩格式(如MP3、AAC)需先将PCM数据通过心理声学模型压缩。
    • HDMI、USB音频接口直接传输PCM数字信号。

PCM的优缺点

  • 优点
    • 保真度高,无损编码时可完全还原原始信号。
    • 处理简单,兼容性强,几乎所有数字系统均支持。
  • 缺点
    • 数据量大(1分钟CD音质PCM约10MB),需依赖压缩技术降低存储/传输负担。
    • 直接传输效率低,通常需结合有损或无损编码(如FLAC)。

PCM与其他编码技术的对比

  • 与DSD(Direct Stream Digital)
    PCM采用多比特量化,DSD使用1bit超高采样率(如2.8MHz),后者更接近模拟波形,但编辑灵活性较差(常用于SACD)。
  • 与压缩编码(MP3/AAC)
    PCM保留完整信息,压缩编码通过舍弃人耳不敏感的频段减少数据量。

总结

PCM是数字信号处理的基石,其标准化和通用性使其成为音频、通信等领域的核心编码方式。尽管数据量大,但通过高采样率和高位深配置(如192kHz/24bit),它能满足从消费级到专业级的高保真需求。现代技术(如MQA编码)仍在PCM基础上优化效率,确保其在未来持续发挥关键作用。

代码

// 测试的PCM数据采用采样率44.1k, 采用精度S16SYS, 通道数2

#include <stdio.h>
#include <SDL2/SDL.h>

#define PCM_BUFFER_SIZE (1024*2*2*2)
// 已知通道数为2 采样深度为16bit(2字节) 每次读取两帧数据 每次采样1024个采样点

static Uint8* s_audio_pos = NULL; // 起始位置
static Uint8* s_audio_end = NULL; // 结束位置
static Uint8* s_audio_buf = NULL; // 数据

void fill_pcm_callback (void *userdata, Uint8 * stream, int len) {
    SDL_memset(stream, 0, len);
    
    if (s_audio_pos >= s_audio_end) {
        return;
    }
    
    int remain_buffer_len = s_audio_end - s_audio_buf; // 计算读取数据的大小
    len = len < remain_buffer_len ? len : remain_buffer_len;
    SDL_MixAudio(stream, s_audio_pos, len, SDL_MIX_MAXVOLUME/6); // 将内存数据拷贝到音频数据流
    printf("len=%d\n", len);
    s_audio_pos += len; // 偏移
}

#undef main

int main(int argc, char* argv[]) {
    int ret = -1;
    FILE* audio_fp = NULL;
    SDL_AudioSpec spec;
    const char* audio_path = "../44100_16bit_2ch.pcm";
    size_t read_buf_len = 0;

    if(SDL_Init(SDL_INIT_AUDIO)) {
        fprintf(stderr, "Could not init sdl\n");
        return ret;
    }

    audio_fp = fopen(audio_path, "rb");

    if (!audio_fp) {
        fprintf(stderr, "Could not open audio file\n");
        return -1;
    }

    s_audio_buf = (Uint8*)malloc(PCM_BUFFER_SIZE);

    spec.channels = 2; // 双通道
    spec.callback = fill_pcm_callback; // 回调函数
    spec.format = AUDIO_S16SYS; // 精度
    spec.userdata = NULL;
    spec.silence = 0;
    spec.samples = 1024; // 采样点
    spec.freq = 44100; // 频率

    if(SDL_OpenAudio(&spec, NULL)) {
        fprintf(stderr, "Failed to open audio\n");
        goto _FAIL;
    }

    SDL_PauseAudio(0);

    int data_count = 0;

    for(;;) {
        read_buf_len = fread(s_audio_buf, 1, PCM_BUFFER_SIZE, audio_fp);
        if (read_buf_len == 0) {
            break;
        }

        data_count += read_buf_len;
        s_audio_pos = s_audio_buf;
        s_audio_end = s_audio_buf + read_buf_len;

        while(s_audio_pos < s_audio_end) {
            SDL_Delay(10);
        }
    }

    SDL_CloseAudio();
_FAIL:


    if (audio_fp) {
        fclose(audio_fp);
    }

    if (s_audio_buf) {
        free(s_audio_buf);
    }
    SDL_Quit();
    return 0;
}

代码解释写在注释里啦!