当前位置: 首页 > news >正文

FFmpeg 核心 API 系列:音频重采样 SwrContext 完全指南(新API版本)

FFmpeg 核心 API 系列:音频重采样 SwrContext 完全指南(新API版本)
📅 更新时间:2025年10月18日
🏷️ 标签:FFmpeg | 多媒体处理 | 音视频编程 | C/C++ | 音频处理 | 重采样

文章目录

  • 📖 前言
  • 🎯 为什么需要音频重采样?
    • 1. 音频的三大属性
      • 📊 采样率(Sample Rate)
      • 📊 采样格式(Sample Format)
      • 📊 声道数(Channels)
    • 2. Planar vs Packed(重要概念!)
      • Packed(交错格式)
      • Planar(平面格式)
      • 为什么需要转换?
    • 3. 音频转换对比表
  • 🔧 核心 API 详解(新版)
    • API 1️⃣:`swr_alloc` - 创建音频转换器
      • 函数原型
      • 参数说明
      • 返回值
      • 作用
      • 基本用法
      • 关键要点
    • API 2️⃣:`av_opt_set_int` - 设置整数选项
      • 函数原型
      • 参数说明
      • 返回值
      • 作用
      • 基本用法
      • 关键要点
    • API 3️⃣:`av_opt_set_sample_fmt` - 设置采样格式
      • 函数原型
      • 参数说明
      • 返回值
      • 作用
      • 基本用法
      • 常用采样格式表
      • ⚠️ 重要注意事项
    • API 4️⃣:`av_opt_set_chlayout` - 设置声道布局
      • 函数原型
      • 参数说明
      • 返回值
      • 作用
      • 基本用法
      • 常用声道布局
      • 如何获取声道数
    • API 5️⃣:`swr_init` - 初始化转换器
      • 函数原型
      • 参数说明
      • 返回值
      • 作用
      • 基本用法
      • ⚠️ 关键要点
    • API 6️⃣:`av_rescale_rnd` - 计算输出采样数
      • 函数原型
      • 参数说明
      • 返回值
      • 作用
      • 为什么需要这个函数?
      • 基本用法
      • 取整方式
    • API 7️⃣:`av_samples_alloc_array_and_samples` - 分配音频缓冲区
      • 函数原型
      • 参数说明
      • 返回值
      • 作用
      • 基本用法
      • 这个函数做了什么?
      • ⚠️ 内存释放注意
    • API 8️⃣:`swr_convert` - 执行音频转换
      • 函数原型
      • 参数说明
      • 返回值
      • 作用
      • 基本用法
      • 关键要点
      • 转换过程示意
    • API 9️⃣:`av_samples_get_buffer_size` - 计算数据字节数
      • 函数原型
      • 参数说明
      • 返回值
      • 作用
      • 基本用法
      • 计算公式
      • 快速计算方法
    • API 🔟:`swr_free` - 释放转换器
      • 函数原型
      • 参数说明
      • 返回值
      • 作用
      • 基本用法
      • 关键要点
  • 🔄 完整转换流程
    • 标准流程图
  • 💻 完整示例代码
    • 输出结果示例
  • ⚠️ 常见错误与注意事项
    • 错误1:采样格式从流参数获取
    • 错误2:忘记调用 swr_init()
    • 错误3:输出缓冲区太小
    • 错误4:释放了 linesize
    • 错误5:混淆采样数和字节数
  • 🎓 验证结果
    • 方法1:使用 FFplay 播放
  • 📋 总结
    • 核心流程回顾
    • 关键API对比
    • 资源释放清单


📖 前言

回顾前四个阶段,我们已经能够:

  • 阶段一:打开文件 → 得到 AVFormatContextAVStream
  • 阶段二:查找解码器 → 配置并打开 AVCodecContext
  • 阶段三:读取并解码 → 得到音视频 AVFrame
  • 阶段四:视频格式转换 → YUV 转 RGB(使用 SwsContext

现在我们已经能处理视频了,但音频呢?解码出来的音频帧格式通常不能直接播放!就像视频需要从YUV转RGB一样,音频也需要格式转换。

本阶段的核心任务

解码后的音频帧(AVFrame)→ 格式转换(SwrContext)→ PCM数据 → 播放或保存

这就是音频播放的关键一步


🎯 为什么需要音频重采样?

1. 音频的三大属性

在理解重采样之前,先搞清楚音频的三大核心属性:

📊 采样率(Sample Rate)

定义:每秒采样的次数

常见值

  • 44100 Hz - CD音质标准
  • 48000 Hz - 专业音频标准
  • 16000 Hz - 语音通话
  • 8000 Hz - 电话质量

类比:就像视频的帧率,采样率越高,音质越好


📊 采样格式(Sample Format)

定义:每个采样点的数据类型和存储方式

常见格式

格式数据类型每采样字节数存储方式说明
AV_SAMPLE_FMT_S1616位整数2Packed交错存储
AV_SAMPLE_FMT_S16P16位整数2Planar平面存储
AV_SAMPLE_FMT_FLT32位浮点4Packed交错存储
AV_SAMPLE_FMT_FLTP32位浮点4Planar最常见

📊 声道数(Channels)

定义:音频的声道数量

常见配置

  • 1 - 单声道(Mono)
  • 2 - 立体声(Stereo)
  • 6 - 5.1环绕声

2. Planar vs Packed(重要概念!)

这是音频处理中最容易混淆的概念:

Packed(交错格式)

左右声道数据交错存储在一起:
[L1][R1][L2][R2][L3][R3]...存储位置:frame->data[0](只用一个平面)

Planar(平面格式)

左右声道数据分开存储:
左声道:[L1][L2][L3][L4]... → frame->data[0]
右声道:[R1][R2][R3][R4]... → frame->data[1]存储位置:每个声道一个平面

为什么需要转换?

FFmpeg解码输出 → 通常是 FLTP(浮点数Planar)↓需要转换↓
音频设备播放 → 需要 S16(整数Packed)

不转换的后果

  • 直接播放会有噪音或无声
  • 声道错乱
  • 播放速度异常

3. 音频转换对比表

转换类型示例用途
采样率转换48000Hz → 44100Hz适配播放设备
格式转换FLTP → S16从浮点转整数
声道转换立体声 → 单声道节省带宽
存储方式转换Planar → Packed适配播放API

🔧 核心 API 详解(新版)

API 1️⃣:swr_alloc - 创建音频转换器

函数原型

struct SwrContext* swr_alloc(void);

参数说明

  • 无参数

返回值

  • 成功:返回 SwrContext* 指针
  • 失败:返回 NULL

作用

创建一个空的音频转换器对象,类似于视频转换中的 SwsContext

基本用法

SwrContext* swr_ctx = swr_alloc();
if(!swr_ctx) {qDebug() << "创建SwrContext失败";return -1;
}
qDebug() << "创建SwrContext成功";

关键要点

  1. 只是创建空对象还需要用后续API设置参数!!!
  2. 不能直接使用:必须调用 swr_init() 初始化后才能用
  3. 只需创建一次:可以重复用于多帧转换

API 2️⃣:av_opt_set_int - 设置整数选项

函数原型

int av_opt_set_int(void *obj, const char *name, int64_t val, int search_flags);

参数说明

参数说明常用值
obj要设置的对象swr_ctx
name选项名称"in_sample_rate" / "out_sample_rate"
val要设置的值采样率(如 44100
search_flags搜索标志0(默认)

返回值

  • 成功:>= 0
  • 失败:< 0(负数错误码)

作用

设置整数类型的音频参数,主要用于设置采样率!!!

基本用法

// 设置输入采样率(从解码器获取)
av_opt_set_int(swr_ctx, "in_sample_rate", audio_ctx->sample_rate, 0);// 设置输出采样率(目标值)
av_opt_set_int(swr_ctx, "out_sample_rate", 44100, 0);

关键要点

  1. 输入采样率来源:从 audio_ctx->sample_rate 获取
  2. 输出采样率选择:常用 4410048000
  3. 顺序无关:先设置输入还是输出都可以

API 3️⃣:av_opt_set_sample_fmt - 设置采样格式

函数原型

int av_opt_set_sample_fmt(void *obj, const char *name, enum AVSampleFormat fmt, int search_flags);

参数说明

参数说明常用值
obj要设置的对象swr_ctx
name选项名称"in_sample_fmt" / "out_sample_fmt"
fmt采样格式AV_SAMPLE_FMT_S16
search_flags搜索标志0

返回值

  • 成功:>= 0
  • 失败:< 0

作用

设置音频的采样格式(整数/浮点、Planar/Packed)。

基本用法

// 设置输入格式(从解码器获取)
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", audio_ctx->sample_fmt, 0);// 设置输出格式(S16是最常用的播放格式)
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);

常用采样格式表

格式常量说明适用场景
AV_SAMPLE_FMT_S1616位整数Packed播放器输出
AV_SAMPLE_FMT_S16P16位整数Planar音频处理
AV_SAMPLE_FMT_FLTP32位浮点Planar解码器常见输出
AV_SAMPLE_FMT_FLT32位浮点Packed音频处理

⚠️ 重要注意事项

采样格式必须从解码器上下文获取,不能从流参数获取!

// ❌ 错误:从流参数获取
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", (AVSampleFormat)audio_stream->codecpar->format, 0);  // 可能不准确// ✅ 正确:从解码器上下文获取
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", audio_ctx->sample_fmt, 0);  // 准确的格式

原因

  • audio_stream->codecpar->format 是文件中存储的格式
  • audio_ctx->sample_fmt 是解码器实际输出的格式
  • 解码器可能会转换格式!

API 4️⃣:av_opt_set_chlayout - 设置声道布局

函数原型

int av_opt_set_chlayout(void *obj, const char *name, const AVChannelLayout *layout, int search_flags);

参数说明

参数说明常用值
obj要设置的对象swr_ctx
name选项名称"in_chlayout" / "out_chlayout"
layout声道布局指针&audio_ctx->ch_layout
search_flags搜索标志0

返回值

  • 成功:>= 0
  • 失败:< 0

作用

设置音频的声道布局(单声道、立体声、环绕声等)。

基本用法

// 设置输入声道布局(从解码器获取)
av_opt_set_chlayout(swr_ctx, "in_chlayout", &audio_ctx->ch_layout, 0);// 设置输出声道布局(立体声)
AVChannelLayout out_layout = AV_CHANNEL_LAYOUT_STEREO;
av_opt_set_chlayout(swr_ctx, "out_chlayout", &out_layout, 0);// 或者单声道
AVChannelLayout out_layout = AV_CHANNEL_LAYOUT_MONO;
av_opt_set_chlayout(swr_ctx, "out_chlayout", &out_layout, 0);

常用声道布局

常量声道数说明
AV_CHANNEL_LAYOUT_MONO1单声道
AV_CHANNEL_LAYOUT_STEREO2立体声(最常用)
AV_CHANNEL_LAYOUT_5POINT165.1环绕声

如何获取声道数

// 从声道布局获取声道数
int channels = audio_ctx->ch_layout.nb_channels;
qDebug() << "声道数:" << channels;

API 5️⃣:swr_init - 初始化转换器

函数原型

int swr_init(struct SwrContext *s);

参数说明

参数说明
sSwrContext指针

返回值

  • 成功:0
  • 失败:< 0(负数错误码)

作用

初始化SwrContext,让之前设置的参数生效。

基本用法

int result = swr_init(swr_ctx);
if(result < 0) {qDebug() << "SwrContext初始化失败";return -1;
}
qDebug() << "SwrContext初始化成功";

⚠️ 关键要点

  1. 必须调用不调用无法使用转换器!!!!
  2. 调用时机:所有参数设置完成后
  3. 只需调用一次:初始化后可重复使用
  4. 相当于"确认配置":就像按下"应用"按钮

错误示例

SwrContext* swr_ctx = swr_alloc();
av_opt_set_int(swr_ctx, "in_sample_rate", 48000, 0);
// 忘记调用 swr_init()
swr_convert(swr_ctx, ...);  // ❌ 会失败或崩溃

API 6️⃣:av_rescale_rnd - 计算输出采样数

函数原型

int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd);

参数说明

参数说明含义
a输入值输入采样数
b乘数输出采样率
c除数输入采样率
rnd取整方式AV_ROUND_UP

返回值

  • 计算结果:(a * b) / c

作用

计算采样率变化后的输出采样数。

为什么需要这个函数?

原因:采样率变化时,采样数也要相应变化!

示例

输入:1024个采样,48000Hz
输出:?个采样,44100Hz计算:1024 * 44100 / 48000 = 941个采样

基本用法

// 计算输出采样数
int out_samples = av_rescale_rnd(frame->nb_samples,              // 输入采样数44100,                          // 输出采样率audio_ctx->sample_rate,         // 输入采样率AV_ROUND_UP                     // 向上取整
);qDebug() << "输入" << frame->nb_samples << "采样";
qDebug() << "输出" << out_samples << "采样";

取整方式

常量说明适用场景
AV_ROUND_UP向上取整分配缓冲区(推荐)
AV_ROUND_DOWN向下取整精确计算
AV_ROUND_ZERO向零取整一般不用

为什么用 AV_ROUND_UP

  • 分配缓冲区时,宁可多分配一点
  • 避免缓冲区不够导致崩溃!!!

API 7️⃣:av_samples_alloc_array_and_samples - 分配音频缓冲区

函数原型

int av_samples_alloc_array_and_samples(uint8_t ***audio_data,          // 输出:缓冲区指针数组int *linesize,                  // 输出:每个平面的大小int nb_channels,                // 声道数int nb_samples,                 // 采样数enum AVSampleFormat sample_fmt, // 采样格式int align                       // 对齐字节数
);

参数说明

参数说明常用值
audio_data指向指针数组的指针(三级指针)&out_data
linesize输出每个平面的字节大小&out_linesize
nb_channels声道数1(单声道)或 2(立体声)
nb_samples采样数av_rescale_rnd() 计算的值
sample_fmt采样格式AV_SAMPLE_FMT_S16
align内存对齐0(默认)

返回值

  • 成功:返回分配的总字节数
  • 失败:< 0(负数错误码)

作用

一次性分配音频数据缓冲区,自动处理Planar/Packed格式。

基本用法

uint8_t** out_data = nullptr;
int out_linesize = 0;int ret = av_samples_alloc_array_and_samples(&out_data,                      // 注意是 &out_data&out_linesize,2,                              // 2声道(立体声)out_samples,                    // 采样数AV_SAMPLE_FMT_S16,             // S16格式0                               // 默认对齐
);if(ret < 0) {qDebug() << "分配音频缓冲区失败";return -1;
}
qDebug() << "分配了" << ret << "字节缓冲区";

这个函数做了什么?

1. 分配 audio_data 数组(指针数组)
2. 分配实际的音频数据内存
3. 自动计算需要的内存大小
4. 处理Planar/Packed格式的差异Packed格式(如S16):- 只有 out_data[0] 有数据- 所有声道交错存储在一起Planar格式(如S16P):- out_data[0] 存第一声道- out_data[1] 存第二声道

⚠️ 内存释放注意

// 使用完后必须释放
if(out_data) {av_freep(&out_data[0]);  // 释放数据av_freep(&out_data);     // 释放指针数组
}

API 8️⃣:swr_convert - 执行音频转换

函数原型

int swr_convert(struct SwrContext *s,       // 转换器上下文uint8_t **out,              // 输出缓冲区int out_count,              // 输出缓冲区能容纳的采样数const uint8_t **in,         // 输入数据int in_count                // 输入采样数
);

参数说明

参数说明常用值
sSwrContext指针swr_ctx
out输出缓冲区out_data
out_count输出缓冲区容量(采样数)out_samples
in输入数据(const uint8_t**)frame->data
in_count输入采样数frame->nb_samples

返回值

  • 成功:返回实际输出的采样数
  • 失败:< 0(负数错误码)

作用

执行音频格式转换,是整个重采样的核心函数。

基本用法

int converted_samples = swr_convert(swr_ctx,                        // 转换器out_data,                       // 输出到这里out_samples,                    // 输出容量(const uint8_t**)frame->data,   // 输入数据frame->nb_samples               // 输入采样数
);if(converted_samples < 0) {qDebug() << "音频转换失败";
} else {qDebug() << "转换了" << converted_samples << "个采样";
}

关键要点

  1. 返回值是采样数:不是字节数!
  2. 输出采样数可能不同:因为采样率可能变化
  3. 可以多次调用:同一个转换器可以重复使用
  4. 输入数据来自AVFrameframe->dataframe->nb_samples

转换过程示意

输入AVFrame:- 48000Hz- FLTP格式(浮点Planar)- 1024个采样- 2声道↓swr_convert↓
输出PCM数据:- 44100Hz- S16格式(整数Packed)- 941个采样- 2声道

API 9️⃣:av_samples_get_buffer_size - 计算数据字节数

函数原型

int av_samples_get_buffer_size(int *linesize,              // 输出:每行大小(可填NULL)int nb_channels,            // 声道数int nb_samples,             // 采样数enum AVSampleFormat sample_fmt, // 采样格式int align                   // 对齐
);

参数说明

参数说明常用值
linesize输出每行字节数nullptr(不需要)
nb_channels声道数2
nb_samples采样数converted_samples
sample_fmt采样格式AV_SAMPLE_FMT_S16
align对齐1(不对齐)

返回值

  • 成功:返回总字节数
  • 失败:< 0

作用

计算音频数据占用的字节数,用于写入文件或播放。

基本用法

// 计算转换后的数据大小(字节)
int data_size = av_samples_get_buffer_size(nullptr,                    // 不需要linesize2,                          // 2声道converted_samples,          // 转换后的采样数AV_SAMPLE_FMT_S16,         // S16格式1                           // 不对齐
);qDebug() << "数据大小:" << data_size << "字节";// 写入文件
fwrite(out_data[0], 1, data_size, pcm_file);

计算公式

S16格式(2字节/采样):

总字节数 = 声道数 × 采样数 × 2

示例:

// 2声道,1024采样,S16格式
data_size = 2 × 1024 × 2 = 4096字节

快速计算方法

如果你知道格式,也可以手动计算:

// 方法1:使用API(推荐,自动处理对齐)
int size = av_samples_get_buffer_size(nullptr, 2, 1024, AV_SAMPLE_FMT_S16, 1);// 方法2:手动计算(简单场景)
int size = 2 * 1024 * 2;  // 声道数 × 采样数 × 字节/采样

推荐使用API,因为它会自动处理:

  • 不同格式的字节数
  • 内存对齐
  • Planar/Packed差异

API 🔟:swr_free - 释放转换器

函数原型

void swr_free(struct SwrContext **s);

参数说明

参数说明
sSwrContext指针的指针(二级指针)

返回值

  • 无返回值

作用

释放SwrContext及其内部资源。

基本用法

swr_free(&swr_ctx);
// 之后 swr_ctx 会变成 NULL

关键要点

  1. 传入二级指针&swr_ctx,不是 swr_ctx
  2. 自动置NULL:释放后指针会被设为NULL
  3. 调用时机:程序结束前调用
  4. 必须调用:避免内存泄漏

🔄 完整转换流程

标准流程图

┌─────────────────────┐
│  打开文件和解码器   │  ← 阶段一、二
└──────────┬──────────┘↓
┌─────────────────────┐
│   swr_alloc()       │  ← 创建转换器
└──────────┬──────────┘↓
┌─────────────────────┐
│ av_opt_set_int()    │  ← 设置采样率
│ av_opt_set_sample_fmt│  ← 设置采样格式
│ av_opt_set_chlayout()│  ← 设置声道布局
└──────────┬──────────┘↓
┌─────────────────────┐
│   swr_init()        │  ← 初始化(必须!)
└──────────┬──────────┘↓┌────────┐┌──│  循环  │──┐│  └────────┘  ││      ↓       ││ ┌─────────────────────┐│ │  av_read_frame()    │  ← 读取packet│ └──────────┬──────────┘│            ↓│ ┌─────────────────────┐│ │ avcodec_send_packet │  ← 发送给解码器│ │avcodec_receive_frame│  ← 接收frame│ └──────────┬──────────┘│            ↓│ ┌─────────────────────┐│ │ av_rescale_rnd()    │  ← 计算输出采样数│ └──────────┬──────────┘│            ↓│ ┌─────────────────────┐│ │av_samples_alloc_... │  ← 分配输出缓冲区│ └──────────┬──────────┘│            ↓│ ┌─────────────────────┐│ │   swr_convert()     │  ← 执行转换│ └──────────┬──────────┘│            ↓│ ┌─────────────────────┐│ │av_samples_get_buffer│  ← 计算字节数│ │      fwrite()       │  ← 写入文件│ └──────────┬──────────┘│            ↓│ ┌─────────────────────┐│ │   av_freep()        │  ← 释放缓冲区│ │   av_frame_unref()  │  ← 释放frame引用│ └──────────┬──────────┘│            ↓└────────────┘↓
┌─────────────────────┐
│   swr_free()        │  ← 释放转换器
│  释放其他资源       │
└─────────────────────┘

💻 完整示例代码

目标:打开视频文件,解码音频流,转换为PCM格式并保存

#include "mainwindow.h"
#include<QDebug>
#include <QApplication>
#include<stdio.h>extern "C"{
#include<libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libswresample/swresample.h>
#include <libavutil/samplefmt.h>
#include <libavutil/channel_layout.h>
}int result=0;int main()
{QString path="D:/桌面/视频录制/搞笑生气猫_爱给网_aigei_com.mp4";// ========== 阶段一:打开文件 ==========AVFormatContext* avfmctx=nullptr;result=avformat_open_input(&avfmctx,path.toUtf8().data(),nullptr,nullptr);if(result<0){qDebug()<<"avformat_open_input is error";return 0;}qDebug()<<"avformat_open_input is success";// ========== 阶段二:找音频流 ==========result=av_find_best_stream(avfmctx,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);if(result<0){qDebug()<<"av_find_best_stream is error";return 0;}qDebug()<<"av_find_best_stream is success";int Audio_index=result;AVStream * Audio_stream=avfmctx->streams[Audio_index];qDebug()<<"this Audio_stream index is "<<Audio_index;// ========== 阶段三:打开解码器 ==========const AVCodec* decodec=avcodec_find_decoder(Audio_stream->codecpar->codec_id);if(!decodec){qDebug()<<"decodec is not find";return 0;}qDebug()<<"decodec is find, name is "<<decodec->name;// 给解码器分配上下文AVCodecContext* avcodec_ctx=avcodec_alloc_context3(decodec);result=avcodec_parameters_to_context(avcodec_ctx,Audio_stream->codecpar);if(result<0){qDebug()<<"avcodec_parameters_to_context is error";return 0;}qDebug()<<"avcodec_parameters_to_context is success";// 打开解码器result=avcodec_open2(avcodec_ctx,decodec,nullptr);if(result<0){qDebug()<<"avcodec_open2 is error";return 0;}qDebug()<<"avcodec_open2 is success";// ========== 打印音频信息 ==========qDebug()<<"音频流信息---------";qDebug()<<"采样率:"<<Audio_stream->codecpar->sample_rate;qDebug()<<"声道数:"<<Audio_stream->codecpar->ch_layout.nb_channels;// ⚠️ 注意:采样格式要从解码器上下文获取,不是从流参数!qDebug()<<"采样格式:"<<av_get_sample_fmt_name(avcodec_ctx->sample_fmt);// ========== 阶段五:创建SwrContext(新API)==========SwrContext* swr_ctx=swr_alloc();if(!swr_ctx){qDebug()<<"创建一个空SwrContext失败";return 0;}qDebug()<<"创建一个空SwrContext成功";// 采样率设置av_opt_set_int(swr_ctx, "in_sample_rate", Audio_stream->codecpar->sample_rate, 0);av_opt_set_int(swr_ctx, "out_sample_rate", 44100, 0);  // 输出:44.1kHz// 采样格式设置// ⚠️ 重要:必须从解码器上下文获取,不是从流参数!av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", avcodec_ctx->sample_fmt, 0);av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);  // 输出:16位整数// 声道布局设置AVChannelLayout out_layout = AV_CHANNEL_LAYOUT_MONO;    // 输出:单声道av_opt_set_chlayout(swr_ctx, "in_chlayout", &Audio_stream->codecpar->ch_layout, 0);av_opt_set_chlayout(swr_ctx, "out_chlayout", &out_layout, 0);// 初始化(必须调用!)result=swr_init(swr_ctx);if(result<0){qDebug()<<"SwrContext初始化失败";return 0;}qDebug()<<"SwrContext初始化成功";// ========== 打开PCM文件 ==========FILE* pcm_file = fopen("E:/output.pcm", "wb");if(!pcm_file) {qDebug() << "无法创建 PCM 文件";return 0;}qDebug() << "PCM 文件创建成功: E:/output.pcm";// ========== 读取、解码、转换 ==========AVPacket* packet=av_packet_alloc();AVFrame* frame=av_frame_alloc();int total_frame=0;  // 只读200帧while(av_read_frame(avfmctx,packet)==0 && total_frame<200){// 只找音频流if(packet->stream_index!=Audio_index){av_packet_unref(packet);continue;}// 将packet发给解码器if(avcodec_send_packet(avcodec_ctx,packet)==0){// 从解码器读取framewhile(avcodec_receive_frame(avcodec_ctx,frame)==0 && total_frame<200){total_frame++;// 计算输出采样数(因为采样率可能变化)int out_samples=av_rescale_rnd(frame->nb_samples,44100,Audio_stream->codecpar->sample_rate,AV_ROUND_UP);uint8_t **out_data = nullptr;  // 输出数据指针int out_linesize = 0;          // 每个平面的大小// 为音频数据分配内存缓冲区int ret = av_samples_alloc_array_and_samples(&out_data,                  // 音频数据指针(二级指针的地址)&out_linesize,              // 每个平面大小的地址1,                          // 声道数(单声道)out_samples,                // 采样数AV_SAMPLE_FMT_S16,          // 采样格式(S16)0                           // 对齐(0表示默认对齐));if(ret < 0) {qDebug() << "分配音频缓冲区失败";av_frame_unref(frame);continue;}// 使用 swr_convert 进行音频转换int converted_samples = swr_convert(swr_ctx,                        // 重采样上下文out_data,                       // 输出缓冲区out_samples,                    // 输出采样数(const uint8_t**)frame->data,   // 输入数据frame->nb_samples               // 输入采样数);if(converted_samples > 0) {// 计算实际数据大小(字节)// S16格式:每个采样2字节,单声道int data_size = converted_samples * 1 * 2;// 写入PCM文件fwrite(out_data[0], 1, data_size, pcm_file);qDebug() << "第" << total_frame << "帧,转换了" << converted_samples << "个采样,写入" << data_size << "字节";}// 释放分配的缓冲区if(out_data) {av_freep(&out_data[0]);av_freep(&out_data);}av_frame_unref(frame);}}av_packet_unref(packet);}qDebug()<<"总共读取了"<<total_frame<<"帧";// ========== 关闭PCM文件 ==========if(pcm_file) {fclose(pcm_file);qDebug() << "PCM 文件已保存: E:/output.pcm";}// ========== 回收资源 ==========swr_free(&swr_ctx);avformat_close_input(&avfmctx);avcodec_free_context(&avcodec_ctx);av_packet_free(&packet);av_frame_free(&frame);return 0;
}

输出结果示例

avformat_open_input is success
av_find_best_stream is success
this Audio_stream index is  1
decodec is find, name is  aac
avcodec_parameters_to_context is success
avcodec_open2 is success
音频流信息---------
采样率: 48000
声道数: 2
采样格式: fltp
创建一个空SwrContext成功
SwrContext初始化成功
PCM 文件创建成功: E:/output.pcm
第 1 帧,转换了 941 个采样,写入 1882 字节
第 2 帧,转换了 941 个采样,写入 1882 字节
第 3 帧,转换了 941 个采样,写入 1882 字节
...
第 200 帧,转换了 941 个采样,写入 1882 字节
总共读取了 200 帧
PCM 文件已保存: E:/output.pcm

⚠️ 常见错误与注意事项

错误1:采样格式从流参数获取

// ❌ 错误:从流参数获取采样格式
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", (AVSampleFormat)audio_stream->codecpar->format, 0);// ✅ 正确:从解码器上下文获取
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", audio_ctx->sample_fmt, 0);

原因

  • audio_stream->codecpar->format 是文件存储的格式
  • audio_ctx->sample_fmt 是解码器实际输出的格式
  • 解码器可能会转换格式,两者可能不同!

实际案例

文件中:AAC编码(compressed)
解码器输出:FLTP格式(解码后)

错误2:忘记调用 swr_init()

// ❌ 错误:忘记初始化
SwrContext* swr_ctx = swr_alloc();
av_opt_set_int(swr_ctx, "in_sample_rate", 48000, 0);
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", AV_SAMPLE_FMT_FLTP, 0);
// 忘记调用 swr_init()
swr_convert(swr_ctx, ...);  // ❌ 会失败或崩溃// ✅ 正确:必须初始化
SwrContext* swr_ctx = swr_alloc();
av_opt_set_int(swr_ctx, "in_sample_rate", 48000, 0);
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", AV_SAMPLE_FMT_FLTP, 0);
swr_init(swr_ctx);  // ✅ 必须调用
swr_convert(swr_ctx, ...);

症状

  • 程序崩溃
  • 转换返回负数错误码
  • 输出的PCM文件全是噪音

错误3:输出缓冲区太小

// ❌ 错误:没有考虑采样率变化
int out_samples = frame->nb_samples;  // 假设输出和输入一样// ✅ 正确:用 av_rescale_rnd 计算
int out_samples = av_rescale_rnd(frame->nb_samples,44100,                       // 输出采样率audio_ctx->sample_rate,      // 输入采样率AV_ROUND_UP                  // 向上取整
);

原因

  • 采样率从48kHz转44.1kHz,采样数会变化
  • 缓冲区不够会导致数据丢失或崩溃

错误4:释放了 linesize

uint8_t** out_data = nullptr;
int out_linesize = 0;
av_samples_alloc_array_and_samples(&out_data, &out_linesize, ...);// ❌ 错误:linesize 是整数,不需要释放
av_freep(&out_linesize);  // 错误!// ✅ 正确:只释放 out_data
av_freep(&out_data[0]);
av_freep(&out_data);

原因

  • out_data 指向动态分配的内存 → 需要释放
  • out_linesize 只是一个整数变量 → 不需要释放

错误5:混淆采样数和字节数

// ❌ 错误:把采样数当成字节数
int converted_samples = swr_convert(...);
fwrite(out_data[0], 1, converted_samples, file);  // 错误!// ✅ 正确:计算字节数
int converted_samples = swr_convert(...);
int data_size = av_samples_get_buffer_size(nullptr, channels, converted_samples, AV_SAMPLE_FMT_S16, 1
);
fwrite(out_data[0], 1, data_size, file);  // 正确

原因

  • swr_convert 返回的是采样数,不是字节数
  • S16格式:每个采样2字节
  • 需要用 av_samples_get_buffer_size 转换

🎓 验证结果

方法1:使用 FFplay 播放

# 格式:ffplay -f s16le -ar 采样率 -ac 声道数 文件名
ffplay -f s16le -ar 44100 -ac 1 output.pcm

参数说明

  • -f s16le:16位有符号整数,小端序
  • -ar 44100:采样率44100Hz
  • -ac 1:1声道(单声道)

如果是立体声

ffplay -f s16le -ar 44100 -ac 2 output.pcm

📋 总结

核心流程回顾

1. swr_alloc()                    ← 创建转换器
2. av_opt_set_int()               ← 设置采样率
3. av_opt_set_sample_fmt()        ← 设置采样格式(从解码器上下文获取)
4. av_opt_set_chlayout()          ← 设置声道布局
5. swr_init()                     ← 初始化(必须!)
6. while(解码循环)
7.     av_rescale_rnd()           ← 计算输出采样数
8.     av_samples_alloc_...()     ← 分配输出缓冲区
9.     swr_convert()              ← 执行转换
10.    av_samples_get_buffer_size ← 计算字节数
11.    fwrite()                   ← 写入文件
12.    av_freep()                 ← 释放缓冲区
13. swr_free()                    ← 释放转换器

关键API对比

API功能调用时机返回值
swr_alloc创建转换器一次SwrContext*
av_opt_set_int设置采样率初始化前0成功
av_opt_set_sample_fmt设置采样格式初始化前0成功
av_opt_set_chlayout设置声道布局初始化前0成功
swr_init初始化设置参数后(必须0成功
av_rescale_rnd计算采样数每帧转换前采样数
swr_convert执行转换每帧输出采样数
av_samples_get_buffer_size计算字节数转换后字节数
swr_free释放转换器程序结束

资源释放清单

资源分配函数释放函数
音频转换器swr_alloc()swr_free()
音频缓冲区av_samples_alloc_array_and_samples()av_freep(&data[0]) + av_freep(&data)
Packetav_packet_alloc()av_packet_free()
Frameav_frame_alloc()av_frame_free()
解码器上下文avcodec_alloc_context3()avcodec_free_context()
格式上下文avformat_open_input()avformat_close_input()

如果您觉得这篇文章对您有帮助,不妨点赞 + 收藏 + 关注,更多 FFmpeg 系列教程将持续更新 🔥!

http://www.dtcms.com/a/499542.html

相关文章:

  • 网站建设数据收集方法南昌网站推广¥做下拉去118cr
  • visio画网站开发类图深圳东道建设集团网站
  • 董付国老师Python小屋编程题答案161-170
  • 国外营销企业网站什么叫高端网站定制
  • Flutter---生命周期
  • 百度网址大全网站互联网家装
  • 专业的东莞网站排名WordPress多站点开启多语言
  • 微信端网站开发流程做网站什么配置够用
  • c# 泛型的详细介绍
  • OceanBase的SQL和执行计划监控视图
  • 网站原创内容优化wordpress 网站内跳转
  • 龙口市规划建设局网站南京app开发公司排名
  • 解决 Hugging Face 国内下载慢的问题:用 ModelScope 替代加速模型获取
  • 从基础到深入:自然语言处理核心技术全梳理(有 ML/DL 基础)
  • 合肥建设公司网站wordpress 个人电脑
  • 做网站需要哪些方面的支出新媒体运营需要学什么
  • 云手机群控是什么意思
  • 【ecfw】ecfw构建基础
  • 常州二建建设有限公司官方网站聊城做wap网站哪儿好
  • php做网站需要html国外设计公司名字
  • CUDA nvjpeg库编码jpeg图像
  • AI 工作流实战 - 调用豆包api实现批量生图
  • 如何编写您的第一个 Linux 设备驱动程序(一)
  • 做更好的自己 网站客户又找不到你
  • Spring MVC 封装全局统一异常处理
  • 海尔建设网站的内容wordpress设置教程
  • Flutter---EQ均衡器
  • 响应式食品企业网站网站的外链是什么
  • 【Protobuf】proto3语法详解1
  • 网站备案要做家居网站设计