2016-01-08 4 views
3

Je suis un débutant dans iOS et j'essaye de concevoir une application de batterie avec Swift. J'ai conçu une vue avec un seul bouton et j'ai écrit le code ci-dessous, mais il a quelques problèmes:Lecture audio simple à faible latence dans iOS Swift

  1. Certains sons sont perdus lorsque je touche le bouton rapidement comme un rouleau de tambour. Toujours dans un "roulement de tambour", le son est interrompu à chaque fois que le bouton est touché, au lieu de laisser l'échantillon jouer jusqu'à la fin. C'est horrible en cymbale, par exemple. J'aimerais entendre chaque échantillon complètement, même si je touche à nouveau le bouton.
  2. Il y a une latence entre le toucher et le son. Je sais que AVAudioPlayer n'est pas le meilleur choix pour l'audio à faible latence, mais en tant que débutant, il est difficile d'apprendre OpenAL, AudioUnit sans échantillons de code ou tutoriels dans Swift. Le problème est similaire à ceci: Which framework should I use to play an audio file (WAV, MP3, AIFF) in iOS with low latency?.

Le code:

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

Je pense que les unités audio peuvent être la seule façon d'obtenir le type de comportement à faible latence que vous recherchez, bien que j'aimerais beaucoup que quelqu'un me dise que j'avais tort. Je travaille sur apprendre à utiliser AudioToolbox dans Swift et serais heureux de partager ce que j'apprends. –

+0

Merci pour votre réponse, j'apprécierais votre aide avec AudioToolbox dans Swift. Comment pouvons-nous entrer en contact? Par email? – msampaio

+0

Avez-vous reçu mon adresse e-mail? Je l'ai laissé ici pendant environ 30 minutes et je l'ai effacé ... –

Répondre

5

Si vous avez besoin d'une latence extrêmement faible, j'ai découvert une solution très simple sur le AVAudioSession singleton (qui est instancié automatiquement lorsqu'un lancement d'applications):

Première , obtenez une référence au singleton AVAudioSession de votre application à l'aide de cette méthode de classe:

(à partir du AVAudioSession Class Reference):

Obtenir la session Parleur

Déclaration SWIFT

class func sharedInstance() -> AVAudioSession

Ensuite, essayez de définir la durée de la mémoire tampon IO préféré quelque chose de très court (comme .002) en utilisant cette méthode d'instance:

Définit le tampon d'E/S audio préféré durée, en secondes.

Déclaration SWIFT

func setPreferredIOBufferDuration(_ duration: NSTimeInterval) throws

Paramètres

duration La durée tampon __gVirt_NP_NN_NNPS<__ E/S audio, en quelques secondes, que vous voulez à utiliser.

outError En entrée, un pointeur vers un objet d'erreur. Si une erreur se produit, le pointeur est défini sur un objet NSError qui décrit l'erreur . Si vous ne voulez pas d'informations sur les erreurs, passez à zéro. Valeur renvoyée true si une demande a été effectuée avec succès, ou false dans le cas contraire.

Discussion

Cette méthode demande une modification de la durée de tampon I/O. Pour déterminer si la modification prend effet, utilisez la propriété IOBufferDuration. Pour plus de détails, voir Configuring the Audio Session.


Gardez à l'esprit la note directement ci-dessus si la propriété est IOBufferDurationeffectivement ensemble à la valeur transmise à la méthode func setPrefferedIOBufferDuration(_ duration: NSTimeInterval) throws, dépend de la fonction ne retourne pas une erreur, et autres facteurs que je ne suis pas complètement clair sur. De plus, dans mes tests, j'ai découvert que si vous définissez cette valeur à une valeur extrêmement basse, la valeur (ou quelque chose de proche) est définie, mais lors de la lecture d'un fichier (par exemple AVAudioPlayerNode) le son être joué. Pas d'erreur, juste pas de son. C'est évidemment un problème. Et je n'ai pas découvert comment tester ce problème, sauf en notant un manque de son qui frappe mon oreille tout en testant sur un appareil réel. Je vais regarder dedans. Mais pour l'instant, je recommande de définir la durée préférée à pas moins de 0,002 ou 0,0015. La valeur de .0015 semble fonctionner pour l'iPad Air (Modèle A1474) que je suis en train de tester. Alors que aussi bas que. 0012 semble bien fonctionner sur mon iPhone 6S.

Et une autre chose à considérer d'un point de vue de l'overhead CPU est le format du fichier audio. Les formats non compressés auront un temps système CPU très faible lors de la lecture. Apple recommande d'utiliser les fichiers CAF pour obtenir la meilleure qualité et le minimum de frais généraux. Pour les fichiers compressés et les plus bas frais généraux, vous devez utiliser la compression IMA4:

(De l'iOS Multimedia Programming Guide):

Formats audio préférés dans iOS non compressés (haute qualité) audio, utilisez 16 bits, peu endian , données audio PCM linéaires regroupées dans un fichier CAF . Vous pouvez convertir un fichier audio à ce format sous Mac OS X en utilisant l'outil de ligne de commande afconvert, comme indiqué ici:

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

Pour moins utilisation de la mémoire lorsque vous devez jouer plusieurs sons simultanément, utilisez la compression IMA4 (IMA/ADPCM). Cela réduit la taille du fichier mais implique un impact CPU minime pendant la décompression. Comme pour les données PCM linéaires , empaquetez les données IMA4 dans un fichier CAF.

Vous pouvez convertir en IMA4 en utilisant afconvert ainsi:

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

+0

La durée minimale du tampon d'E/S est d'au moins 0,005 s (256 trames) mais peut être inférieure selon le matériel utilisé. – zsong

0

J'ai passé un après-midi à essayer de résoudre ce problème en jouant avec AVAudioPlayer & AVAudioSession, mais je ne pouvais aller nulle part avec il. (J'ai aussi essayé AudioToolbox, mais j'ai trouvé que la performance obtenue était à peu près la même - un délai clairement perceptible entre l'action de l'utilisateur concerné et l'audio.

Après avoir écumé l'Internet un peu plus, je suis tombé sur ceci:

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

La section sur AVAudioEngine avéré être très utile.Le code ci-dessous est un léger remaniement:

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") 
    } 
} 

}

Cela pourrait ne pas être parfait comme je suis un débutant Swift et n'ont pas utilisé AVAudioEngine avant. Cela semble fonctionner, cependant!