音频重采样的概念(Audio Resample)
将一个音频X,更改X的采样率、采样格式、声道数等参数,最终转换成音频Y,这个过程就叫做音频重采样
例如X音频(sampleRate:44100,format:s16le,channel:2)---->Y音频(sampleRate:48000,format:f32le,channel:1),其实只要更改一个参数就算是重采样了。
上面的流程图就描述了一个重采样的过程图,下面让我们通过命令行来操作一下ffmpeg如何重采样音频
命令行操控
将44100_s16le_2 转换成4800_f32l3_1
ffmpeg -ar 44100 -ac 2 -f s16le -i 44100_s16le_2.pcm -ar 48000 -ac 1 -f f32le 48000_f32le_1.pcm
在目录下面可以看到多生成了一个48000_f32le_1.pcm,这个就是我们重采样后的音频数据
播放 48000_f32le_1.pcm
这时候我们就不能采用原来的音频播放格式来播放这个采样后的数据,因此播放命令应该如下:ffplay -ar 48000 -ac 1 -f 32le 48000_f32le_1.pcm 音频数据正常播放
程序代码实现音频重采样
下面给出详细代码
- 播放工具类ffmpegs.h
#ifndef FFMPEGS_H
#define FFMPEGS_H
extern "C" {
#include
}
typedef struct{
const char *filename;
int sampleRate;
AVSampleFormat sampleFmt;
int chLayout;
} ResampleAudioSpec;
class FFmpegs
{
public:
FFmpegs();
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 outChaLayout);
};
#endif // FFMPEGS_H
- 播放工具类ffmpegs.cpp
#include "ffmpegs.h"
#include
#include
extern "C" {
#include
#include
}
#define ERROR_BUF(ref) \
char errbuf[1024]; \
av_strerror(ret,errbuf,sizeof (errbuf));
FFmpegs::FFmpegs()
{
}
void FFmpegs::resampleAudio(ResampleAudioSpec &in, ResampleAudioSpec &out){
resampleAudio(in.filename,in.sampleRate,in.sampleFmt,in.chLayout,
out.filename,out.sampleRate,out.sampleFmt,out.chLayout);
}
void FFmpegs::resampleAudio(const char *inFilename,
int inSampleRate,
AVSampleFormat inSampleFmt,
int inChLayout,
const char *outFilename,
int outSampleRate,
AVSampleFormat outSampleFmt,
int outChaLayout){
// 向下取整,AV_ROUND_DOWN(2.66) = 2
// qDebug() << av_rescale_rnd(8, 1, 3, AV_ROUND_DOWN);
// 向上取整,AV_ROUND_UP(1.25) = 2
// qDebug() << av_rescale_rnd(5, 1, 4, AV_ROUND_UP);
//文件名
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 outLinessize = 0;
//声道数
int outChs = av_get_channel_layout_nb_channels(outChaLayout);
//一个样本的大小
int outBytesPerSample = outChs * av_get_bytes_per_sample(outSampleFmt);
//缓冲区的样本数量
int outSamples = av_rescale_rnd(outSampleRate,inSamples,inSampleRate,AV_ROUND_UP);
//计算公式
/*
inSampleRate inSamples
------------- = -------------
outSampleRate outSamples
outSamples = outSampleRate * inSamples / inSampleRate
*/
qDebug() << "输入缓冲区" << inSampleRate << inSamples;
qDebug() << "输出缓冲区" << outSampleRate << outSamples;
// 返回结果
int ret = 0;
//创建重采样上下文
SwrContext *ctx = swr_alloc_set_opts(nullptr,
//取出参数
outChaLayout,outSampleFmt,outSampleRate,
//输入参数
inChLayout,inSampleFmt,inSampleRate,
0,nullptr);
if(!ctx){
qDebug() << "swr_alloc_set_opts error";
goto end;
}
//初始化重采样上下文
ret = swr_init(ctx);
if(ret < 0){
ERROR_BUF(ret);
qDebug() << "swr_init error:" << errbuf;
goto end;
}
/* 指针类型(64bit,8个字节)
int *;
double *;
void *;
int **;
int ***;
int ******;
*/
// int *p;
// *(p + i) == p[i]
// *(p + 0) == p[0]
// *p == p[0]
// int *p = new int[15];
// int *p = av_calloc(15, sizeof (int));
// int **pp = av_calloc(7, sizeof (int *));
// uint8_t **inData = av_calloc(1, sizeof(uint8_t *));
//创建输入缓冲区
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,
&outLinessize,
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:" << outFilename;
goto end;
}
//之前漏掉下面这一句,导致项目一直在报 QIODevice::write (QFile, "/Users/songlin/audio/resample/test/48000_f32le_1.pcm"): device not open
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;
}
// int size = av_samples_get_buffer_size(nullptr, outChs, ret, outSampleFmt, 1);
// outFile.write((char *) outData[0], size);
// 将转换后的数据写入到输出文件中
// outData[0] == *outData
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);
// void *ptr = malloc(100);
// freep(&ptr);
// free(ptr);
}
- 子线程audioThread.h
#ifndef AUDIOTHREAD_H
#define AUDIOTHREAD_H
#include
class AudioThread : public QThread
{
Q_OBJECT
private:
void run();
public:
explicit AudioThread(QObject *parent = nullptr);
~AudioThread();
signals:
};
#endif // AUDIOTHREAD_H
- 子线程audioThread.cpp
#include "audiothread.h"
#include
#include "ffmpegs.h"
AudioThread::AudioThread(QObject *parent) : QThread(parent) {
// 当监听到线程结束时(finished),就调用deleteLater回收内存
connect(this, &AudioThread::finished,
this, &AudioThread::deleteLater);
}
AudioThread::~AudioThread() {
// 断开所有的连接
disconnect();
// 内存回收之前,正常结束线程
requestInterruption();
// 安全退出
quit();
wait();
qDebug() << this << "析构(内存被回收)";
}
void AudioThread::run() {
// 44100_s16le_2 -> 48000_f32le_2 -> 48000_s32le_1 -> 44100_s16le_2
ResampleAudioSpec ras1;
ras1.filename = "/Users/songlin/audio/resample/44100_s16le_2.pcm";
ras1.sampleFmt = AV_SAMPLE_FMT_S16;
ras1.sampleRate = 44100;
ras1.chLayout = AV_CH_LAYOUT_STEREO;
ResampleAudioSpec ras2;
ras2.filename = "/Users/songlin/audio/resample/test/48000_f32le_1.pcm";
ras2.sampleFmt = AV_SAMPLE_FMT_FLT;
ras2.sampleRate = 48000;
ras2.chLayout = AV_CH_LAYOUT_MONO;
ResampleAudioSpec ras3;
ras3.filename = "/Users/songlin/audio/resample/test/48000_s32le_1.pcm";
ras3.sampleFmt = AV_SAMPLE_FMT_S32;
ras3.sampleRate = 48000;
ras3.chLayout = AV_CH_LAYOUT_MONO;
ResampleAudioSpec ras4 = ras1;
ras4.filename = "/Users/songlin/audio/resample/test/44100_s16le_2_new.pcm";
FFmpegs::resampleAudio(ras1, ras2);
FFmpegs::resampleAudio(ras2, ras3);
FFmpegs::resampleAudio(ras3, ras4);
}
- mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include
#include
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_audioButton_clicked();
private:
Ui::MainWindow *ui;
AudioThread *_audioThread = nullptr;
};
#endif // MAINWINDOW_H
- mainwindo.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_audioButton_clicked()
{
_audioThread = new AudioThread(this);
_audioThread->start();
}
到这里,重采样几乎已经实现了,代码中不懂之处,欢迎留言提问