采样格式

通过前面学习我们知道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. P9989 [Ynoi Easy Round 2023] TEST_69 题解

    题目链接: [Ynoi Easy Round 2023] TEST_69 首先GCD有比较良好的一些性质.我们观察到一次 \(GCD(a_i,x)\) 操作,会有以下两种变化. 如果 \(x \bmo ...

  2. Java-获取当前时间并进行格式化

    获取年月日 时分秒 import java.text.SimpleDateFormat; import java.util.Date; Date dt = new Date(); SimpleDate ...

  3. [Ngbatis源码学习] Ngbatis 源码学习之资源加载器 DaoResourceLoader

    Ngbatis 源码学习之资源加载器 DaoResourceLoader DaoResourceLoader 是 Ngbatis 的资源文件加载器,扩展自 MapperResourceLoader.本 ...

  4. Visual Studio部署matplotlib绘图库的C++版本

      本文介绍在Visual Studio软件中配置.编译C++环境下matplotlibcpp库的详细方法.   matplotlibcpp库是一个C++环境下的绘图工具,其通过调用Python接口, ...

  5. NC25045 [USACO 2007 Jan S]Balanced Lineup

    题目链接 题目 题目描述 For the daily milking, Farmer John's N cows (1 ≤ N ≤ 100,000) always line up in the sam ...

  6. 【Unity3D】顶点和片元着色器

    1 前言 ​ 上文介绍了渲染管线.固定管线着色器和表面着色器,如下: 渲染管线 固定管线着色器一 固定管线着色器二 表面着色器 ​ 固定管线着色器通过命令方式实现光照和贴图等效果,表面着色器通过给 S ...

  7. image could not be accessed on a registry to record its digest

    问题说明: 在管理节点执行docker stack xxx 方式运行服务,报如题错误. 问题原因: docker swarm运行需要一个镜像仓库才行,所有节点都去这个仓库统一镜像. 来看下官方的解释: ...

  8. PLSQL编译存储过程无响应

    解决方法如下: 1:查V$DB_OBJECT_CACHE SELECT * FROM V$DB_OBJECT_CACHE WHERE name='CRM_LASTCHGINFO_DAY' AND LO ...

  9. Spring boot项目实战之记录应用访问日志

    1.说明 系统上线后往往我们需要知道都有哪些用户访问了应用的那些功能,以便更好的了解用户需求.防止恶意访问等.为此我们需要给应用添加记录访问日志的功能.下面就开始吧: 2.建表 CREATE TABL ...

  10. win32 - DIB 与 DDB

    设备相关位图(DDB): DDB不包含颜色值,因为每个设备可以具有自己的一组颜色,所以为一个设备创建的DDB可能无法在其他设备上很好地显示. DDB通常被称为兼容位图,并且它通常比DIB具有更好的GD ...