2016-01-08 107 views
3

我是iOS的初學者,我試圖用Swift設計鼓組應用程序。我用一個按鈕設計了一個視圖並編寫了下面的代碼,但它有一些問題:iOS中的簡單低延遲音頻播放Swift

  1. 當我像滾鼓一樣快速觸摸按鈕時,有些聲音會丟失。
  2. 仍然在「滾筒滾動」中,每次觸摸按鈕時都會中斷聲音,而不是讓樣本播放直到結束。例如,在cy片中這很糟糕。即使再次觸摸按鈕,我也希望聽到每個聽起來完整的聲音。
  3. 觸摸和聲音之間存在延遲。我知道,AVAudioPlayer不是低延遲音頻的最佳選擇,但作爲一個初學者,很難學習OpenALAudioUnit無代碼示例或教程Swift。問題類似於:Which framework should I use to play an audio file (WAV, MP3, AIFF) in iOS with low latency?

代碼:

override func viewDidLoad() { 
    super.viewDidLoad() 

    // Enable multiple touch for the button 
    for v in view.subviews { 
     if v.isKindOfClass(UIButton) { 
      v.multipleTouchEnabled = true 
     } 
    } 

    // Init audio 
    audioURL = NSBundle.mainBundle().URLForResource("snareDrum", withExtension: "wav")! 
    do { 
     player = try AVAudioPlayer(contentsOfURL: audioURL) 
     player?.prepareToPlay() 
    } catch { 
     print("AVAudioPlayer Error") 
    } 
} 

override func viewDidDisappear(animated: Bool) { 
    super.viewDidDisappear(animated) 

    player?.stop() 
    player = nil 
} 

@IBAction func playSound(sender: UIButton) { 
    player?.currentTime = 0 
    player?.play() 
} 
+0

我認爲音頻單元可能是獲得您所期待的那種低延遲行爲的唯一方法,但如果有人告訴我我錯了,我會喜歡它。我正在學習如何在Swift中使用AudioToolbox,並很樂意分享我正在學習的東西。 –

+0

感謝您的回覆,我很感謝您在Swift中使用AudioToolbox的幫助。我們如何與我們取得聯繫?通過電子郵件? – msampaio

+0

您是否收到我的電子郵件地址?我把它留在這裏約30分鐘,然後刪除它... –

回答

5

如果您需要極低的延遲,我發現在AVAudioSession單可用一個非常簡單的解決方案(也就是當自動實例化的應用程序啓動):

第一,使用此類方法獲得對應用的AVAudioSession單例的引用:

(來自AVAudioSession Class Reference):

獲取共享音頻會話

宣言SWIFT

class func sharedInstance() -> AVAudioSession

然後,使用,嘗試將優選的IO緩衝器持續時間爲非常短 (如0.002)此實例方法:

設置首選音頻I/O緩衝區持續時間,以秒計。

宣言SWIFT

func setPreferredIOBufferDuration(_ duration: NSTimeInterval) throws

參數

duration音頻I/O的緩存時間,以秒計,要 使用。

outError在輸入上,指向錯誤對象的指針。如果錯誤 發生時,指針被設置到描述 誤差的NSError對象。如果您不想要錯誤信息,請傳入nil。返回值 如果請求成功,則爲true;否則爲false。

討論

此方法請求更改I/O緩衝區持續時間。要確定 更改是否生效,請使用IOBufferDuration屬性。 詳情請參閱Configuring the Audio Session


記住的說明直接上文的IOBufferDuration屬性是否是實際上設置爲傳遞到func setPrefferedIOBufferDuration(_ duration: NSTimeInterval) throws方法的價值,取決於函數不返回一個錯誤,其他我不完全清楚的因素。另外,在我的測試中,我發現如果將此值設置爲非常低的值,則值(或與其相近的值)的確設置,但在播放文件時(例如使用AVAudioPlayerNode),聲音不會被播放。沒有錯誤,只是沒有聲音。這顯然是一個問題。我還沒有發現如何測試這個問題,除非在實際設備上測試時發現缺少聲音。我會研究它。但現在,我建議將首選持續時間設置爲不低於.002或.0015。 .0015的值似乎適用於我測試的iPad Air(型號A1474)。雖然低至0.0012似乎在我的iPhone 6S上運行良好。

另一件需要考慮的事情是CPU音頻文件的格式。播放時,未壓縮格式的CPU開銷非常低。 Apple建議爲了獲得最高質量和最低開銷,您應該使用CAF文件。對於壓縮文件和最低的開銷,你應該使用IMA4壓縮:

(從iOS Multimedia Programming Guide):

首選音頻格式的iOS對於壓縮(最高質量) 音頻,使用16位,小端,包裝在 CAF文件中的線性PCM音頻數據。您可以使用afconvert命令行工具的音頻文件轉換爲這種格式在Mac OS X ,如下所示:

/usr/bin/afconvert -f caff -d LEI16 {INPUT} {OUTPUT}

對於使用更少的內存,當你需要播放多種聲音 同時使用IMA4(IMA/ADPCM)壓縮。這減少了文件大小,但是在解壓期間CPU影響最小。與 線性PCM數據一樣,將IMA4數據封裝在CAF文件中。

可以使用afconvert以及轉換爲IMA4:

/usr/bin/afconvert -f AIFC -d ima4 [file]

+0

最小I/O緩衝區持續時間至少爲0.005秒(256幀),但可能會更低,具體取決於所使用的硬件。 – zsong

0

我花了一個下午試圖通過AVAudioPlayer & AVAudioSession玩耍來解決這個問題,但我不能讓隨地它。 (遺憾的是,在這裏設置IO緩衝區的持續時間與接受的答案建議似乎沒有幫助。)我也嘗試過AudioToolbox,但是我發現結果表現幾乎相同 - 相關的用戶操作之間明顯可察覺的延遲和音頻。

在網上淘多一點後,我碰到這樣的:

www.rockhoppertech.com/blog/swift-avfoundation/

上AVAudioEngine的部分被證明是非常有用的。下面的代碼是一個輕微的改造:

import UIKit 
import AVFoundation 

class ViewController: UIViewController { 

var engine = AVAudioEngine() 
var playerNode = AVAudioPlayerNode() 
var mixerNode: AVAudioMixerNode? 
var audioFile: AVAudioFile? 

@IBOutlet var button: UIButton! 

override func viewDidLoad() { 
    super.viewDidLoad() 

    engine.attach(playerNode) 
    mixerNode = engine.mainMixerNode 

    engine.connect(playerNode, to: mixerNode!, format: mixerNode!.outputFormat(forBus: 0)) 

    do { 
     try engine.start() 
    } 

    catch let error { 
     print("Error starting engine: \(error.localizedDescription)") 
    } 

    let url = Bundle.main.url(forResource: "click_04", withExtension: ".wav") 

    do { 
     try audioFile = AVAudioFile(forReading: url!) 
    } 

    catch let error { 
     print("Error opening audio file: \(error.localizedDescription)") 
    } 
} 

@IBAction func playSound(_ sender: Any) { 

    engine.connect(playerNode, to: engine.mainMixerNode, format: audioFile?.processingFormat) 
    playerNode.scheduleFile(audioFile!, at: nil, completionHandler: nil) 

    if engine.isRunning{ 
     playerNode.play() 
    } else { 
     print ("engine not running") 
    } 
} 

}

這可能不是完美的,因爲我是一個新手斯威夫特之前沒有使用過AVAudioEngine。它似乎工作,但!