如何将Int16音频样本的数据转换为浮点音频样本数组

时间:2017-08-24 13:41:49

标签: swift audio nsarray avfoundation nsdata

我目前正在处理音频样本。 我从AVAssetReader获取它们并且CMSampleBuffer有类似这样的内容:

guard let sampleBuffer = readerOutput.copyNextSampleBuffer() else {
guard reader.status == .completed else { return nil }
// Completed
// samples is an array of Int16
let samples = sampleData.withUnsafeBytes {
  Array(UnsafeBufferPointer<Int16>(
  start: $0, count: sampleData.count / MemoryLayout<Int16>.size))
 }

 // The only way I found to convert [Int16] -> [Float]...
 return samples.map { Float($0) / Float(Int16.max)}
}

guard let blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer) else {
return nil
}

let length = CMBlockBufferGetDataLength(blockBuffer)
let sampleBytes = UnsafeMutablePointer<UInt8>.allocate(capacity: length)
      CMBlockBufferCopyDataBytes(blockBuffer, 0, length, sampleBytes)

      sampleData.append(sampleBytes, count: length)
}

正如你所看到的那样,我发现只转换[Int16] - &gt; [Float]是samples.map { Float($0) / Float(Int16.max),但通过这样做我的处理时间正在增加。是否存在将Int16的指针强制转换为Float指针的其他方法?

4 个答案:

答案 0 :(得分:3)

“强制转换”或“重新绑定”指针只会改变内存的方式 解释。您希望从整数计算浮点值, 新值具有不同的内存表示(也是不同的 尺寸)。

因此,您不得不必须迭代所有输入值 并计算新值。你可以做的是省略Array 创建:

let samples = sampleData.withUnsafeBytes {
    UnsafeBufferPointer<Int16>(start: $0, count: sampleData.count / MemoryLayout<Int16>.size)
}
return samples.map { Float($0) / Float(Int16.max) }

另一种选择是使用来自的 vDSP 函数 加速框架:

import Accelerate
// ...

let numSamples = sampleData.count / MemoryLayout<Int16>.size
var factor = Float(Int16.max)
var floats: [Float] = Array(repeating: 0.0, count: numSamples)

// Int16 array to Float array:
sampleData.withUnsafeBytes {
    vDSP_vflt16($0, 1, &floats, 1, vDSP_Length(numSamples))
}
// Scaling:
vDSP_vsdiv(&floats, 1, &factor, &floats, 1, vDSP_Length(numSamples))

我不知道这是否更快,你必须检查。 (更新:正如ColGraff在答案中所表明的那样,它更快。)

显式循环也比使用map快得多:

let factor = Float(Int16.max)
let samples = sampleData.withUnsafeBytes {
    UnsafeBufferPointer<Int16>(start: $0, count: sampleData.count / MemoryLayout<Int16>.size)
}
var floats: [Float] = Array(repeating: 0.0, count: samples.count)
for i in 0..<samples.count {
    floats[i] = Float(samples[i]) / factor
}
return floats

您的案例中的其他选项可能是使用CMBlockBufferGetDataPointer()而不是CMBlockBufferCopyDataBytes() 分配内存。

答案 1 :(得分:2)

如果您使用Accelerate Framework进行转换,则可以做得更好:

import Accelerate

// Set up random [Int]
var randomInt = [Int16]()

randomInt.reserveCapacity(10000)
for _ in 0..<randomInt.capacity {
  let value = Int16(Int32(arc4random_uniform(UInt32(UInt16.max))) - Int32(UInt16.max / 2))
  randomInt.append(value)
}

// Time elapsed helper: https://stackoverflow.com/a/25022722/887210
func printTimeElapsedWhenRunningCode(title:String, operation:()->()) {
  let startTime = CFAbsoluteTimeGetCurrent()
  operation()
  let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
  print("Time elapsed for \(title): \(timeElapsed) s.")
}

// Testing

printTimeElapsedWhenRunningCode(title: "vDSP") {
  var randomFloat = [Float](repeating: 0, count: randomInt.capacity)
  vDSP_vflt16(randomInt, 1, &randomFloat, 1, vDSP_Length(randomInt.capacity))
}

printTimeElapsedWhenRunningCode(title: "map") {
  randomInt.map { Float($0) }
}

// Results
//
// Time elapsed for vDSP   : 0.000429034233093262 s.
// Time elapsed for flatMap: 0.00233501195907593 s.

它的速度提高了约5倍。

(编辑:添加了Martin R建议的一些更改)

答案 2 :(得分:2)

@MartinR和@ColGraff给出了非常好的答案,谢谢大家和快速回复。 但是我发现了一种更简单的方法,无需任何计算。 AVAssetReaderAudioMixOutput需要音频设置字典。在里面我们可以设置键AVLinearPCMIsFloatKey: true。这样我就会像这样读取我的数据

let samples = sampleData.withUnsafeBytes {
    UnsafeBufferPointer<Float>(start: $0, 
                               count: sampleData.count / MemoryLayout<Float>.size)
}

答案 3 :(得分:0)

for: Xcode 8.3.3•Swift 3.1

extension Collection where Iterator.Element == Int16 {
    var floatArray: [Float] {
        return flatMap{ Float($0) }
    }
}

用法:

let int16Array: [Int16] = [1, 2, 3 ,4]    
let floatArray = int16Array.floatArray