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

Posted

技术标签:

【中文标题】如何将 Int16 音频样本的数据转换为浮点音频样本数组【英文标题】:How to convert Data of Int16 audio samples to array of float audio samples 【发布时间】:2017-08-24 13:41:49 【问题描述】:

我目前正在处理音频样本。 我从 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] -> [Float] 的是samples.map Float($0) / Float(Int16.max),但这样做我的处理时间正在增加。是否存在其他方式将 Int16 的指针转换为 Float 的指针?

【问题讨论】:

如果您的意图是将整数 -32767 ... 32767 转换为 -1.0 ... 1.0,那么就是这样做的。但是你不需要创建一个Array,你可以映射UnsafeBufferPointer。 您将如何“投射指针”? 1) Int16 为 2 个字节,Float 为 4 个字节。 2) 你从整数中计算浮点值。 @MartinR 实际上,我希望迭代通过执行Int16value / Int16.max 来计算浮点数。不是在 Swift 中说“这是一个 Int16 的指针,你能给我计算出的浮点指针吗”?当我使用 UnsafeMutableRawPointer(UInt8 的指针)时,我能够执行以下操作:var floatValues = bytes.bindMemory(to: Float.self, capacity: bytesTotal) 强制转换(或 Swift 术语中的“重新绑定”)只会改变内存的解释方式,它不会改变内存本身。 【参考方案1】:

“铸造”或“重新绑定”指针只会改变内存的方式 解释。您想从整数中计算浮点值, 新值具有不同的内存表示(以及不同的 大小)。

因此您以某种方式必须遍历所有输入值 并计算新值。你可以做的是省略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() 到分配的内存中。

【讨论】:

第一个代码块遇到问题,将UnsafeBufferPointer&lt;Int16&gt;.init start 参数从UnsafeRawBufferPointer 转换为UnsafePointer&lt;_&gt;? 时遇到问题 @ColGraff:我的sampleData 的类型为Data。我从问题中的sampleData.withUnsafeBytes()猜测 明白了,这是有道理的。【参考方案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://***.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 建议的一些更改)

【讨论】:

似乎我们有相同的想法:) – 一些评论:您的代码崩溃,因为 randomFloat 是在 before 设置 randomInt.capacity 时创建的,这使得它成为一个空数组。您可以简单地将&amp;randomFloat 传递给 vDSP_vflt16()。 vDSP 测量应该包括 randomFloat 数组的创建。没有理由使用flatMap,只需randomInt.map Float($0) @MartinR 啊,是的,我已将 randomFloat 的初始化移到更早的时间以整理代码,而忽略了对其进行测试。我会更正它并重新测试。谢谢! 有趣的是,for i in 0..&lt;randomInt.count randomFloat[i] = Float(randomInt[i]) 与 vDSP 方法一样快。 @MartinR 更近了。在使用 vDSP 与 for 循环时,我仍然看到了一些改进,可能是 50% 到 75% 的时间。 更多关于 vDSP、缓存以及在此聊天中对测试的影响的讨论:chat.***.com/rooms/152775/swift-vdsp【参考方案3】:

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

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

【讨论】:

【参考方案4】:

适用于: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    

【讨论】:

对不起,也许我解释得不好,但我想将 Int16 音频样本转换为浮动音频样本,而不像在 floatArray 方法中那样重复整个样本。

以上是关于如何将 Int16 音频样本的数据转换为浮点音频样本数组的主要内容,如果未能解决你的问题,请参考以下文章

将音频样本转换为可听文件格式

将 32 位浮点音频转换为 16 位字节数组?

在 C/C+ 中从 16 位线性 PCM 音频转换为 32 位浮点的最佳方法?

如何将 PCM 音频样本流转换为音量?

混合 PCM 音频样本

将32位浮点音频转换为16位