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

    mChannelMask = channelMask;
    mChannelCount = audio_channel_count_from_out_mask(channelMask);

    // save mBalance into balance for later restoring, then reset
    const float balance = mBalance;
    mBalance = 0.f;

    // reset mVolumes将vector数组重置大小为mChannelCount
    mVolumes.resize(mChannelCount);
        //填充mVolumes所有值为1.f
    std::fill(mVolumes.begin(), mVolumes.end(), 1.f);

    // reset ramping variables
    mRampBalance = 0.f;
    mRampVolumes.clear();
    //如果mChannelMask是按照序号法来表示,如index 0表示主通道、1副通道,
    //则不是按照声场位置表示法AUDIO_CHANNEL_REPRESENTATION_POSITION
    if (audio_channel_mask_get_representation(mChannelMask)
            == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
        mSides.clear();       // mSides unused for channel index masks.
        setBalance(balance);  // recompute balance
        return;
    }
}

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

  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 range
        return;
    }   
    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,其值为1
        const 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,没有无限接近于1
            fabs(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 balance
                for (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.f
    volume[front_right] = mCurve(1.f+balanceX)
    volume[back_left] = mCurve(1.f-volumeY)
    volume[back_right] = mCurve(1.f-volumeY)

back_right可能还需要在调整调整,这里只是阐明一个方案而已

ok,以上就是对Balance的理解!如有不正确的地方,可以在评论区指出

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

姚期智、张亚勤、薛澜、Stuart Russell、Max Tegmark,DeepMind研究员等共话全球AI治理丨大会回顾...

为什么AI安全已迫在眉睫&#xff1f;如何构建全球范围内的合作&#xff1f;民众该如何参与到其中&#xff1f;未来的AI系统将是什么样的&#xff1f; 2024年6月15日&#xff0c;智源大会第二天&#xff0c;多位AI安全领域专家进行圆桌讨论&#xff0c;连接中国北京和美国加利福…

Android隐藏状态栏和修改状态栏颜色_亲测有效

本文记录了隐藏状态栏和修改状态栏颜色以及电量、WiFi标志等内容的模式显示&#xff0c;亲测有效。 1、隐藏屏幕状态栏 public void hideStatusBar(BaseActivity activity) {Window window activity.getWindow();//没有这一行无效window.addFlags(WindowManager.LayoutParam…

基于自组织长短期记忆神经网络的时间序列预测(MATLAB)

LSTM是为了解决RNN 的梯度消失问题而诞生的特殊循环神经网络。该网络开发了一种异于普通神经元的节点结构&#xff0c;引入了3 个控制门的概念。该节点称为LSTM 单元。LSTM 神经网络避免了梯度消失的情况&#xff0c;能够记忆更长久的历史信息&#xff0c;更能有效地拟合长期时…

Spring Cloud LoadBalancer基础入门与应用实践

官网地址&#xff1a;https://docs.spring.io/spring-cloud-commons/reference/spring-cloud-commons/loadbalancer.html 【1】概述 Spring Cloud LoadBalancer是由SpringCloud官方提供的一个开源的、简单易用的客户端负载均衡器&#xff0c;它包含在SpringCloud-commons中用…

[OtterCTF 2018]Recovery

里克必须找回他的文件&#xff01;用于加密文件的随机密码是什么 恢复他的文件 &#xff0c;感染的文件 &#xff1f; vmware-tray.ex 前面导出的3720.dmp 查找一下 搜索主机 strings -e l 3720.dmp | grep “WIN-LO6FAF3DTFE” 主机名 后面跟着一串 代码 aDOBofVYUNVnmp7 是不…

C++并发之环形队列(ring,queue)

目录 1 概述2 实现3 测试4 运行 1 概述 最近研究了C11的并发编程的线程/互斥/锁/条件变量&#xff0c;利用互斥/锁/条件变量实现一个支持多线程并发的环形队列&#xff0c;队列大小通过模板参数传递。 环形队列是一个模板类&#xff0c;有两个模块参数&#xff0c;参数1是元素…

【操作系统期末速成】 EP02 | 学习笔记(基于五道口一只鸭)

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、正文&#xff1a;☀️☀️☀️2.1 考点二&#xff1a;操作系统的功能及接口2.2 考点三&#xff1a;操作系统的发展及分类2.3 考点四&#xff1a;操作系统的运行环境&#xff08;重要&#xff09; 一、前言&#x…

C++输出彩色方块

1.使用方法 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0xab); ———————————————————————————————————————— 0 黑色 1 蓝色 2 绿色 3 湖蓝色 4 红色 5 紫色 6 黄色 7 白色 8 灰色 9 …

【限免】线性调频信号的脉冲压缩及二维分离SAR成像算法【附MATLAB代码】

文章来源&#xff1a;微信公众号&#xff1a;EW Frontier QQ交流群&#xff1a;949444104 程序一 对线性调频信号进行仿真&#xff0c;输出其时频域的相关信息&#xff0c;并模拟回波信号&#xff0c; 对其进行脉冲压缩和加窗处理。 实验记录&#xff1a; 1.线性调频信号时…

24年hvv前夕,微步也要收费了,情报共享会在今年结束么?

一个人走的很快&#xff0c;但一群人才能走的更远。吉祥同学学安全https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247483727&idx1&sndb05d8c1115a4539716eddd9fde4e5c9&scene21#wechat_redirect这个星球&#x1f517;里面已经沉淀了&#xff1a; 《Ja…

自闭症早期风险判别和干预新路径

谷禾健康 自闭症谱系障碍 (ASD) 是一组神经发育疾病&#xff0c;其特征是社交互动和沟通的质量障碍、兴趣受限以及重复和刻板行为。 环境因素在自闭症中发挥重要作用&#xff0c;多项研究以及谷禾队列研究文章表明肠道微生物对于自闭症的发生和发展以及存在明显的菌群和代谢物的…

智慧校园-报修管理系统总体概述

智慧校园报修管理系统是专为优化教育机构内部维修报障流程而设计的信息化解决方案&#xff0c;它通过集成现代信息技术&#xff0c;为校园设施的维护管理带来革新。该系统以用户友好和高效运作为核心&#xff0c;确保了从报修请求提交到问题解决的每一个步骤都顺畅无阻。 师生或…

Apache IoTDB 监控详解 | 分布式系统监控基础

IoTDB 分布式系统监控的基础“须知”&#xff01; 我这个环境的系统性能一直无法提升&#xff0c;能否帮我找到系统的瓶颈在哪里&#xff1f; 系统优化后&#xff0c;虽然写入性能有所提升&#xff0c;但查询延迟却增加了&#xff0c;下一步我该如何排查和优化呢&#xff1f; 请…

【折腾笔记】兰空图床使用Minio作为储存策略

前言 花了几个小时研究了一下在兰空图床中使用Minio作为存储策略,官方并没有给出太多关于minio的储存策略配置文档,我是经过反复尝试,然后根据错误日志的提示以及查阅兰空图床在GitHub上面的issues悟出来的配置方法。 因为我的兰空图床和Minio都是基于群晖的NAS设备DS423+…

[OtterCTF 2018]Bit 4 Bit

我们已经发现这个恶意软件是一个勒索软件。查找攻击者的比特币地址。** 勒索软件总喜欢把勒索标志丢在显眼的地方&#xff0c;所以搜索桌面的记录 volatility.exe -f .\OtterCTF.vmem --profileWin7SP1x64 filescan | Select-String “Desktop” 0x000000007d660500 2 0 -W-r-…

Debian/Ubuntu Linux安装OBS

先决条件 建议使用 xserver-xorg 1.18.4 或更新版本&#xff0c;以避免 OBS 中某些功能&#xff08;例如全屏投影仪&#xff09;出现潜在的性能问题。在 Linux 上使用 OBS Studio 需要 OpenGL 3.3&#xff08;或更高版本&#xff09;支持。在终端中输入以下内容来检查系统支持…

几个常见的FPGA问题之序列发生器、编码器、D触发器

几个常见的FPGA问题之序列发生器、编码器、D触发器 语言 :Verilg HDL 、VHDL EDA工具: Vivado 几个常见的FPGA问题之序列发生器、编码器、D触发器一、引言二、背景1、序列发生器(Sequence Generator)2、编码器(Encoder)3、D触发器(D Flip-Flop)二、问题及解决方案1. 序…

了解WPF控件:OpenFileDialog常用属性与用法(十六)

掌握WPF控件&#xff1a;熟练OpenFileDialog常用属性&#xff08;十六&#xff09; OpenFileDialog控件在WPF中用于需要用户指定文件路径&#xff0c;为用户提供了一个直观且易用的界面来浏览和选择本地文件系统中的文件。例如&#xff0c;当用户需要打开一个已存在的文本文件…

AD9026芯片开发实录5-ADRV9026 - FAQ

1. What information should I provide to help speed resolution of my issue?  Please provide as much detail as possible including all of the detail described in the table below 2. What are the key specifications of ADRV9026 chip?  The ADRV9026 is a 4…

Python自动化测试:web自动化测试——selenium API、unittest框架的使用

web自动化测试2 1. 设计用例的方法——selenium API1.1 基本元素定位1&#xff09;定位单个唯一元素2&#xff09;定位一组元素3&#xff09;定位多窗口/多框架4&#xff09;定位连续层级5&#xff09;定位下拉框6&#xff09;定位div框 1.2 基本操作1.3 等待1.4 浏览器操作1.5…