音频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;}
}

以上函数主要做了以下几件事情:

  1. 从ChannelMask中获取通道数,并根据通道数resize重置mVolumes(vector类型)大小为channelCount,因为balance就是根据通道来进行加减乘除的
  2. 如果channelMask是AUDIO_CHANNEL_REPRESENTATION_INDEX格式,也就是index方式来表示通道数据,无需用mSide位置来表示
  3. 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进行设置:

  1. 小于0,说明平衡点在左侧,左边声道肯定取值1,保持音频本身音量输出,而右声道通过1-balance在用mCurve归一化映射函数映射到小于1的值
  2. 大于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个点:

  1. g(0)一定等于0
  2. g(x<1)的情况一定也是小于1的
    最终, g ( x ) g(x) g(x)的值在0~1范围内;
    算法如上代码:
  3. 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)
  4. 所以在if条件中
fabs(r - T(1)) > std::numeric_limits<T>::epsilon() * 3)

r − T ( 1 ) r-T(1) rT(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把多个位置的平衡都归一化到一维上去了,那么如何改变如何扩展呢?

最常见的一个例子就是车上,四门四喇叭,设置平衡点在左前方,那么用户肯定喜欢左前方喇叭声音保持输出,其余三个喇叭适当降音;但是按照以上代码,会将后排两个喇叭都归一化到左右两个喇叭上,最终左侧喇叭声音音量一样大,右边两个喇叭一样小

思考几分钟,如何扩展呢???

扩展办法

以下是我理解的方案:

  1. 在setChannelMask的时候sideFromChannel数组的取值增加几个取值:0-front_left、1-front_right、2-back_left、3-back_right和4-center;
    这样处理后,就可以把声道channel转换为前后左右、中间几个位置了
  2. 在setBalance时,传入进来的参数就要发生变化:
setBalance(float balance)
转变为:
setBalance(float balanceX, float balanceY)
  1. 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的理解!如有不正确的地方,可以在评论区指出

顺便说个事,有个盆友要找音频开发类的工作,有合适的可以推荐以下,地点成都;

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

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

相关文章

vue 组件批量删除

element ui table表格中前面这个勾选框 对应 type"selection" 属性&#xff0c;绑定事件时selection-change,当你勾选全面的框时就会触发selection-change 对应的事件 绑定事件里面这样定义方法时&#xff0c;这个val 就是选中的时候那一行的数据&#xff0c;如下图…

【数据分享】《中国县城建设统计年鉴》2015-2022

而今天要免费分享的数据就是2015-2022年间出版的《中国县城建设统计年鉴》并以多格式提供免费下载。&#xff08;无需分享朋友圈即可获取&#xff09; 数据介绍 在中国快速城镇化的进程中&#xff0c;县城作为连接城市与乡村的重要节点&#xff0c;其建设与发展受到了广泛关注…

高性价比 ESP32 网络收音机:OLED 显示+编码器控制 (源码开源)

摘要: 本文将详细介绍如何使用 ESP32 开发板制作一个功能完备的网络收音机。我们将涵盖硬件选择、软件架构、网络连接、音频流解码、用户界面设计等方面&#xff0c;并提供完整的代码示例和详细的解释&#xff0c;帮助您轻松构建自己的网络收音机。 关键词: ESP32, 网络收音机…

免交互和嵌入执行模式

目录 概念 语法格式 统计行数 赋值变量 修改密码​编辑往文件里添加内容 ​编辑​编辑引入变量 整体赋值​编辑 加引号不赋值变量 expect实现免交互 免交互设置密码 免交互切换用户 嵌入执行模式 添加用户并免交互设置密码 免交互登录 传参实现ssh 练习 概念 …

梦想CAD二次开发

1.mxdraw简介 mxdraw是一个HTML5 Canvas JavaScript框架&#xff0c;它在THREE.js的基础上扩展开发&#xff0c;为用户提供了一套在前端绘图更为方便&#xff0c;快捷&#xff0c;高效率的解决方案&#xff0c;mxdraw的实质为一个前端二维绘图平台。你可以使用mxdraw在画布上绘…

复盘|接口自动化测试框架建设的经验与教训

为什么选择这个话题&#xff1f; 一是发现很多“点工”在转型迷茫期都会问一些自动化测试相关的问题&#xff0c;可以说自动化测试是“点工”升级的必经之路&#xff1b;二是Google一下接口自动化测试&#xff0c;你会发现很多自动化测试框架相关的文章&#xff0c;但是大部分…

VBA 批量变换文件名

1. 页面布局 在“main”Sheet中按照下面的格式编辑。 2. 实现代码 Private wsMain As Worksheet Private intIdx As LongPrivate Sub getExcelBookList(strPath As String)Dim fso As ObjectDim objFile As ObjectDim objFolder As ObjectSet fso CreateObject("Script…

CAN和CANFD数据写入.asc文件的dll

因为工作需要&#xff0c;需要做一些硬件不是CANoe的上位机&#xff08;比如说周立功CAN,NI-CAN&#xff09;&#xff0c;上位机需要有记录数据的功能&#xff0c;所以用Qt制作了一个记录数据的dll&#xff0c;方便重复使用&#xff08;因为有的客户指定了编程软件&#xff0c;…

Redis主从复制、哨兵以及Cluster集群

1.Redis高可用 在web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准是在多长时间内可以提供正常服务&#xff08;99.9%、99.99%、99.999%等等&#xff09;。 但是在Redis语境中&#xff0c;高可用的含义似乎要宽泛一些&#xff0c;除了保证提供…

加密教程:pdf怎么加密?7个pdf加密技巧任你选(图文详解)

pdf作为一种便携式文档&#xff0c;是展示内容的首选格式&#xff0c;目前也已广泛应用于交换和分享重要等温&#xff0c;例如内部报告、人力资源文件&#xff0c;以及商业提案等包含敏感信息的文档。然而&#xff0c;在如今的数字化时代&#xff0c;随着越来越多的企业将其文档…

capitalize()方法——字符串首字母转换为大写

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 capitalize()方法用于将字符串的首字母转换为大写&#xff0c;其他字母为小写&#xff0c;例如图1所示的效果。 图1 字符串首字母大写效果…

动手实现一个可发送短信随机密码的高安全性用户密码系统

动手实现一个可发送短信随机密码的高安全性用户密码系统 1、背景2、设计3、代码实现3.1、首先先看一下ThreadSpecificSecureRandom组件代码实现,如图所示3.2、调用nextInt的UserPasswordSystemManager组件的代码实现3.3、UserPasswordSystemManager组件所提供的方法,请看下图…

智慧数据中心可视化:高效管理与直观监控的未来

随着数据中心的规模和复杂性不断增加&#xff0c;传统管理方式难以满足需求。智慧数据中心通过图扑可视化实现实时数据监控和智能分析&#xff0c;将复杂的基础设施直观呈现&#xff0c;极大提升了运维效率、故障排查速度和资源优化能力&#xff0c;为企业提供现代化、智能化的…

农业新质生产力数据(2012-2022年)原始+dofile+测算数据集

数据简介&#xff1a;农业新质生产力是指在现代农业发展中&#xff0c;通过融合尖端科技、信息技术与创新管理模式&#xff0c;实现农业生产效率飞跃、产品质量显著提升及生产可持续性增强的一种革新性生产能力&#xff0c;农业新质生产力代表了从依赖传统资源转向依靠科技创新…

一加Ace3 刷机救砖简化说明

注意&#xff1a;工具使用英文目录&#xff0c;支持救砖和降级。PJE110国行版&#xff0c;CPH2609国际版。目前国行版不能完美转换国际版&#xff0c;每次升级都需要刷oplusstanvbk&#xff0c;不建议使用。跨国转换或ROOT一定先解锁Bootloader&#xff0c;可以使用“一加全能工…

为什么用excel求出的和是错误的?

Excel中求和结果错误的原因可能有几种常见的情况&#xff1a;1. **数据格式问题**&#xff1a;有时候数字可能被错误地视为文本格式。这种情况下&#xff0c;Excel 在求和时会忽略这些单元格。你可以通过将这些单元格的格式改为数值格式来解决。2. **隐藏的行或列**&#xff1a…

01_基于git代码代码运行JADE的第一个Agent

第一步&#xff1a;下载JADE代码 进入官网地址 Jade (jade-project.gitlab.io) 使用git下载JADE代码 第二步&#xff1a;编译并打包代码 在代码目录下执行mvn install 注意事项 1. maven版本需大于3.2.6 第三步&#xff1a;新建一个常规maven项目 <?xml version&q…

encode()方法——编码字符串

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 编码是将文本&#xff08;字符串&#xff09;转换成字节流&#xff0c;Unicode格式转换成其他编码格式。在Python中提供了encode()方法&am…

Linux_应用篇(24) CAN 应用编程基础

本章我们学习 CAN 应用编程&#xff0c; CAN 是目前应用非常广泛的现场总线之一&#xff0c;主要应用于汽车电子和工业领域&#xff0c;尤其是汽车领域&#xff0c;汽车上大量的传感器与模块都是通过 CAN 总线连接起来的。 CAN 总线目前是自动化领域发展的热点技术之一&#xf…

活动|华院计算受邀参加2024全球人工智能技术大会(GAITC),探讨法律大模型如何赋能社会治理

6月22至23日&#xff0c;备受瞩目的2024全球人工智能技术大会&#xff08;GAITC&#xff09;在杭州市余杭区未来科技城隆重举行。本届大会以“交叉、融合、相生、共赢”为主题&#xff0c;集“会、展、赛”为一体&#xff0c;聚“产、学、研”于一堂。值得一提的是&#xff0c;…