如何自然地平移音频样本数据?

如何解决如何自然地平移音频样本数据?

我正在开发 Flutter 插件,目前仅针对 Android。这是一种综合性的东西;用户可以将音频文件加载到内存中,他们可以使用名为 Oboe 的音频库调整音高(而不是音高移位)并以最少的延迟播放多个声音。

我设法从 MediaCodec 类支持的音频文件中获取 PCM 数据,并且还通过手动访问 PCM 数组来操纵播放,成功地处理了音高。

这个PCM数组存储为浮点数组,范围从-1.0到1.0。我现在想支持平移功能,就像什么内部Android类如SoundPool。我打算关注 SoundPool 如何处理平移。在执行平移效果时,我必须将 2 个值传递给 SoundPool:左和右。这两个值是浮点数,必须在 0.0 到 1.0 之间。

例如,如果我通过 (1.0F,0.0F),那么用户只能通过左耳听到声音。 (1.0F,1.0F) 将是正常的(中心)。平移不是问题......直到我遇到处理立体声。我知道如何使用立体声 PCM 数据执行平移,但我不知道如何执行自然平移。

如果我尝试将所有声音移到左侧,则必须在左侧播放右声道。相反,如果我尝试将所有声音移到右侧,则必须在右侧播放左声道的声音。我还注意到有一个叫做 Panning Rule 的东西,这意味着当它移到一边时,声音一定会大一点(大约 +3dB)。我试图找到一种实现自然平移效果的方法,但我真的找不到算法或参考。

下面是浮动立体声PCM数组的结构,我在解码音频文件时实际上没有修改数组,所以应该是通用结构

[left_channel_sample_0,right_channel_sample_0,left_channel_sample_1,right_channel_sample_1,...,left_channel_sample_n,right_channel_sample_n]

并且我必须将此 PCM 数组传递给音频流,例如下面的 C++ 代码

void PlayerQueue::renderStereo(float * audioData,int32_t numFrames) {
    for(int i = 0; i < numFrames; i++) {
        //When audio file is stereo...
        if(player->isStereo) {
            if((offset + i) * 2 + 1 < player->data.size()) {
                audioData[i * 2] += player->data.at((offset + i) * 2);
                audioData[i * 2 + 1] += player->data.at((offset + i) * 2 + 1);
            } else {
                //PCM data reached end
                break;
            }
        } else {
            //When audio file is mono...
            if(offset + i < player->data.size()) {
                audioData[i * 2] += player->data.at(offset + i);
                audioData[i * 2 + 1] += player->data.at(offset + i);
            } else {
                //PCM data reached end
                break;
            }
        }

        //Prevent overflow
        if(audioData[i * 2] > 1.0)
            audioData[i * 2] = 1.0;
        else if(audioData[i * 2] < -1.0)
            audioData[i * 2] = -1.0;

        if(audioData[i * 2 + 1] > 1.0)
            audioData[i * 2 + 1] = 1.0;
        else if(audioData[i * 2 + 1] < -1.0)
            audioData[i * 2 + 1] = -1.0;
    }

    //Add numFrames to offset,so it can continue playing PCM data in next session
    offset += numFrames;

    if(offset >= player->data.size()) {
        offset = 0;
        queueEnded = true;
    }
}

我排除了播放操作的计算以简化代码。如您所见,我必须手动将 PCM 数据传递给 audioData 浮点数组。我正在添加 PCM 数据以混合多种声音,包括相同的声音。

  1. 如何使用此 PCM 阵列执行平移效果?如果我们能遵循SoundPool的机制就好了,但只要我能正确地执行平移效果就可以了。 (例如:平移值可以只是 -1.0 到 1.0,0 表示居中)

  2. 应用平移规则时,PCM和分贝之间的关系是什么?我知道如何使声音更大,但我不知道如何以精确的分贝使声音更大。这有什么公式吗?

解决方法

泛规则或泛法的实施因制造商而异。

一种经常使用的实现是,当声音完全平移到一侧时,该侧以最大音量播放,而另一侧则完全衰减。如果声音在中心播放,两侧会衰减大约 3 分贝。

为此,您可以将声源乘以计算的幅度。例如(未经测试的伪代码)

player->data.at((offset + i) * 2) * 1.0; // left signal at full volume
player->data.at((offset + i) * 2 + 1) * 0.0; // right signal fully attenuated

要获得所需的幅度,您可以对左声道使用 sin 函数,对右声道使用 cos 函数。

enter image description here

请注意,当 sin 和 cos 的输入为 pi/4 时,两侧的幅度为 0.707。这将使您在大约 3 分贝的两侧衰减。

所以剩下要做的就是将范围 [-1,1] 映射到范围 [0,pi/2] 例如假设您的 pan 值在 [-1,1] 范围内。 (未经测试的伪代码)

pan_mapped = ((pan + 1) / 2.0) * (Math.pi / 2.0);

left_amplitude = sin(pan_mapped);
right_amplitude = cos(pan_mapped); 

更新:

另一个经常使用的选项(例如 ProTools DAW)是在每一侧都有一个声像设置。有效地将立体声源视为 2 个单声道源。这使您可以在立体声场中自由放置左源,而不会影响右源。

要做到这一点,您将:(未经测试的伪代码)

left_output  += left_source(i)  * sin(left_pan)
right_output += left_source(i)  * cos(left_pan)
left_output  += right_source(i) * sin(right_pan)
right_output += right_source(i) * cos(right_pan)

这 2 个平移的设置由操作员决定,取决于录音和所需的效果。 您希望如何将其映射到单个平移控件取决于您。我只是建议,当平移为 0(居中)时,左声道仅在左侧播放,右声道仅在右侧播放。否则会干扰原始立体声录音。

一种可能性是段 [-1,0) 控制右侧平移,而左侧保持不变。 [0,1] 反之亦然。

hPi = math.pi / 2.0
  
def stereoPan(x):
    if (x < 0.0):
        print("left source:")
        print(1.0) # amplitude to left channel
        print(0.0) # amplitude to right channel
        print("right source:")
        print(math.sin(abs(x) * hPi)) # amplitude to left channel
        print(math.cos(abs(x) * hPi)) # amplitude to right channel

    else:
        print("left source:")
        print(math.cos(x * hPi)) # amplitude to left channel
        print(math.sin(x * hPi)) # amplitude to right channel  
        print("right source:")
        print(0.0) # amplitude to left channel
        print(1.0) # amplitude to right channel
,

以下内容并不与@ruff09 给出的出色答案相矛盾。我只想添加一些我认为在尝试模拟平移时相关的想法和理论。

我想指出,简单地使用音量差异有几个缺点。首先,它与现实世界的现象不符。想象一下,你正走在人行道上,马上在街上,在你的右边,是一个拿着手提钻的工人。我们可以使声音在右侧 100% 音量和左侧 0% 音量。但实际上,我们从那个来源听到的大部分声音也来自左耳,淹没了其他声音。

如果您省略手提电锤的左耳音量以获得最大的右声像,那么即使左侧的安静声音也会被听到(这是荒谬的),因为它们不会与左轨道上的手提电钻内容竞争。如果您的手提钻有左耳音量,那么基于音量的平移效果会使位置更靠近中心。进退两难!

在这种情况下,我们的耳朵如何区分位置?我知道有两个过程可能可以合并到平移算法中,以使平移更加“自然”。一个是过滤组件。与小于我们头部宽度的波长相匹配的高频会被衰减。因此,您可以为声音添加一些差分低通滤波。另一个方面是,在我们的场景中,手提钻的声音在到达左耳之前几毫秒到达右耳。因此,您还可以根据平移角度添加一些延迟。基于时间的平移效果最明显地适用于波长大于我们头部的频率内容(例如,某些高通滤波也是一个组件)。

关于我们耳朵的形状如何对声音产生不同的过滤效果,也有大量研究。我认为随着我们的成长,我们会通过潜意识地将不同的音色与不同的位置(尤其是关于海拔高度和前后立体声问题)相关联来学会使用它。

虽然有很大的计算成本。因此,诸如坚持纯粹基于幅度的平移之类的简化是常态。因此,对于 3D 世界中的声音,最好为需要动态位置更改的项目选择单声道源内容,并且仅将立体声内容用于背景音乐或不需要基于播放器位置动态平移的环境内容。

我想对动态的基于时间的平移结合一点幅度进行更多的实验,看看这是否可以有效地与立体声提示一起使用。实现动态延迟有点棘手,但没有过滤那么昂贵。我想知道是否有办法记录声源(对其进行预处理),使其更适合结合实时过滤器和基于时间的操作,从而实现有效的平移。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res