Qt+FFmpeg 實(shí)現(xiàn)音頻重采樣的示例代碼
本文面向編程小白,用最通俗的語言講解如何基于 Qt+FFmpeg 實(shí)現(xiàn)音頻重采樣(簡單說:把音頻文件的采樣率 / 采樣格式 / 聲道數(shù)轉(zhuǎn)換成目標(biāo)規(guī)格)。全程避開復(fù)雜術(shù)語,只講核心邏輯,代碼可直接運(yùn)行~
3 個核心概念
| 概念 | 通俗解釋 |
|---|---|
| 采樣率 | 每秒采集的音頻 “樣本數(shù)”,比如 44100Hz = 每秒采 44100 個樣本,數(shù)值越高音質(zhì)越細(xì)膩 |
| 采樣格式 | 每個音頻樣本的 “存儲格式”,比如 S16(16 位整數(shù))、F32(32 位浮點(diǎn)) |
| 聲道數(shù) | 單聲道(MONO,1 個聲道)、立體聲(STEREO,2 個聲道) |
| 音頻重采樣 | 把音頻的采樣率 / 采樣格式 / 聲道數(shù),轉(zhuǎn)換成目標(biāo)規(guī)格的過程(本文核心) |
整體流程
- 用 Qt 做一個帶按鈕的簡單界面;
- 點(diǎn)擊按鈕后,啟動一個獨(dú)立線程(避免界面卡死);
- 線程中調(diào)用 FFmpeg 的音頻重采樣接口,把指定的 PCM 音頻文件轉(zhuǎn)換成新格式;
- 生成轉(zhuǎn)換后的新音頻文件。
環(huán)境準(zhǔn)備
- Qt:隨便一個版本(比如 Qt 5.15),用來做界面和線程管理;
- FFmpeg:編譯好的庫(重點(diǎn)包含
swresample模塊,音頻重采樣專用); - PCM 文件:測試用的無壓縮音頻文件(本文用
44100_s16le_2.pcm,格式:44100 采樣率、16 位整數(shù)、立體聲)。
完整代碼
目錄結(jié)構(gòu)
├── main.cpp // 程序入口 ├── mainwindow.h/.cpp // 主界面(帶按鈕) ├── audiothread.h/.cpp// 音頻處理線程(避免界面卡死) ├── ffmpegs.h/.cpp // FFmpeg重采樣核心邏輯
1. main.cpp(程序入口)
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[]) {
// 創(chuàng)建Qt應(yīng)用程序?qū)ο螅ǔ绦蚩傞_關(guān))
QApplication a(argc, argv);
// 創(chuàng)建主窗口
MainWindow w;
// 顯示窗口
w.show();
// 啟動Qt事件循環(huán)(讓窗口能點(diǎn)擊、能響應(yīng)操作)
return a.exec();
}
小白解析:所有 Qt 程序的標(biāo)準(zhǔn)入口,作用就是 “啟動程序 + 顯示窗口”。
2. 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:
// 按鈕點(diǎn)擊的響應(yīng)函數(shù)
void on_audioButton_clicked();
private:
// 界面控件(比如按鈕)的管理對象
Ui::MainWindow *ui;
// 音頻線程對象(避免界面卡死)
AudioThread *_audioThread = nullptr;
};
#endif // MAINWINDOW_H
3. mainwindow.cpp(主窗口實(shí)現(xiàn))
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow) {
// 初始化界面(加載Qt設(shè)計(jì)師畫的按鈕)
ui->setupUi(this);
}
MainWindow::~MainWindow() {
// 銷毀界面控件,釋放內(nèi)存
delete ui;
}
void MainWindow::on_audioButton_clicked() {
// 創(chuàng)建音頻線程對象
_audioThread = new AudioThread(this);
// 啟動線程(執(zhí)行AudioThread的run函數(shù))
_audioThread->start();
}
4. audiothread.h(音頻線程頭文件)
#ifndef AUDIOTHREAD_H
#define AUDIOTHREAD_H
#include <QThread>
class AudioThread : public QThread {
Q_OBJECT
private:
// 線程啟動后自動執(zhí)行的函數(shù)(核心邏輯寫這)
void run();
public:
explicit AudioThread(QObject *parent = nullptr);
~AudioThread();
};
#endif // AUDIOTHREAD_H
5. audiothread.cpp(音頻線程實(shí)現(xiàn))
#include "audiothread.h"
#include <QDebug>
#include "ffmpegs.h"
AudioThread::AudioThread(QObject *parent) : QThread(parent) {
// 線程結(jié)束后自動釋放內(nèi)存(Qt內(nèi)存管理小技巧)
connect(this, &AudioThread::finished, this, &AudioThread::deleteLater);
}
AudioThread::~AudioThread() {
// 斷開所有連接
disconnect();
// 要求線程安全退出
requestInterruption();
quit();
wait();
qDebug() << this << "析構(gòu)(內(nèi)存被回收)";
}
void AudioThread::run() {
// 定義轉(zhuǎn)換規(guī)則:44100立體聲16位 → 48000單聲道浮點(diǎn) → 48000單聲道32位 → 還原成原始格式
ResampleAudioSpec ras1;
ras1.filename = "D:/44100_s16le_2.pcm"; // 原始文件路徑
ras1.sampleFmt = AV_SAMPLE_FMT_S16; // 16位整數(shù)格式
ras1.sampleRate = 44100; // 44100采樣率
ras1.chLayout = AV_CH_LAYOUT_STEREO; // 立體聲
ResampleAudioSpec ras2;
ras2.filename = "D:/48000_f32le_1.pcm"; // 轉(zhuǎn)換后文件1
ras2.sampleFmt = AV_SAMPLE_FMT_FLT; // 浮點(diǎn)格式
ras2.sampleRate = 48000; // 48000采樣率
ras2.chLayout = AV_CH_LAYOUT_MONO; // 單聲道
ResampleAudioSpec ras3;
ras3.filename = "D:/48000_s32le_1.pcm"; // 轉(zhuǎn)換后文件2
ras3.sampleFmt = AV_SAMPLE_FMT_S32; // 32位整數(shù)格式
ras3.sampleRate = 48000; // 48000采樣率
ras3.chLayout = AV_CH_LAYOUT_MONO; // 單聲道
ResampleAudioSpec ras4 = ras1;
ras4.filename = "D:/44100_s16le_2_new.pcm"; // 最終還原的文件
// 調(diào)用FFmpeg重采樣函數(shù),一步步轉(zhuǎn)換
FFmpegs::resampleAudio(ras1, ras2);
FFmpegs::resampleAudio(ras2, ras3);
FFmpegs::resampleAudio(ras3, ras4);
}
6. ffmpegs.h(FFmpeg 核心頭文件)
#ifndef FFMPEGS_H
#define FFMPEGS_H
extern "C" {
#include <libavformat/avformat.h>
}
// 封裝音頻規(guī)格的結(jié)構(gòu)體(方便傳參)
typedef struct {
const char *filename; // 文件路徑
int sampleRate; // 采樣率
AVSampleFormat sampleFmt; // 采樣格式
int chLayout; // 聲道布局(單聲道/立體聲)
} ResampleAudioSpec;
class FFmpegs {
public:
FFmpegs();
// 重載的重采樣函數(shù)(傳結(jié)構(gòu)體更方便)
static void resampleAudio(ResampleAudioSpec &in, ResampleAudioSpec &out);
// 核心重采樣函數(shù)(傳單個參數(shù))
static void resampleAudio(const char *inFilename,
int inSampleRate,
AVSampleFormat inSampleFmt,
int inChLayout,
const char *outFilename,
int outSampleRate,
AVSampleFormat outSampleFmt,
int outChLayout);
};
#endif // FFMPEGS_H
7. ffmpegs.cpp(FFmpeg 重采樣核心實(shí)現(xiàn))
#include "ffmpegs.h"
#include <QDebug>
#include <QFile>
extern "C" {
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
}
// 錯誤信息封裝(小白不用摳,復(fù)制就行)
#define ERROR_BUF(ret) \
char errbuf[1024]; \
av_strerror(ret, errbuf, sizeof (errbuf));
FFmpegs::FFmpegs() {}
// 重載函數(shù):調(diào)用核心重采樣函數(shù)
void FFmpegs::resampleAudio(ResampleAudioSpec &in, ResampleAudioSpec &out) {
resampleAudio(in.filename, in.sampleRate, in.sampleFmt, in.chLayout,
out.filename, out.sampleRate, out.sampleFmt, out.chLayout);
}
// 核心重采樣函數(shù)
void FFmpegs::resampleAudio(const char *inFilename,
int inSampleRate,
AVSampleFormat inSampleFmt,
int inChLayout,
const char *outFilename,
int outSampleRate,
AVSampleFormat outSampleFmt,
int outChLayout) {
// ===== 1. 初始化變量 =====
QFile inFile(inFilename); // 輸入文件
QFile outFile(outFilename); // 輸出文件
// 輸入緩沖區(qū)相關(guān)
uint8_t **inData = nullptr; // 輸入緩沖區(qū)指針
int inLinesize = 0; // 輸入緩沖區(qū)大小
int inChs = av_get_channel_layout_nb_channels(inChLayout); // 輸入聲道數(shù)
int inBytesPerSample = inChs * av_get_bytes_per_sample(inSampleFmt); // 輸入單個樣本大小
int inSamples = 1024; // 輸入緩沖區(qū)樣本數(shù)
int len = 0; // 讀取文件的字節(jié)數(shù)
// 輸出緩沖區(qū)相關(guān)
uint8_t **outData = nullptr;// 輸出緩沖區(qū)指針
int outLinesize = 0; // 輸出緩沖區(qū)大小
int outChs = av_get_channel_layout_nb_channels(outChLayout); // 輸出聲道數(shù)
int outBytesPerSample = outChs * av_get_bytes_per_sample(outSampleFmt); // 輸出單個樣本大小
// 計(jì)算輸出緩沖區(qū)樣本數(shù)(按采樣率比例縮放,向上取整)
int outSamples = av_rescale_rnd(outSampleRate, inSamples, inSampleRate, AV_ROUND_UP);
int ret = 0; // FFmpeg函數(shù)返回值
// ===== 2. 創(chuàng)建并初始化重采樣上下文 =====
// 創(chuàng)建上下文(配置輸入/輸出參數(shù))
SwrContext *ctx = swr_alloc_set_opts(nullptr,
// 輸出參數(shù)
outChLayout, outSampleFmt, outSampleRate,
// 輸入?yún)?shù)
inChLayout, inSampleFmt, inSampleRate,
0, nullptr);
if (!ctx) {
qDebug() << "創(chuàng)建重采樣上下文失敗";
goto end; // 跳轉(zhuǎn)到釋放資源的位置
}
// 初始化上下文
ret = swr_init(ctx);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "初始化重采樣上下文失敗:" << errbuf;
goto end;
}
// ===== 3. 創(chuàng)建輸入/輸出緩沖區(qū) =====
// 創(chuàng)建輸入緩沖區(qū)
ret = av_samples_alloc_array_and_samples(&inData, &inLinesize, inChs, inSamples, inSampleFmt, 1);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "創(chuàng)建輸入緩沖區(qū)失敗:" << errbuf;
goto end;
}
// 創(chuàng)建輸出緩沖區(qū)
ret = av_samples_alloc_array_and_samples(&outData, &outLinesize, outChs, outSamples, outSampleFmt, 1);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "創(chuàng)建輸出緩沖區(qū)失?。? << errbuf;
goto end;
}
// ===== 4. 打開文件 =====
if (!inFile.open(QFile::ReadOnly)) {
qDebug() << "打開輸入文件失?。? << inFilename;
goto end;
}
if (!outFile.open(QFile::WriteOnly)) {
qDebug() << "打開輸出文件失?。? << outFilename;
goto end;
}
// ===== 5. 循環(huán)讀取→重采樣→寫入 =====
while ((len = inFile.read((char *) inData[0], inLinesize)) > 0) {
// 計(jì)算實(shí)際讀取的樣本數(shù)
inSamples = len / inBytesPerSample;
// 核心:調(diào)用FFmpeg重采樣函數(shù)
ret = swr_convert(ctx, outData, outSamples, (const uint8_t **) inData, inSamples);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "重采樣失?。? << errbuf;
goto end;
}
// 把轉(zhuǎn)換后的數(shù)據(jù)寫入輸出文件
outFile.write((char *) outData[0], ret * outBytesPerSample);
}
// 處理緩沖區(qū)殘留的樣本(避免數(shù)據(jù)丟失)
while ((ret = swr_convert(ctx, outData, outSamples, nullptr, 0)) > 0) {
outFile.write((char *) outData[0], ret * outBytesPerSample);
}
// ===== 6. 釋放資源 =====
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);
}
核心邏輯:
- 初始化變量:定義輸入 / 輸出文件、緩沖區(qū)參數(shù);
- 創(chuàng)建重采樣上下文:告訴 FFmpeg “輸入格式” 和 “輸出格式”;
- 創(chuàng)建緩沖區(qū):用來存放讀取的音頻數(shù)據(jù)和轉(zhuǎn)換后的音頻數(shù)據(jù);
- 打開文件:準(zhǔn)備讀原始文件、寫新文件;
- 循環(huán)處理:讀一點(diǎn)數(shù)據(jù)→轉(zhuǎn)格式→寫一點(diǎn)數(shù)據(jù),直到文件讀完;
- 釋放資源:FFmpeg 的資源要手動釋放,避免內(nèi)存泄漏。
運(yùn)行步驟
- 把代碼中的文件路徑(比如
D:/44100_s16le_2.pcm)換成自己的 PCM 文件路徑; - 配置 Qt 工程,鏈接 FFmpeg 的
swresample庫; - 編譯運(yùn)行程序,點(diǎn)擊界面上的按鈕;
- 去指定路徑(比如 D 盤)查看轉(zhuǎn)換后的 PCM 文件。
核心總結(jié)
- 為啥用線程?→ 避免界面卡死,讓 “界面操作” 和 “音頻處理” 并行;
- FFmpeg 核心?→
swr_alloc_set_opts(配置參數(shù))、swr_init(初始化)、swr_convert(重采樣); - 內(nèi)存管理?→ FFmpeg 的緩沖區(qū) / 上下文要手動釋放,Qt 線程要安全退出;
- 核心流程?→ 讀原始 PCM→轉(zhuǎn)格式→寫新 PCM。
常見問題
- 運(yùn)行報錯 “找不到 FFmpeg 庫”→ 檢查 Qt 工程的庫鏈接路徑;
- 轉(zhuǎn)換后文件沒聲音→ 檢查原始 PCM 文件路徑是否正確、格式參數(shù)是否匹配;
- 界面卡死→ 確認(rèn)音頻處理邏輯在獨(dú)立線程中執(zhí)行。
到此這篇關(guān)于Qt+FFmpeg 實(shí)現(xiàn)音頻重采樣的示例代碼的文章就介紹到這了,更多相關(guān)Qt FFmpeg 音頻重采樣內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++數(shù)據(jù)結(jié)構(gòu)之單鏈表的實(shí)現(xiàn)
線性表的鏈?zhǔn)酱鎯τ址Q為單鏈表,它是指通過一組任意的存儲單元來存儲線性表中的數(shù)據(jù)元素。本文將用C++實(shí)現(xiàn)單鏈表,需要的可以參考一下2022-05-05
C語言編程中從密碼文件獲取數(shù)據(jù)的函數(shù)總結(jié)
這篇文章主要介紹了C語言編程中從密碼文件獲取數(shù)據(jù)的函數(shù)總結(jié),包括getpw()函數(shù)和getpwnam()函數(shù)以及getpwuid()函數(shù),需要的朋友可以參考下2015-08-08
C++類靜態(tài)成員與類靜態(tài)成員函數(shù)詳解
靜態(tài)成員不可在類體內(nèi)進(jìn)行賦值,因?yàn)樗潜凰性擃惖膶ο笏蚕淼?。你在一個對象里給它賦值,其他對象里的該成員也會發(fā)生變化。為了避免混亂,所以不可在類體內(nèi)進(jìn)行賦值2013-09-09
c++中移動語義和完美轉(zhuǎn)發(fā)及易錯點(diǎn)
C++ 中的移動語義和完美轉(zhuǎn)發(fā)是 C++11 引入的兩個重要特性,它們分別用于提高性能和靈活性,這篇文章主要介紹了c++中移動語義和完美轉(zhuǎn)發(fā),需要的朋友可以參考下2023-09-09

