音频重采样说明
音频重采样(Audio Resample):将音频A转换成音频B,并且音频A、B的参数(采样率、采样格式、声道数)并不完全相同。比如:
-
音频A的参数
- 采样率:48000
- 采样格式:s32le
- 声道数:2
-
音频B的参数
- 采样率:44100
- 采样格式:f16le
- 声道数:1
创建一个Qt项目
修改CMakeLists.txt
添加FFmpeg头文件和库文件路径
set(ffmpeg444 "../ffmpeg444")
# 引入头文件目录
include_directories(${ffmpeg444}/include/)
# 引入库文件目录,注意link_directories要放在add_executable之前
link_directories(${ffmpeg444}/lib/)
target_link_libraries(audio_resample PRIVATE Qt${QT_VERSION_MAJOR}::Widgets swresample avutil)
添加按钮
创建一个线程类
头文件
#ifndef AUDIOTHREAD_H
#define AUDIOTHREAD_H
#include <QThread>
class AudioThread: public QThread
{
Q_OBJECT
private:
void run();
public:
explicit AudioThread(QObject *parent = nullptr);
~AudioThread();
};
#endif // AUDIOTHREAD_H
实现文件
#include "audiothread.h"
#include <QDebug>
#include <QFile>
extern "C" {
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
}
AudioThread::AudioThread(QObject *parent): QThread{parent} {
connect(this, &AudioThread::finished, this, &AudioThread::deleteLater);
}
AudioThread::~AudioThread() {
disconnect();
requestInterruption();
quit();
wait();
qDebug() << this << "析构了";
}
void AudioThread::run() {
const char* inFileName = "/home/gillbert/Music/in.pcm";
const char* outFileName = "/home/gillbert/Music/out.pcm";
// 输入参数
AVSampleFormat inSampleFmt = AV_SAMPLE_FMT_S16;
int inSampleRate = 44100;
int inChLayout = AV_CH_LAYOUT_STEREO;
// 输出参数
AVSampleFormat outSampleFmt = AV_SAMPLE_FMT_FLT;
int outSampleRate = 44100;
int outChLayout = AV_CH_LAYOUT_MONO;
// 创建重采样上下文
SwrContext* ctx = swr_alloc_set_opts(nullptr,
outChLayout,
outSampleFmt,
outSampleRate,
inChLayout,
inSampleFmt,
inSampleRate,
0,
nullptr);
if (!ctx) {
qDebug() << "swr_alloc_set_opts create ctx error";
return;
}
// 初始化重采样上下文
int ret = swr_init(ctx);
if (ret < 0) {
char errbuf[1024];
av_strerror(ret, errbuf, sizeof(errbuf));
qDebug() << "swr_init error:" << errbuf;
// 释放重采样上下文
swr_free(&ctx);
return;
}
// 创建输入缓冲区
uint8_t **inData = nullptr; // 指向缓冲区的指针
int inLineSize = 0; // 缓冲区的大小
int inChs = av_get_channel_layout_nb_channels(inChLayout); // av_get_channel_layout_nb_channels 函数可以根据声道布局获取声道数
int inSample = 1024; // 输入缓冲区的样本数
int inBytesPerSample = inChs * av_get_bytes_per_sample(inSampleFmt); // 一个样本占用多少字节
ret = av_samples_alloc_array_and_samples(&inData, &inLineSize, inChs, inSample, inSampleFmt, 1); // 创建缓冲区
if (ret < 0) {
char errbuf[1024];
av_strerror(ret, errbuf, sizeof(errbuf));
qDebug() << "av_samples_alloc_array_and_samples error:" << errbuf;
// 释放重采样上下文
swr_free(&ctx);
return;
}
// 创建输出缓冲区
uint8_t **outData = nullptr; // 指向缓冲区的指针
int outLineSize = 0; // 缓冲区的大小
int outChs = av_get_channel_layout_nb_channels(outChLayout); // av_get_channel_layout_nb_channels 函数可以根据声道布局获取声道数
int outSample = av_rescale_rnd(outSampleRate, inSample, inSampleRate, AV_ROUND_UP); // 输入缓冲区的样本数,因为重采样了,需要重新计算下
int outBytesPerSample = outChs * av_get_bytes_per_sample(outSampleFmt); // 一个样本占用多少字节
ret = av_samples_alloc_array_and_samples(
&outData,
&outLineSize,
outChs,
outSample,
outSampleFmt,
1 // 1表示不需要内存对齐
); // 创建缓冲区
if (ret < 0) {
char errbuf[1024];
av_strerror(ret, errbuf, sizeof(errbuf));
qDebug() << "av_samples_alloc_array_and_samples error:" << errbuf;
// 释放输入缓冲区
av_freep(&inData);
// 释放重采样上下文
swr_free(&ctx);
return;
}
// 读取数据进行重采样
QFile inFile(inFileName); // 打开输入文件
if (!inFile.open(QFile::ReadOnly)) {
qDebug() << "file open error: " << inFileName;
// 释放输出缓冲区
av_freep(&outData);
// 释放输入缓冲区
av_freep(&inData);
// 释放重采样上下文
swr_free(&ctx);
return;
}
QFile outFile(outFileName); // 打开输入文件
if (!outFile.open(QFile::WriteOnly)) {
qDebug() << "file open error: " << outFileName;
// 释放输出缓冲区
av_freep(&outData);
// 释放输入缓冲区
av_freep(&inData);
// 释放重采样上下文
swr_free(&ctx);
return;
}
int len = 0; // 读取的文件数据大小
while ((len = inFile.read((char *)inData[0], inLineSize)) > 0) {
// 读取的样本数量
int readInSample = len / inBytesPerSample;
// 重采样, 返回值为输出缓冲区中的样本数量
ret = swr_convert(ctx, outData, outSample, (const uint8_t **)inData, readInSample);
if (ret < 0) {
char errbuf[1024];
av_strerror(ret, errbuf, sizeof(errbuf));
qDebug() << "swr_convert error:" << errbuf;
if (outData) {
av_freep(&outData[0]);
}
// 释放输出缓冲区
av_freep(&outData);
if (inData) {
av_freep(&inData[0]);
}
// 释放输入缓冲区
av_freep(&inData);
// 释放重采样上下文
swr_free(&ctx);
return;
}
// 将转换后的数据写入文件
outFile.write((char *)outData[0], ret * outBytesPerSample);
}
// 预防输出缓冲区还有残留
while ((ret = swr_convert(ctx, outData, outSample, nullptr, 0)) > 0) {
outFile.write((char *)outData[0], ret * outBytesPerSample);
}
// 关闭文件
outFile.close();
inFile.close();
if (outData) {
av_freep(&outData[0]);
}
// 释放输出缓冲区
av_freep(&outData);
if (inData) {
av_freep(&inData[0]);
}
// 释放输入缓冲区
av_freep(&inData);
// 释放重采样上下文
swr_free(&ctx);
}
点击的槽函数代码
点击按钮,创建新的线程开始重采样进行转换
void MainWindow::on_resampleButton_clicked()
{
_audioThread = new AudioThread(this);
_audioThread->start();
}
测试
查看PCM对应的格式
在代码中我们的输出文件采样格式为AV_SAMPLE_FMT_FLT
, 因此在使用命令行进行播放时候我们需要指定-f f32le
参数
➜ ffmpeg -hide_banner -formats | grep PCM
DE alaw PCM A-law
DE f32be PCM 32-bit floating-point big-endian
DE f32le PCM 32-bit floating-point little-endian
DE f64be PCM 64-bit floating-point big-endian
DE f64le PCM 64-bit floating-point little-endian
DE mulaw PCM mu-law
DE s16be PCM signed 16-bit big-endian
DE s16le PCM signed 16-bit little-endian
DE s24be PCM signed 24-bit big-endian
DE s24le PCM signed 24-bit little-endian
DE s32be PCM signed 32-bit big-endian
DE s32le PCM signed 32-bit little-endian
DE s8 PCM signed 8-bit
DE u16be PCM unsigned 16-bit big-endian
DE u16le PCM unsigned 16-bit little-endian
DE u24be PCM unsigned 24-bit big-endian
DE u24le PCM unsigned 24-bit little-endian
DE u32be PCM unsigned 32-bit big-endian
DE u32le PCM unsigned 32-bit little-endian
DE u8 PCM unsigned 8-bit
DE vidc PCM Archimedes VIDC
播放
ffplay -ar 44100 -ac 1 -f f32le out.pcm