免责声明:我不考虑用于声音和语音识别的任何算法和API。 本文介绍音频问题以及如何使用Go解决问题。

phono
是用于处理声音的应用程序框架。 它的主要功能是利用各种技术来制造输送机,以处理声音 为你 以您需要的方式。
除了采用不同的技术外,输送机还需要做什么?为什么还要使用其他框架? 现在让我们弄清楚。
声音从哪里来?
到2018年,声音已成为人类与技术互动的标准方式。 大多数IT巨头已经创建了自己的语音助手,或者现在正在这样做。 语音控制已经在大多数操作系统上,并且语音消息传递是任何Messenger的典型功能。 在世界上, 大约一千家初创公司正在从事自然语言处理, 约有200家正在从事语音识别。
与音乐类似的故事。 它可以在任何设备上播放,并且录音对所有拥有计算机的人都可用。 音乐软件是由全球数百家公司 和数千名爱好者开发的。
如果您必须处理声音,那么以下条件听起来应该很熟悉:
- 必须从文件,设备,网络等获得音频。
- 必须处理音频:添加效果,转码,分析等。
- 音频必须传输到文件,设备,网络等。
- 数据在小缓冲区中传输。
事实证明,这是一条常规的管道-数据流经过多个处理阶段。
解决方案
为了清楚起见,让我们从现实生活中完成一项任务。 例如,您需要将语音转换为文本:
- 我们从设备录制音频
- 消除噪音
- 均衡
- 将信号传递到语音识别API
像其他任何任务一样,此任务有多种解决方案。
额头
仅铁杆 骑单车的人 程序员。 我们直接通过声卡驱动程序录制声音,编写智能降噪和多频段均衡器。 这非常有趣,但是您可能会忘记几个月的原始任务。
很长很困难。
正常的
一种替代方法是使用现有的API。 您可以使用ASIO,CoreAudio,PortAudio,ALSA等录制音频。 还有多种类型的插件需要处理:AAX,VST2,VST3,AU。
多种选择并不意味着您可以一次使用所有内容。 通常,以下限制适用:
- 作业系统 并非所有API在所有操作系统上都可用。 例如,AU是本机OS X技术,仅在此可用。
- 程式语言 大多数音频库都是用C或C ++编写的。 1996年,Steinberg发行了第一个版本的VST SDK,它仍然是最受欢迎的插件标准。 20年后,不再需要用C / C ++编写:对于VST,Java,Python,C#,Rust中都有包装器,还有谁知道。 尽管语言仍然有局限性,但现在甚至可以在JavaScript中处理声音。
- 功能性。 如果任务简单明了,则无需编写新的应用程序。 相同的FFmpeg可以做很多事情。
在这种情况下,复杂程度取决于您的选择。 在最坏的情况下,您必须处理多个库。 如果您根本不走运,那么您可以使用复杂的抽象和完全不同的界面。
结果如何?
您必须在非常复杂和复杂之间选择:
- 要么处理几个底层API来编写自行车
- 要么处理多个API,然后尝试与他们成为朋友
无论选择哪种方法,任务始终落在传送带上。 所使用的技术可能有所不同,但本质是相同的。 问题是,除了解决实际问题,您还必须编写 自行车 传送带。
但是有一个出路。
唱机

phono
创建是为了解决常见问题-“ 接收,处理和传输 ”声音。 为此,他使用管道作为最自然的抽象。 Go 官方博客上有一篇文章描述了管道模式。 管道的主要思想是,数据处理有多个阶段,这些阶段彼此独立工作并通过通道交换数据。 您需要什么。
为什么去
首先,大多数音频程序和库都是用C编写的,而Go通常被称为其后继程序。 此外,还有cgo和许多现有音频库的活页夹 。 您可以使用。
其次,以我个人的观点,Go是一门好语言。 我不会深入,但是我会注意到它的多线程 。 通道和gorutins大大简化了输送机的实施。
抽象化
phono
的心脏是pipe.Pipe
类型。 实现管道的是他。 就像博客示例中一样 ,共有三种类型的阶段:
pipe.Pump
(英式泵)- 接收声音,仅输出通道pipe.Processor
(英语处理器)-声音处理 ,输入和输出通道pipe.Sink
(英语水槽)-声音传输 ,仅输入通道
在pipe.Pipe
内部pipe.Pipe
数据在缓冲区中传递。 建立管道的规则:

- 一管
pipe.Pump
pipe.Processor
一个接一个地放置- 一个或多个
pipe.Sink
平行放置 - 所有
pipe.Pipe
组件必须具有相同的内容:
最低配置为泵和一个水槽,其余为可选。
让我们看几个例子。
简单的
任务:播放wav文件。
让我们将其转换为“ 接收,处理,转移 ”的形式:
- 从WAV文件获取音频
- 将音频传输到端口音频设备

音频被读取并立即播放。
代号 package example import ( "github.com/dudk/phono" "github.com/dudk/phono/pipe" "github.com/dudk/phono/portaudio" "github.com/dudk/phono/wav" )
首先,我们创建未来管道的元素: wav.Pump
和portaudio.Sink
并将它们传递给pipe.New
构造函数。 p.Do(pipe.actionFn) error
函数启动管道并等待其完成。
更难
任务:将wav文件拆分为样本,从样本中组成曲目,保存结果并同时播放。
音轨是样本序列,样本是一小段音频。 要剪切音频,必须首先将其加载到内存中。 为此,请使用phono/asset
包中的asset.Asset
类型。 我们将任务分为标准步骤:
- 从WAV文件获取音频
- 将音频传输到内存
现在,我们用手制作样本,将其添加到轨道中并完成任务:
- 从轨道获取音频
- 将音频传输到

同样,没有处理阶段,但是有两个管道!
代号 package example import ( "github.com/dudk/phono" "github.com/dudk/phono/asset" "github.com/dudk/phono/pipe" "github.com/dudk/phono/portaudio" "github.com/dudk/phono/track" "github.com/dudk/phono/wav" )
与前面的示例相比,有两个pipe.Pipe
。 第一个将数据传输到内存,以便您可以剪切样本。 第二个在末尾有两个收件人: wav.Sink
和portaudio.Sink
。 使用此方案,声音可以同时记录在wav文件中并进行播放。
更难
任务:读取两个wav文件,混合,处理vst2插件并保存到新的wav文件。
phono/mixer
mixer.Mixer
有一个简单的mixer.Mixer
。 它可以从多个源传输信号并混合一个。 为此,它同时实现了pipe.Pump
和pipe.Sink
。
同样,该任务包含两个子任务。 第一个看起来像这样:
- 获取音频WAV文件
- 将音频传输到调音台
第二:
- 从调音台获取音频。
- 处理音频插件
- 将音频传输到WAV文件

代号 package example import ( "github.com/dudk/phono" "github.com/dudk/phono/mixer" "github.com/dudk/phono/pipe" "github.com/dudk/phono/vst2" "github.com/dudk/phono/wav" vst2sdk "github.com/dudk/vst2" )
已经有三个pipe.Pipe
,它们都通过一个混合器互连。 首先,使用p.Begin(pipe.actionFn) (pipe.State, error)
函数。 与p.Do(pipe.actionFn) error
,它不会阻止调用,而只是返回一个状态,然后可以等待p.Wait(pipe.State) error
。
接下来是什么?
我希望phono
成为最方便的应用程序框架。 如果您在声音方面遇到问题,则无需了解复杂的API,也无需花时间研究标准。 所需要做的就是用合适的元件建造一条输送机并运行它。
半年来,拍摄了以下包装:
phono/wav
读取/写入WAV文件phono/vst2
-VST2 SDK的绑定不完整,而您只能打开插件并调用其方法,但不能调用所有结构phono/mixer
-混音器,添加N个信号,没有平衡和音量phono/asset
-缓冲采样phono/track
-顺序读取样本(分层不完整)phono/portaudio
实验期间的信号播放
除了此列表以外,新想法和想法的积压也不断增加,其中包括:
- 倒数计时
- 动态变化的管道
- HTTP泵/水槽
- 参数自动化
- 重采样处理器
- 搅拌机平衡和体积
- 实时泵
- 多轨同步泵
- 完整的vst2
在以下文章中,我将分析:
pipe.Pipe
生命周期-由于结构复杂,其状态由最终原子控制- 如何编写管道阶段
这是我的第一个开源项目,因此,我将感谢您的帮助和建议。 不客气
参考文献