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

这篇具有很好参考价值的文章主要介绍了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,不太清楚具体缘由。
qt 声级计,《Qt 项目实战经历全记录》,qt,音视频,分贝

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

可以看到目前的参数
qt 声级计,《Qt 项目实战经历全记录》,qt,音视频,分贝

设置绝对音量,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. 分贝计算(参考网上资源)

公式:

qt 声级计,《Qt 项目实战经历全记录》,qt,音视频,分贝

参数: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、效果图

qt 声级计,《Qt 项目实战经历全记录》,qt,音视频,分贝
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文章来源地址https://www.toymoban.com/news/detail-787124.html

到了这里,关于qt 实现音视频的分贝检测系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Qt音视频开发45-音视频类结构体参数的设计

    视频监控内核组件重构和完善花了一年多时间,整个组件个人认为设计的最好的部分就是各种结构体参数的设计,而且分门别类,有枚举值,也有窗体相关的结构体参数,解码相关的结构体参数,同时将部分常用的结构体参数的获取和设置单独提供了函数,参阅海康大华等大

    2024年02月05日
    浏览(56)
  • Qt之基于QMediaPlayer的音视频播放器(支持常见音视频格式)

    Qt自带了一个Media Player的例子,如下图所示: 但是运行这个例子机会发现,连最基本的MP4格式视频都播放不了。因为QMediaPlayer是个壳(也可以叫框架),依赖本地解码器,视频这块默认基本上就播放个MP4,甚至连MP4都不能播放,如果要支持其他格式需要下载k-lite或者LAVFilter

    2024年02月02日
    浏览(48)
  • Qt/C++音视频开发46-音视频同步保存到MP4

    用ffmpeg单独做视频保存不难,单独做音频保存也不难,难的是音视频同步保存到MP4中,重点是音视频要同步,其实这也不难,只要播放那边音视频同步后的数据,写入到文件即可。最难的是在播放过程中不断随机的切换播放进度,而且还会暂停播放、暂停录制的情况出现,这

    2024年02月17日
    浏览(51)
  • 使用Qt进行音视频播放

      Qt对音视频的播放和控制,相机拍照,收音机等多媒体应用提供了强大的支持。Qt5使用了全新的Qt Multimedia模块来实现多媒体应用,而原来Qt4中用于实现多媒体功能的Phonon模块已经被移除。   新的Qt Multimedia模块提供了丰富的接口,使读者可以轻松地使用平台的多媒体功

    2024年02月03日
    浏览(30)
  • 音视频--DTMF信号发送及检测

    https://zh.wikipedia.org/wiki/%E5%8F%8C%E9%9F%B3%E5%A4%9A%E9%A2%91 https://www.cnblogs.com/lijingcheng/p/4454932.html 双音多频信号(英语:Dual-Tone Multi-Frequency,简称:DTMF),电话系统中电话机与交换机之间的一种信令,最常用于拨号时发送被叫号码。不过双音多频的发明,除了缩短拨号时间,也扩展

    2024年02月12日
    浏览(29)
  • Qt音视频开发38-ffmpeg视频暂停录制的设计

    基本上各种播放器提供的录制视频接口,都是只有开始录制和结束录制两个,当然一般用的最多的也是这两个接口,但是实际使用过程中,还有一种可能需要中途暂停录制,暂停以后再次继续录制,将中间部分视频不需要录制,跳过这部分不需要的视频,而且录制的视频文件

    2023年04月20日
    浏览(47)
  • Qt/C++音视频开发24-视频显示QOpenGLWidget方式(占用GPU)

    采用painter的方式绘制解码后的图片,方式简单易懂,巨大缺点就是占CPU,一个两个通道还好,基本上CPU很低,但是到了16个64个通道的时候,会发现CPU也是很吃紧(当然强劲的电脑配置另当别论),这就需要考虑用opengl来绘制了,采用opengl走的GPU,会占用很少的CPU(一般是部

    2024年02月11日
    浏览(37)
  • 浏览器网页内嵌Qt-C++音视频播放器的实现,支持软硬解码,支持音频,支持录像截图,支持多路播放等,提供源码工程下载

        在浏览器中实现播放RTSP实时视频流,⼤体上有如下⼏个⽅案: ⽅案一:浏览器插件⽅案 ActiveX、NPAPI、PPAPI     ActiveX插件适用于IE浏览器,NPAPI与PPAPI插件适用于谷歌浏览器,不过这些插件都已经不被浏览器所支持。 ⽅案二:先转码再转流⽅案     ⼯作原理是架设一

    2024年01月17日
    浏览(66)
  • 音视频开发系列(10):基于qt的音频推流

    今天分享一下利用qt录制音频,然后再利用ffmpeg推流到nginx服务器,最后再利用vlc进行拉流的demo。 首先介绍一下如何利用qt来进行音频的录制,qt的音频录制主要利用qt的QAudioFormat先进行音频信息的配置。主要需要配置以下的信息: 然后使用QAudioDeviceInfo来获取是否支持改设置

    2024年02月02日
    浏览(43)
  • Qt音视频开发40-ffmpeg采集桌面并录制

    之前用ffmpeg打通了各种视频文件和视频流以及本地摄像头设备的采集,近期有个客户需求要求将整个桌面屏幕采集下来,并可以录制保存成MP4文件,以前也遇到过类似的需求,由于没有搞过,也没有精力去摸索和测试,所以也就一直耽搁着,近期刚好这个需求又来了,定下心

    2023年04月25日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包