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

项目场景:

目前的产品经常播放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);//Lfwrite(sample, 1, 2, fp1);//Rfwrite(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");//字节序elseui->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");elseui->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);elsevalue = qFromBigEndian<quint16>(ptr);} else if (audioFormat.sampleSize() == 16 && audioFormat.sampleType() == QAudioFormat::SignedInt) {if (audioFormat.byteOrder() == QAudioFormat::LittleEndian)value = qAbs(qFromLittleEndian<qint16>(ptr));elsevalue = qAbs(qFromBigEndian<qint16>(ptr));} else if (audioFormat.sampleSize() == 32 && audioFormat.sampleType() == QAudioFormat::UnSignedInt) {if (audioFormat.byteOrder() == QAudioFormat::LittleEndian)value = qFromLittleEndian<quint32>(ptr);elsevalue = qFromBigEndian<quint32>(ptr);} else if (audioFormat.sampleSize() == 32 && audioFormat.sampleType() == QAudioFormat::SignedInt) {if (audioFormat.byteOrder() == QAudioFormat::LittleEndian)value = qAbs(qFromLittleEndian<qint32>(ptr));elsevalue = 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 ofdefault:break;}break;default:break;}qDebug() << __func__ << "m_maxAmplitude =" << m_maxAmplitude;
}

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

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

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

相关文章

C++头文件和std命名空间

C 是在C语言的基础上开发的&#xff0c;早期的 C 还不完善&#xff0c;不支持命名空间&#xff0c;没有自己的编译器&#xff0c;而是将 C 代码翻译成C代码&#xff0c;再通过C编译器完成编译。 这个时候的 C 仍然在使用C语言的库&#xff0c;stdio.h、stdlib.h、string.h 等头…

FFmpeg<第一篇>:环境配置

1、官网地址 http://ffmpeg.org/download.html2、linux下载ffmpeg 下载&#xff1a; wget https://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2解压&#xff1a; tar xvf ffmpeg-snapshot.tar.bz23、FFmpeg ./configure编译参数汇总 解压 ffmpeg-snapshot.tar.bz2 之后&…

【附安装包】Python-3.9.5安装教程

软件下载 软件&#xff1a;Python版本&#xff1a;3.9.5语言&#xff1a;英文大小&#xff1a;26.9M安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU2.5GHz 内存2G(或更高&#xff09;下载通道①百度网盘丨64位下载链接&#xff1a;https://pan.baidu.com/…

CSS概念

1、CSS与HTML结合方式 1.1 第一种方式 内联/行内样式 就是在我们的HTML标签上通过style属性来引用CSS代码。 优点:简单方便 &#xff1b; 缺点:只能对一个标签进行修饰。 1.2 第二种方式 内部样式 我们通过<style>标签来声明我们的CSS. 通常<style>标签我们推荐写在…

Spark最后一课

1.Spark的提交过程(YarnCluster) 1.命令输入脚本启动,启动submit任务 2.解析参数 看是cluster还是yarn单点模式 3.创建客户端YarnClusterApplication 4.封装提交命令交给RM 5.RM在NM上启动ApplicationMaster(AM) 注意AM消耗的资源都是container的 6.AM根据参数启动Driver并且…

大数据Flink(六十七):SQL Table 简介及运行环境

文章目录 SQL & Table 简介及运行环境 一、​​​​​​​​​​​​​​简介 二、案例

es的索引管理

概念 &#xff08;1&#xff09;集群&#xff08;Cluster&#xff09;&#xff1a; ES可以作为一个独立的单个搜索服务器。不过&#xff0c;为了处理大型数据集&#xff0c;实现容错和高可用性&#xff0c;ES可以运行在许多互相合作的服务器上。这些服务器的集合称为集群。 &…

【电能质量扰动】基于ML和DWT的电能质量扰动分类方法研究(Matlab实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

c语言调用mciSendString播放音乐

如下所示&#xff0c;这是一个使用c语言调用系统方法mciSendString()&#xff0c;让系统播放音乐的示例&#xff1a; baihuaxiang 代码&#xff1a; #include <graphics.h> #include <Windows.h> #include <mmsystem.h>#pragma comment(lib,"WINMM.LIB…

virtuoso61x中集成calibre

以virtuoso618为例&#xff0c;在搭建完电路、完成前仿工作之后绘制版图&#xff0c;版图绘制完成之后需要进行drc和lvs【仅对于学校内部通常的模拟后端流程而言】&#xff0c;一般采用mentor的calibre来完成drc和lvs。 服务器上安装有virtuoso和calibre&#xff0c;但是打开la…

Mybatis与Spring集成配置

目录 具体操作 1.1.添加依赖 1.2创建spring的配置文件 1.3. 注解式开发 Aop整合pagehelper插件 1. 创建一个AOP切面 2. Around("execution(* *..*xxx.*xxx(..))") 表达式解析 前言&#xff1a; 上篇我们讲解了关于Mybatis的分页&#xff0c;今天我们讲Mybatis与…

Android Studio开发之路 (五)导入OpenCV以及报错解决

一、步骤 官网下载opencv包&#xff08;我下的是4.7.0&#xff09;并解压&#xff0c;openvc官网 先创建一个空项目&#xff0c;简单跑一下能正常输出helloworld 点击file->new->Import Module选择解压之后的opencv-android-sdk文件夹中的SDk文件夹&#xff0c; modu…

政府网站定期巡检:构建高效、安全与透明的数字政务

在数字时代&#xff0c;政府网站已不仅仅是一个信息发布窗口&#xff0c;更是政府与公众互动的桥梁、政务服务的主要渠道以及数字化治理的重要平台。因此&#xff0c;确保政府网站的高效运行、信息安全与透明公开就显得尤为重要。在此背景下&#xff0c;定期的网站巡检与巡查成…

中文乱码处理

&#x1f600;前言 中文乱码处理 &#x1f3e0;个人主页&#xff1a;尘觉主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是尘觉&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满意是我的动力&#x1f609;&#x1f609; 在csdn获奖荣誉: &#x1f3c…

nmon的安装与使用

一、Linux服务器配置信息 操作系统&#xff1a;CentOS 7.6 64位&#xff08;可用命令&#xff1a;cat /etc/redhat-release和uname -a查看&#xff09; CPU&#xff1a;1核&#xff08;可用命令top查看&#xff09; 内存&#xff1a;2GB&#xff08;可用命令free查看&#xff…

picGo+gitee+typora设置图床

picGogiteetypora设置图床 picGogitee设置图床下载picGo软件安装picGo软件gitee操作在gitee中创建仓库在gitee中配置私人令牌 配置picGo在插件设置中搜索gitee插件并进行下载 TyporapicGo设置Typora 下载Typora进行图像设置 picGogitee设置图床 当我了解picGogitee可以设置图床…

开源与云计算:新的合作模式

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

【附安装包】EViews 13.0安装教程|计量经济学|数据处理|建模分析

软件下载 软件&#xff1a;EViews版本&#xff1a;13.0语言&#xff1a;英文大小&#xff1a;369.46M安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU2.0GHz 内存4G(或更高&#xff09;下载通道①百度网盘丨64位下载链接&#xff1a;https://pan.baidu.com…

Kafka 简介 + 学习笔记

消息队列 先说明消息队列是什么&#xff1a; 亚马逊&#xff1a; 消息队列是一种异步的服务间通信方式&#xff0c;适用于微服务架构。消息在被处理和删除之前一直存储在队列上。每条消息仅可被一位用户处理一次。消息队列可被用于分离重量级处理、缓冲或批处理工作以及缓解高…

edge浏览器进行qq截图过保爆决过程

edge浏览器进行qq截图过保解决过程 参考&#xff1a;电脑截屏曝光特别高怎么解决&#xff1f; - 知乎 问题展示 饱和度过高&#xff0c;刺眼 1. 在chrome地址栏输入chrome://flags/ 2. 在页面的搜索栏搜索force color profile 3. 在选项中选择所对应的颜色管理。&#xff08…