Nehmen Sie mit Multipeer Connectivity Ton auf und übertragen Sie ihn von Gerät zu Gerät


Guten Tag, lieber Leser! Vor einiger Zeit habe ich mich entschlossen, den aufgenommenen Ton von Gerät zu Gerät aufzunehmen und zu übertragen. Als Mittel zur Übertragung von aufgezeichnetem Ton fiel die Wahl auf das MultipeerConnectivity- Framework. In diesem Artikel erkläre ich Ihnen, wie es geht.

Zunächst benötigen wir zwei Geräte für die Aufnahme und Wiedergabe von Ton. Dementsprechend müssen wir eine Klasse schreiben, um diese Aktionen auszuführen.

Audioaufnahme in Echtzeit von einem Gerät


Für die Audioaufnahme werden die üblichen AVAudioEngine und AVAudioMixerNode verwendet, die im Lieferumfang von AVFoundation enthalten sind .

Beispiel für eine Audioaufnahme:

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

Im Allgemeinen ist das nichts Ungewöhnliches. Mit Hilfe eines Mixers werden Echtzeitdaten abgerufen und an unsere onRecodedAction-Funktion gesendet. Um das aufgenommene Audio weiter zu übertragen, müssen wir es in Daten konvertieren. Dafür habe ich die nächste Erweiterung vorbereitet.

Beispiel für die Konvertierung von PCMBuffer in Daten :

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

Wiedergabe von empfangenem Audio


Das gleiche Framework wird für die Audiowiedergabe verwendet, daher ist die Wiedergabe nicht kompliziert. Wir erstellen lediglich einen Knoten und weisen ihn der Engine zu. Anschließend konvertieren wir unsere Daten zurück in PCMBuffer und geben sie für die Wiedergabe an unseren Knoten weiter.

Wiedergabebeispiel:

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

Beispielerweiterung zum Übersetzen von Daten zurück in PCMBuffer und unser AVAudioFormat zum Abspielen von 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) } 

Übertragen Sie aufgenommenes Audio von Gerät zu Gerät


Nun, endlich kommen wir zum wichtigsten Punkt - der Übertragung aufgezeichneter Audiodaten von Gerät zu Gerät mithilfe von MultipeerConnectivity . Dazu müssen wir ein MCPeerID- Objekt (bestimmt unser Gerät) und zwei Instanzen der Klassen MCNearbyServiceAdvertiser und MCNearbyServiceBrowser erstellen , mit denen nach Geräten gesucht wird und die von anderen Geräten gefunden werden können (auch Verbindungsanfragen von anderen Geräten annehmen). Wir erstellen auch eine Sitzung, mit deren Hilfe wir aufgezeichnetes Audio übertragen und unsere Geräte „manipulieren“.

Beispielklasse zum Senden und Empfangen von Daten:

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

Ich habe mich entschieden, nicht den gesamten Code meiner Anwendung hochzuladen, da meiner Meinung nach das Wesentliche zur Verwendung ausreicht, wie im Beispiel gezeigt. Ich stimme zu, dass es einige Dinge gibt, die anders implementiert werden könnten, aber ich habe den Ansatz verwendet, den ich in diesem Fall brauchte.

Während der Verwendung von MultipeerConnectivity wurden einige negative Aspekte festgestellt, z. B. die Entfernung der Verbindung. Daher empfehle ich diesen Datenübertragungsansatz nicht, wenn Sie kontinuierlich etwas Ähnliches wie Echtzeit-Audio übertragen müssen.

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


All Articles