Rekam dan transfer suara dari perangkat ke perangkat menggunakan Konektivitas Multipeer


Selamat siang, pembaca yang budiman! Beberapa waktu lalu, saya memutuskan untuk mencoba merekam dan mentransfer rekaman suara dari satu perangkat ke perangkat lainnya. Sebagai cara mentransmisikan suara yang direkam, pilihan jatuh pada kerangka kerja MultipeerConnectivity . Pada artikel ini saya akan memberi tahu Anda cara melakukannya.

Pertama-tama, kita membutuhkan dua perangkat untuk merekam dan memutar suara. Oleh karena itu, kita perlu menulis kelas untuk melakukan tindakan ini.

Rekaman audio real-time dari perangkat


Untuk merekam audio, AVAudioEngine dan AVAudioMixerNode biasa digunakan, yang disertakan dengan kerangka kerja AVFoundation .

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

Secara umum, tidak ada yang aneh, dengan bantuan mixer, kami mendapatkan data waktu nyata dan mengirimkannya ke fungsi onRecodedAction kami. Untuk mentransfer audio yang kami rekam, kami perlu mengubahnya menjadi data. Untuk ini, saya menyiapkan ekstensi berikutnya.

Contoh mengkonversi PCMBuffer ke Data :

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

Putar ulang audio yang diterima


Kerangka kerja yang sama digunakan untuk memutar audio, sebagai hasilnya, tidak ada yang rumit, singkatnya, kami hanya membuat simpul dan menetapkannya ke mesin, kemudian mengonversi data kami kembali ke PCMBuffer dan memberikannya ke simpul kami untuk diputar.

Contoh pemutaran:

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

Contoh ekstensi untuk menerjemahkan kembali Data ke PCMBuffer dan AVAudioFormat kami untuk memutar 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) } 

Transfer rekaman audio dari satu perangkat ke perangkat lainnya


Nah, akhirnya, kita sampai pada hal yang paling penting - transfer rekaman audio dari perangkat ke perangkat menggunakan MultipeerConnectivity . Untuk melakukan ini, kita perlu membuat objek MCPeerID (Akan menentukan perangkat kita) dan dua contoh kelas MCNearbyServiceAdvertiser dan MCNearbyServiceBrowser , yang akan digunakan untuk mencari perangkat dan agar perangkat lain dapat menemukan kita (Juga menerima permintaan koneksi dari perangkat lain). Kami juga membuat sesi dengan bantuan yang akan kami kirimkan audio yang direkam dan "memanipulasi" perangkat kami.

Kelas contoh untuk mengirim dan menerima data:

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

Saya memutuskan untuk tidak mengunggah semua kode aplikasi saya, karena menurut saya esensi dari cara menggunakannya sudah cukup, seperti yang ditunjukkan dalam contoh. Saya setuju bahwa ada beberapa hal yang dapat diimplementasikan secara berbeda, tetapi saya menggunakan pendekatan yang saya butuhkan dalam kasus ini.

Selama penggunaan MultipeerConnectivity , beberapa aspek negatif diidentifikasi, misalnya, jarak koneksi, oleh karena itu saya tidak menyarankan menggunakan pendekatan transfer data ini jika Anda perlu mengirimkan sesuatu yang mirip dengan audio real-time secara berkelanjutan.

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


All Articles