让我们用Go处理声音

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

phono是用于声音处理的应用程序框架。 它的主要目的–创建不同技术的管道,以您需要的方式处理声音。

管道使用不同的技术做什么,为什么我们需要另一个框架? 我们现在会解决。

声音从何而来?

在2018年,声音成为人类与技术之间互动的标准方式。 大多数IT巨人要么已经具有语音助手,要么现在正在研究它。 集成到主要操作系统和语音消息中的语音控制是任何现代Messenger的默认功能。 大约有上千家初创公司从事自然语言处理,约有200家从事语音识别。

音乐也有同样的故事。 它可以通过任何设备播放,并且任何有计算机的人都可以使用录音。 音乐软件是由全球数百家公司和数千名爱好者开发的。

常见任务

如果您执行过任何声音处理任务,那么您应该熟悉以下条件:

  • 应该从文件,设备,网络等接收音频。
  • 处理音频:添加FX,编码,分析等。
  • 音频应发送到文件,设备,网络等中。
  • 数据在小缓冲区中传输

它变成了一条流水线-数据流经过多个处理阶段。

解决方案

为了清楚起见,让我们来看一个现实生活中的问题:我们需要将语音转换为文本:

  • 用设备录制音频
  • 消除噪音
  • 均衡
  • 发送信号到语音识别API

与其他任何问题一样,该问题有几种解决方案。

蛮力

仅限于铁杆开发人员。 通过声音接口驱动程序直接录制声音,编写智能降噪器和多轨均衡器。 这很有趣,但是您可能会忘记几个月的原始任务。

耗时且非常复杂。

正常方式

替代方法是使用现有的音频API。 可以使用ASIO,CoreAudio,PortAudio,ALSA等录制音频。 可以使用多种标准的插件进行处理:AAX,VST2,VST3,AU。

丰富的选择并不意味着可以使用所有内容。 通常,以下限制适用:

  1. 操作系统。 并非所有API在所有操作系统上都可用。 例如,AU是OS X的本机技术,仅在此可用。
  2. 编程语言。 大多数音频库都是用C或C ++编写的。 1996年,Steinberg发布了第一个版本的VST SDK,它仍然是最受欢迎的插件格式。 如今,您不再需要用C / C ++编写:Java,Python,C#,Rust等许多VST包装器。 尽管语言仍然是一种局限性,但如今人们甚至可以使用Java处理声音。
  3. 功能性。 如果问题很简单,则无需编写新的应用程序。 FFmpeg具有大量功能。

在这种情况下,复杂程度取决于您的选择。 最坏的情况是-您必须处理多个库。 如果您真的很不幸,那么您将拥有复杂的抽象和完全不同的接口。

到底是什么?

我们必须在非常复杂复杂之间进行选择:

  • 处理几个低级API来发明我们自己的轮子
  • 处理几个API并尝试使其成为朋友

选择哪种方式都无关紧要,任务总是落在管道上。 技术可能有所不同,但本质没有改变。 同样,问题在于,除了解决问题之外,我们还必须建立一条管道。

但是有一个选择。

唱机

创建phono是为了解决常见的任务-“ 接收,处理和发送 ”声音。 它利用管道作为最自然的抽象。 Go官方博客中有一篇文章。 它描述了管道模式。 流水线模式的核心思想是,数据处理分为几个阶段,这些阶段独立工作并通过通道交换数据。 那就是需要的。

但是,为什么要走?

最初,许多音频软件和库都是用C编写的。Go被称为其继任者。 最重要的是,我们可以采用和使用cgo和现有音频API的各种绑定。

其次,我认为Go是一门好语言。 我不想深入探讨,但是我注意到它的并发可能性。 通道和goroutines使管道的实现变得更加容易。

抽象

pipe.Pipe结构是phono的核心,它实现了流水线模式。 就像在博客示例中一样,定义了三个阶段:

  1. pipe.Pump接收声音,仅输出通道
  2. pipe.Processor处理声音,输入和输出通道
  3. pipe.Sink发送声音,仅输入通道

数据在pipe.Pipe内的缓冲区中pipe.Pipe 。 允许您构建管道的规则:

  1. 单管pipe.Pump
  2. 多个pipe.Processor ,按顺序放置
  3. 单个或多个pipe.Sink ,平行放置
  4. 所有pipe.Pipe组件应具有相同的内容:
  • 缓冲区大小
  • 采样率
  • 通道数

最小配置是带有单个水槽的泵,其余是可选的。

让我们来看几个例子。

简单

问题:播放wav文件。

让我们以“ 接收,处理,发送 ”形式表达一个问题:

  1. 从WAV文件接收音频
  2. 音频发送到端口音频设备

音频被读取并立即播放。

再次,没有处理阶段,但是我们现在有两个管道!

在这里,我们有三个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生命周期–由于内部结构复杂,其状态由有限状态机管理
  • 如何编写自己的管道阶段

这是我的第一个开源项目,因此很高兴获得任何帮助和建议。

链接

  • 唱机
  • 并发模式:管道和取消