当前位置: 首页 > news >正文

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

相关文章:

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

项目场景&#xff1a; 目前的产品经常播放m3u8流&#xff0c;有的视频声音正常&#xff0c;有的视频声音就偏低&#xff0c;即使放到最大音量声音也是比较小&#xff0c;所以就产生了某种需求&#xff0c;能否自动感知视频声音的大小&#xff0c;如果发现声音比较小的情况&…...

SSM框架和Spring Boot+Mybatis框架的性能比较?

SSM框架和Spring BootMybatis框架的性能比较&#xff0c;没有一个绝对的答案&#xff0c;因为它们的性能受到很多因素的影响&#xff0c;例如项目的规模、复杂度、需求、技术栈、团队水平、测试环境、测试方法等。因此&#xff0c;我们不能简单地说哪个框架的性能更好&#xff…...

6个月的测试,来面试居然要18K,我一问连8K都不值

2023年7月份我入职了深圳某家创业公司&#xff0c;刚入职还是很兴奋的&#xff0c;到公司一看我傻了&#xff0c;公司除了我一个自动化测试&#xff0c;公司的测试人员就只有2个开发3个前端1个测试还有2个UI&#xff0c;在粗略了解公司的业务后才发现是一个从零开始的项目&…...

优美而高效:解决服务器通信问题

题目背景 在这个问题中&#xff0c;我们面临着一幅服务器分布图。图中的每个单元格可能有服务器&#xff08;标记为1&#xff09;或者没有&#xff08;标记为0&#xff09;。我们的任务是找出能够与至少一台其他服务器进行通信的服务器数量。 算法思路 为了解决这个问题&…...

C++模板的模板参数(五)

1.模板的模板参数 在C中&#xff0c;模板的模板参数&#xff08;Template Template Parameters&#xff09;是一种特殊的模板参数&#xff0c;允许我们将另一个模板作为模板参数传递给一个模板。这种技术可以用于实现更灵活和通用的模板设计。 模板的模板参数使用两个 “temp…...

基于jeecg-boot的flowable流程加签功能实现

更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/nbcio-boot 前端代码&#xff1a;https://gitee.com/nbacheng/nbcio-vue.git 在线演示&#xff08;包括H5&#xff09; &#xff1a; http://122.227.135.243:9888 今天我…...

day-03 基于TCP的服务器端/客户端

一.理解TCP和UDP TCP&#xff08;Transmission Control Protocol&#xff09;和UDP&#xff08;User Datagram Protocol&#xff09;是两种常见的传输层协议&#xff0c;用于在计算机网络中提供可靠的数据传输。 1.TCP&#xff1a; 连接导向&#xff1a;TCP是一种面向连接的…...

匿名对象和一般对象的区别

1.格式的不同 一般对象的格式&#xff1a; ​ Object obj new Object(); ​ 匿名对象的格式&#xff1a; ​ new Object(); 2.作为参数传递机制的不同 2.1先看看一般对象的使用机制 执行步骤&#xff1a; 1.首先程序进入main()函数&#xff0c;执行Object obj&#xff0c;…...

[MyBatis系列⑥]注解开发

&#x1f343;作者简介&#xff1a;准大三本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐MyBatis系列①&#xff1a;增删改查 ⭐MyBatis系列②&#xff1a;两种Dao开发方式 ⭐MyBatis系列③&#xff1a;动态SQL ⭐MyBatis系列④&#xff1a;核心…...

[ACL2023] Exploring Lottery Prompts for Pre-trained Language Models

Exploring Lottery Prompts for Pre-trained Language Models 文章链接 清深的工作&#xff0c;比较有意思的一篇。作者先给出假设&#xff0c;对于分类问题&#xff0c;在有限的语料空间内总能找到一个prompt让这个问题分类正确&#xff0c;作者称之为lottery prompt。为此&…...

【Python编程】将同一种图片分类到同一文件夹下,并且将其分类的路径信息写成txt文件进行保存

注&#xff1a;数据结构同上一篇博文类似 一、代码 import os import cv2 import shutilpath0os.getcwd()\\apple\\RGB path1os.getcwd()\\apple\\tof_confidence # path2os.getcwd()\\apple\\tof_depth # path3os.getcwd()\\apple\\tof_depthRGB # path4os.getcwd()\\apple\…...

单例模式的相关知识

饿汉模式 package Thread; class Singleton{private static Singleton instance new Singleton();public static Singleton getInstance(){return instance;}private Singleton(){} }public class demo1 {public static void main(String[] args) {Singleton S1 Singleton.ge…...

vue问题相关记录

1. vue的 nextTick的原理 首先vue实现响应式并不是数据发生变化后dom立即更新&#xff0c;而是按照一定的策略 异步执行dom更新的。 vue在修改数据后&#xff0c;试图不会立即进行更新&#xff0c;而是要等同一事件循环机制内所有数据变化完成之后&#xff0c;在统一更新 next…...

skywalking服务部署

一、前言 Apache SkyWalking 是一个开源的分布式跟踪、监控和诊断系统&#xff0c;旨在帮助用户监控和诊断分布式应用程序、微服务架构和云原生应用的性能和健康状况。它提供了可视化的分析工具&#xff0c;帮助开发人员和运维团队深入了解应用程序的性能、调用链和异常情况 …...

【uni-app】压缩图片并添加水印

总体思路 dom 结点 这里的 cvHeight 和 cvWidth 初始时要设置为你后续需要压缩后的最大宽高。假设我们在图片上传后图片最大为 350 * 350 <u-upload :fileList"baseInfoFormData.entrustFileList" afterRead"afterFileRead" multiple></u-uploa…...

《每天十分钟》-红宝书第4版-变量、作用域与内存

最近有点忙&#xff0c;好长时间没抄经了&#xff0c;今天继续&#xff0c;之前语言基础相对简单&#xff0c;跳过一部分操作符。 变量 js 的变量是特殊的松散类型&#xff0c;由于没有规则定义变量必须包含什么数据类型&#xff0c;变量的值和数据类型在脚本生命期内可以改变…...

NFTScan | 08.21~08.27 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。周期&#xff1a;2023.08.21~ 2023.08.27 NFT Hot News 01/ NFT 品牌体验平台 Recur 将于 11 月 16 日彻底关闭&#xff0c;此前曾获 5000 万美元融资 8 月 21 日&#xff0c;NFT 品牌体验平台 Recur 在 X…...

【Java 中级】一文精通 Spring MVC - 数据验证(七)

&#x1f449;博主介绍&#xff1a; 博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家&#xff0c;WEB架构师&#xff0c;阿里云专家博主&#xff0c;华为云云享专家&#xff0c;51CTO 专家博主 ⛪️ 个人社区&#x…...

css奇数偶数选择器

前端项目开发中&#xff0c;需要根据行数的奇数和偶数的不同&#xff0c;设置不同的颜色显示&#xff0c;以在视觉上给用户以良好的浏览体验&#xff0c;这里就需要使用css奇数偶数选择器。 主要用的&#xff1a;:nth-of-type或者:nth-child。 方式一:nth-child div:nth-chi…...

【算法】双指针求解盛最多水的容器

Problem: 11. 盛最多水的容器 文章目录 题目解析算法原理讲解复杂度Code 题目解析 首先我们来解析一下本题 题目中说到&#xff0c;要找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 那我们现在来看最外侧的两根&#xff0c;一个高度为8&#…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

站群服务器的应用场景都有哪些?

站群服务器主要是为了多个网站的托管和管理所设计的&#xff0c;可以通过集中管理和高效资源的分配&#xff0c;来支持多个独立的网站同时运行&#xff0c;让每一个网站都可以分配到独立的IP地址&#xff0c;避免出现IP关联的风险&#xff0c;用户还可以通过控制面板进行管理功…...

Vue ③-生命周期 || 脚手架

生命周期 思考&#xff1a;什么时候可以发送初始化渲染请求&#xff1f;&#xff08;越早越好&#xff09; 什么时候可以开始操作dom&#xff1f;&#xff08;至少dom得渲染出来&#xff09; Vue生命周期&#xff1a; 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...