Audio Kit 查看文件波形的相关源代码,看了下,挺简单的
拿到音频采样数据,用视图去展现,完了
1,拿到音频采样数据
获取的浮点数数据,长这样
-0.014434814
-0.016998291
-0.0184021
-0.017547607
// ...
获取的浮点数数据,在 -1 到 1 之间
1.1 拿到音频文件
let url = Bundle.main.resourceURL?.appendingPathComponent("Samples/beat.aiff")
let file = try! AVAudioFile(forReading: url!)
1.2 拿到第一个声道的数据
public
convenience init?(file: AVAudioFile) {
let size = Int(file.length)
self.init(count: size)
guard let data = file.toFloatChannelData() else { return nil }
// 循环大法好
for i in 0 ..< size {
// 取第一个声道的数据
self[i] = data[0][i]
}
}
1.2.1 获取声道的浮点数数据
public func toFloatChannelData() -> FloatChannelData? {
guard let pcmBuffer = toAVAudioPCMBuffer(),
let data = pcmBuffer.toFloatChannelData() else { return nil }
return data
}
音频文件转 pcm buffer,
public func toAVAudioPCMBuffer() -> AVAudioPCMBuffer? {
guard let buffer = AVAudioPCMBuffer(pcmFormat: processingFormat,
frameCapacity: AVAudioFrameCount(length)) else { return nil }
do {
framePosition = 0
// 文件的数据,直接读进 buffer
try read(into: buffer)
} catch let error as NSError {
return buffer
}
获取音频 pcm buffer 里面的浮点数数据
public func toFloatChannelData() -> FloatChannelData? {
guard let pcmFloatChannelData = floatChannelData else {
return nil
}
let channelCount = Int(format.channelCount)
let frameLength = Int(self.frameLength)
let stride = self.stride
var result = Array(repeating: [Float](zeros: frameLength), count: channelCount)
// 每一帧的数据,分为多个通道,
// 立体声有左声道,和右声道
for channel in 0 ..< channelCount {
// 获取每一帧的数据
for sampleIndex in 0 ..< frameLength {
result[channel][sampleIndex] = pcmFloatChannelData[channel][sampleIndex * stride]
}
}
return result
}
2,拿到音频采样数据.用视图去展现
绘制,就是把一个点、一个点,连接起来
对于点,重点就是 x 和 y 坐标
public override func draw(_ rect: CGRect) {
let width = Double(frame.width)
let height = Double(frame.height) / 2.0
let padding = 0.9
// ...
// 画边框、背景和中间线
let bezierPath = UIBezierPath()
// 开始一个点
// 起点
bezierPath.move(to: CGPoint(x: 0.0, y: (1.0 - Double(table[0]) / absmax) * height))
for index in 1..<table.count {
// 算坐标,连线
let xPoint = Double(index) / Double(table.count) * width
let yPoint = (1.0 - Double(table[index]) / absmax * padding) * height
bezierPath.addLine(to: CGPoint(x: xPoint, y: yPoint))
}
// 结尾一个点
// 终点
bezierPath.addLine(to: CGPoint(x: Double(frame.width), y: (1.0 - Double(table[0]) / absmax * padding) * height))
UIColor.green.setStroke()
bezierPath.lineWidth = 1
bezierPath.stroke()
}
- x 坐标, 比较好计算,
给了一个矩形,把点均匀分布
// width 固定,每一个点的 x 值,就按照比例来
let xPoint = Double(index) / Double(table.count) * width
- 计算 y 坐标
height 是矩形高度的一半
let height = Double(frame.height) / 2.0
先移动到中线
1.0 * height
再翻转 y 值,因为我们习惯看到的 y 坐标向上增长,iOS 默认的 y 坐标,像下增长
- Double(table[index]) * height
对 y 值先放大,再规整小一些
absmax 是娶到点里面的最大值,
例子: 取到的点都在 0.1 ~ 0.2 之间,不放大,坐标会很难看
- Double(table[index]) * height * absmax
为了给顶部和底部留空,规整下
- Double(table[index]) * height * padding
2.1 ,SwiftUI 调用 UIView 对象
通过使用 UIViewRepresentable 协议的结构体封装
struct TableDataView: UIViewRepresentable {
// TableView, 这里实际渲染的 View, 是一个 class
typealias UIViewType = TableView
var view: TableView
// 静态界面,这么来一下,就好了
func makeUIView(context: Context) -> TableView {
view.backgroundColor = UIColor.black
return view
}
func updateUIView(_ uiView: TableView, context: Context) {
//
}
}
另一个声道的波形
一般音频文件,左声道的数据与右声道的,一样
使用另一个声道的数据,看对应的波形
public
convenience init?(file: AVAudioFile) {
let size = Int(file.length)
self.init(count: size)
guard let data = file.toFloatChannelData() else { return nil }
// 循环大法好
for i in 0 ..< size {
// 取另一个声道的数据
// 把 0 改为 1
self[i] = data[1][i]
}
}
波形为什么一般长这样?
波形应该是一条折线,怎么一般都是一团一团的
本文例子中音频时长 8.78 秒,采样率 44100, 双声道,
有 387_072 帧,图中矩形宽度为 343,
一个点,会画 1128 条线,
所以会成一团