音频Balance源码总结
音频Balance源码总结
何为音频Balance?
顾名思义,Balance及平衡,平衡也就是涉及多方,音频左右甚至四通道,调节所有通道的音量比,使用户在空间内听到各个通道的音频大小不一,好似置身于真实环境中;
博主分析的Balance源码在
./system/media/audio_utils/include/audio_utils/Balance.h
./system/media/audio_utils/Balance.cpp
Balance原理
如下图,提供给用户一个设置进度条:
用户设置balance在-0.5,那就给channel left的音量为1,channel right的音量x(0~1,通过一定算法计算得到),最后我们遍历buffer中的每一个音频数据,左侧通道乘1就会保持不变,右侧乘小于1的数据,音量就会减小,这样就达成了左右channel输出的音频不同了
如上原理,很简单吧!但是我们要考虑几个问题
- 音频数据的格式类别(双通道、2.1通道的数据格式)
- 各个通道的音频分量如何确定?
辨别音频数据格式类别
这个也是Balance类第一步要做的事情,对应代码中,在执行Balance之间必须要先调用
void setChannelMask(audio_channel_mask_t channelMask);
插播一个知识点,在ChannelMask中,表示音频数据存储方式有以下2种:
- AUDIO_CHANNEL_REPRESENTATION_POSITION
位置表示法,音频数据中每个bit位表示每个位置的音频数据,如前左 前右等音响 - AUDIO_CHANNEL_REPRESENTATION_INDEX
序号法表示,每个bit位表示不同的通道,如0位是主通道,1位是辅助通道等
用audio_channel_mask_get_representation(channelMask)函数可以得到mask是用哪种方法表示;
接着上setChannelMask看,主要根据channelMask不同表示方法进行不同的处理:
深入代码,看看Balance如何区分每个channel的情况:
AUDIO_CHANNEL_REPRESENTATION_INDEX处理方式
void Balance::setChannelMask(audio_channel_mask_t channelMask)
{//去掉haptic震动反馈通道,这个不属于balance范畴channelMask &= ~ AUDIO_CHANNEL_HAPTIC_ALL;//如果部署输出类型的mask,或者与之前的mask相同,就没必要再次设置if (!audio_is_output_channel(channelMask) // invalid mask|| mChannelMask == channelMask) { // no need to do anythingreturn;}mChannelMask = channelMask;mChannelCount = audio_channel_count_from_out_mask(channelMask);// save mBalance into balance for later restoring, then resetconst float balance = mBalance;mBalance = 0.f;// reset mVolumes将vector数组重置大小为mChannelCountmVolumes.resize(mChannelCount);//填充mVolumes所有值为1.fstd::fill(mVolumes.begin(), mVolumes.end(), 1.f);// reset ramping variablesmRampBalance = 0.f;mRampVolumes.clear();//如果mChannelMask是按照序号法来表示,如index 0表示主通道、1副通道,//则不是按照声场位置表示法AUDIO_CHANNEL_REPRESENTATION_POSITIONif (audio_channel_mask_get_representation(mChannelMask)== AUDIO_CHANNEL_REPRESENTATION_INDEX) {mSides.clear(); // mSides unused for channel index masks.setBalance(balance); // recompute balancereturn;}
}
以上函数主要做了以下几件事情:
- 从ChannelMask中获取通道数,并根据通道数resize重置mVolumes(vector类型)大小为channelCount,因为balance就是根据通道来进行加减乘除的
- 如果channelMask是AUDIO_CHANNEL_REPRESENTATION_INDEX格式,也就是index方式来表示通道数据,无需用mSide位置来表示
- setBalance为每个声道设置对应的音量,后面展开
AUDIO_CHANNEL_REPRESENTATION_POSITION处理方式
以下这段代码也是在setChannelMask方法中的,是当channelMask为AUDIO_CHANNEL_REPRESENTATION_POSITION的处理方式
//sideFromChannel也就是把每一个channel映射到声场的位置,0-left,1-right,2-center//比如以下为9x1u的channel对应左边的声场位置,其他依次类推static constexpr int sideFromChannel[] = {0, // AUDIO_CHANNEL_OUT_FRONT_LEFT = 0x1u,1, // AUDIO_CHANNEL_OUT_FRONT_RIGHT = 0x2u,2, // AUDIO_CHANNEL_OUT_FRONT_CENTER = 0x4u,2, // AUDIO_CHANNEL_OUT_LOW_FREQUENCY = 0x8u, //低频分量的数据,专门传输到低音炮的外放装置中0, // AUDIO_CHANNEL_OUT_BACK_LEFT = 0x10u,1, // AUDIO_CHANNEL_OUT_BACK_RIGHT = 0x20u,0, // AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x40u,1, // AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80u,2, // AUDIO_CHANNEL_OUT_BACK_CENTER = 0x100u,0, // AUDIO_CHANNEL_OUT_SIDE_LEFT = 0x200u,1, // AUDIO_CHANNEL_OUT_SIDE_RIGHT = 0x400u,2, // AUDIO_CHANNEL_OUT_TOP_CENTER = 0x800u,0, // AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT = 0x1000u,2, // AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER = 0x2000u,1, // AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT = 0x4000u,0, // AUDIO_CHANNEL_OUT_TOP_BACK_LEFT = 0x8000u,2, // AUDIO_CHANNEL_OUT_TOP_BACK_CENTER = 0x10000u,1, // AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT = 0x20000u,0, // AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT = 0x40000u,1, // AUDIO_CHANNEL_OUT_TOP_SIDE_RIGHT = 0x80000u,};mSides.resize(mChannelCount);for (unsigned i = 0, channel = channelMask; channel != 0; ++i) {//计算channel从低位开始第一个1的位置const int index = __builtin_ctz(channel);if (index < std::size(sideFromChannel)) {mSides[i] = sideFromChannel[index];} else {mSides[i] = 2; // consider center}channel &= ~(1 << index);}setBalance(balance); // recompute balance
可以看到android定义的channel的位置有很多的,如AUDIO_CHANNEL_OUT_FRONT_LEFT、AUDIO_CHANNEL_OUT_BACK_RIGHT、AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT等等,但是这么多的位置的喇叭,对应到sideFromChannel里面去的取值,只有0、1、2,也就是left、right和center;相当于把上面20个位置转换到3个位置上去,多维转换到一维了;(显然这么做是不合理的,Android这么做后期肯定是可以优化的)
最后面的for循环,就是记录当前的channelMask的几个channel对应那几个位置side,mSide数组就是保存了channel的位置side,用以下一幅图表示就是:
确定每个通道的音频音量
接上面代码,当确定好每个channel的位置后,接下来就是为每个channel计算他的音量了,也就是setBalance
//balance参数取值范围在[-1,1],这个函数的意义在于为每个channel计算它的音量值
void Balance::setBalance(float balance)
{//如何这次设置的balance和上次相同if (mBalance == balance // no change//balance值非法,或者绝对值大于1,就认为非法|| isnan(balance) || fabs(balance) > 1.f) { // balance out of rangereturn;} mBalance = balance;//通道数小于2个也没必要if (mChannelCount < 2) { return; } //如果音频是双通道或者是用INDEX表示音频数据格式if (mChannelMask == AUDIO_CHANNEL_OUT_STEREO|| audio_channel_mask_get_representation(mChannelMask)== AUDIO_CHANNEL_REPRESENTATION_INDEX) {//那就只需要计算左右两个通道的音量值,mVolumes[0]保存左声道的音量,mVolumes[1]保存右声道的音量computeStereoBalance(balance, &mVolumes[0], &mVolumes[1]);return;} //side位置表示法格式的音频,则需要计算3个位置的音量//计算好当前声道平衡balance对应在left、right和center的音量值float balanceVolumes[3]; // left, right, center//同上这里只计算左右left\right声道的音量值,中间的取音频本身的音量值computeStereoBalance(balance, &balanceVolumes[0], &balanceVolumes[1]);balanceVolumes[2] = 1.f; // center TODO: consider center scaling.for (size_t i = 0; i < mVolumes.size(); ++i) {//mSides表示当前channel的声场位置,mSides[i]可能是0、1、2取值,分别代表//left位置、right位置和中间位置,在根据上面计算这3个位置对应的音量balanceVolumes//就可以得到每个side的音量增益了mVolumes[i] = balanceVolumes[mSides[i]];}
}
上面代码很简单,主要是初始化好每个channel的音量数据,将参数传入到computeStereoBalance函数进行计算得到,看看是如何计算每个声道的音量:
/*
* 当blance值在[-1,0],表示左声道最大音量1.f,右声道为1.f与balance差值
* 当blance值在[0,1],表示有声道要比作声道大,同理
*/
void Balance::computeStereoBalance(float balance, float *left, float *right) const
{ //balance大于0,说明用户是要设置声道平衡在右侧if (balance > 0.f) {//1-balance肯定是一个小于1的数字,mCurve是一个函数,可以把[0~1]范围的值映射到[0~1]*left = mCurve(1.f - balance);//右边音量取1.f,保持音频本身的音量*right = 1.f;//小于0,道理同上} else if (balance < 0.f) {*left = 1.f;*right = mCurve(1.f + balance);//等于0的话,说明平衡点在中间,左右都保持原本的音量输出} else {*left = 1.f;*right = 1.f;}
}
上面代码也很简单,可以看我写的注释,主要根据用户设置的balance进行设置:
- 小于0,说明平衡点在左侧,左边声道肯定取值1,保持音频本身音量输出,而右声道通过1-balance在用mCurve归一化映射函数映射到小于1的值
- 大于0,说明平衡点在右侧,方法同上
这里我认为mCurve(1.f-balance)这个算法不是唯一的,我们可以根据实际测试结果进行修改,比如按照上面的配置设置后,在balance小于0的情况下,right声道完全听不到声音,可以加一个基础音量等,这个改进方法就仁者见仁智者见智了
最后,把我们的计算好的音量保存到mVolumes成员中取即可,它是一个vector类型,每个的index代表这个通道/位置的音量值,一幅图总结如下:
附加知识点—mCurve函数如何确定的
在Balance头文件中有定义,如下:
explicit Balance(bool ramp = true, //是否用于渐变音量std::function<float(float)> curve = [](float x) { return x * (x + 0.2f); }) //音量映射函数: mRamp(ramp), mCurve(normalize(std::move(curve))) { }
以下是 f ( x ) = x ( x + 0.2 f ) f(x)=x(x+0.2f) f(x)=x(x+0.2f)的函数图像:
为什么要选取这种函数呢?为啥不默认一个 y = x y=x y=x这线性函数,估计可能是音量本身不是线性的,音量增加和测量结果分贝是一种非线性关系,更像对数函数的图像,这里 f ( x ) = x ( x + 0.2 ) 这种抛物线曲线也类似的,所以就选取这种函数了 f(x)=x(x+0.2)这种抛物线曲线也类似的,所以就选取这种函数了 f(x)=x(x+0.2)这种抛物线曲线也类似的,所以就选取这种函数了
其中,normalize函数如下:
template<typename T>static std::function<T(T)> normalize(std::function<T(T)> f) {const T f0 = f(0);//T(1)相当于使用构造函数,构造了一个对象T,其值为1const T r = T(1) / (f(1) - f0); //计算得到f(1)-f(0)差值与1的大小比if (f0 != T(0) || // must be exactly 0 at 0, since we promise g(0) == 0//numeric_limits是c++中表达个类型数的极值库,这里表示T类型,epsilon计算机可表达//的T类型最小正实数,比如T为float类型,则比较两个float是否相等,可以让二者相剪//差值小于等于epsilon即可;//进入第二个条件,也就是f0 = 0,r与1之间的差值绝对值,如果小于后者,说明函数f从入参[0,1]//可以正常映射到[0,1],如果大于,则f函数的映射范围则有可能大于1,或者小于1,没有无限接近于1fabs(r - T(1)) > std::numeric_limits<T>::epsilon() * 3) { // some fudge allowed on r.//r*(fx-f0)=T(1)*(fx - f0)/(f1-f0),也就是x在[0,1]范围内比例大小,乘1,最终结果肯定在//[0,1]之间return [f, f0, r](T x) { return r * (f(x) - f0); };}// no translation required.return f;}
假设normalize函数命令为g(x),它能保证我们最终的映射函数2个点:
- g(0)一定等于0
- g(x<1)的情况一定也是小于1的
最终, g ( x ) g(x) g(x)的值在0~1范围内;
算法如上代码: - r可以理解为f函数在[0,1]两个点上的y值对比,假设 f ( x ) = x f(x)=x f(x)=x,那这个函数刚好满足 f ( 0 ) = 0 f(0)=0 f(0)=0且最终映射的值在y轴上也是在(0,1)
- 所以在if条件中
fabs(r - T(1)) > std::numeric_limits<T>::epsilon() * 3)
r − T ( 1 ) r-T(1) r−T(1)的绝对值就是在判断这个f函数与 y = x y=x y=x线性函数相差多大,epsilon()函数理解为T类型值得最小正整数,乘3是为了放款比较范围;这里相减后的绝对值
- 小于的话,就理解为r和T(1)理论相等,f函数无限逼近类似于 y = x y=x y=x的函数图像,
- 大于的话,就理解r和T(1)不相等,f函数远离逼近类似于 y = x y=x y=x的函数图像,可能最终映射的y值比1大,或者比1小的太多,映射不合理
理解如下这副图像:
举例y=x线性函数可能不合理,上图中y=0.5x和y=2x和代码中epsilon()也是不相符的,只是为了能清楚解释这段代码的原理,随机选择的函数
最后这行代码r * (f(x) - f0)带入r的表达式,也就是 T ( 1 ) ∗ ( f ( x ) − f ( 0 ) ) f ( 1 ) − f ( 0 ) \frac{T(1)*(f(x)-f(0))}{f(1)-f(0)} f(1)−f(0)T(1)∗(f(x)−f(0))等比例映射值而已,最终结果肯定小于1的
音量应用到音频数据中去
音量分配到音频数据中就更简单了,遍历每个音频数据,遍历每个channel,依次乘以每个channel的音量即可
void Balance::process(float *buffer, size_t frames)
{........if (mRamp) {if (mRampVolumes.size() != mVolumes.size()) {// If mRampVolumes is empty, we do not ramp in this process() but directly// apply the existing mVolumes. We save the balance and volume state here// and fall through to non-ramping code below. The next process() will ramp if needed.mRampBalance = mBalance;mRampVolumes = mVolumes;} else if (mRampBalance != mBalance) {if (frames > 0) {std::vector<float> mDeltas(mVolumes.size());//这里为啥要用1.f来作除法,估计是转换成float型,方便后续的计算const float r = 1.f / frames;for (size_t j = 0; j < mChannelCount; ++j) {//通道j的开始音量mRampVolumes[j],最终音量mVolumes[j],乘r也就是除frames;//最后mDeltas[j]就是通道j每一帧的音频增量mDeltas[j] = (mVolumes[j] - mRampVolumes[j]) * r;}// ramped balancefor (size_t i = 0; i < frames; ++i) {const float findex = i;for (size_t j = 0; j < mChannelCount; ++j) { // better precision: delta * i//等号后面的和在乘*buffer,开始音量+音频帧数index乘增量*buffer++ *= mRampVolumes[j] + mDeltas[j] * findex;}}}mRampBalance = mBalance;mRampVolumes = mVolumes;return;}}for (size_t i = 0; i < frames; ++i) {//遍历每个通道,依次乘通道音量平衡值for (size_t j = 0; j < mChannelCount; ++j) {*buffer++ *= mVolumes[j];}}
}
上面代码很简单,主要就是ramp时渐变音量不好理解,只需要弄清ramp中的几个参数:
mRampVolumes[]:数组保存每个channel的开始音量值
mVolumes[]:数组保存每个channel最终的音量值
mDeltas[]:保存了起始音量到最终音量差值,除以音频帧数frames的值
最后*buffer++ *= mRampVolumes[j] + mDeltas[j] * findex就好理解了
扩展阅读
这里的balance把多个位置的平衡都归一化到一维上去了,那么如何改变如何扩展呢?
最常见的一个例子就是车上,四门四喇叭,设置平衡点在左前方,那么用户肯定喜欢左前方喇叭声音保持输出,其余三个喇叭适当降音;但是按照以上代码,会将后排两个喇叭都归一化到左右两个喇叭上,最终左侧喇叭声音音量一样大,右边两个喇叭一样小
思考几分钟,如何扩展呢???
扩展办法
以下是我理解的方案:
- 在setChannelMask的时候sideFromChannel数组的取值增加几个取值:0-front_left、1-front_right、2-back_left、3-back_right和4-center;
这样处理后,就可以把声道channel转换为前后左右、中间几个位置了 - 在setBalance时,传入进来的参数就要发生变化:
setBalance(float balance)
转变为:
setBalance(float balanceX, float balanceY)
- compute计算每个位置音量可以按照xy坐标组成的象限来区分:
if(balanceXb、alanceX在第一象限)volume[front_left] = 1.fvolume[front_right] = mCurve(1.f+balanceX)volume[back_left] = mCurve(1.f-volumeY)volume[back_right] = mCurve(1.f-volumeY)
back_right可能还需要在调整调整,这里只是阐明一个方案而已
ok,以上就是对Balance的理解!如有不正确的地方,可以在评论区指出
顺便说个事,有个盆友要找音频开发类的工作,有合适的可以推荐以下,地点成都;
相关文章:

音频Balance源码总结
音频Balance源码总结 何为音频Balance? 顾名思义,Balance及平衡,平衡也就是涉及多方,音频左右甚至四通道,调节所有通道的音量比,使用户在空间内听到各个通道的音频大小不一,好似置身于真实环境…...
CesiumJS【Basic】- #043 绘制脉冲线(Entity方式)- 需要自定义着色器
文章目录 绘制脉冲线(Entity方式)- 需要自定义着色器1 目标2 代码2.1 main.ts3 资源文件绘制脉冲线(Entity方式)- 需要自定义着色器 1 目标 使用Entity方式绘制脉冲线 2 代码 2.1 main.ts import * as Cesium from cesium;const viewer = new Cesium.Viewer(cesiumCont…...

Linux命令 wc(word count)-l(lines)用于统计文件中的行数。
文章目录 1、wc -l2、实战3、wc --help 1、wc -l 在命令 wc -l 中,-l 的英文全称是 lines。这个选项用于指定 wc(word count,单词计数)命令来统计文件的行数。 例如,当你运行 wc -l load_user_100w_sort.sql 时&…...

数据结构 - C/C++ - 链表
目录 结构特性 内存布局 结构样式 结构拓展 单链表 结构定义 节点关联 插入节点 删除节点 常见操作 双链表 环链表 结构容器 结构设计 结构特性 线性结构的存储方式 顺序存储 - 数组 链式存储 - 链表 线性结构的链式存储是通过任意的存储单元来存储线性…...

sheng的学习笔记-AI-高斯混合模型(GMM)
AI目录:sheng的学习笔记-AI目录-CSDN博客 需要学习前置知识: 聚类,可参考 sheng的学习笔记-AI-聚类(Clustering)-CSDN博客 EM算法,可参考 sheng的学习笔记-AI-EM算法-CSDN博客 贝叶斯,可参考 sheng的学习笔记-AI-…...

OFDM的缺点与关键技术
子载波间干扰英文简写ICI,ICI可能由各种原因引起 在多径信道中,CP小于最大附加时延时收发系统载波频率偏差和采样偏差收发系统相对移动,存在多普勒频移 ICI是制约OFDM系统性能的主要重要因素之一 对频率偏差敏感----->同步技术࿰…...

电脑录音软件哪个好?7款录制音频工具大盘点,赶快学起来!(2024)
也许你渴望提取你最喜欢的节目的背景音乐,或者你希望录制自己的声音制作教程。如果是这样,你就需要一款优秀的电脑录音软件,来帮助你捕捉任何你想要的声音,而且不会损失音质。目前市场上存在着大量的录制音频工具,面对…...
【Android面试八股文】你说你使用Leakcanary进行内存泄漏检测,那你能说一说Leakcanary的原理吗?
文章目录 一、 Java四大引用二、 LeakCanary示例工作机制注意事项三、 Leakcanary的原理四、 Leakcanary的源码分析LeakCanary#Install创建RefWatcherAndroidRefWatcherBuilder#buildAndInstall监听Activity的引用 : ActivityRefWatcher检查引用Dump Heap解析hprof定位泄露的引…...

蒂升电梯职业性格和Verify认知能力SHL测评答题攻略及薪资待遇解密!
一、蒂升电梯职业性格和认知能力测评考什么 您好!蒂升电梯公司邀请您参加的OPQ职业性格测评和Verify认知能力测评是两种常见的评估工具,用于帮助了解个人的职场性格特点和认知能力。 OPQ职业性格测评 这是一种性格测试,通常用于评估个人在…...

window上部署sql server改动端口、和sqlserver的一些还原、批量插入存储过程的命令
1.端口的查看和启动 --windows上安装上sql server数据库后,搜索界面搜索sql,会出现配置管理器,点击进入 --进入后再次选择配置管理器 2. sqlserver数据库还原图形化 sqlserver还原数据库时会使数据库进入一个restore的还原状态,…...

【单片机与嵌入式】stm32串口通信入门
一、串口通信/协议 (一)串口通信简介 串口通信是一种通过串行传输方式在电子设备之间进行数据交换的通信方式。它通常涉及两条线(一条用于发送数据,一条用于接收数据),适用于各种设备,从微控制…...
启动Redis服务器
名人说:一点浩然气,千里快哉风。 ——苏轼 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、在 Linux 或 macOS 上启动 Redis二、在 Windows 上启动 Redis三、配置 Redis 为服务启动&…...
uniapp中使用threejs加载几何体
我的建议是使用这个库 https://github.com/deepkolos/three-platformize 为什么?我试了uniapp推荐的和threejs-miniprogram这个小程序官方库,都加载不出来我的obj模型。所有我推荐不要用obj模型最好,挺多都支持GLTF模型的,但是我不…...
【SQL注入】 数据库基础
MySQL中的库名 information_schema(信息库)—— 保存其他数据库里所有信息(数据库名、表、字段的数据类型/访问权限) mysql—— 存储用户名 密码 host performance_schema——内存数据库 数据放在内存中直接操作的数据库 sys—…...

文件操作~
目录 1.为什么使用文件? 2.什么是文件? 2.1 程序文件 2.2 数据文件 2.3 文件名 3.⼆进制文件和文本文件? 4.文件的打开和关闭 4.1 流和标准流 4.1.1 流 4.1.2 标准流 4.2 文件指针 4.3 ⽂件的打开和关闭 5.文件的顺序读写 5.1 …...

身边的故事(十二):阿文的故事:消失
那以后就再也没有任何阿文的消息。刚开始还打过几次电话,他都没接。后来也就慢慢的淡忘了,为自己的工作生活而奔波,时间冲淡一切。在那几年里,阿文就像消失了一样。直到2021的某一天,电话那端传来了熟悉但是有点陌生的…...
智能扫地机器人程序中出现的问题可以参考的解决方案
在解决智能扫地机器人程序中可能遇到的问题时,可以参考以下分点表示和归纳的解决方案: 环境感知与地图构建 ① 使用先进的传感器技术:如激光雷达、超声波和红外传感器,以提高环境感知的准确性和可靠性。 ② 优化地图构建算法&a…...

如何借用物联网快速实现高标准农田信息化
如何借用物联网快速实现高标准农田信息化 高标准农田信息化,作为现代农业发展的重要基石,是指在建设高产、稳产、节水、环保的农田基础上,深度融合现代信息技术,实现农田管理的精准化、智能化和高效化。物联网(Intern…...
计算机网络基础入门
计算机网络基础入门 目录: 简介网络分层模型数据封装与解封装IP地址与子网掩码网络协议示例代码 1. 简介 计算机网络是指将地理位置不同的多台计算机及外部设备通过通信线路连接起来,实现信息资源共享和信息传递的系统。计算机网络是现代信息社会的基…...
uniApp vue2 vue3配置代理
vue2配置代理 在manifest.json中增加如下配置 "h5" : {"router" : {"mode" : "history"},"devServer" : {"disableHostCheck" : true,"proxy" : {"/api" : {"target" : "请…...
从golang的sync.pool到linux的slab分配器
最近学习golang的时候,看到golang并发编程中有一个sync.pool,即对象池,猛地一看这不跟linux的slab分配器类似嘛,赶紧学习记录下 这里先总结下设计sync.pool和slab的目的 sync.pool 为了缓解特定类型的对象频繁创建和销毁&#x…...

作为过来人,浅谈一下高考、考研、读博
写在前面 由于本人正在读博,标题中的三个阶段都经历过或正在经历,本意是闲聊,也算是给将要经历的读者们做个参考、排雷。本文写于2022年,时效性略有落后,不过逻辑上还是值得大家参考,若所述存在偏颇&#…...

第2章:Neo4j安装与配置
在了解了Neo4j的基本概念和优势之后,下一步就是将其安装并配置好,以便开始实际操作。本章将详细介绍Neo4j的各种部署方式,涵盖不同操作系统的安装步骤,深入探讨关键配置项,并介绍常用的管理工具,为读者顺利…...

C# Wkhtmltopdf HTML转PDF碰到的问题
最近碰到一个Html转PDF的需求,看了一下基本上都是需要依赖Wkhtmltopdf,需要在Windows或者linux安装这个可以后使用。找了一下选择了HtmlToPDFCore,这个库是对Wkhtmltopdf.NetCore简单二次封装,这个库的好处就是通过NuGet安装HtmlT…...

Edge(Bing)自动领积分脚本部署——基于python和Selenium(附源码)
微软的 Microsoft Rewards 计划可以通过 Bing 搜索赚取积分,积分可以兑换礼品卡、游戏等。每天的搜索任务不多,我们可以用脚本自动完成,提高效率,解放双手。 本文将手把手教你如何部署一个自动刷积分脚本,并解释其背…...
html表格转换为markdown
文章目录 工具功能亮点1.核心实现解析1. 剪贴板交互2. HTML检测与提取3. 转换规则设计 2. 完整代码 在日常工作中,我们经常遇到需要将网页表格快速转换为Markdown格式的场景。无论是文档编写、知识整理还是数据迁移,手动转换既耗时又容易出错。本文将介绍…...
【PmHub面试篇】性能监控与分布式追踪利器Skywalking面试专题分析
你好,欢迎来到本次关于PmHub整合性能监控与分布式追踪利器Skywalking的面试系列分享。在这篇文章中,我们将深入探讨这一技术领域的相关面试题预测。若想对相关内容有更透彻的理解,强烈推荐参考之前发布的博文:【PmHub后端篇】Skyw…...
Java 集合面试题 PDF 及常见考点解析与备考指南
为了帮助你更好地学习Java集合相关知识,我将围绕Java集合面试题展开,介绍常见的技术方案及应用实例。这些内容涵盖了集合框架的基本概念、常见集合类的特点与使用场景,以及在实际开发中可能遇到的问题及解决方案。 Java集合面试题࿱…...

怎么让大语言模型(LLMs)自动生成和优化提示词:APE
怎么让大语言模型(LLMs)自动生成和优化提示词:APE https://arxiv.org/pdf/2211.01910 1. 研究目标:让机器自己学会设计提示词 问题:大语言模型(如GPT-3)很强大,但需要精心设计的“提示词”才能发挥最佳效果。过去靠人工设计提示词,费时费力,还可能因表述差异导致模…...
阿里140 补环境日志
所有属性值是 __cheng________ 都是我做的防止套代理 非140环境检测代码 这个日志绝大多数 是做和浏览器tostring结果 处理一致 方法: toString 函数: ...... 结果: ..... 当前代码补了事件和dom 实际手补 比这少些 下方为环境日志: VM526 vm.js:…...