PCM 详解
PCM(Pulse Code Modulation,脉冲编码调制)是未经压缩的原始音频数据格式,是所有音频编解码的基础------所有压缩音频(MP3/AAC/FLAC 等)最终都要解码为 PCM 才能被声卡播放,FFmpeg 中所有音频处理、编解码流程也均围绕 PCM 展开。
本文将从PCM 核心原理 →FFmpeg 中 PCM 数据表示 →FFmpeg 源码级 PCM 操作 →完整流程示例 ,一步步拆解,所有源码均来自 FFmpeg 6.x 核心库(libavutil/libavformat/libavcodec),保证实用性和准确性。
一、PCM 核心原理(基础必懂,为源码解析铺垫)
PCM 的本质是对模拟音频的"采样-量化-编码"三步离散化过程,最终得到二进制原始数据,无任何压缩、无任何附加头信息,仅存纯音频采样值。
1. 采样(Sampling)
模拟音频是连续的声波(时间+振幅连续),采样 是在时间轴上离散化------以固定频率(采样率)从连续声波中"截取"样本,把无限的时间点转化为有限的采样点。
- 核心参数:采样率(sample rate),单位 Hz(次/秒),表示每秒采集的样本数;
- 常见值:44100Hz(CD 标准)、48000Hz(视频标准)、22050Hz、16000Hz(语音);
- 物理意义:采样率越高,音频还原度越高,数据量也越大(奈奎斯特采样定理:采样率≥2倍音频最高频率,才能无失真还原)。
2. 量化(Quantization)
量化 是在振幅轴上离散化------把采样得到的连续振幅值(无限精度)映射为有限个离散的整数/浮点数,把模拟量转化为数字量。
- 核心参数:采样位深(sample format/bit depth),单位 bit,表示每个采样点用多少位二进制表示;
- 常见值:8bit、16bit(最常用)、24bit、32bit(整数/浮点);
- 物理意义:位深越高,振幅的精度越高,音频动态范围(音量差)越大,数据量也越大。
3. 编码(Coding)
PCM 的编码 是无压缩的直接二进制编码------把量化后的离散值直接转换为二进制数据,无冗余、无加密、无封装,最终得到纯采样值序列。
- 关键特性:编码后无任何附加信息,PCM 文件(如
.pcm)仅存采样值,播放时必须手动指定采样率、位深、声道数,否则会播放失真/噪音。
4. PCM 数据量计算(核心公式,FFmpeg 中频繁用到)
PCM 是裸数据,数据量与核心参数线性相关,公式为:
每秒数据量(字节)=采样率×采样位深×声道数8\text{每秒数据量(字节)} = \frac{\text{采样率} \times \text{采样位深} \times \text{声道数}}{8}每秒数据量(字节)=8采样率×采样位深×声道数
- 示例:44100Hz + 16bit + 立体声(2声道)→ 44100×16×2/8 = 176400 字节/秒(172.26KB/s);
- FFmpeg 中会基于此公式计算音频帧的大小、缓冲区容量,是音频处理的基础。
5. 声道布局(Channel Layout)
PCM 中多声道的采样值按固定顺序存储,FFmpeg 对声道布局有严格的标准化定义(源码中核心枚举),常见布局:
- 单声道(MONO):1 声道,仅左/右通道,采样值序列:
[sample1], [sample2], ...; - 立体声(STEREO):2 声道,左声道优先,交替存储 (FFmpeg 固定规则),序列:
[L1], [R1], [L2], [R2], ...; - 多声道:5.1/7.1 等,FFmpeg 定义了固定的声道顺序(如 FL/FR/FC/LFE/BL/BR),源码中通过
AVChannelLayout描述。
二、FFmpeg 中 PCM 核心数据结构(源码级定义)
FFmpeg 中所有 PCM 数据的存储、处理、传递,均基于3 个核心数据结构 ,分别对应「采样格式」「声道布局」「PCM 数据载体」,源码主要分布在 libavutil/samplefmt.h、libavutil/channel_layout.h、libavutil/frame.h。
1. 采样格式:AVSampleFormat 枚举(samplefmt.h)
FFmpeg 用枚举类型 定义所有支持的 PCM 采样格式,核心区分「整数型」和「浮点型」,「有符号」和「无符号」,且所有格式均为小端序(FFmpeg 跨平台固定规则,无需考虑大小端兼容)。
核心源码定义(关键枚举值)
c
// libavutil/samplefmt.h
enum AVSampleFormat {
AV_SAMPLE_FMT_NONE = -1,
AV_SAMPLE_FMT_U8, ///< 8位无符号整数 PCM (0~255)
AV_SAMPLE_FMT_S16, ///< 16位有符号整数 PCM (-32768~32767) 最常用
AV_SAMPLE_FMT_S32, ///< 32位有符号整数 PCM
AV_SAMPLE_FMT_FLT, ///< 32位浮点型 PCM (-1.0~1.0) 高精度处理用
AV_SAMPLE_FMT_DBL, ///< 64位浮点型 PCM (-1.0~1.0)
// 平面格式(Planar),后缀 P,核心特性:每个声道单独占一个数据缓冲区
AV_SAMPLE_FMT_U8P,
AV_SAMPLE_FMT_S16P,
AV_SAMPLE_FMT_S32P,
AV_SAMPLE_FMT_FLTP,
AV_SAMPLE_FMT_DBLP,
AV_SAMPLE_FMT_NB ///< 格式总数,用于遍历/校验
};
关键概念:打包格式(Packed)vs 平面格式(Planar)
这是 FFmpeg 中 PCM 最核心的存储方式区别,直接影响源码中数据的读写方式:
- 打包格式(Packed,无 P 后缀) :所有声道的采样值交错存储在同一个缓冲区 ,对应前文的立体声序列
[L1,R1,L2,R2,...],适合文件存储、声卡播放; - 平面格式(Planar,带 P 后缀) :每个声道独占一个独立的缓冲区 ,立体声为例:左声道缓冲区
[L1,L2,L3,...],右声道缓冲区[R1,R2,R3,...],适合 FFmpeg 内部音频处理(重采样、滤波、编解码),因为单独操作声道更高效。
配套实用函数(源码中高频调用)
c
// 判断是否为平面格式
int av_sample_fmt_is_planar(enum AVSampleFormat sf);
// 获取采样格式的字节数(如 AV_SAMPLE_FMT_S16 → 2 字节)
int av_get_bytes_per_sample(enum AVSampleFormat sf);
// 采样格式转字符串(如 AV_SAMPLE_FMT_S16 → "s16"),打印/日志用
const char *av_get_sample_fmt_name(enum AVSampleFormat sf);
2. 声道布局:AVChannelLayout 结构体(channel_layout.h)
FFmpeg 5.0 及以上版本废弃了旧的 uint64_t channel_layout ,改用 AVChannelLayout 结构体统一描述声道布局,支持动态声道数,更灵活,是当前源码的标准用法。
核心源码定义(简化版,保留关键字段)
c
// libavutil/channel_layout.h
typedef struct AVChannelLayout {
uint64_t mask; ///< 声道掩码(如立体声 → AV_CH_LAYOUT_STEREO)
int nb_channels; ///< 声道数(如立体声 → 2),直接通过该字段获取更高效
// 省略动态声道名等扩展字段(基础用法无需关注)
} AVChannelLayout;
核心声道掩码宏定义(源码中直接使用)
c
// 单声道/立体声(最常用)
#define AV_CH_LAYOUT_MONO (AV_CH_FRONT_CENTER)
#define AV_CH_LAYOUT_STEREO (AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT)
// 多声道
#define AV_CH_LAYOUT_5POINT1 (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT)
配套实用函数(源码中高频调用)
c
// 初始化声道布局(如初始化立体声:av_channel_layout_init(&ch_layout, AV_CH_LAYOUT_STEREO))
int av_channel_layout_init(AVChannelLayout *chl, uint64_t mask);
// 声道布局转字符串(如立体声 → "stereo"),打印/日志用
char *av_channel_layout_describe(const AVChannelLayout *chl, char *buf, size_t buf_size);
3. PCM 数据载体:AVFrame 结构体(frame.h)
FFmpeg 中所有原始音频(PCM)和原始视频(YUV/RGB)均通过 AVFrame 传递,是编解码、滤镜、重采样的核心数据载体,PCM 相关的核心字段均在该结构体中。
核心源码定义(PCM 相关关键字段,简化版)
c
// libavutil/frame.h
typedef struct AVFrame {
uint8_t *data[AV_NUM_DATA_POINTERS]; ///< PCM 数据缓冲区指针数组
int linesize[AV_NUM_DATA_POINTERS]; ///< 每个缓冲区的行大小(对齐用,PCM 中一般为采样数×采样字节数)
int nb_samples; ///< 该帧的 PCM 采样点数(核心!表示一帧有多少个采样)
enum AVSampleFormat format; ///< PCM 采样格式(如 AV_SAMPLE_FMT_S16)
AVChannelLayout ch_layout; ///< 声道布局(FFmpeg 5.0+)
int sample_rate; ///< 采样率(如 44100)
int64_t pts; ///< 时间戳(播放同步用,单位:采样数/时基)
// 省略视频相关/扩展字段
} AVFrame;
PCM 中 AVFrame 的核心使用规则(源码级)
data数组与平面/打包格式的对应 :- 打包格式(如 AV_SAMPLE_FMT_S16):仅使用
data[0],所有声道的采样值交错存储在data[0]指向的缓冲区,data[1...]为 NULL; - 平面格式(如 AV_SAMPLE_FMT_S16P):
data[i]对应第i个声道的缓冲区,立体声则data[0]=左声道,data[1]=右声道,依次类推;
- 打包格式(如 AV_SAMPLE_FMT_S16):仅使用
nb_samples:一帧 PCM 的采样点数(不是字节数!),FFmpeg 中音频帧的大小均以「采样点数」为单位,是重采样、帧分配的核心参数;- 缓冲区管理 :
AVFrame的 PCM 缓冲区需通过av_frame_get_buffer()分配,通过av_frame_unref()释放,禁止手动 malloc/free(避免内存泄漏)。
三、FFmpeg 源码级 PCM 核心操作(分步实现,可直接复用)
基于上述核心数据结构,FFmpeg 中对 PCM 的分配、读写、格式转换、重采样 是最核心的操作,以下为分步源码实现,包含关键注释和注意事项,均为 FFmpeg 标准用法。
步骤 1:初始化 PCM 相关参数(固定流程)
先定义并初始化 FFmpeg 中 PCM 的三大核心参数,作为后续所有操作的基础:
c
#include <libavutil/samplefmt.h>
#include <libavutil/channel_layout.h>
#include <libavutil/frame.h>
// 1. 定义核心参数
enum AVSampleFormat pcm_fmt = AV_SAMPLE_FMT_S16; // 16位有符号整数 PCM(最常用)
AVChannelLayout pcm_ch_layout; // 声道布局
int pcm_sample_rate = 44100; // 采样率 44100Hz
int pcm_nb_samples = 1024; // 每帧采样点数(FFmpeg 常用 1024/2048)
int nb_channels; // 实际声道数
// 2. 初始化声道布局(立体声)
av_channel_layout_init(&pcm_ch_layout, AV_CH_LAYOUT_STEREO);
nb_channels = pcm_ch_layout.nb_channels; // 获取声道数:2
步骤 2:分配 AVFrame 并初始化 PCM 缓冲区(源码标准用法)
AVFrame 是 PCM 数据的载体,必须通过 FFmpeg 提供的 API 分配缓冲区 ,自动处理「平面/打包格式」「内存对齐」等问题,核心函数:av_frame_alloc() + av_frame_get_buffer()。
c
AVFrame *pcm_frame = NULL;
int ret;
// 1. 分配 AVFrame 结构体(仅分配结构体,不分配数据缓冲区)
pcm_frame = av_frame_alloc();
if (!pcm_frame) {
fprintf(stderr, "分配 AVFrame 失败\n");
return -1;
}
// 2. 设置 PCM 核心参数到 AVFrame
pcm_frame->format = pcm_fmt;
pcm_frame->ch_layout = pcm_ch_layout; // 直接赋值(FFmpeg 5.0+)
pcm_frame->sample_rate = pcm_sample_rate;
pcm_frame->nb_samples = pcm_nb_samples;
// 3. 分配 PCM 数据缓冲区(关键!自动处理平面/打包格式)
// 第二个参数 0 表示使用默认内存对齐(16字节,FFmpeg 推荐)
ret = av_frame_get_buffer(pcm_frame, 0);
if (ret < 0) {
fprintf(stderr, "分配 PCM 缓冲区失败:%s\n", av_err2str(ret));
av_frame_free(&pcm_frame); // 失败时释放 AVFrame
return -1;
}
// 此时:pcm_frame->data[] 已指向有效缓冲区,可直接读写 PCM 数据
步骤 3:读写 PCM 数据(源码级,区分打包/平面格式)
PCM 数据为原始二进制值 ,读写时需根据「打包/平面格式」区分操作,直接操作 pcm_frame->data[] 缓冲区即可,以下为16位立体声的读写示例(最常用场景)。
场景 1:打包格式(AV_SAMPLE_FMT_S16,立体声)
仅操作 data[0],缓冲区为int16_t 类型(16位有符号),采样值按「左→右→左→右」交错存储:
c
// 转换缓冲区指针为 16位有符号整数(匹配 AV_SAMPLE_FMT_S16)
int16_t *pcm_data = (int16_t *)pcm_frame->data[0];
// 一帧总采样数 = 每帧采样点数 × 声道数
int total_samples = pcm_frame->nb_samples * nb_channels;
// 写 PCM 数据:生成静音(所有采样值为 0)
for (int i = 0; i < total_samples; i++) {
pcm_data[i] = 0; // 静音,实际场景可替换为采集/解码的采样值
}
// 读 PCM 数据:获取第 n 个采样点的左/右声道值
int n = 5; // 第5个采样点
int16_t L = pcm_data[2*n]; // 左声道:2*n 位置
int16_t R = pcm_data[2*n+1]; // 右声道:2*n+1 位置
场景 2:平面格式(AV_SAMPLE_FMT_S16P,立体声)
data[0]=左声道缓冲区,data[1]=右声道缓冲区,各自存储对应声道的采样值,无交错:
c
// 转换各声道缓冲区指针为 16位有符号整数
int16_t *pcm_data_L = (int16_t *)pcm_frame->data[0]; // 左声道
int16_t *pcm_data_R = (int16_t *)pcm_frame->data[1]; // 右声道
int nb_samples = pcm_frame->nb_samples;
// 写 PCM 数据:生成静音
for (int i = 0; i < nb_samples; i++) {
pcm_data_L[i] = 0;
pcm_data_R[i] = 0;
}
// 读 PCM 数据:获取第 n 个采样点的左/右声道值
int n = 5;
int16_t L = pcm_data_L[n];
int16_t R = pcm_data_R[n];
步骤 4:PCM 格式转换/重采样(FFmpeg 核心功能,源码简化版)
实际开发中,经常需要将 PCM 从一种格式转换为另一种(如 48000Hz→44100Hz、S16→FLT、立体声→单声道),FFmpeg 提供 libswresample 库实现高效重采样 ,核心是 SwrContext 上下文。
核心流程(源码级,可直接复用)
c
#include <libswresample/swresample.h>
// 输入参数(原 PCM):48000Hz、AV_SAMPLE_FMT_S16、立体声
AVChannelLayout in_ch_layout;
av_channel_layout_init(&in_ch_layout, AV_CH_LAYOUT_STEREO);
enum AVSampleFormat in_fmt = AV_SAMPLE_FMT_S16;
int in_sample_rate = 48000;
// 输出参数(目标 PCM):44100Hz、AV_SAMPLE_FMT_S16、立体声
AVChannelLayout out_ch_layout = pcm_ch_layout;
enum AVSampleFormat out_fmt = pcm_fmt;
int out_sample_rate = pcm_sample_rate;
SwrContext *swr_ctx = NULL;
AVFrame *out_frame = NULL; // 目标 PCM 帧
// 1. 分配并初始化重采样上下文
swr_ctx = swr_alloc_set_opts2(&swr_ctx, // 重采样上下文
&out_ch_layout, // 输出声道布局
out_fmt, // 输出采样格式
out_sample_rate, // 输出采样率
&in_ch_layout, // 输入声道布局
in_fmt, // 输入采样格式
in_sample_rate, // 输入采样率
0, // 日志级别
NULL); // 日志回调
if (!swr_ctx) {
fprintf(stderr, "初始化重采样上下文失败\n");
return -1;
}
// 2. 初始化重采样上下文(必须调用)
if (swr_init(swr_ctx) < 0) {
fprintf(stderr, "重采样上下文初始化失败\n");
swr_free(&swr_ctx);
return -1;
}
// 3. 分配输出 PCM 帧(同步骤 2,略)
out_frame = av_frame_alloc();
out_frame->format = out_fmt;
out_frame->ch_layout = out_ch_layout;
out_frame->sample_rate = out_sample_rate;
out_frame->nb_samples = pcm_nb_samples;
av_frame_get_buffer(out_frame, 0);
// 4. 执行重采样(核心!将 in_frame 转换为 out_frame)
ret = swr_convert_frame(swr_ctx, out_frame, pcm_frame);
if (ret < 0) {
fprintf(stderr, "重采样失败:%s\n", av_err2str(ret));
return -1;
}
// 此时:out_frame 即为转换后的 PCM 数据,可直接使用
步骤 5:释放资源(FFmpeg 内存管理标准用法)
FFmpeg 中所有通过 av_*_alloc() 分配的资源,必须通过对应的 av_*_free()/av_*_unref() 释放,避免内存泄漏,核心释放顺序:先释放子资源,再释放主资源。
c
// 1. 释放 AVFrame(av_frame_free 会自动释放内部缓冲区)
if (pcm_frame) av_frame_free(&pcm_frame);
if (out_frame) av_frame_free(&out_frame);
// 2. 释放重采样上下文
if (swr_ctx) swr_free(&swr_ctx);
// 3. 释放声道布局(FFmpeg 5.0+,可选,简单布局可省略)
av_channel_layout_uninit(&pcm_ch_layout);
av_channel_layout_uninit(&in_ch_layout);
四、FFmpeg 中 PCM 完整流程示例(解码→PCM→播放/存储)
结合 FFmpeg 编解码流程,完整的「音频文件解码为 PCM」流程是开发中最常用的场景,核心步骤:
打开输入文件 → 查找音频流 → 初始化解码器 → 读取包(AVPacket) → 解码为帧(AVFrame,PCM) → 处理 PCM(播放/存储/重采样) → 释放资源。
核心源码框架(简化版,可直接编译)
c
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/frame.h>
#include <libavutil/samplefmt.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "用法:%s 输入音频文件(mp3/aac/flac 等)\n", argv[0]);
return -1;
}
const char *in_filename = argv[1];
AVFormatContext *fmt_ctx = NULL;
AVCodecContext *dec_ctx = NULL;
AVCodec *dec = NULL;
int audio_stream_idx = -1;
AVPacket pkt;
AVFrame *frame = NULL;
int ret;
// 步骤 1:注册所有组件(FFmpeg 4.0+ 可省略,自动注册)
av_register_all();
// 步骤 2:打开输入文件,获取格式上下文
ret = avformat_open_input(&fmt_ctx, in_filename, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "打开文件失败:%s\n", av_err2str(ret));
return -1;
}
// 步骤 3:查找流信息
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "查找流信息失败:%s\n", av_err2str(ret));
avformat_close_input(&fmt_ctx);
return -1;
}
// 步骤 4:查找音频流索引
audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &dec, 0);
if (audio_stream_idx < 0) {
fprintf(stderr, "未找到音频流\n");
avformat_close_input(&fmt_ctx);
return -1;
}
// 步骤 5:初始化解码器上下文
dec_ctx = avcodec_alloc_context3(dec);
ret = avcodec_parameters_to_context(dec_ctx, fmt_ctx->streams[audio_stream_idx]->codecpar);
if (ret < 0) {
fprintf(stderr, "初始化解码器上下文失败:%s\n", av_err2str(ret));
avformat_close_input(&fmt_ctx);
avcodec_free_context(&dec_ctx);
return -1;
}
// 步骤 6:打开解码器
ret = avcodec_open2(dec_ctx, dec, NULL);
if (ret < 0) {
fprintf(stderr, "打开解码器失败:%s\n", av_err2str(ret));
avformat_close_input(&fmt_ctx);
avcodec_free_context(&dec_ctx);
return -1;
}
// 步骤 7:分配帧(存储解码后的 PCM)
frame = av_frame_alloc();
av_init_packet(&pkt);
// 步骤 8:循环读取包并解码为 PCM
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
if (pkt.stream_index == audio_stream_idx) {
// 发送包到解码器
ret = avcodec_send_packet(dec_ctx, &pkt);
if (ret < 0) continue;
// 接收解码后的帧(PCM 数据)
while (ret >= 0) {
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
// 核心:frame 中即为解码后的原始 PCM 数据
// 可执行:存储为 .pcm 文件、重采样、传递给声卡播放等操作
printf("解码得到 PCM 帧:采样率 %d,格式 %s,采样点数 %d\n",
frame->sample_rate,
av_get_sample_fmt_name((enum AVSampleFormat)frame->format),
frame->nb_samples);
// 示例:将 PCM 写入文件(二进制模式)
FILE *pcm_file = fopen("output.pcm", "ab");
fwrite(frame->data[0], 1, frame->linesize[0], pcm_file);
fclose(pcm_file);
}
}
av_packet_unref(&pkt); // 释放包资源
}
// 步骤 9:释放所有资源
av_frame_free(&frame);
avcodec_close(dec_ctx);
avcodec_free_context(&dec_ctx);
avformat_close_input(&fmt_ctx);
printf("解码完成,PCM 文件已保存为 output.pcm\n");
printf("播放方式:ffplay -f s16le -ar 44100 -ac 2 output.pcm(需根据实际参数调整)\n");
return 0;
}
编译与运行(Linux/macOS)
bash
# 编译(链接 FFmpeg 核心库)
gcc -o pcm_decode pcm_decode.c `pkg-config --cflags --libs libavformat libavcodec libavutil`
# 运行(解码 mp3 为 pcm)
./pcm_decode input.mp3
# 播放生成的 PCM 文件(ffplay 需指定参数,否则失真)
ffplay -f s16le -ar 44100 -ac 2 output.pcm
- 注意:
ffplay播放.pcm文件时,必须手动指定采样格式、采样率、声道数 ,参数对应:-f s16le:16位小端有符号整数(AV_SAMPLE_FMT_S16);-ar 44100:采样率 44100Hz;-ac 2:立体声(2声道)。
五、FFmpeg 中 PCM 关键注意事项(源码开发避坑)
- 平面/打包格式的区分 :FFmpeg 内部编解码、重采样默认使用平面格式 ,而文件存储、声卡播放一般使用打包格式 ,转换时可通过
swresample实现; - 数据类型匹配 :操作 PCM 缓冲区时,必须将
uint8_t*转换为对应采样格式的类型(如 S16→int16_t,FLT→float),避免字节操作错误; - 内存管理 :禁止手动操作
AVFrame/SwrContext的缓冲区,必须使用 FFmpeg 提供的 API(av_frame_get_buffer()/av_frame_free()/swr_free()); - 跨平台兼容 :FFmpeg 中所有 PCM 格式均为小端序,无需处理大小端问题,直接跨平台使用;
- 采样点数 :FFmpeg 中音频帧的
nb_samples一般为 2的幂(1024/2048/4096),是编码器/解码器的固定要求,自定义分配时建议遵循; - 错误处理:FFmpeg 所有 API 返回值均需检查,失败时必须释放已分配的资源,避免内存泄漏。
六、总结
本文从原理→源码结构→核心操作→完整流程,结合 FFmpeg 5.x/6.x 源码详细解析了 PCM,核心要点总结:
- PCM 是无压缩原始音频,由「采样-量化-编码」生成,数据量与采样率、位深、声道数线性相关;
- FFmpeg 中 PCM 的核心描述:
AVSampleFormat(采样格式)、AVChannelLayout(声道布局)、AVFrame(数据载体); - FFmpeg 中 PCM 分打包格式 (交错存储,适合存储/播放)和平面格式(分声道存储,适合内部处理),是源码开发的关键区分点;
- FFmpeg 操作 PCM 的标准流程:参数初始化→
AVFrame分配→缓冲区分配→数据读写→重采样/处理→资源释放; - 所有压缩音频解码后均为 PCM,FFmpeg 中通过
avcodec_receive_frame()获取 PCM 帧,可直接存储、重采样或播放。