音頻重采樣的概念(Audio Resample)
將一個(gè)音頻X,更改X的采樣率、采樣格式、聲道數(shù)等參數(shù),最終轉(zhuǎn)換成音頻Y,這個(gè)過(guò)程就叫做音頻重采樣
例如X音頻(sampleRate:44100,format:s16le,channel:2)---->Y音頻(sampleRate:48000,format:f32le,channel:1),其實(shí)只要更改一個(gè)參數(shù)就算是重采樣了。

重采樣.png
上面的流程圖就描述了一個(gè)重采樣的過(guò)程圖,下面讓我們通過(guò)命令行來(lái)操作一下ffmpeg如何重采樣音頻
命令行操控
將44100_s16le_2 轉(zhuǎn)換成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
在目錄下面可以看到多生成了一個(gè)48000_f32le_1.pcm,這個(gè)就是我們重采樣后的音頻數(shù)據(jù)
播放 48000_f32le_1.pcm
這時(shí)候我們就不能采用原來(lái)的音頻播放格式來(lái)播放這個(gè)采樣后的數(shù)據(jù),因此播放命令應(yīng)該如下:ffplay -ar 48000 -ac 1 -f 32le 48000_f32le_1.pcm 音頻數(shù)據(jù)正常播放
程序代碼實(shí)現(xiàn)音頻重采樣
下面給出詳細(xì)代碼
- 播放工具類(lèi)ffmpegs.h
#ifndef FFMPEGS_H
#define FFMPEGS_H
extern "C" {
#include <libavformat/avformat.h>
}
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
- 播放工具類(lèi)ffmpegs.cpp
#include "ffmpegs.h"
#include <QDebug>
#include <QFile>
extern "C" {
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
}
#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);
//輸入緩沖區(qū)
//指向緩沖區(qū)的指針
uint8_t **inData = nullptr;
//緩沖區(qū)的大小
int inLinesize = 0;
//聲道數(shù)
int inChs = av_get_channel_layout_nb_channels(inChLayout);
//一個(gè)樣本的大小
int inBytesPerSample = inChs * av_get_bytes_per_sample(inSampleFmt);
//緩沖區(qū)的樣本數(shù)量
int inSamples = 1024;
//讀取文件數(shù)據(jù)的大小
int len = 0;
//輸出緩沖區(qū)
//指向緩沖區(qū)的指針
uint8_t **outData = nullptr;
//緩沖區(qū)的大小
int outLinessize = 0;
//聲道數(shù)
int outChs = av_get_channel_layout_nb_channels(outChaLayout);
//一個(gè)樣本的大小
int outBytesPerSample = outChs * av_get_bytes_per_sample(outSampleFmt);
//緩沖區(qū)的樣本數(shù)量
int outSamples = av_rescale_rnd(outSampleRate,inSamples,inSampleRate,AV_ROUND_UP);
//計(jì)算公式
/*
inSampleRate inSamples
------------- = -------------
outSampleRate outSamples
outSamples = outSampleRate * inSamples / inSampleRate
*/
qDebug() << "輸入緩沖區(qū)" << inSampleRate << inSamples;
qDebug() << "輸出緩沖區(qū)" << outSampleRate << outSamples;
// 返回結(jié)果
int ret = 0;
//創(chuàng)建重采樣上下文
SwrContext *ctx = swr_alloc_set_opts(nullptr,
//取出參數(shù)
outChaLayout,outSampleFmt,outSampleRate,
//輸入?yún)?shù)
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;
}
/* 指針類(lèi)型(64bit,8個(gè)字節(jié))
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 *));
//創(chuàng)建輸入緩沖區(qū)
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;
}
//創(chuàng)建輸出緩沖區(qū)
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;
}
//打開(kāi)文件
if(!inFile.open(QFile::ReadOnly)){
qDebug() << "file open error:" << outFilename;
goto end;
}
//之前漏掉下面這一句,導(dǎo)致項(xiàng)目一直在報(bào) 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;
}
//讀取文件數(shù)據(jù)
//inData[0] == *inData;
while((len = inFile.read((char *) inData[0],inLinesize)) > 0){
//讀取的樣本數(shù)量
inSamples = len / inBytesPerSample;
//重采樣(返回值轉(zhuǎn)換后的樣本數(shù)量)
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);
// 將轉(zhuǎn)換后的數(shù)據(jù)寫(xiě)入到輸出文件中
// outData[0] == *outData
outFile.write((char *) outData[0],ret * outBytesPerSample);
}
end:
// 釋放資源
// 關(guān)閉文件
inFile.close();
outFile.close();
// 釋放輸入緩沖區(qū)
if (inData) {
av_freep(&inData[0]);
}
av_freep(&inData);
// 釋放輸出緩沖區(qū)
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 <QThread>
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 <QDebug>
#include "ffmpegs.h"
AudioThread::AudioThread(QObject *parent) : QThread(parent) {
// 當(dāng)監(jiān)聽(tīng)到線程結(jié)束時(shí)(finished),就調(diào)用deleteLater回收內(nèi)存
connect(this, &AudioThread::finished,
this, &AudioThread::deleteLater);
}
AudioThread::~AudioThread() {
// 斷開(kāi)所有的連接
disconnect();
// 內(nèi)存回收之前,正常結(jié)束線程
requestInterruption();
// 安全退出
quit();
wait();
qDebug() << this << "析構(gòu)(內(nèi)存被回收)";
}
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 <QMainWindow>
#include <audiothread.h>
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();
}
到這里,重采樣幾乎已經(jīng)實(shí)現(xiàn)了,代碼中不懂之處,歡迎留言提問(wèn)