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

音视频学习(五十四):基于ffmpeg实现音频重采样

概述

音频重采样指的是将音频数据的采样率从一个值转换到另一个值。

ffmpeg重采样

关键概念

  • AVFrame: FFmpeg中用于存储解码后的原始(raw)音视频数据的数据结构。对于音频,它包含了音频样本数据、采样率、声道布局、样本格式等信息。
  • AVCodecContext: 编解码器的上下文,包含了编解码器所需的各种参数,如码率、分辨率、采样率等。在重采样中,虽然我们不直接使用编解码器,但它帮助我们理解音频流的参数。
  • AVSampleFormat: 音频样本的格式,例如 AV_SAMPLE_FMT_S16(16位有符号整数)、AV_SAMPLE_FMT_FLT(32位浮点数)等。FFmpeg支持多种格式,重采样通常需要在不同格式之间进行转换。
  • AVChannelLayout: 声道布局,例如 AV_CHANNEL_LAYOUT_STEREO(立体声)、AV_CHANNEL_LAYOUT_MONO(单声道)等。

相关函数

swr_alloc()

  • 功能: 分配并返回一个 SwrContext 结构体,它是音频重采样的核心上下文。
  • 函数原型: SwrContext *swr_alloc(void);
  • 说明: 这是一个非常重要的步骤,它为后续的重采样操作提供了一个句柄。如果内存分配失败,该函数会返回 nullptr

av_opt_set_int()av_opt_set_sample_fmt()

  • 功能: 设置 SwrContext 的各种选项。
  • 函数原型:
    • int av_opt_set_int(void *obj, const char *name, int64_t val, int search_flags);
    • int av_opt_set_sample_fmt(void *obj, const char *name, enum AVSampleFormat fmt, int search_flags);
  • 说明: libswresample 库的参数设置是通过 libavutil 库的选项系统实现的。我们通过键值对的形式设置输入和输出的参数。
    • obj: 目标对象,这里是 SwrContext
    • name: 选项的名称,如 "in_channel_layout", "in_sample_rate", "out_sample_fmt" 等。
    • valfmt: 选项的值。
    • search_flags: 搜索标志,通常设置为 0。

swr_init()

  • 功能: 使用之前设置的参数初始化 SwrContext
  • 函数原型: int swr_init(SwrContext *s);
  • 说明: 在调用 swr_convert 之前,必须调用此函数。它会检查参数的有效性,并为重采样器分配必要的内部缓冲区和数据结构。初始化成功返回 0,失败返回负数。

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);
  • 说明: 这个函数是处理FFmpeg原始音频数据的一个便利工具。
    • audio_data: 一个指向 uint8_t** 的指针,函数会在这里分配缓冲区数组的内存。对于平面(planar)格式,audio_data[i] 指向第 i 个声道的样本数据。
    • linesize: 一个指向 int 的指针,函数会在这里返回缓冲区的大小。
    • nb_channels: 声道数。
    • nb_samples: 每声道的样本数。
    • sample_fmt: 样本格式。
    • align: 内存对齐要求。通常设置为 0,表示使用默认对齐。

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 确保缓冲区足够大。

swr_get_delay()

  • 功能: 获取重采样器的当前延迟。
  • 函数原型: int64_t swr_get_delay(SwrContext *s, int64_t base);
  • 说明: 由于重采样器内部的滤波和缓冲机制,它会引入一定的延迟。这个函数可以估算这种延迟,以便在计算输出缓冲区大小时考虑进去,从而避免数据丢失。

swr_convert()

  • 功能: 执行音频重采样。
  • 函数原型: int swr_convert(SwrContext *s, uint8_t **out, int out_count, const uint8_t **in, int in_count);
  • 说明: 这是重采样操作的核心函数。
    • s: SwrContext 句柄。
    • out: 指向目标缓冲区的指针数组。
    • out_count: 目标缓冲区可以容纳的最大样本数。
    • in: 指向源缓冲区的指针数组。如果为 nullptrin_count 为 0,则表示需要刷新重采样器内部的剩余数据。
    • in_count: 源缓冲区中的样本数。
  • 返回值: 返回实际写入目标缓冲区的样本数,负数表示失败。

av_freep()

  • 功能: 释放通过 FFmpeg 分配的内存。
  • 函数原型: void av_freep(void *ptr);
  • 说明: 这是一个安全的内存释放函数,它会先检查指针是否为 nullptr,然后释放内存并把指针设为 nullptr

swr_free()

  • 功能: 释放 SwrContext 及其内部资源。
  • 函数原型: void swr_free(SwrContext **s);
  • 说明: 在程序结束时,调用此函数来清理资源,防止内存泄漏。它会自动将传入的指针设置为 nullptr

示例

// 使用ffmpeg6以上版本
#include <iostream>
#include <vector>extern "C" {
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libavutil/error.h>
#include <libavutil/channel_layout.h>
}// 错误处理宏
#define CHECK_RET(ret) do { \if ((ret) < 0) { \char errbuf[AV_ERROR_MAX_STRING_SIZE]; \av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); \std::cerr << "FFmpeg Error: " << errbuf << std::endl; \return 1; \} \
} while(0)// 打印音频参数
void print_audio_params(const char* label, int sample_rate, enum AVSampleFormat format, const AVChannelLayout* channel_layout) {char layout_str[256];av_channel_layout_describe(channel_layout, layout_str, sizeof(layout_str));std::cout << "--- " << label << " ---" << std::endl;std::cout << "Sample Rate: " << sample_rate << " Hz" << std::endl;std::cout << "Sample Format: " << av_get_sample_fmt_name(format) << std::endl;std::cout << "Channel Layout: " << layout_str << std::endl;std::cout << "Number of Channels: " << channel_layout->nb_channels << std::endl;
}int main() {// 源音频参数int src_sample_rate = 44100;enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_S16;AVChannelLayout src_channel_layout;av_channel_layout_default(&src_channel_layout, 2); // 2声道int src_nb_channels = src_channel_layout.nb_channels;int src_nb_samples = 1024;// 目标音频参数int dst_sample_rate = 48000;enum AVSampleFormat dst_sample_fmt = AV_SAMPLE_FMT_FLT;AVChannelLayout dst_channel_layout;av_channel_layout_default(&dst_channel_layout, 1); // 1声道int dst_nb_channels = dst_channel_layout.nb_channels;print_audio_params("Source Audio", src_sample_rate, src_sample_fmt, &src_channel_layout);print_audio_params("Destination Audio", dst_sample_rate, dst_sample_fmt, &dst_channel_layout);SwrContext *swr_ctx = swr_alloc();if (!swr_ctx) {std::cerr << "Could not allocate SwrContext" << std::endl;return 1;}av_opt_set_chlayout(swr_ctx, "in_chlayout", &src_channel_layout, 0);av_opt_set_int(swr_ctx, "in_sample_rate", src_sample_rate, 0);av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);av_opt_set_chlayout(swr_ctx, "out_chlayout", &dst_channel_layout, 0);av_opt_set_int(swr_ctx, "out_sample_rate", dst_sample_rate, 0);av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);int ret = swr_init(swr_ctx);CHECK_RET(ret);uint8_t **src_data = nullptr;int src_linesize = 0;ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize, src_nb_channels, src_nb_samples, src_sample_fmt, 0);CHECK_RET(ret);for (int i = 0; i < src_nb_samples * src_nb_channels; ++i) {if (src_sample_fmt == AV_SAMPLE_FMT_S16) {((int16_t*)src_data[0])[i] = i;}}int dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, src_sample_rate) + src_nb_samples, dst_sample_rate, src_sample_rate, AV_ROUND_UP);uint8_t **dst_data = nullptr;int dst_linesize = 0;ret = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels, dst_nb_samples, dst_sample_fmt, 0);CHECK_RET(ret);ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, (const uint8_t **)src_data, src_nb_samples);CHECK_RET(ret);std::cout << "Successfully resampled " << src_nb_samples << " samples to " << ret << " samples." << std::endl;while (true) {ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, nullptr, 0);if (ret <= 0) break;std::cout << "Flushed " << ret << " remaining samples." << std::endl;}if (src_data) {av_freep(&src_data[0]);av_freep(&src_data);}if (dst_data) {av_freep(&dst_data[0]);av_freep(&dst_data);}swr_free(&swr_ctx);return 0;
}

编译运行:

g++ resample.cpp -o resample_example -I/usr/local/ffmpeg/include -L/usr/local/ffmpeg/lib -lavcodec -lswresample -lavutil

输出:

--- Source Audio ---
Sample Rate: 44100 Hz
Sample Format: s16
Channel Layout: stereo
Number of Channels: 2
--- Destination Audio ---
Sample Rate: 48000 Hz
Sample Format: flt
Channel Layout: mono
Number of Channels: 1
Successfully resampled 1024 samples to 1098 samples.
Flushed 17 remaining samples.
http://www.dtcms.com/a/334156.html

相关文章:

  • 【科普向-第一篇】数字钥匙生态全景:手机厂商、车厂与协议之争
  • GPFS集群性能压测
  • C++编程学习阶段性总结
  • 2025年生成式引擎优化(GEO)服务商技术能力评估报告
  • 企业运维规划及Linux介绍虚拟环境搭建
  • ROS相关的ubuntu基础教程
  • 神经网络 常见分类
  • 视觉语言模型(VLA)分类方法体系
  • 6JSON格式转python并实现数据可视化
  • RJ45 网口集成万兆(10Gbps)以太网的核心是通过物理层技术革新和信号处理优化,在传统铜缆(双绞线)介质上突破速率限制,其原理可从以下几个关键维度解析
  • Express开发快速学习
  • 探秘gRPC——gRPC原理详解
  • B3924 [GESP202312 二级] 小杨的H字矩阵
  • Flink Stream API 源码走读 - window 和 sum
  • Kubernetes Service
  • Google C++ 风格指南
  • 大模型教机器人叠衣服:2025年”语言理解+多模态融合“的智能新篇
  • Cmake学习笔记
  • 小白学习《PCI Express体系结构导读》——第Ⅰ篇第1章PCI总线的基本知识
  • 如何使用 Git 修改已推送 Commit 的用户名和邮箱
  • FFmpeg QoS 处理
  • 正点原子【第四期】Linux之驱动开发篇学习笔记-1.1 Linux驱动开发与裸机开发的区别
  • C语言(11)—— 数组(超绝详细总结)
  • [论文阅读] 人工智能 | 对话中的属性与情感:LLM如何通过多代理反思实现细粒度理解
  • 利用爬虫按图搜索淘宝商品(拍立淘)实战指南
  • 教材采购管理系统(java)
  • OpenEuler 等 Linux 系统中运行 Vue 项目的方法
  • 【P14 3-6 】OpenCV Python——视频加载、摄像头调用、视频基本信息获取(宽、高、帧率、总帧数)
  • C++ string类操作全解析(含模拟实现)
  • 高等数学 8.4 空间直线及其方程