qt 实现音视频的分贝检测系统

news2025/2/27 7:16:21

项目场景:

目前的产品经常播放m3u8流,有的视频声音正常,有的视频声音就偏低,即使放到最大音量声音也是比较小,所以就产生了某种需求,能否自动感知视频声音的大小,如果发现声音比较小的情况,就自动放大比如系统音量增益等。


解决该问题所尝试的研究

一、命令行工具

1、tinymix

linux中主流的音频体系结构是ALSA(Advanced Linux Sound Architecture),ALSA在内核驱动层提供了alsa-driver,在应用层提供了alsa-lib,应用程序只需要调用alsa-lib提供的API就可以完成对底层硬件的操作。但是Android中没有使用标准的ALSA,而是一个ALSA的简化版叫做tinyalsa。Android中使用tinyalsa控制管理所有模式的音频通路,我们也可以使用tinyalsa提供的工具进行查看、调试。

编译tinyalsa后生成四个小工具,本次尝试使用tinymix工具来解决问题
tinymix是一个可以在Android平台上进行音频控制的命令行工具。它是Android Open Source Project (AOSP)中的一部分,可以被用于控制Android设备上的音量。

tinymix 命令可以控制音量、开关声音、调整声道平衡和控制麦克风增益等等。这个工具的主要用途是在不影响当前正在运行的程序的情况下,调整音频设置。

  1. tinymix
  2. tinyplay
  3. tinycap
  4. tinypcminfo

如下图所示,直接输入tinymix可以得到音频通路相关的各项配置参数。也可以通过添加参数修改其中的配置,如下面的系统命令通过设置 VBC DACL DG 和 VBC DACR DG ,便是设置数字增益,其范围是0~126,不过实际运行过程中发现,比如设置了 tinymix VBC DACL DG 3,过几秒之后,再查看发现其值又回到了26,不太清楚具体缘由。
在这里插入图片描述

system(QString("tinymix -D 0 \"VBC DACL DG Set\" %1").arg(20).toLatin1().data()); //14
system(QString("tinymix -D 0 \"VBC DACR DG Set\" %1").arg(20).toLatin1().data());

2、pactl

查看一下设备索引

pactl list sinks

可以看到目前的参数
在这里插入图片描述

设置绝对音量,0%-100%,1表示声卡号。

pactl set-sink-volume 1 90%

设置相对音量,增大10%

pactl set-sink-volume 1 +10%

设置相对音量,减小10%

pactl set-sink-volume 1 -10%

增加3db

pactl set-sink-volume 1 +3dB

该命令可以尝试实现

二、检测程序

通过实时检测视频中音频信息,计算出分贝值,来判断该视频的声音大小。

1.PCM数据基础

PCM(Pulse Code Modulation)也被称为脉冲编码调制,是数字通信的编码方式之一。PCM中的声音数据没有被压缩,它将输入的模拟信号进行采样、量化和编码,用二进制进行编码的数来代表模拟信号的幅度,即标准的数字音频数据。

采样率

采样率表示音频信号每秒的数字快照数。该速率决定了音频文件的频率范围。采样率越高,数字波形的形状越接近原始模拟波形。低采样率会限制可录制的频率范围,这可导致录音表现原始声音的效果不佳。一般数字音频常用的采样率电话频率8kHz、CD频率44.1kHz、DVD频率48kHz。

位深度

位深度决定动态范围。采样声波时,为每个采样指定最接近原始声波振幅的振幅值。较高的位深度可提供更多可能的振幅值,产生更大的动态范围、更低的噪声基准和更高的保真度。普通的CD是16-bit。

通道

通道个数。常见的音频有立体声(stereo)和单声道(mono)两种类型,立体声包含左声道和右声道。另外还有环绕立体声等其它不太常用的类型。

Sign

表示样本数据是否是有符号位,比如用一字节表示的样本数据,有符号的话表示范围为-128 ~ 127,无符号是0 ~ 255。

字节序

字节序是little-endian还是big-endian。通常均为little-endian
PCM信号的两个重要指标是采样频率和量化精度,当在播放音乐时,应用程序从存储介质中读取音频数据(MP3、WMA、AAC等),经过解码后,最终送到音频驱动程序中的就是PCM数据,反过来,在录音时,音频驱动不停地把采样所得的PCM数据送回给应用程序,由应用程序完成压缩、存储等任务。下面我们展开介绍下PCM音频的存储及操作

PCM音频数据存储方式

如果是单声道的音频文件,采样数据按时间的先后顺序依次存入(有的时候也会采用LRLRLR方式存储,只是另一个声道的数据为0),如果是双声道的话就按照LRLRLR的方式存储,存储的时候与字节序有关。

2.数据提取

涉及到多通道的数据如何排列和提取
按照双声道的LRLRLR的PCM音频数据可以通过将它们交叉的读出来的方式来分离左右声道的数据。

int pcm_s16le_split(const char* file, const char* out_lfile, const char* out_rfile) {
     FILE *fp = fopen(file, "rb+");
     if (fp == NULL) {
         printf("open %s failed\n", file);
         return -1;
     }
     FILE *fp1 = fopen(out_lfile, "wb+");
     if (fp1 == NULL) {
         printf("open %s failed\n", out_lfile);
         return -1;
     }
     FILE *fp2 = fopen(out_rfile, "wb+");
     if (fp2 == NULL) {
         printf("open %s failed\n", out_rfile);
         return -1;
     }
     char * sample = (char *)malloc(4);
     while(!feof(fp)) {
         fread(sample, 1, 4, fp);
         //L
         fwrite(sample, 1, 2, fp1);
         //R
         fwrite(sample + 2, 1, 2, fp2);
     }
     free(sample);
     fclose(fp);
     fclose(fp1);
     fclose(fp2);
     return 0;
 }

3. 分贝计算(参考网上资源)

公式:

在这里插入图片描述

参数:Pref:就是声音总的振幅最大值;Prms:就是当前声音的振幅值;Lp:就是我们需要的声音分贝值了。

比如:我们声音是无符号16bit深度的,那么其每个采样点的值应该在(02^16-1既:065535)范围内,带入公式我们可以计算到(不用除以最大振幅值):20*log(65535)=96.32db,所以根据这个我们只要拿到某个采样点的振幅值,也就是当前声音采样点转成16bit后的值就可以计算出相应的分贝值了。那么怎么求声音采样点的振幅呢?这是一个问题,不过也有解决办法了。

获取pcm声音采样点的振幅:

这里以我项目中用OpenSL来播放FFmpeg重采样生成的PCM声音为例,PCM声音是重采样为无符号16bit的深度的,然后我们需要得到某一时间(一般是零点几毫秒)PCM所在内存的地址和PCM声音的大小,而16bit也就是16bit/8bit=2byte,在c语言中2byte用short int来表示,因此我们可以从PCM所在地址里面按顺序取出2个byte的数据然后转化成short int的值就可以拿到当前采样点的振幅了,获取的方式是用c语言中的memcpy拷贝2个字节的数据求值就可以了。(注:因为采用点很密集,如果每个采用点都计算一下分贝的话,会消耗一定的性能或者导致声音播放不连贯,所这里采用取其绝对值和的平均值就可以了,因为在这段时间内,我们看不出任何的区别。)

/**
* 获取所有振幅之平均值 计算db (振幅最大值 2^16-1 = 65535 最大值是 96.32db)
* 16 bit == 2字节 == short int
* 无符号16bit:96.32=20*lg(65535);
*
* @param pcmdata 转换成char类型,才可以按字节操作
* @param size pcmdata的大小
* @return
*/
 
int Audio::getPcmDB(const unsigned char *pcmdata, size_t size) {
 
    int db = 0;
    short int value = 0;
    double sum = 0;
 
    for(int i = 0; i < size; i += 2)
    {
        memcpy(&value, pcmdata+i, 2); //获取2个字节的大小(值)
        sum += abs(value); //绝对值求和
    }
    sum = sum / (size / 2); //求平均值(2个字节表示一个振幅,所以振幅个数为:size/2个)
 
    if(sum > 0)
    {
        db = (int)(20.0*log10(sum));
    }
    return db;
 
}

本项目解决方案

1、效果图

在这里插入图片描述
1 支持单文件检测
2 支持目录检测
3 支持常用的音视频格式比如 mp3、 mp4、 wav、 mov等
4 支持检测报告输出
5 在线的资源播放依赖于系统的解码能力,比如windows下依赖 directshow linux下依赖 gstreamer
6 目前只提供windows版本

注意:windows下选择m3u8文件需支持其传输协议,需要安装directshow库支持
提供绿色免安装版本,直接运行QAudio.exe即可。

2、关键代码

其中最核心的类QAudioProbe

player = new QMediaPlayer(this);
probe  = new QAudioProbe; //探测器
probe->setSource(player);

connect(probe,&QAudioProbe::audioBufferProbed,
        this,&MainWindow::processBuffer); //关联函数
connect(player,&QMediaPlayer::stateChanged,
        this,&MainWindow::onStateChanged);
void MainWindow::processBuffer(const QAudioBuffer &buffer)
{
    //qDebug() << buffer.sampleCount() <<buffer.frameCount() <<buffer.byteCount();
    QAudioFormat audioFormat=buffer.format();//缓冲区格式
    getMaxAmplitude(audioFormat);
    //qDebug() << audioFormat.channelCount() << audioFormat.sampleSize() << audioFormat.sampleRate() << audioFormat.bytesPerFrame();
    if(m_paraUpdated == false)
    {
        //处理探测到的缓冲区
        ui->spin_byteCount->setValue(buffer.byteCount());//缓冲区字节数
        ui->spin_duration->setValue(buffer.duration()/1000);//缓冲区时长
        ui->spin_frameCount->setValue(buffer.frameCount());//缓冲区帧数
        ui->spin_sampleCount->setValue(buffer.sampleCount());//缓冲区采样数

        ui->spin_channelCount->setValue(audioFormat.channelCount()); //通道数
        ui->spin_sampleSize->setValue(audioFormat.sampleSize());//采样大小
        ui->spin_sampleRate->setValue(audioFormat.sampleRate());//采样率
        ui->spin_bytesPerFrame->setValue(audioFormat.bytesPerFrame());//每帧字节数

        if (audioFormat.byteOrder()==QAudioFormat::LittleEndian)
            ui->edit_byteOrder->setText("LittleEndian");//字节序
        else
            ui->edit_byteOrder->setText("BigEndian");

        ui->edit_codec->setText(audioFormat.codec());//编码格式

        if (audioFormat.sampleType()==QAudioFormat::SignedInt)//采样点类型
            ui->edit_sampleType->setText("SignedInt");
        else if(audioFormat.sampleType()==QAudioFormat::UnSignedInt)
            ui->edit_sampleType->setText("UnSignedInt");
        else if(audioFormat.sampleType()==QAudioFormat::Float)
            ui->edit_sampleType->setText("Float");
        else
            ui->edit_sampleType->setText("Unknown");

        m_paraUpdated = true;
    }


    Q_ASSERT(audioFormat.sampleSize() % 8 == 0);
    const int channelBytes = audioFormat.sampleSize() / 8;
    const int sampleBytes = audioFormat.channelCount() * channelBytes;
    //   Q_ASSERT(len % sampleBytes == 0);
    //   const int numSamples = len / sampleBytes;

    quint32 maxValue = 0;
    double sum = 0;
    int db = 0;
    const unsigned char *ptr = reinterpret_cast<const unsigned char *>(buffer.data());
    int frameCount = buffer.frameCount();
    int channelCount = audioFormat.channelCount();
    int nnum = 0;
    for (int i = 0; i < frameCount; ++i) {
        for (int j = 0; j < channelCount; ++j) {
            quint32 value = 0;

            if (audioFormat.sampleSize() == 8 && audioFormat.sampleType() == QAudioFormat::UnSignedInt) {
                value = *reinterpret_cast<const quint8*>(ptr);
            } else if (audioFormat.sampleSize() == 8 && audioFormat.sampleType() == QAudioFormat::SignedInt) {
                value = qAbs(*reinterpret_cast<const qint8*>(ptr));
            } else if (audioFormat.sampleSize() == 16 && audioFormat.sampleType() == QAudioFormat::UnSignedInt) {
                if (audioFormat.byteOrder() == QAudioFormat::LittleEndian)
                    value = qFromLittleEndian<quint16>(ptr);
                else
                    value = qFromBigEndian<quint16>(ptr);
            } else if (audioFormat.sampleSize() == 16 && audioFormat.sampleType() == QAudioFormat::SignedInt) {
                if (audioFormat.byteOrder() == QAudioFormat::LittleEndian)
                    value = qAbs(qFromLittleEndian<qint16>(ptr));
                else
                    value = qAbs(qFromBigEndian<qint16>(ptr));
            } else if (audioFormat.sampleSize() == 32 && audioFormat.sampleType() == QAudioFormat::UnSignedInt) {
                if (audioFormat.byteOrder() == QAudioFormat::LittleEndian)
                    value = qFromLittleEndian<quint32>(ptr);
                else
                    value = qFromBigEndian<quint32>(ptr);
            } else if (audioFormat.sampleSize() == 32 && audioFormat.sampleType() == QAudioFormat::SignedInt) {
                if (audioFormat.byteOrder() == QAudioFormat::LittleEndian)
                    value = qAbs(qFromLittleEndian<qint32>(ptr));
                else
                    value = qAbs(qFromBigEndian<qint32>(ptr));
            } else if (audioFormat.sampleSize() == 32 && audioFormat.sampleType() == QAudioFormat::Float) {
                value = qAbs(*reinterpret_cast<const float*>(ptr) * 0x7fffffff); // assumes 0-1.0
            }

            sum += qAbs(value);
            maxValue = qMax(value, maxValue);
            ptr += channelBytes;
            nnum++;
        }
    }

    //qDebug() << "***" << frameCount*channelCount << nnum << channelBytes;
    sum = sum / (frameCount*channelCount);
    //sum = sum / (frameCount);

    maxValue = qMin(maxValue, m_maxAmplitude);
    m_level = qreal(maxValue) / m_maxAmplitude;
    emit update();
    db = (int)(20.0*log10(sum ));
    if(db > 0)
    {
        m_sumDb += db;
        m_processedFrame++;
        qDebug() <<__func__ << "level =" << m_level << db;
    }
    m_totalFrame += buffer.frameCount();
    ui->LabFrameValue->setText(QString::number(m_totalFrame));

}
void MainWindow::getMaxAmplitude(QAudioFormat audioFormat)
{
    if(m_maxAmplitude != 0)
    {
        //qDebug() << __func__ << m_maxAmplitude;
        return;
    }
    switch (audioFormat.sampleSize()) {
    case 8:
        switch (audioFormat.sampleType()) {
        case QAudioFormat::UnSignedInt:
            m_maxAmplitude = 255;
            break;
        case QAudioFormat::SignedInt:
            m_maxAmplitude = 127;
            break;
        default:
            break;
        }
        break;
    case 16:
        switch (audioFormat.sampleType()) {
        case QAudioFormat::UnSignedInt:
            m_maxAmplitude = 65535;
            break;
        case QAudioFormat::SignedInt:
            m_maxAmplitude = 32767;
            break;
        default:
            break;
        }
        break;

    case 32:
        switch (audioFormat.sampleType()) {
        case QAudioFormat::UnSignedInt:
            m_maxAmplitude = 0xffffffff;
            break;
        case QAudioFormat::SignedInt:
            m_maxAmplitude = 0x7fffffff;
            break;
        case QAudioFormat::Float:
            m_maxAmplitude = 0x7fffffff; // Kind of
        default:
            break;
        }
        break;

    default:
        break;
    }
    qDebug() << __func__ << "m_maxAmplitude =" << m_maxAmplitude;
}

源码已提交,可在此下载 https://download.csdn.net/download/u011942101/88251529

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

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

相关文章

vue3项目初始

yarn add types/node -D是这个 下面 少到了S 这一步 就是 配置配置 src

高通 A12 设置-存储 存储总大小显示不正确问题

总存储大小计算原理&#xff1a; 系统获取存储大小是通过获取”/system”和”/data” 两个Directory 的和来计算的&#xff0c;即Environment.getDataDirectory().getTotalSpace() Environment.getRootDirectory().getTotalSpace() 问题一 &#xff1a;实际存储大小大于等于1…

网安周报|国防承包商Belcan泄露了带有漏洞列表的管理员密码

1.国防承包商Belcan泄露了带有漏洞列表的管理员密码 网络新闻研究团队发现了一个开放的 Kibana 实例&#xff0c;其中包含有关 Belcan、其员工和内部基础设施的敏感信息。Belcan 是一家政府、国防和航空航天承包商&#xff0c;提供全球设计、软件、制造、供应链、信息技术和数字…

企业在选择低代码平台时,应该注意哪些方面?

在 IT行业&#xff0c;“低代码”这个词可以说是近几年的热词了。低代码开发平台&#xff08;Low-Code Platform&#xff09;是一种新型的软件开发工具&#xff0c;它可以通过少量代码快速开发应用程序。通过采用低代码技术&#xff0c;开发者可以减少自己编写和测试应用程序的…

国际数字影像文创产业园开展企业法律的讲座

2023年8月18日14:30-16:10由成都市金牛区人民政府五块石街道办事处指导&#xff0c;国际数字影像文创产业园区、成都树观法律咨询服务有限公司主办&#xff0c;成都目莓商业运营管理有限公司协办的“法律讲座沙龙”活动在数媒大厦5楼共享会议室成功开展。 本次活动主题为“企业…

网络工程----小型网络配置1

此次作业设计&#xff1a; 硬件&#xff1a;二层交换机、三层交换机、路由、服务器、pc 配置知识&#xff1a;dhcp, dns配置&#xff0c;vlan划分&#xff0c;不同vlan间通信&#xff0c;静态路由&#xff0c;Nat动态地址&#xff0c; nat server映射&#xff0c;acl 配置命…

海外ios应用商店优化排名因素之评级与评论

评分显示在搜索结果中&#xff0c;直接影响转化率&#xff0c;而评论可以在应用页面上看到&#xff0c;评级和评论是我们无法直接控制的因素。但是我们仍然可以通过了解用户的需求并兑现承诺来尝试改进它。 1、关于用户的评论。 抱怨的用户在讲述某个问题时总是会给出最好的反…

Keil编译告警 warning: #1-D: last line of file ends without a newline

如题所示&#xff0c;Keil编译的时候&#xff0c;如果遇到了这个问题&#xff0c;就是文件最后一行并不是一个新行结尾。这个问题不影响编译结果&#xff0c;但是强迫症就受不了。 原因是源文件最后一行可能空出来了&#xff0c;但是有空格&#xff0c;如下所示&#xff1a; 这…

ACL2023 Prompt 相关文章速通 Part 1

Accepted Papers link: ACL2023 main conference accepted papers 文章目录 Accepted PapersPrompter: Zero-shot Adaptive Prefixes for Dialogue State Tracking Domain AdaptationQuery Refinement Prompts for Closed-Book Long-Form QAPrompting Language Models for Lin…

亚马逊评论后多久显示?有没有快速留评的方法?

通常情况下&#xff0c;亚马逊上的产品评论会在提交后的一到两天内显示出来。然而&#xff0c;实际的显示时间可能会因多种因素而有所不同&#xff0c;包括评论审核时间、产品销量、亚马逊服务器负载等等。在某些情况下&#xff0c;评论可能会更快地显示出来&#xff0c;而在其…

Ubuntu 22.04.3 LTS 维护更新发布

导读近日消息&#xff0c;Canonical 今天发布了代号为 Jammy Jellyfish、长期支持的 Ubuntu 22.04 第 3 个维护版本更新&#xff0c;距离上个版本相隔 6 周时间。 Ubuntu 22.04.3 LTS 最大的亮点在于内核升级到 Linux Kernel 6.2&#xff0c;此外 Mesa 图形堆栈也升级到 23.0.…

徐庆臣(黑客洗白者)个人介绍

徐庆臣&#xff08;黑客洗白者&#xff09;&#xff0c;具备10年以上研发、渗透测试和网络信息安全领域从业经验&#xff0c;具有较强的网络安全管理、技术实战项目经验。曾就职于新浪、用友等互联网知名企业&#xff0c;并担任项目经理和安全技术总监等负责人角色&#xff0c;…

Modelica由入门到精通—为什么要学习Modelica语言

1.为什么要学习Modelica语言 本人正在研究Modelica 多领域统一建模仿真语言&#xff0c;特此做学习入门介绍&#xff0c;希望可以帮助需要的小伙伴。 文章目录 1.为什么要学习Modelica语言一、背景二、系统建模与仿真2.1 系统仿真与系统模型2.2 仿真价值与可靠性 三、物理建模…

kettle开发-Day42-远程执行作业

目录 前言&#xff1a; 一、远程执行 1、先看定义 2、前置条件 2.1网络畅通 2.2数据库DB连接一致 二、实战案例-Windows 1、初始配置-被远程端 1.1启动carte服务 1.2cmd 命令启动carte服务 2、初始化-远程端 3、实际应用 3.1、错误案例 3.2、正确案例 三、总结 前言&…

react import 引用失效 node_modules/@types/react/index.d.ts not a module.ts

问题描述 react ts的项目&#xff0c;正常使用vs code打开&#xff0c; 先运行 npm install 安装依赖过后 结果所有的react引用依旧标红&#xff0c;如下图所示&#xff1a; 点击红线 show problem(查看问题)&#xff0c;提示node_modules/types/react/index.d.ts not a mod…

day-31 代码随想录算法训练营(19)贪心part01

455.分发饼干 思路一&#xff1a;贪心思路&#xff0c;大饼干先分给大胃口 思路二&#xff1a;小饼干先分给小胃口 376.摆动序列 分析摆动&#xff1a;记 presub 为前面与当前数之差&#xff0c;lastsub 为当前与后面数之差 思路&#xff1a; 1.正常摆动时&#xff0c;需要 …

Talk | 上海交通大学官同坤:识别任意文本,隐式注意力与字符间蒸馏在文本识别中的应用

本期为TechBeat人工智能社区第525期线上Talk&#xff01; 北京时间8月23日(周三)20:00&#xff0c;上海交通大学博士生—官同坤的Talk已准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “隐式注意力与字符间蒸馏在文本识别中的应用”&#xff0c;分享了识别…

基于Jenkins+Git+Ansible 发布PHP 项目-------从小白到大神之路之学习运维第88天

第四阶段提升 时 间&#xff1a;2023年8月25日 参加人&#xff1a;全班人员 内 容&#xff1a; 基于JenkinsGitAnsible 发布PHP 项目 目录 基于JenkinsGitAnsible 发布PHP 项目 一、部署PHP 运行环境 二、主机环境配置 三、Tomcat主机操作&#xff1a; 四、Jenkins主…

【PHP】echo 输出数组报Array to string conversion解决办法

代码&#xff1a; <?PHP echo "Hello World!";$demoName array("kexuexiong","xiong");echo "<pre>";var_dump($demoName);echo $demoName; print_r($demoName);echo "</pre>"; ?>输出结果&#xff1…

【点云分割】points3d框架学习01 —— 安装和配置

安装 $ pip install torch1.12.1cu113 torchvision0.13.1cu113 torchaudio0.12.1 --extra-index-url https://download.pytorch.org/whl/cu113 $ pip install torch-points3d $ pip install ipython $ pip install trame $ pip install h5py $ pip install gdown案例 from to…