2017-07-22 126 views
1

我已經在Swift中編寫了一個示例項目來嘗試使用相對較新的Core Audio V3 API。一切似乎都圍繞創建自定義音頻單元並在過程中加載它。但實際的音頻渲染效果並不理想。我經常讀到渲染代碼需要使用C或C++,但我也聽說Swift速度很快,並認爲我可以在其中編寫一些最小的音頻渲染代碼。音頻單元在Swift中渲染塊?

渲染代碼

override var internalRenderBlock: AUInternalRenderBlock { 
    get { 
     return { 
      (_ actionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>, 
      _ timeStamp: UnsafePointer<AudioTimeStamp>, 
      _ frameCount: AUAudioFrameCount, 
      _ outputBusNumber: Int, 
      _ bufferList: UnsafeMutablePointer<AudioBufferList>, 
      _ renderEvent: UnsafePointer<AURenderEvent>?, 
      _ pull: AudioToolbox.AURenderPullInputBlock?) -> AUAudioUnitStatus in 

      let bufferList = bufferList.pointee 
      let theBuffers = bufferList.mBuffers // only one (AudioBuffer) ?? 

      guard let theBufferData = theBuffers.mData?.assumingMemoryBound(to: Float.self) else { 
       return 1 // come up with better error? 
      } 

      let amountFrames = Int(frameCount) 

      for frame in 0...amountFrames/2 { 
       let frame = theBufferData.advanced(by: frame) 
       frame.pointee = sin(self.phase) 
       self.phase += 0.0001 
      } 

      return noErr 
     } 
    } 
} 

聽起來很糟糕

產生的聲音是不是我所期望的。我最初的想法是Swift是錯誤的選擇。然而有趣的是,AudioToolbox確實爲它看起來像這樣AUAudioUnit的渲染屬性typealias:

public typealias AUInternalRenderBlock = (UnsafeMutablePointer<AudioUnitRenderActionFlags>, UnsafePointer<AudioTimeStamp>, AUAudioFrameCount, Int, UnsafeMutablePointer<AudioBufferList>, UnsafePointer<AURenderEvent>?, AudioToolbox.AURenderPullInputBlock?) -> AUAudioUnitStatus 

這將導致我認爲,這也許是可以寫在斯威夫特渲染代碼。

觀察到的問題

但是,仍然有一些事情在這裏出錯。 (除了我明顯缺乏Swift內存管理能力外)。

A)儘管theBuffers稱其mNumberOfBuffers爲2,theBuffers卷取不是陣列而是(AudioBuffer)類型。我不明白括號的必要性。我找不到第二個AudioBuffer

B)更重要的是,當我寫一個基本的正弦波到我可以訪問的一個AudioBuffer時,產生的聲音是失真和不一致的。這可能是斯威夫特的錯嗎?在Swift中編寫任何音頻單元渲染代碼是不可能的?或者在這裏做出了一些假設,以某種方式打破我的渲染?

最後

如果只是寫這部分斯威夫特是不可行的話,那麼我想對互操作斯威夫特和C音頻單元渲染塊的一些資源。那麼,返回閉包的屬性是否可以用Swift編寫,但閉包的實現是否會調用C?還是該屬性不得不簡單地返回一個C函數,該函數的原型與閉包的類型相匹配?

在此先感謝。

對於上下文,本項目的其餘部分可以看到here

+0

[在Swift中使用AudioBufferList](https://stackoverflow.com/questions/24838106/using-audiobufferlist-with-swift) –

+0

在最近一次關於Core Audio的WWDC會議中,Apple明確表示要做某些音頻上下文代碼在C中,而不是Swift或Obj C.(但我的github要求在Swift中使用V3渲染似乎無論如何...當前...可能會中斷)。 – hotpaw2

+0

在這一點上我已經清楚了。我在公開的「標題」中被Swift typealias拋棄了。爲什麼會那樣? –

回答

1

您聽到失真聲音的主要原因是0.0001的相位增量太小,這將需要62832個樣本來填充正弦波的一個週期 - 僅爲0.70赫茲! (假設您的採樣率爲44100)

除了超低頻正弦波,您正在聆聽大約44100/512 = 86.1 Hz的聲音,因爲您只填充了音頻緩衝區的一半(amountFrames/2)。所以聲音是音頻渲染週期的近似矩形波,幅度在0.70 Hz左右緩慢變化。

我可以基於代碼寫工作正弦波發生器單元:

override var internalRenderBlock: AUInternalRenderBlock { 
    return { (_, _, frameCount, _, bufferList, _, _) in 
     let srate = Float(self.bus.format.sampleRate) 
     var phase = self.phase 

     for buffer in UnsafeMutableAudioBufferListPointer(bufferList) { 
      phase = self.phase 
      assert(buffer.mNumberChannels == 1, "interleaved channel not supported") 

      let frames = buffer.mData!.assumingMemoryBound(to: Float.self) 

      for i in 0 ..< Int(frameCount) { 
       frames[i] = sin(phase) 
       phase += 2 * .pi * 440/srate // 440 Hz 
       if phase > 2 * .pi { 
        phase -= 2 * .pi // to avoid floating point inaccuracy 
       } 
      } 
     } 

     self.phase = phase 
     return noErr 
    } 
} 

關於所觀察到的問題的,該AudioBufferList爲可變長度C結構,其中第一場mNumberBuffers指示號碼的包裝緩衝器的(即,非交錯信道的數量),並且所述第二字段是可變長度數組:

typedef struct AudioBufferList { 
    UInt32 mNumberBuffers; 
    AudioBuffer mBuffers[1]; 
} AudioBufferList; 

這個結構的用戶,在Objective-C或C++,預計分配mNumberBuffers * sizeof(AudioBuffer) BYT es,這足以存儲多個mBuffers。由於C不對陣列執行邊界檢查,因此用戶只需寫入mBuffers[1]mBuffers[2]即可訪問第二個或第三個緩衝區。

由於Swift沒有這個可變長度數組功能,Apple提供了UnsafeMutableAudioBufferListPointer,它可以像Swift集合AudioBuffer s一樣使用;我在上面的外圈for循環中使用了這個。因爲訪問Swift或Objective-C對象可能會引入意外的延遲,這就是爲什麼Apple建議在C/C++中編寫渲染循環的原因。最後,我試圖在代碼的最內層循環中不訪問self,因爲訪問Swift或Objective-C對象可能會引入意外延遲,這就是Apple建議在C/C++中編寫渲染循環的原因。但是對於這樣的簡單情況,我會說在Swift中編寫代碼要容易得多,並且延遲仍然可以管理。