dmrschmidt/DSWaveformImage
GitHub: dmrschmidt/DSWaveformImage
这是一个Swift库,用于从音频文件生成波形图像,支持iOS、macOS和visionOS平台的实时与静态渲染。
Stars: 1237 | Forks: 127
# DSWaveformImage - iOS、macOS 和 visionOS 实时音频波形渲染
[](https://swift.org/package-manager)
DSWaveformImage 提供了一个用于绘制音频数据包络波形的原生接口,
适用于 **iOS**、**iPadOS**、**macOS**、**visionOS** 或通过 Catalyst。为此,你可以使用
* [`WaveformImageView`](Sources/DSWaveformImageViews/UIKit/WaveformImageView.swift) (UIKit) / [`WaveformView`](Sources/DSWaveformImageViews/SwiftUI/WaveformView.swift) (SwiftUI) 从音频文件渲染静态波形,或者
* [`WaveformLiveView`](Sources/DSWaveformImageViews/UIKit/WaveformLiveView.swift) (UIKit) / [`WaveformLiveCanvas`](Sources/DSWaveformImageViews/SwiftUI/WaveformLiveCanvas.swift) (SwiftUI) 实时渲染音频数据(例如来自 `AVAudioRecorder`)的波形
* `WaveformImageDrawer` 从音频文件生成波形 `UIImage`
此外,你也可以直接获取波形的(归一化)`[Float]` 样本,只需
创建一个 `WaveformAnalyzer` 实例。
## 示例界面(包含在仓库中)
有关 SwiftUI 实时音频录音波形渲染的实际示例用法,请参阅 [RecordingIndicatorView](Example/DSWaveformImageExample-iOS/SwiftUIExample/SwiftUIExampleView.swift)。
## 更多相关 iOS 控件
你可能也会对以下用 Swift 编写的 iOS 控件感兴趣:
* [SwiftColorWheel](https://github.com/dmrschmidt/SwiftColorWheel) - 一款令人愉悦的颜色选择器
* [QRCode](https://github.com/dmrschmidt/QRCode) - 一个可自定义的二维码生成器
## 如果你真的很喜欢这个库(又称赞助)
我做这一切都是为了乐趣和快乐,并且我坚信开源的力量。不过,万一使用我的库给你带来了快乐,而你只想说声“谢谢”,如果你通过赞助按钮支持我,我会像一个得到巨大冰淇淋甜筒的 4 岁孩子一样微笑 ☺️💕
或者,考虑通过下载我的一个副项目 iOS 应用来支持我。如果你有心情向别人表达美好的谢意,也许可以看看我的 iOS 应用 [💌 SoundCard](https://www.soundcard.io),给他们发送一张带有个人音频信息的真实明信片。或者下载我的带广告免费游戏 [🕹️ Snekris for iOS](https://apps.apple.com/us/app/snekris-play-like-its-1999/id6446217693)。
## 安装
* 使用 SPM:添加 `https://github.com/dmrschmidt/DSWaveformImage` 并将版本策略设置为 “Up to Next Major”,版本号为 "14.0.0"
```
import DSWaveformImage // for core classes to generate `UIImage` / `NSImage` directly
import DSWaveformImageViews // if you want to use the native UIKit / SwiftUI views
```
## 使用
`DSWaveformImage` 提供 3 种工具供使用
* 原生 SwiftUI 视图 - [SwiftUI 示例用法代码](Example/DSWaveformImageExample-iOS/SwiftUIExample/SwiftUIExampleView.swift)
* 原生 UIKit 视图 - [UIKit 示例用法代码](Example/DSWaveformImageExample-iOS/ViewController.swift)
* 访问原始渲染器和处理器
核心渲染器和处理器以及 SwiftUI 视图原生支持 iOS 和 macOS,分别使用 `UIImage` 和 `NSImage`。
### SwiftUI
#### `WaveformView` - 从音频文件渲染一次性波形:
```
@State var audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
WaveformView(audioURL: audioURL)
```
如果需求更复杂,可以覆盖默认样式:
```
@State var audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
WaveformView(audioURL: audioURL) { waveformShape in
waveformShape
.stroke(LinearGradient(colors: [.red, [.green, red, orange], startPoint: .zero, endPoint: .topTrailing), lineWidth: 3)
}
```
类似于 [AsyncImage](https://developer.apple.com/documentation/swiftui/asyncimage/init(url:scale:content:placeholder:)), 可以设置占位符在加载和渲染操作成功完成前显示。感谢 [@alfogrillo](https://github.com/alfogrillo)!
```
WaveformView(audioURL: audioURL) { waveformShape in
waveformShape
.stroke(LinearGradient(colors: [.red, [.green, red, orange], startPoint: .zero, endPoint: .topTrailing), lineWidth: 3)
} placeholder: {
ProgressView()
}
```
#### `WaveformLiveCanvas` - 从 `(0...1)` 归一化样本渲染实时波形:
```
@StateObject private var audioRecorder: AudioRecorder = AudioRecorder() // just an example
WaveformLiveCanvas(samples: audioRecorder.samples)
```
### UIKit
#### `WaveformImageView` - 从音频文件渲染一次性波形:
```
let audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
waveformImageView = WaveformImageView(frame: CGRect(x: 0, y: 0, width: 500, height: 300)
waveformImageView.waveformAudioURL = audioURL
```
#### `WaveformLiveView` - 从 `(0...1)` 归一化样本渲染实时波形:
完整示例请参阅 [示例项目的 RecordingViewController](Example/DSWaveformImageExample-iOS/RecordingViewController.swift)。
```
let waveformView = WaveformLiveView()
// configure and start AVAudioRecorder
let recorder = AVAudioRecorder()
recorder.isMeteringEnabled = true // required to get current power levels
// after all the other recording (omitted for focus) setup, periodically (every 20ms or so):
recorder.updateMeters() // gets the current value
let currentAmplitude = 1 - pow(10, recorder.averagePower(forChannel: 0) / 20)
waveformView.add(sample: currentAmplitude)
```
### 原始 API
#### 配置
*注意:* 计算总是在后台线程执行并返回,因此在进行任何 UI 工作之前,请确保切换回主线程。
在 [WaveformImageTypes](./Sources/DSWaveformImage/WaveformImageTypes.swift) 中查看 `Waveform.Configuration` 了解各种配置选项。
#### `WaveformImageDrawer` - 从音频文件创建 `UIImage` 波形:
```
let waveformImageDrawer = WaveformImageDrawer()
let audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
let image = try await waveformImageDrawer.waveformImage(
fromAudioAt: audioURL,
with: .init(size: topWaveformView.bounds.size, style: .filled(UIColor.black)),
renderer: LinearWaveformRenderer()
)
// need to jump back to main queue
DispatchQueue.main.async {
self.topWaveformView.image = image
}
```
#### `WaveformAnalyzer` - 计算音频文件的波形样本:
```
let audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
waveformAnalyzer = WaveformAnalyzer()
let samples = try await waveformAnalyzer.samples(fromAudioAt: audioURL, count: 200)
print("samples: \(samples)")
```
### 多声道(立体声)支持
声道选择位于渲染器上——渲染器决定它解释音频的哪个声道。`Waveform.Configuration` 纯粹是关于视觉样式的。
```
let audioURL = Bundle.main.url(forResource: "example_stereo", withExtension: "m4a")!
let waveformImageDrawer = WaveformImageDrawer()
// Render left channel only (channel 0)
let leftChannelImage = try await waveformImageDrawer.waveformImage(
fromAudioAt: audioURL,
with: .init(size: waveformView.bounds.size, style: .filled(.blue)),
renderer: LinearWaveformRenderer(channelSelection: .specific(0))
)
// Render right channel only (channel 1)
let rightChannelImage = try await waveformImageDrawer.waveformImage(
fromAudioAt: audioURL,
with: .init(size: waveformView.bounds.size, style: .filled(.red)),
renderer: LinearWaveformRenderer(channelSelection: .specific(1))
)
// Render both stereo channels in one image (left on top, right on bottom)
let stereoImage = try await waveformImageDrawer.waveformImage(
fromAudioAt: audioURL,
with: .init(size: waveformView.bounds.size, style: .gradient([.blue, .cyan])),
renderer: LinearWaveformRenderer.stereo
)
// All channels merged is the default; no extra parameter needed
let mergedImage = try await waveformImageDrawer.waveformImage(
fromAudioAt: audioURL,
with: .init(size: waveformView.bounds.size, style: .filled(.black))
)
```
如果你直接调用 `WaveformAnalyzer`(以获取原始样本用于你自己的可视化),请在那里传递 `channelSelection`:
```
let waveformAnalyzer = WaveformAnalyzer()
// Get samples for left channel only
let leftSamples = try await waveformAnalyzer.samples(
fromAudioAt: audioURL,
count: 200,
channelSelection: .specific(0)
)
// Get [allLeft..., allRight...] for stereo rendering
let stereoSamples = try await waveformAnalyzer.samples(
fromAudioAt: audioURL,
count: 200,
channelSelection: .stereo
)
```
### 播放进度指示
如果你正在播放音频文件,并想向用户指示播放进度,可以[在示例应用中寻找灵感](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Example/DSWaveformImageExample-iOS/ProgressViewController.swift)。提供了 UIKit 和 [SwiftUI](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Example/DSWaveformImageExample-iOS/SwiftUIExample/ProgressWaveformView.swift) 示例。
两种方法都会产生类似下图的效果。
目前没有计划将其作为库本身的一等公民集成,因为每个应用都有不同的设计需求,而且从示例中可以看出,`WaveformImageDrawer` 和 `WaveformAnalyzer` 的使用与视图一样简单。
### 从 URL 加载远程音频文件
有关显示远程 URL 音频文件波形的一种示例方法,请参阅 https://github.com/dmrschmidt/DSWaveformImage/issues/22。
## 外观展示
波形可以通过 2 种不同的方式和每种 5 种不同的样式进行渲染。
默认使用 [`LinearWaveformRenderer`](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Sources/DSWaveformImage/Renderers/LinearWaveformRenderer.swift),它绘制线性二维振幅包络。
[`CircularWaveformRenderer`](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Sources/DSWaveformImage/Renderers/CircularWaveformRenderer.swift) 作为替代方案可用,可以分别传递给 `WaveformView` 或 `WaveformLiveView`。它绘制圆形二维振幅包络。
你可以通过实现 [`WaveformRenderer`](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Sources/DSWaveformImage/Renderers/WaveformRenderer.swift) 来实现自己的渲染器。
以下样式可应用于任一渲染器:
- **填充**:为波形使用纯色。
- **描边**:使用提供的厚度将包络绘制为轮廓。
- **渐变**:为波形使用基于颜色的渐变。
- **渐变描边**:为波形使用基于颜色的渐变。使用提供的厚度将包络绘制为轮廓。
- **条纹**:为波形使用基于颜色的条纹填充。
### 实时波形渲染
https://user-images.githubusercontent.com/69365/127739821-061a4345-0adc-4cc1-bfd6-f7cfbe1268c9.mov
## 迁移指南
### 版本 14.0.0
* 最低 iOS 部署目标为 15.0,macOS 为 12.0,以移除对已弃用 API 的内部使用
* `WaveformAnalyzer` 和 `WaveformImageDrawer` 在与 completionHandler 一起使用时,现在返回 `Result<[Float] | DSImage, Error>`,以实现更好的错误处理
* `WaveformAnalyzer` 现在是无状态的,并且需要在其 `.samples(fromAudioAt:count:qos:)` 方法中传递 URL,而不是在构造函数中
* SwiftUI 的 `WaveformView` 有一个新的构造函数,可选地提供对底层 `WaveformShape` 的访问,该形状现在用于渲染,参见 [#78](https://github.com/dmrschmidt/DSWaveformImage/issues/78)
### 版本 13.0.0
* 任何提及 `dampening` 和类似术语的地方都在 [11460b8b](https://github.com/dmrschmidt/DSWaveformImage/commit/11460b8b8203f163868ba774d1533116d2fe68a1) 中更正为 `damping` 等。最值得注意的是在 `Waveform.Configuration` 中。参见 [#64](https://github.com/dmrschmidt/DSWaveformImage/issues/64)。
* 样式 `.outlined` 和 `.gradientOutlined` 已添加到 `Waveform.Style`,参见 https://github.com/dmrschmidt/DSWaveformImage#what-it-looks-like
* `Waveform.Position` 已被移除。如果你之前用它来放置视图位置,请像处理其他视图一样,将此职责移交给其父视图进行定位。
### 版本 12.0.0
* 渲染管线已从分析中分离出来。你现在可以通过子类化 [`WaveformRenderer`](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Sources/DSWaveformImage/Renderers/WaveformRenderer.swift) 来创建自己的渲染器。
* 添加了一个新的 [`CircularWaveformRenderer`](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Sources/DSWaveformImage/Renderers/CircularWaveformRenderer.swift)。
* `position` 已从 `Waveform.Configuration` 中移除,参见 [0447737](https://github.com/dmrschmidt/DSWaveformImage/commit/044773782092becec0424527f6feef061988db7a)。
* 添加了新的 `Waveform.Style` 选项,需要在 `switch` 语句等中加以考虑。
### 版本 11.0.0
该库被拆分为两个:`DSWaveformImage` 和 `DSWaveformImageViews`。如果你之前使用过任何原生视图,只需添加额外的 `import DSWaveformImageViews`。
SwiftUI 视图已从接受 Binding 改为接受相应的纯值。
### 版本 9.0.0
一些公共 API 已稍作更改以使其更简洁。所有类型也已归类到 `Waveform` 枚举命名空间下。这意味着例如 `WaveformConfiguration` 已变为 `Waveform.Configuration`,依此类推。
### 版本 7.0.0
颜色已移至相应 `style` 枚举的关联值中。
`Waveform` 和 `UIImage` 类别已在 6.0.0 中移除以简化 API。
请参阅 `使用` 部分了解当前用法。
## 现场查看
[SoundCard - 带声音的明信片](https://www.soundcard.io) 让你可以发送带有音频信息的真实、实体明信片。直接通过你的 iOS 设备。
DSWaveformImage 用于绘制由 [SoundCard - 带音频的明信片](https://www.soundcard.io) 发送的明信片上印刷的音频信息的波形。
## 更多相关 iOS 控件
你可能也会对以下用 Swift 编写的 iOS 控件感兴趣:
* [SwiftColorWheel](https://github.com/dmrschmidt/SwiftColorWheel) - 一款令人愉悦的颜色选择器
* [QRCode](https://github.com/dmrschmidt/QRCode) - 一个可自定义的二维码生成器
## 如果你真的很喜欢这个库(又称赞助)
我做这一切都是为了乐趣和快乐,并且我坚信开源的力量。不过,万一使用我的库给你带来了快乐,而你只想说声“谢谢”,如果你通过赞助按钮支持我,我会像一个得到巨大冰淇淋甜筒的 4 岁孩子一样微笑 ☺️💕
或者,考虑通过下载我的一个副项目 iOS 应用来支持我。如果你有心情向别人表达美好的谢意,也许可以看看我的 iOS 应用 [💌 SoundCard](https://www.soundcard.io),给他们发送一张带有个人音频信息的真实明信片。或者下载我的带广告免费游戏 [🕹️ Snekris for iOS](https://apps.apple.com/us/app/snekris-play-like-its-1999/id6446217693)。
## 安装
* 使用 SPM:添加 `https://github.com/dmrschmidt/DSWaveformImage` 并将版本策略设置为 “Up to Next Major”,版本号为 "14.0.0"
```
import DSWaveformImage // for core classes to generate `UIImage` / `NSImage` directly
import DSWaveformImageViews // if you want to use the native UIKit / SwiftUI views
```
## 使用
`DSWaveformImage` 提供 3 种工具供使用
* 原生 SwiftUI 视图 - [SwiftUI 示例用法代码](Example/DSWaveformImageExample-iOS/SwiftUIExample/SwiftUIExampleView.swift)
* 原生 UIKit 视图 - [UIKit 示例用法代码](Example/DSWaveformImageExample-iOS/ViewController.swift)
* 访问原始渲染器和处理器
核心渲染器和处理器以及 SwiftUI 视图原生支持 iOS 和 macOS,分别使用 `UIImage` 和 `NSImage`。
### SwiftUI
#### `WaveformView` - 从音频文件渲染一次性波形:
```
@State var audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
WaveformView(audioURL: audioURL)
```
如果需求更复杂,可以覆盖默认样式:
```
@State var audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
WaveformView(audioURL: audioURL) { waveformShape in
waveformShape
.stroke(LinearGradient(colors: [.red, [.green, red, orange], startPoint: .zero, endPoint: .topTrailing), lineWidth: 3)
}
```
类似于 [AsyncImage](https://developer.apple.com/documentation/swiftui/asyncimage/init(url:scale:content:placeholder:)), 可以设置占位符在加载和渲染操作成功完成前显示。感谢 [@alfogrillo](https://github.com/alfogrillo)!
```
WaveformView(audioURL: audioURL) { waveformShape in
waveformShape
.stroke(LinearGradient(colors: [.red, [.green, red, orange], startPoint: .zero, endPoint: .topTrailing), lineWidth: 3)
} placeholder: {
ProgressView()
}
```
#### `WaveformLiveCanvas` - 从 `(0...1)` 归一化样本渲染实时波形:
```
@StateObject private var audioRecorder: AudioRecorder = AudioRecorder() // just an example
WaveformLiveCanvas(samples: audioRecorder.samples)
```
### UIKit
#### `WaveformImageView` - 从音频文件渲染一次性波形:
```
let audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
waveformImageView = WaveformImageView(frame: CGRect(x: 0, y: 0, width: 500, height: 300)
waveformImageView.waveformAudioURL = audioURL
```
#### `WaveformLiveView` - 从 `(0...1)` 归一化样本渲染实时波形:
完整示例请参阅 [示例项目的 RecordingViewController](Example/DSWaveformImageExample-iOS/RecordingViewController.swift)。
```
let waveformView = WaveformLiveView()
// configure and start AVAudioRecorder
let recorder = AVAudioRecorder()
recorder.isMeteringEnabled = true // required to get current power levels
// after all the other recording (omitted for focus) setup, periodically (every 20ms or so):
recorder.updateMeters() // gets the current value
let currentAmplitude = 1 - pow(10, recorder.averagePower(forChannel: 0) / 20)
waveformView.add(sample: currentAmplitude)
```
### 原始 API
#### 配置
*注意:* 计算总是在后台线程执行并返回,因此在进行任何 UI 工作之前,请确保切换回主线程。
在 [WaveformImageTypes](./Sources/DSWaveformImage/WaveformImageTypes.swift) 中查看 `Waveform.Configuration` 了解各种配置选项。
#### `WaveformImageDrawer` - 从音频文件创建 `UIImage` 波形:
```
let waveformImageDrawer = WaveformImageDrawer()
let audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
let image = try await waveformImageDrawer.waveformImage(
fromAudioAt: audioURL,
with: .init(size: topWaveformView.bounds.size, style: .filled(UIColor.black)),
renderer: LinearWaveformRenderer()
)
// need to jump back to main queue
DispatchQueue.main.async {
self.topWaveformView.image = image
}
```
#### `WaveformAnalyzer` - 计算音频文件的波形样本:
```
let audioURL = Bundle.main.url(forResource: "example_sound", withExtension: "m4a")!
waveformAnalyzer = WaveformAnalyzer()
let samples = try await waveformAnalyzer.samples(fromAudioAt: audioURL, count: 200)
print("samples: \(samples)")
```
### 多声道(立体声)支持
声道选择位于渲染器上——渲染器决定它解释音频的哪个声道。`Waveform.Configuration` 纯粹是关于视觉样式的。
```
let audioURL = Bundle.main.url(forResource: "example_stereo", withExtension: "m4a")!
let waveformImageDrawer = WaveformImageDrawer()
// Render left channel only (channel 0)
let leftChannelImage = try await waveformImageDrawer.waveformImage(
fromAudioAt: audioURL,
with: .init(size: waveformView.bounds.size, style: .filled(.blue)),
renderer: LinearWaveformRenderer(channelSelection: .specific(0))
)
// Render right channel only (channel 1)
let rightChannelImage = try await waveformImageDrawer.waveformImage(
fromAudioAt: audioURL,
with: .init(size: waveformView.bounds.size, style: .filled(.red)),
renderer: LinearWaveformRenderer(channelSelection: .specific(1))
)
// Render both stereo channels in one image (left on top, right on bottom)
let stereoImage = try await waveformImageDrawer.waveformImage(
fromAudioAt: audioURL,
with: .init(size: waveformView.bounds.size, style: .gradient([.blue, .cyan])),
renderer: LinearWaveformRenderer.stereo
)
// All channels merged is the default; no extra parameter needed
let mergedImage = try await waveformImageDrawer.waveformImage(
fromAudioAt: audioURL,
with: .init(size: waveformView.bounds.size, style: .filled(.black))
)
```
如果你直接调用 `WaveformAnalyzer`(以获取原始样本用于你自己的可视化),请在那里传递 `channelSelection`:
```
let waveformAnalyzer = WaveformAnalyzer()
// Get samples for left channel only
let leftSamples = try await waveformAnalyzer.samples(
fromAudioAt: audioURL,
count: 200,
channelSelection: .specific(0)
)
// Get [allLeft..., allRight...] for stereo rendering
let stereoSamples = try await waveformAnalyzer.samples(
fromAudioAt: audioURL,
count: 200,
channelSelection: .stereo
)
```
### 播放进度指示
如果你正在播放音频文件,并想向用户指示播放进度,可以[在示例应用中寻找灵感](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Example/DSWaveformImageExample-iOS/ProgressViewController.swift)。提供了 UIKit 和 [SwiftUI](https://github.com/dmrschmidt/DSWaveformImage/blob/main/Example/DSWaveformImageExample-iOS/SwiftUIExample/ProgressWaveformView.swift) 示例。
两种方法都会产生类似下图的效果。
标签:iOS开发, macOS开发, SwiftUI视图, Swift包管理, UIKit视图, visionOS开发, 原生接口, 图像绘制, 图形库, 实时音频处理, 录音波形, 波形图像生成, 波形渲染, 移动应用开发, 跨平台库, 音频分析, 音频可视化, 音频录制, 音频波形

