一个简单的录音软件(利用QT录音,ffmpeg进行音频重采样,fdk-aac编码)

news2024/9/21 19:01:40

         录音软件是一种非常有用的工具,可以帮助我们记录和存储语音信息。在本文中,我们将介绍一个简单的录音软件,该软件利用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博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1994430.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

绕过微信电脑版旧版本限制,版本过低不给登录的问题

这张图&#xff0c;对于还在使用低版本微信电脑版的人很熟悉了吧&#xff01;因为微信逐步开始限制低版本的客户端了&#xff0c;导致无法登陆进去。 为什么这么多人还在使用旧版&#xff1f; 因为很多机器人、框架、HOOK版本的微信等等都是在旧版的基础上开发的&#xff0c;…

嘉立创PCB板子降层(从4层到2层实例)

降层导致的改变 走线和连接&#xff0c;若想正常设计先把要用的内容全部移动到其他层。若不使用可以按照下面方式全部删除。 删除定义使用的规则 删除在需要删除层的走线等所有内容

相同的 LLM 在「不同 GPU 上」会产生不同输出?为什么?

编者按&#xff1a; 在大语言模型(LLMs)的部署及其相关的算力扩容过程中&#xff0c;更换 GPU 是否也可能会对模型的输出产生重大影响&#xff1f;这个问题的答案对于确保 LLMs 在不同硬件环境下的一致性和可靠性至关重要。 我们今天为大家带来的这篇文章&#xff0c;作者的核心…

利用docker部署图形化工具 portainer

docker查找图形化工具 Portainer 拉取镜像 docker pull portainer/portainer启动docker UI容器 docker run -d -p 9209:9000 --name portainer --restart always -v /var/run/docker.sock:/var/run/docker.sock -v /opt/data3/mydocker/portainer_data:/data portainer/porta…

MFC多个控件组合存在显示不出来现象

MFC多个控件组合存在显示不出来现象 1、找到rc文件 &#xff0c; 右键查看代码 2、 3、将基础组件放在最前面即可

C++(week16): C++提高:(六) Qt基础

文章目录 零、课前须知一、Qt基础1.CLI与GUI2.事件驱动模型3.Qt快捷键 二、QtCreator1.Qt的安装&#xff1a;Qt框架、IDE2.Qt的六大模式3.核心模块4.布局5.Qt项目中的文件6.信号与槽机制7.添加资源&#xff1a;资源文件qrc8.main.cpp解析(1)ui文件 和 纯代码(2)按钮 信号槽机制…

在嵌入式Linux平台上使用Nginx搭建RTMP流媒体服务器

概述 Nginx是一个以高效稳定著称的高性能的HTTP和反向代理web服务器&#xff0c;它同时也是基于事件驱动开发的异步高性能跨平台服务器。Nginx-RTMP是基于Nginx框架的模块开发&#xff0c;很好地继承了Nginx的异步高性能以及扩展性好的优点。 RTMP 是 Real Time Messaging Pr…

docker 部署 ElasticSearch;Kibana

ELasticSearch 创建网络 docker network create es-netES配合Kibana使用时需要组网&#xff0c;使两者运行在同一个网络下 命令 docker run -d \ --name es \ -e "discovery.typesingle-node" \ -v /usr/local/es/data:/usr/share/elasticsearch/data \ -v /usr/…

C语言——编译与链接

目录 引言 翻译环境与运行环境 翻译环境 1.翻译环境的简述 2.编译过程 2.1 预处理&#xff08;预编译&#xff09; 2.2 编译 2.2.1 词法分析 2.2.2 语法分析 2.2.3 语义分析 2.3 汇编 3.链接 运行环境 结束语 引言 C语言编译与链接过程是理解程序如何从代码转化…

8月5日学习笔记 glibc安装与安全用户角色权限

一&#xff0c;glibc安装 https://www.mysql.com/ 官⽹ https://downloads.mysql.com/archives/community/ https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.33-li nux-glibc2.12-x86_64.tar 安装步骤 1.安装依赖库 [rootlocalhost ~]# yum list installed |g…

在vscode中使用ssh运行docker:从下载到运行全流程

首先在本机或者服务器上下载docker并运行 本文目的旨在本机下载docker并打包&#xff0c;然后在服务器上进行加载 docker -v Docker version 27.0.3, build 7d4bcd8有输出说明在运行 一、下载 在docker hub上下载docker以tensorflow为例 点击tag搜索自己想要的版本 copy命…

All-Reduce通信原语;Reduce+LayerNorm+Broadcast算子;gRPC:远程过程调用(RPC)框架;

目录 All-Reduce通信原语 定义与作用 实例说明 示例图解(以Ring算法为例) 结论 Reduce+LayerNorm+Broadcast算子 1. Reduce算子 2. LayerNorm算子 3. Broadcast算子 组合使用场景 gRPC:远程过程调用(RPC)框架 All-Reduce通信原语 是计算机科学中,特别是在分布式…

解锁成都跃享未来教育咨询抖音小店

在数字化浪潮汹涌的今天&#xff0c;教育行业的变革与创新层出不穷&#xff0c;其中&#xff0c;成都跃享未来教育咨询以其敏锐的洞察力和前瞻性的教育理念&#xff0c;在抖音平台上开设的小店&#xff0c;正悄然改变着人们的学习方式和教育资源的获取途径。本文将深入探讨成都…

骰子游戏的UML分析

一、需求分析 游戏者掷两个骰子,如果总点数是7则赢得游戏,否则为输 二、概要设计 2.1 设计用例 用例是基于某个场景(注:包括成功和失败场景,重要体现需求的边界)说明了用户如何通过系统实现实现其目标。 示例:玩游戏场景用例 用例名称:玩游戏 主要参与者: 游戏用户 前…

实时数据监控,三防平板在工业领域的应用解析

随着工业4.0时代的到来&#xff0c;数字化转型已成为各行各业的共同目标。在这一过程中&#xff0c;实时数据监控扮演着至关重要的角色&#xff0c;为企业提供数据驱动的决策支持&#xff0c;提升效率、降低成本、提高安全性。而作为移动终端设备&#xff0c;三防平板凭借其可靠…

深兰科技荣获2024年度金势奖“AI出海先锋品牌”金奖

近日&#xff0c;由金势奖组委会、凤凰网、营销国际协会等国内外知名机构、集团共同主办的“第四届未来营销大会暨锐品牌盛典”在上海举行。大会揭晓了第四届“金势奖锐品牌大赏”奖项的评选结果&#xff0c;深兰科技凭借自身在机器人产品出口和海外市场开拓等出海全球化发展方…

2-59 基于matlab的全离散法单自由度稳定极限切深叶瓣图绘制、两自由度稳定极限切深叶瓣图绘制

基于matlab的全离散法单自由度稳定极限切深叶瓣图绘制、两自由度稳定极限切深叶瓣图绘制&#xff0c;特定切削力系数进行数值积分。输出相应的叶瓣图。程序已调通&#xff0c;可直接运行。 2-59 两自由度稳定极限切深叶瓣图 - 小红书 (xiaohongshu.com)

【计算机组成原理】3.程序的执行

程序的执行 进程与线程 一个程序&#xff0c;读入内存&#xff0c;全是0和1构成 从内存读入到CPU计算&#xff0c;这个时候要通过总线 怎么区分一段01的数据到底是数据还是指令&#xff1f; 总线分类为三种&#xff1a;控制线 地址线 数据线 一个程序的执行&#xff0c;首…

美团创始人的亲授产品课

2020年王慧文受邀在清华大学演讲了个人的非产品公开课&#xff0c;课程内容以美团早期的实战经验结合规模效益、马太理论等诸多知名理论为主。 前两天重新翻阅的时候&#xff0c;还是有很多新的感悟&#xff0c;所以也借此机会把课程内容和大家分享一下。 规模效益 一个业务有…

视频压缩文件太大了怎么缩小?这6个视频压缩方法真有效

视频压缩文件太大了怎么缩小&#xff1f;视频文件太大不仅会占据磁盘空间&#xff0c;而且会影响分享传输&#xff0c;因此太大的视频文件我们可以通过压缩缩小来减少体积&#xff0c;那么要怎么压缩视频文件大小呢&#xff1f;在这里小编要分享亲测有用的6个视频压缩方法&…