Enregistrez et transférez le son d'un appareil à l'autre à l'aide de la connectivité Multipeer


Bonjour, cher lecteur! Il y a quelque temps, j'ai décidé d'essayer d'enregistrer et de transférer le son enregistré d'un appareil à l'autre. Pour transmettre le son enregistré, le choix s'est porté sur le framework MultipeerConnectivity . Dans cet article, je vais vous expliquer comment procéder.

Tout d'abord, nous avons besoin de deux appareils pour enregistrer et jouer du son. En conséquence, nous devons écrire une classe pour effectuer ces actions.

Enregistrement audio en temps réel à partir d'un appareil


Pour l'enregistrement audio, les habituels AVAudioEngine et AVAudioMixerNode sont utilisés, fournis avec le framework AVFoundation .

Exemple d'enregistrement audio:

final class Recorder { private let engine = AVAudioEngine() private let mixer = AVAudioMixerNode() var onRecordedAction: ((Data) -> Void)? init() { setupAudioSession() } private func setupAudioSession() { let audioSession = AVAudioSession.sharedInstance() do { try audioSession.setCategory(.record) try audioSession.setMode(.measurement) try audioSession.setActive(true) } catch { debugPrint(error.localizedDescription) } } func startRecording() { let input = engine.inputNode let inputFormat = input.outputFormat(forBus: 0) engine.attach(mixer) engine.connect(input, to: mixer, format: inputFormat) mixer.installTap(onBus: 0, bufferSize: 1024, format: mixer.outputFormat(forBus: 0)) { [weak self] buffer, _ in self?.onRecordedAction?(buffer.data) } engine.prepare() do { try engine.start() } catch { debugPrint(error.localizedDescription) } } func stopRecording() { engine.stop() } } 

En général, rien d'inhabituel, avec l'aide d'un mélangeur, nous obtenons des données en temps réel et les envoyons à notre fonction onRecodedAction. Afin de transférer davantage l'audio que nous avons enregistré, nous devons le convertir en données. Pour cela, j'ai préparé la prochaine extension.

Exemple de conversion de PCMBuffer en données :

 extension AVAudioPCMBuffer { var data: Data { let channels = UnsafeBufferPointer(start: floatChannelData, count: 1) let data = Data(bytes: channels[0], count: Int(frameCapacity * format.streamDescription.pointee.mBytesPerFrame)) return data } } 

Lecture audio reçue


Le même cadre est utilisé pour lire l'audio, en conséquence, rien de compliqué, en un mot, nous créons simplement un nœud et l'affectons au moteur, puis reconvertissons nos données en PCMBuffer et les donnons à notre nœud pour la lecture.

Exemple de lecture:

 final class Player { private let engine = AVAudioEngine() private var playerNode = AVAudioPlayerNode() init() { setupAudioSession() } private func setupAudioSession() { let audioSession = AVAudioSession.sharedInstance() do { try audioSession.setCategory(.playback) try audioSession.setActive(true) } catch { debugPrint(error.localizedDescription) } } private func setupPlayer(buffer: AVAudioPCMBuffer) { engine.attach(playerNode) engine.connect(playerNode, to: engine.mainMixerNode, format: buffer.format) engine.prepare() } private func tryStartEngine() { do { try engine.start() } catch { debugPrint(error.localizedDescription) } } func addPacket(packet: Data) { guard let format = AVAudioFormat.common, let buffer = packet.pcmBuffer(format: format) else { debugPrint("Cannot convert buffer from Data") return } if !engine.isRunning { setupPlayer(buffer: buffer) tryStartEngine() playerNode.play() } playerNode.volume = 1 playerNode.scheduleBuffer(buffer, completionHandler: nil) } } 

Exemple d'extension pour traduire les données vers PCMBuffer et notre AVAudioFormat pour lire l'audio:

 private extension Data { func pcmBuffer(format: AVAudioFormat) -> AVAudioPCMBuffer? { let streamDesc = format.streamDescription.pointee let frameCapacity = UInt32(count) / streamDesc.mBytesPerFrame guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: frameCapacity) else { return nil } buffer.frameLength = buffer.frameCapacity let audioBuffer = buffer.audioBufferList.pointee.mBuffers withUnsafeBytes { addr in guard let baseAddress = addr.baseAddress else { return } audioBuffer.mData?.copyMemory(from: baseAddress, byteCount: Int(audioBuffer.mDataByteSize)) } return buffer } } extension AVAudioFormat { static let common = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 44100, channels: 1, interleaved: false) } 

Transférer l'audio enregistré d'un appareil à un autre


Eh bien, enfin, nous arrivons à la chose la plus importante - le transfert de l'audio enregistré d'un appareil à l'autre à l'aide de MultipeerConnectivity . Pour ce faire, nous devons créer un objet MCPeerID (déterminera notre appareil) et deux instances de la classe MCNearbyServiceAdvertiser et MCNearbyServiceBrowser , qui seront utilisées pour rechercher des appareils et pour que d'autres appareils puissent nous trouver (accepter également une demande de connexion à partir d'autres appareils). Nous créons également une session à l'aide de laquelle nous transmettrons des enregistrements audio et «manipulons» nos appareils.

Exemple de classe pour la transmission et la réception de données:

 private struct Constants { static var serviceType = "bn-radio" static var timeOut: Double = 10 } final class Connectivity: NSObject { private var advertiser: MCNearbyServiceAdvertiser? = nil private var browser: MCNearbyServiceBrowser? = nil private let peerID: MCPeerID private let session: MCSession private var invitationHandler: ((Bool, MCSession) -> Void)? = nil var onDeviceFoundedAction: ((MCPeerID) -> Void)? var onDeviceLostedAction: ((MCPeerID) -> Void)? var onInviteAction: ((MCPeerID) -> Void)? var onConnectingAction: (() -> Void)? var onConnectedAction: (() -> Void)? var onDisconnectedAction: (() -> Void)? var onPacketReceivedAction: ((Data) -> Void)? init(deviceID: String) { peerID = MCPeerID(displayName: deviceID) session = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .none) super.init() session.delegate = self } func startHosting() { advertiser = MCNearbyServiceAdvertiser(peer: peerID, discoveryInfo: nil, serviceType: Constants.serviceType) advertiser?.delegate = self advertiser?.startAdvertisingPeer() } func findHost() { browser = MCNearbyServiceBrowser(peer: peerID, serviceType: Constants.serviceType) browser?.delegate = self browser?.startBrowsingForPeers() } func stop() { advertiser?.stopAdvertisingPeer() browser?.stopBrowsingForPeers() } func invite(peerID: MCPeerID) { browser?.invitePeer(peerID, to: session, withContext: nil, timeout: Constants.timeOut) } func handleInvitation(isAccepted: Bool) { invitationHandler?(isAccepted, session) } func send(data: Data) { try? self.session.send(data, toPeers: session.connectedPeers, with: .unreliable) } } extension Connectivity: MCSessionDelegate { func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { switch state { case .connecting: onConnectingAction?() case .connected: onConnectedAction?() case .notConnected: onDisconnectedAction?() @unknown default: debugPrint("Error during session state changed on: \(state)") } } func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { onPacketReceivedAction?(data) } func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { } func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { } func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { } func session(_ session: MCSession, didReceiveCertificate certificate: [Any]?, fromPeer peerID: MCPeerID, certificateHandler: @escaping (Bool) -> Void) { certificateHandler(true) } } extension Connectivity: MCNearbyServiceAdvertiserDelegate { func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) { self.invitationHandler = invitationHandler onInviteAction?(peerID) } } extension Connectivity: MCNearbyServiceBrowserDelegate { func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) { onDeviceFoundedAction?(peerID) } func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) { onDeviceLostedAction?(peerID) } } 

J'ai décidé de ne pas télécharger tout le code de mon application, car à mon avis, l'essence même de la façon de l'utiliser est suffisante, comme le montre l'exemple. Je suis d'accord qu'il y a certaines choses qui pourraient être mises en œuvre différemment, mais j'ai utilisé l'approche dont j'avais besoin dans ce cas.

Lors de l'utilisation de MultipeerConnectivity , certains aspects négatifs ont été identifiés, par exemple la distance de la connexion, donc je ne recommande pas d'utiliser cette approche de transfert de données si vous devez transmettre quelque chose de similaire à l'audio en temps réel sur une base continue.

Source: https://habr.com/ru/post/fr483174/


All Articles