录音软件是一种非常有用的工具,可以帮助我们记录和存储语音信息。在本文中,我们将介绍一个简单的录音软件,该软件利用QT进行录音,使用ffmpeg进行音频重采样,并使用fdk-aac编码。
一、 环境介绍
1、QT版本: QT5.12.6
2、编译器 : MSVC2017 64
3、ffmpeg版本: 6.1.1
4、完整工程下载地址(下载即可编译运行): https://download.csdn.net/download/u012959478/89624722
二、软件介绍
本文是一个简单的录音软件的示例,使用QT的QAudioInput来录制原始音频数据,并使用QIODevice作为输入和输出来读取和写入数据,ffmpeg进行音频重采样,fdk-aac进行编码。最终的编码结果保存为AAC格式的文件。
首先,让我们来介绍一下QT。QT是一个跨平台的应用程序开发框架,它提供了丰富的功能和界面设计工具,可以帮助我们快速开发各种应用程序。在我们的录音软件中,我们将使用QT的多媒体模块的QAudioInput来进行录音。
接下来,让我们来了解一下ffmpeg。ffmpeg是一个开源的跨平台多媒体处理工具,它可以处理各种音频和视频格式。在我们的录音软件中,我们将使用ffmpeg的音频重采样功能来将录制的音频转换为我们需要的格式。
最后,让我们来介绍一下fdk-aac。fdk-aac是一个高质量的音频编码器,它可以将音频转换为AAC格式。在我们的录音软件中,我们将使用fdk-aac来对录制的音频进行编码。
现在,让我们来看看录音软件的主要功能。首先,我们需要实现一个界面,用户可以点击开始录音按钮来开始录音。当用户点击停止录音按钮时,录音将停止并保存为一个音频文件。
在录音过程中,我们将使用QT的录音类来实现录音功能。当录音停止后,我们将使用ffmpeg进行音频重采样,以将音频转换为我们所需的采样率和格式。最后,我们将使用fdk-aac对音频进行编码,并将其保存为AAC格式的文件。
在我们的录音软件中,用户还可以选择保存音频文件的路径和文件名。当用户点击保存按钮时,我们将使用QT的文件对话框来选择保存路径和文件名。
三、示例代码
audiothread.h
#ifndef AUDIOTHREAD_H
#define AUDIOTHREAD_H
#include <QThread>
#include <QFile>
extern "C"
{
#include <libswresample/swresample.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/audio_fifo.h>
}
class QAudioInput;
class AudioThread : public QThread
{
Q_OBJECT
public:
explicit AudioThread(QObject *parent = nullptr);
~AudioThread();
signals:
void timeChanged(unsigned long long ms);
private:
void run();
bool init();
bool initResample();//初始化重采样
bool InitAudioCodec();//初始化音频编码器
void encode(AVCodecContext *ctx,AVFrame *frame,AVPacket *pkt, QFile &outFile);
void increaseVolume(AVFrame *frame, double volume);//提高音量
private:
SwrContext *_swr_ctx = nullptr;
QAudioInput *_input = nullptr;
AVCodecContext *_ac = nullptr;
};
#endif // AUDIOTHREAD_H
audiothread.cpp
#include "audiothread.h"
#include <QAudioInput>
#include <QMutex>
#include <QDebug>
#include <iostream>
extern QString g_srcDirPath;
AudioThread::AudioThread(QObject *parent):QThread(parent)
{
connect(this, &AudioThread::finished,this, &AudioThread::deleteLater);
initResample();
InitAudioCodec();
}
AudioThread::~AudioThread()
{
requestInterruption();
swr_free(&_swr_ctx);
avcodec_free_context(&_ac);
// 安全退出
quit();
wait();
}
void AudioThread::run()
{
if(!init())
return;
QFile outFile(g_srcDirPath);
if (!outFile.open(QFile::WriteOnly)) {
return;
}
//开始录制音频
QIODevice *io = _input->start();
//音频重采样输出空间分配
AVFrame *pcm = av_frame_alloc();
pcm->format = AV_SAMPLE_FMT_S16;
pcm->channels = 2;
pcm->channel_layout = av_get_default_channel_layout(pcm->channels);
pcm->nb_samples = 1024; //一帧音频一通道的采用数量
int ret = av_frame_get_buffer(pcm, 0); // 给pcm分配存储空间
if (ret != 0)
{
return;
}
//一次读取一帧音频的字节数
int in_nb_samples = av_rescale_rnd(1024, 48000, 44100, AV_ROUND_UP);
int readSize = in_nb_samples*_input->format().bytesPerFrame();
char *buf = new char[readSize];
AVPacket pkt = { 0 };
while (!isInterruptionRequested())
{
//一次读取一帧音频
if (_input->bytesReady() < readSize)
{
QThread::msleep(1);
continue;
}
int size = 0;
while (size != readSize)
{
int len = io->read(buf + size, readSize - size);
if (len < 0)break;
size += len;
}
if (size != readSize)continue;
//已经读一帧源数据
//重采样源数据
const uint8_t *indata[AV_NUM_DATA_POINTERS] = { 0 };
indata[0] = (uint8_t *)buf;
swr_convert(_swr_ctx, pcm->data, pcm->nb_samples, indata, in_nb_samples);
increaseVolume(pcm,10);//麦克风录音声音轻,提高点音量
encode(_ac,pcm,&pkt,outFile);
}
_input->stop();
delete []buf;
outFile.close();
av_frame_free(&pcm);
}
bool AudioThread::init()
{
if(QAudioDeviceInfo::availableDevices(QAudio::AudioInput).size()<1)
{
qDebug()<<"没有录音设备";
return false;
}
int sampleRate = 48000;
int channels = 2;
int sampleByte = 2;
//qt音频参数设置
QAudioFormat fmt;
fmt.setSampleRate(sampleRate);
fmt.setChannelCount(channels);
fmt.setSampleSize(sampleByte * 8);
fmt.setCodec("audio/pcm");
fmt.setByteOrder(QAudioFormat::LittleEndian);
fmt.setSampleType(QAudioFormat::SignedInt);
QAudioDeviceInfo info=QAudioDeviceInfo::defaultInputDevice();
if (!info.isFormatSupported(fmt))
{
qDebug() << "Audio format not support!";
fmt = info.nearestFormat(fmt);
}
_input = new QAudioInput(fmt);
if( !initResample() || !InitAudioCodec())
return false;
return true;
}
bool AudioThread::InitAudioCodec()
{
const AVCodec *codec = avcodec_find_encoder_by_name("libfdk_aac");
if(!codec){
return false;
}
_ac = avcodec_alloc_context3(codec);
if (!_ac) {
return false;
}
_ac->sample_fmt = AV_SAMPLE_FMT_S16; // 输入音频的采样大小。fdk_aac需要16位的音频输 入数据
_ac->channel_layout = AV_CH_LAYOUT_STEREO; // 输入音频的CHANNEL LAYOUT
_ac->channels = 2; // 输入音频的声道数
_ac->sample_rate = 44100; // 输入音频的采样率
_ac->bit_rate = 0; // AAC : 128K AAV_HE: 64K AAC_HE_V2: 32K. bit_rate为0时会查找profile属性值
// 打开编码器
int ret = avcodec_open2(_ac,codec,nullptr);
if (ret < 0) {
return false;
}
return true;
}
bool AudioThread::initResample()
{
_swr_ctx = swr_alloc_set_opts(nullptr,
AV_CH_LAYOUT_STEREO,AV_SAMPLE_FMT_S16,44100, //输出参数
AV_CH_LAYOUT_STEREO,AV_SAMPLE_FMT_S16,48000, //输入参数
0,nullptr);
if (swr_init(_swr_ctx) < 0)
{
return false;
}
return true;
}
void AudioThread::encode(AVCodecContext *ctx,AVFrame *frame,AVPacket *pkt, QFile &outFile)
{
// 发送数据到编码器
int ret = avcodec_send_frame(ctx, frame);
if (ret < 0)
{
qDebug() << "avcodec_send_frame error" ;
return;
}
// 不断从编码器中取出编码后的数据
while (true)
{
// 获取编码后的音频数据
ret = avcodec_receive_packet(ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
return;
}
else if (ret < 0)
{
return;
}
// 将编码后的数据写入文件
outFile.write((char *) pkt->data, pkt->size);
// 释放pkt内部的资源
av_packet_unref(pkt);
}
}
void AudioThread::increaseVolume(AVFrame *frame, double volume)
{
int16_t *samples = (int16_t *)frame->data[0];
int nb_samples = frame->nb_samples;
int channels = av_get_channel_layout_nb_channels(frame->channel_layout);
// 提高音量
for (int i = 0; i < nb_samples; i++)
{
for (int ch = 0; ch < channels; ch++)
{
// 使用线性插值来提高音量
int pcmval = samples[ch] * volume;
if (pcmval < 32767 && pcmval > -32768)
{
samples[ch] = pcmval;
}
else if (pcmval > 32767)
{
samples[ch] = 32767;
}
else if (pcmval < -32768)
{
samples[ch] = -32768;
}
}
samples += channels;
}
}
界面设计mainwindow.ui
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTimer>
#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();
void onTimeChanged();
void onAudioThreadFinished();
void on_saveButton_clicked();
private:
Ui::MainWindow *ui;
AudioThread *_audioThread = nullptr;
int m_num;
QTimer *_timer;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QTime>
#include <QFileDialog>
#include <QMessageBox>
QString g_srcDirPath;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->lcdNumber->setDigitCount(8);
m_num = -1;
onTimeChanged();
_timer = new QTimer(this);
connect(_timer,&QTimer::timeout,this,&MainWindow::onTimeChanged);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_audioButton_clicked()
{
if(g_srcDirPath.isEmpty())
{
QMessageBox::warning(NULL, "warning", "请设置文件保存路径!\n", QMessageBox::Yes, QMessageBox::Yes);
return;
}
if(!_audioThread)
{
_audioThread = new AudioThread(this);
_audioThread->start();
connect(_audioThread,&AudioThread::finished,this,&MainWindow::onAudioThreadFinished);
ui->audioButton->setText("结束录音");
m_num = -1;
onTimeChanged();
_timer->start(1000);
}
else
{
_audioThread->requestInterruption();
}
}
void MainWindow::onTimeChanged()
{
m_num++;
QTime time(0, 0, 0);
QString text = time.addSecs(m_num).toString("HH:mm:ss");
ui->lcdNumber->display(text);
}
void MainWindow::onAudioThreadFinished()
{
_audioThread = nullptr;
ui->audioButton->setText("开始录音");
_timer->stop();
}
void MainWindow::on_saveButton_clicked()
{
QString runPath = g_srcDirPath;
if(runPath.isEmpty())
{
runPath = QCoreApplication::applicationDirPath() + "/save.aac";
}
g_srcDirPath = QFileDialog::getSaveFileName(this, "保存文件",runPath,"AAC文件(*.aac)",nullptr,QFileDialog::DontConfirmOverwrite);
}
通过以上的实现,我们就可以得到一个简单的录音软件,它可以利用QT实现录音,使用ffmpeg进行音频重采样,并使用fdk-aac进行编码。这个录音软件不仅简单易用,可以帮助我们记录和存储语音信息,是一个非常实用的工具。
四、运行效果
谢谢您的阅读。希望本文能对您有所帮助,并且给您带来了一些新的观点和思考。如果您有任何问题或意见,请随时与我联系。再次感谢您的支持!
五、相关文章
Windosw下Visual Studio2022编译FFmpeg(支持x264、x265、fdk-acc)-CSDN博客