web-dev-qa-db-fra.com

Audio en temps réel avec AVAudioEngine

Hej. Je souhaite implémenter une application audio en temps réel avec la nouvelle AVAudioEngine dans Swift. Quelqu'un at-il une expérience avec le nouveau cadre? Comment fonctionnent les applications en temps réel?

Ma première idée était de stocker les données d'entrée (traitées) dans un objet AVAudioPCMBuffer et de les laisser ensuite jouer avec une AVAudioPlayerNode comme vous pouvez le voir dans ma classe de démonstration:

import AVFoundation

class AudioIO {
    var audioEngine: AVAudioEngine
    var audioInputNode : AVAudioInputNode
    var audioPlayerNode: AVAudioPlayerNode
    var audioMixerNode: AVAudioMixerNode
    var audioBuffer: AVAudioPCMBuffer

    init(){
        audioEngine = AVAudioEngine()
        audioPlayerNode = AVAudioPlayerNode()
        audioMixerNode = audioEngine.mainMixerNode

        let frameLength = UInt32(256)
        audioBuffer = AVAudioPCMBuffer(PCMFormat: audioPlayerNode.outputFormatForBus(0), frameCapacity: frameLength)
        audioBuffer.frameLength = frameLength

        audioInputNode = audioEngine.inputNode

        audioInputNode.installTapOnBus(0, bufferSize:frameLength, format: audioInputNode.outputFormatForBus(0), block: {(buffer, time) in
            let channels = UnsafeArray(start: buffer.floatChannelData, length: Int(buffer.format.channelCount))
            let floats = UnsafeArray(start: channels[0], length: Int(buffer.frameLength))

            for var i = 0; i < Int(self.audioBuffer.frameLength); i+=Int(self.audioMixerNode.outputFormatForBus(0).channelCount)
            {
                // doing my real time stuff
                self.audioBuffer.floatChannelData.memory[i] = floats[i];
            }
            })

        // setup audio engine
        audioEngine.attachNode(audioPlayerNode)
        audioEngine.connect(audioPlayerNode, to: audioMixerNode, format: audioPlayerNode.outputFormatForBus(0))
        audioEngine.startAndReturnError(nil)

        // play player and buffer
        audioPlayerNode.play()
        audioPlayerNode.scheduleBuffer(audioBuffer, atTime: nil, options: .Loops, completionHandler: nil)
    }
}

Mais c'est loin du temps réel et pas très efficace. Des idées ou des expériences? Et peu importe, si vous préférez Objective-C ou Swift, je vous suis reconnaissant pour toutes les notes, remarques, commentaires, solutions, etc.

13
Michael Dorner

J'ai expérimenté AVAudioEngine à la fois en Objective-C et en Swift. Dans la version Objective-C de mon moteur, tout le traitement audio est effectué uniquement en C (en mettant en cache les pointeurs d'échantillon C bruts disponibles via AVAudioPCMBuffer et en opérant sur les données avec uniquement du code C). La performance est impressionnante. Par curiosité, j'ai porté ce moteur à Swift. Avec des tâches telles que la lecture linéaire de fichiers audio ou la génération de sons via la synthèse FM, les performances sont assez bonnes, mais dès que des tableaux sont impliqués (par exemple, avec une synthèse granulaire, où des parties de l’audio sont lues et manipulées de manière non linéaire. ), il y a un impact significatif sur les performances. Même avec la meilleure optimisation, l'utilisation du processeur est 30 à 40% supérieure à celle de la version Objective-C/C. Swift étant nouveau pour moi, il existe peut-être d'autres optimisations que je ne connais pas, mais pour autant que je sache, le C/C++ reste le meilleur choix pour l'audio en temps réel. Regardez aussi The Amazing Audio Engine. J'envisage cela, ainsi que l'utilisation directe de l'ancienne API C.

Si vous devez traiter de l'audio en direct, AVAudioEngine peut ne pas vous convenir. Voir ma réponse à cette question: Je souhaite appeler 20 fois par seconde le fichier installTapOnBus: bufferSize: format: block:

8
Jason McClinsey

Je pense que cela fait ce que vous voulez: https://github.com/arielelkin/SwiftyAudio

Commentez les distorsions et vous avez une boucle propre.

4
zzzel

Apple a officiellement pris position sur le codage en temps réel dans Swift. Lors de la session 2017 de WWDC sur Core Audio, un ingénieur Apple a déclaré ne pas utiliser de méthodes Swift ou Objective C dans le contexte de rappel audio en temps réel (impliquant uniquement l'utilisation de C, ou peut-être de C++ et d'ASM).

Cela implique que le schéma approprié pourrait être d'utiliser Objective C pour votre classe de contrôleur AVAudioEngine, mais uniquement le sous-ensemble C (de Objective C) à l'intérieur du bloc tapOnBus.

2
hotpaw2

Selon Apple lors de la WWDC2017 dans le Quoi de neuf dans Core Audio Video vers 19h20, l’ingénieur dit que l’utilisation de Swift n’était "pas sûre" dans un contexte en temps réel. Posté de la transcription.

Maintenant, comme le rendu du moteur s'effectue à partir d'un contexte En temps réel, vous ne pourrez pas utiliser la méta de rendu Objective-C ou Swift que vous avez vue dans la démonstration.

Et c’est parce qu’il n’est pas sûr d’utiliser Objective-C ou Swift À partir d’un contexte temps réel. Au lieu de cela, le moteur lui-même Vous fournit un bloc de rendu que vous pouvez rechercher et mettre en cache, puis Utilisera ce bloc de rendu ultérieurement pour rendre le moteur à partir du temps réel le contexte. La prochaine chose à faire est de configurer votre nœud d’entrée de manière à ce que Puisse fournir vos données d’entrée au moteur. Et ici, vous Spécifiez le format de l’entrée que vous allez fournir, ce qui peut être Un format différent de celui de la sortie. Et vous fournissez également un bloc que Le moteur appellera chaque fois qu'il aura besoin des données d'entrée. Et lorsque ce bloc Sera appelé, le moteur vous indiquera le nombre de trames d’entrée Dont il a réellement besoin.

1
Lloyd Rochester