Swift语言的一个烦恼是它无休止地改变语法(以及语法转换器的不可靠性)。 那,以及语言设计师对指针的恐惧。
我一直在处理大量的音频数据。当然,我也使用Apple的相当不错的Accelerate框架来进行面包和黄油操作,例如FFT,窗口函数等。
如果我有这样的数组:
var veryBigArray = Float [重复:0.0,计数:1024 * 1024]
我想在1024个元素上进行窗口操作+ FFT,从元素n开始,结果令人惊讶地发现它非常棘手。对第n个元素的引用会导致编译器将第n个元素复制到其他位置,然后为我提供对新临时位置的引用。当然,这对我来说完全没用。这可能是因为引用可能导致元素内容发生变化,从而触发写入时的复制(我认为这会让人们担心可变性超出合理范围 - 类已经具有这种可变性,并且天空没有下降。)
我可以使用:
数组(veryBigArray [n ..< n + 1024])
但是这导致了可怕的复制操作,如果在循环中完成,很快就会使机器瘫痪。我可以传递一个切片,但我不知道可能发生的任何未记录的复制操作的规则是什么。
我实际上已经开始将其迁移到C,当时我想也许我可以使用这样的东西:
var basePtr = UnsafeMutableBufferPointer(start: &veryBigArray, count: veryBigArray.count)
let ptr = UnsafePointer<Float>(basePtr.baseAddress)
var ptr1 = ptr! + n
现在我可以调用Accelerate函数。例如:
vDSP_sve(ptr1, 1, &aResultFloat, vDSP_Length(1024))
这实际上是有效的,甚至可能是我最好的选择(考虑到我对Swift进行了大量投资,即使使用C本来是FAR更合理的选择)。缺点主要是它很笨重,指针是无主的,这就排除了使用ARC。
扩展一下,我所做的是从资源中读取一个audiobuffer。我想拥有一个指向缓冲区的指针,当我希望ARC处理缓冲区时,我可以将其清空。
以下是我阅读资源的方法:
func loadAudioFile(fileName: String) -> (UnsafePointer<Float>?, Int) {
// let audioSession = AVAudioSession.sharedInstance()
let fileURL = Bundle.main.url(forResource: fileName, withExtension: "aiff")
if fileURL == nil {return (nil, 0) }
if let audioFile = try? AVAudioFile(forReading: fileURL!) {
let buffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: UInt32(audioFile.length))
if ((try? audioFile.read(into: buffer)) != nil) {
return (UnsafePointer(buffer.floatChannelData?.pointee), Int(audioFile.length))
}
}
return (nil, 0)
}
但我想知道是否有更好的选择?
ContiguousArray主要是吸引人的,但它基本上没有文档记录,并且与常规数组没有特别的互操作性。它也遭受了一个令人难以置信的愚蠢决定,即不允许对数组元素进行地址引用(它们的处理方式与常规数组相同)。
我可以在我的系统周围来回传递UnsafeMutableBufferPointers。如果我给这个绕口令一个类型(它已经被重命名为关联类型,让任何人真正猜出它的用途),它可能不会完全可怕......并且它有保证连续的好处。但它看起来比笨重的东西更加笨拙,而不是为了完成繁重的工作。
我必须承认对我看来是一个非常简单且非常常见的操作感到沮丧,这在Swift中几乎不可能实现。
迄今为止我见过的最好的事情是Chris Liscio几年前发布的。当然,自从他发布它以来,语法已经改变,语法转换器不能用于他的代码,但我当然可以把它当作一个起点,并构建满足我需要的东西。
当斯威夫特被宣布时,我真的很兴奋。该语言似乎设计得很好,甚至编译成本机代码,这对我来说很重要。它应该与C互操作。所以我做了一个重要的承诺。但是过去两年的特点是对于片状编译器的无尽挫败感,每个测试版都看似改变的语法,以及Swift团队对那些真正想要使用他们的玩具语言做的人的明显敌意实际工作(这就是为什么我认为没有必要提出一个保证无处可去的语言提案。)
有没有人有更好的方法来解决将大音频缓冲区内的位置地址传递给Accelerate函数的问题?随着时间的流逝,我发现有更聪明的人找到了比我更好的办法,所以我很乐意听到任何有正确解决方案的人。甚至是一个可能指向一个好解决方案的想法。
答案 0 :(得分:0)
你与UnsafeMutableBufferPointer
走在正确的轨道上,但在那里
(至少在理论上)是一个问题:根据文件
var basePtr = UnsafeMutableBufferPointer(start: &veryBigArray, count: veryBigArray.count)
将指针传递给数组的开头,这是“延长生命周期
对于呼叫的持续时间“。这意味着指针不是
保证在UnsafeMutableBufferPointer
之后有效
构造函数返回。
此外,不需要可变指针,因为vDSP_sve()
不会修改给定的数组。
(在我看来)正确的解决方案是
veryBigArray.withUnsafeBufferPointer {
vDSP_sve($0.baseAddress! + n, 1, &aResultFloat, vDSP_Length(1024))
}
传递一个指向数组连续的指针 存放到关闭。如果不存在此类存储,则首先创建它。
如果需要指针,还有withUnsafeMutableBufferPointer()
到阵列的可变连续存储。