采样格式

通过前面学习我们知道FFmpeg和SDL都有自己的采样格式的表达式,那么他们都表示什么意思呢?

FFmpeg的采样格式的表达式:

enum AVCodecID {
......
AV_CODEC_ID_PCM_S16LE = 0x10000,
AV_CODEC_ID_PCM_S16BE,
AV_CODEC_ID_PCM_U16LE,
AV_CODEC_ID_PCM_U16BE,
......
AV_CODEC_ID_PCM_S32LE,
AV_CODEC_ID_PCM_S32BE,
......
AV_CODEC_ID_PCM_F32BE,
AV_CODEC_ID_PCM_F32LE,
......
}
enum AVSampleFormat {
......
AV_SAMPLE_FMT_S16, ///< signed 16 bits
AV_SAMPLE_FMT_S32, ///< signed 32 bits
AV_SAMPLE_FMT_FLT, ///< float
......
}

SDL的采样格式的表达式:


#define AUDIO_U16LSB 0x0010 /**< Unsigned 16-bit samples */
#define AUDIO_S16LSB 0x8010 /**< Signed 16-bit samples */
#define AUDIO_U16MSB 0x1010 /**< As above, but big-endian byte order */
#define AUDIO_S16MSB 0x9010 /**< As above, but big-endian byte order */ #define AUDIO_S32LSB 0x8020 /**< 32-bit integer samples */
#define AUDIO_S32MSB 0x9020 /**< As above, but big-endian byte order */ #define AUDIO_F32LSB 0x8120 /**< 32-bit floating point samples */
#define AUDIO_F32MSB 0x9120 /**< As above, but big-endian byte order */

采样格式能表达如下三种信息∶

  1. 位深度(采样大小)
  2. 有符号(Signed)\无符号(Unsigned)浮点数
  3. 大端(Big-Endian)\小端(Little-Endian)

举例:

  1. FFmpeg的AVCodecID枚举中如S16LE的S表示的是有符号、16表示的是位深度16位、LE表示的是小端;F32BE的F表示的是浮点数、32表示位深度32位、BE表示的是大端。
  2. 而FFmpeg的AVSampleFormat枚举中没有LE或者BE的字母,那么怎么区分大小端呢?其实它们默认就是小端模式。
  3. SDL中的LSB和MSB是什么意思呢?
    • LSB(Least Significant Bit\Byte) 最低有效位\字节,小端
    • MSB(Most Significant Bit\Byte) 最高有效位\字节,大端
    • 例如:0x11223344
      • 小端:在网络上传输最低有效位\字节读取顺序 0x44 0x33 0x22 0x11
      • 大端:在网络上传输最高有效位\字节读取顺序 0x11 0x22 0x33 0x44

音频重采样

什么叫音频重采样

音频重采样(Audio Resample):将音频A转换成音频B,并且音频A、B的参数(采样率、采样格式、声道数)并不完全相同。比如:

  • 音频A的参数

    • 采样率:48000
    • 采样格式:f32le
    • 声道数:1
  • 音频B的参数

    • 采样率:44100
    • 采样格式:s16le
    • 声道数:2

为什么需要音频重采样

这里列举一个音频重采样的经典用途。

有些音频编码器对输入的原始PCM数据是有特定参数要求的,比如要求必须是44100_s16le_2。但是你提供的PCM参数可能是48000_f32le_1。这个时候就需要先将48000_f32le_1转换成44100_s16le_2,然后再使用音频编码器对转换后的PCM进行编码。

命令行

通过下面的命令行可以将44100_s16le_2转换成48000_f32le_1。

// ffmpeg 输入文件参数 -i 输入文件 输出文件参数 输出文件
ffmpeg -ar 44100 -ac 2 -f s16le -i 44100_s16le_2.pcm -ar 48000 -ac 1 -f f32le 48000_f32le_1.pcm

编程

音频重采样需要用到2个库:

  • swresample
  • avutil

函数声明

为了让音频重采样功能更加通用,设计成以下函数:

// ffmpegutil.h

// 音频参数
typedef struct {
const char *filename;
int sampleRate;
AVSampleFormat sampleFmt;
int chLayout;
} ResampleAudioSpec; class FFmpegUtil {
public:
static void resampleAudio(ResampleAudioSpec &in,
ResampleAudioSpec &out); static void resampleAudio(const char *inFilename,
int inSampleRate,
AVSampleFormat inSampleFmt,
int inChLayout, const char *outFilename,
int outSampleRate,
AVSampleFormat outSampleFmt,
int outChLayout);
}; // ffmpegutil.cpp // 导入头文件
extern "C" {
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
} // 处理错误码
#define ERROR_BUF(ret) \
char errbuf[1024]; \
av_strerror(ret, errbuf, sizeof (errbuf)); void FFmpegUtil::resampleAudio(ResampleAudioSpec &in,
ResampleAudioSpec &out) {
resampleAudio(in.filename, in.sampleRate, in.sampleFmt, in.chLayout,
out.filename, out.sampleRate, out.sampleFmt, out.chLayout);
}

函数调用

// audioThread.cpp

#ifdef Q_OS_WIN
// PCM文件的文件名
#define IN_FILENAME "../test/44100_s16le_2.pcm"
#define OUT_FILENAME "../test/48000_f32le_1.pcm"
#else
#define FILENAME "/Users/zuojie/QtProjects/audio-video-dev/test"
#define IN_FILENAME "/Users/zuojie/QtProjects/audio-video-dev/test/44100_s16le_2.pcm"
#define OUT_FILENAME "/Users/zuojie/QtProjects/audio-video-dev/test/48000_f32le_1.pcm"
#endif // 输入参数
ResampleAudioSpec in;
in.filename = IN_FILENAME;
in.sampleFmt = AV_SAMPLE_FMT_S16;
in.sampleRate = 44100;
in.chLayout = AV_CH_LAYOUT_STEREO; // 输出参数
ResampleAudioSpec out;
out.filename = OUT_FILENAME;
out.sampleFmt = AV_SAMPLE_FMT_FLT;
out.sampleRate = 48000;
out.chLayout = AV_CH_LAYOUT_MONO; // 进行音频重采样
FFmpegUtil::resampleAudio(in, out);

函数实现

变量定义

为了简化释放资源的代码,函数中用到了goto语句,所以把需要用到的变量都定义到了前面。

// ffmpegutil.cpp

// 文件名
QFile inFile(inFilename);
QFile outFile(outFilename); // 输入缓冲区
// 指向缓冲区的指针
uint8_t **inData = nullptr;
// 缓冲区的大小
int inLinesize = 0;
// 声道数
int inChs = av_get_channel_layout_nb_channels(inChLayout);
// 一个样本的大小
int inBytesPerSample = inChs * av_get_bytes_per_sample(inSampleFmt);
// 缓冲区的样本数量
int inSamples = 1024;
// 读取文件数据的大小
int len = 0; // 输出缓冲区
// 指向缓冲区的指针
uint8_t **outData = nullptr;
// 缓冲区的大小
int outLinesize = 0;
// 声道数
int outChs = av_get_channel_layout_nb_channels(outChLayout);
// 一个样本的大小
int outBytesPerSample = outChs * av_get_bytes_per_sample(outSampleFmt);
// 缓冲区的样本数量(AV_ROUND_UP是向上取整)
int outSamples = av_rescale_rnd(outSampleRate, inSamples, inSampleRate, AV_ROUND_UP); /*
inSampleRate inSamples
------------- = -----------
outSampleRate outSamples outSamples = outSampleRate * inSamples / inSampleRate
*/ // 返回结果
int ret = 0;

我们设置了输入缓冲区样本数量为1024,然后根据输入输出采样率的比例计算出输出缓冲区样本数量,计算公式如下:

 inSampleRate     inSamples
------------- = -----------
outSampleRate outSamples outSamples = outSampleRate * inSamples / inSampleRate

FFmpeg 提供了现成的 API 计算输出缓冲区样本数量:

/**
* Rescale a 64-bit integer with specified rounding.
*
* The operation is mathematically equivalent to `a * b / c`, but writing that
* directly can overflow, and does not support different rounding methods.
*
* @see av_rescale(), av_rescale_q(), av_rescale_q_rnd()
*/
int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const;

此函数的操作等价于我们上边的计算公式,并且做了防止溢出处理。rnd:取整模式选择向上取整AV_ROUND_UP。实际上输入输出缓冲区样本大小全都设置为1024重采样后的音频有时也是可以播放的,听起来并没有什么不同,但是通过观察转码后的音频文件大小你可能会发现丢失了部分音频数据。

创建重采样上下文

// 创建重采样上下文
SwrContext *ctx = swr_alloc_set_opts(nullptr,
// 输出参数
outChLayout, outSampleFmt, outSampleRate,
// 输入参数
inChLayout, inSampleFmt, inSampleRate,
0, nullptr);
if (!ctx) {
qDebug() << "swr_alloc_set_opts error";
goto end;
}

初始化重采样上下文

// 初始化重采样上下文
int ret = swr_init(ctx);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "swr_init error:" << errbuf;
goto end;
}

创建缓冲区

// 创建输入缓冲区
ret = av_samples_alloc_array_and_samples(
&inData,
&inLinesize,
inChs,
inSamples,
inSampleFmt,
1);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "av_samples_alloc_array_and_samples error:" << errbuf;
goto end;
} // 创建输出缓冲区
ret = av_samples_alloc_array_and_samples(
&outData,
&outLinesize,
outChs,
outSamples,
outSampleFmt,
1);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "av_samples_alloc_array_and_samples error:" << errbuf;
goto end;
}

读取文件数据

// 打开文件
if (!inFile.open(QFile::ReadOnly)) {
qDebug() << "file open error:" << inFilename;
goto end;
}
if (!outFile.open(QFile::WriteOnly)) {
qDebug() << "file open error:" << outFilename;
goto end;
} // 读取文件数据
// inData[0] == *inData
while ((len = inFile.read((char *) inData[0], inLinesize)) > 0) {
// 读取的样本数量
inSamples = len / inBytesPerSample; // 重采样(返回值转换后的样本数量)
ret = swr_convert(ctx,
outData, outSamples,
(const uint8_t **) inData, inSamples
); if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "swr_convert error:" << errbuf;
goto end;
} // 将转换后的数据写入到输出文件中
// outData[0] == *outData
outFile.write((char *) outData[0], ret * outBytesPerSample);
}

刷新输出缓冲区

// 检查一下输出缓冲区是否还有残留的样本(已经重采样过的,转换过的)
while ((ret = swr_convert(ctx,
outData, outSamples,
nullptr, 0)) > 0) {
outFile.write((char *) outData[0], ret * outBytesPerSample);
}

回收释放资源

end:
// 释放资源
// 关闭文件
inFile.close();
outFile.close(); // 释放输入缓冲区
if (inData) {
av_freep(&inData[0]);
}
av_freep(&inData); // 释放输出缓冲区
if (outData) {
av_freep(&outData[0]);
}
av_freep(&outData); // 释放重采样上下文
swr_free(&ctx);

代码链接

12_采样格式&音频重采样的更多相关文章

  1. FFMpeg音频重采样和视频格式转

    一.视频像素和尺寸转换函数 1.sws_getContext : 像素格式上下文  --------------->多副图像(多路视频)进行转换同时显示 2.struct SwsContext  ...

  2. FFMpeg笔记(三) 音频处理基本概念及音频重采样

    Android放音的采样率固定为44.1KHz,录音的采样率固定为8KHz,因此底层的音频设备驱动需要设置好这两个固定的采样率.如果上层传过来的采样率不符的话,需要进行resample重采样处理. 几 ...

  3. 基于sinc的音频重采样(二):实现

    上篇(基于sinc的音频重采样(一):原理)讲了基于sinc方法的重采样原理,并给出了数学表达式,如下:                  (1) 本文讲如何基于这个数学表达式来做软件实现.软件实现的 ...

  4. 简洁明了的插值音频重采样算法例子 (附完整C代码)

    近一段时间在图像算法以及音频算法之间来回游走. 经常有一些需求,需要将音频进行采样转码处理. 现有的知名开源库,诸如: webrtc , sox等, 代码阅读起来实在闹心. 而音频重采样其实也就是插值 ...

  5. FFmpeg进行视频帧提取&音频重采样-Process.waitFor()引发的阻塞超时

    由于产品需要对视频做一系列的解析操作,利用FFmpeg命令来完成视频的音频提取.第一帧提取作为封面图片.音频重采样.字幕压缩等功能: 前一篇文章已经记录了FFmpeg在JAVA中的使用-音频提取&am ...

  6. FFmpeg(11)-基于FFmpeg进行音频重采样(swr_init(), swr_convert())

    一.包含头文件和库文件 修改CMakeLists # swresample add_library(swresample SHARED IMPORTED) set_target_properties( ...

  7. 基于傅里叶变换的音频重采样算法 (附完整c代码)

    前面有提到音频采样算法: WebRTC 音频采样算法 附完整C++示例代码 简洁明了的插值音频重采样算法例子 (附完整C代码) 近段时间有不少朋友给我写过邮件,说了一些他们使用的情况和问题. 坦白讲, ...

  8. FFmpeg4.0笔记:封装ffmpeg的音频重采样功能类CSwr

    Github https://github.com/gongluck/FFmpeg4.0-study/tree/master/Cff CSwr.h /************************* ...

  9. 7.SwrContext音频重采样使用

    头文件位于#include <libswresample/swresample.h>   SwrContext常用函数如下所示 SwrContext *swr_alloc(void); / ...

  10. Android 音视频开发(一):PCM 格式音频的播放与采集

    什么是 PCM 格式 声音从模拟信号转化为数字信号的技术,经过采样.量化.编码三个过程将模拟信号数字化. 采样 顾名思义,对模拟信号采集样本,该过程是从时间上对信号进行数字化,例如每秒采集 44100 ...

随机推荐

  1. CommentTest

    public class CommentTest{ /* 这是多行注释 可以声明多行注释的信息 1. Java注释的种类: 单行注释,多行注释,文档注释(Java特有) 2. 单行注释,多行注释 ① ...

  2. 案例:推进GTID解决MySQL主主不同步问题

    之前文章介绍过MySQL修改lower_case_table_names参数,如果之前大写存储的表将无法识别,需要特殊处理. 最近遇到一例应用开发人员在修改这个参数之后,为了清除之前大写存储的表,做了 ...

  3. Delphi Vista,Win7,Win8 的 Uac,管理员身份运行

    要用就用下面我自己总结的官方的做法: 1.首先搜到delphi 自带的manifest,然后在其基础上改一个单词 2.将里面的asInvoker改为requireAdministrator 3.修改为 ...

  4. 《ASP.NET Core 与 RESTful API 开发实战》-- (第8章)-- 读书笔记(中)

    第 8 章 认证和安全 8.2 ASP.NET Core Identity Identity 是 ASP.NET Core 中提供的对用户和角色等信息进行存储与管理的系统 Identity 由3层构成 ...

  5. VMware虚拟机Ubuntu系统连接网络过程

    网络和Internet设置--高级网络设置--更多网络适配器选项--WLAN. 右键选择属性--共享,勾选允许连接,选择VMnet8.(若勾选了其它,之后再想换回来,可以先取消勾选,点确定,再进入勾选 ...

  6. 【LeetCode栈与队列#06】前K个高频元素(TopK问题),以及pair、priority_queue的使用

    前 K 个高频元素 力扣题目链接(opens new window) 给定一个非空的整数数组,返回其中出现频率前 k 高的元素. 示例 1: 输入: nums = [1,1,1,2,2,3], k = ...

  7. 第127篇:异步函数(async和await)练习题(异步,消息队列)

    好家伙,本篇为做题思考 书接上文   题目如下:  1.请给出下列代码的输出结果,并配合"消息队列"写出相关解释 async function foo() { console.lo ...

  8. maven创建父子工程

    目录 创建父工程 创建子工程 将项目编译为eclipse项目 将项目导入eclipse 修改依赖关系:service依赖dao,web依赖service JavaProject的pom.xml文件说明 ...

  9. 【Azure Redis】Redis客户端出现15分钟的超时异常

    问题描述 客户端使用 Lettuce.io 连接 Azure Redis,出现了长达15分钟的Timeout异常. 问题解答 Azure Redis作为PaaS服务,由于一些平台的升级操作而引发的故障 ...

  10. 【Azure 应用服务】能否通过 Authentication 模块配置 Azure AD 保护 API 应用?

    问题描述 在App Service Authentication 中配置 Azure AD 注册的应用信息后,根据官方文档,可以让前端应用实现用户 AAD 登录,然后通过前端应用获取的Token,来访 ...