рдорд▓реНрдЯреАрдкреНрдкрд░ рдХрдиреЗрдХреНрдЯрд┐рд╡рд┐рдЯреА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдбрд┐рд╡рд╛рдЗрд╕ рд╕реЗ рдбрд┐рд╡рд╛рдЗрд╕ рдкрд░ рд░рд┐рдХреЙрд░реНрдб рдФрд░ рд╕реНрдерд╛рдирд╛рдВрддрд░рдг рдзреНрд╡рдирд┐


рд╢реБрдн рджреЛрдкрд╣рд░, рдкреНрд░рд┐рдп рдкрд╛рдардХ! рдХреБрдЫ рд╕рдордп рдкрд╣рд▓реЗ, рдореИрдВрдиреЗ рдбрд┐рд╡рд╛рдЗрд╕ рд╕реЗ рдбрд┐рд╡рд╛рдЗрд╕ рдкрд░ рд░рд┐рдХреЙрд░реНрдб рдХреА рдЧрдИ рдзреНрд╡рдирд┐ рдХреЛ рд░рд┐рдХреЙрд░реНрдб рдХрд░рдиреЗ рдФрд░ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рдиреЗ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛ред рд░рд┐рдХреЙрд░реНрдб рдХрд┐рдП рдЧрдП рдзреНрд╡рдирд┐ рдХреЛ рдкреНрд░рд╕рд╛рд░рд┐рдд рдХрд░рдиреЗ рдХреЗ рд╕рд╛рдзрди рдХреЗ рд░реВрдк рдореЗрдВ, рд╡рд┐рдХрд▓реНрдк рдорд▓реНрдЯреАрдкреНрдХреЗрд░реЛрдирд┐рдЯрд┐рд╡рд┐рдЯреА рдлреНрд░реЗрдорд╡рд░реНрдХ рдкрд░ рдЧрд┐рд░ рдЧрдпрд╛ред рдЗрд╕ рд▓реЗрдЦ рдореЗрдВ рдореИрдВ рдЖрдкрдХреЛ рдмрддрд╛рдКрдВрдЧрд╛ рдХрд┐ рдпрд╣ рдХреИрд╕реЗ рдХрд░рдирд╛ рд╣реИред

рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рд╣рдореЗрдВ рд░рд┐рдХреЙрд░реНрдбрд┐рдВрдЧ рдФрд░ рдзреНрд╡рдирд┐ рдЪрд▓рд╛рдиреЗ рдХреЗ рд▓рд┐рдП рджреЛ рдЙрдкрдХрд░рдгреЛрдВ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рддрджрдиреБрд╕рд╛рд░, рд╣рдореЗрдВ рдЗрди рдХрд╛рд░реНрдпреЛрдВ рдХреЛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рд╡рд░реНрдЧ рд▓рд┐рдЦрдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред

рдПрдХ рдбрд┐рд╡рд╛рдЗрд╕ рд╕реЗ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд╕рдордп рдСрдбрд┐рдпреЛ рд░рд┐рдХреЙрд░реНрдбрд┐рдВрдЧ


рд░рд┐рдХреЙрд░реНрдбрд┐рдВрдЧ рдСрдбрд┐рдпреЛ рдХреЗ рд▓рд┐рдП, рд╕рд╛рдорд╛рдиреНрдп рд░реВрдк рд╕реЗ AVAudioEngine рдФрд░ AVAudioMixerNode рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдЬрд┐рдиреНрд╣реЗрдВ AVFounding рдврд╛рдВрдЪреЗ рдХреЗ рд╕рд╛рде рдЖрдкреВрд░реНрддрд┐ рдХреА рдЬрд╛рддреА рд╣реИред

рдСрдбрд┐рдпреЛ рд░рд┐рдХреЙрд░реНрдбрд┐рдВрдЧ рдЙрджрд╛рд╣рд░рдг:

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

рд╕рд╛рдорд╛рдиреНрдп рддреМрд░ рдкрд░, рдХреБрдЫ рднреА рдЕрд╕рд╛рдорд╛рдиреНрдп рдирд╣реАрдВ рд╣реИ, рдПрдХ рдорд┐рдХреНрд╕рд░ рдХреА рдорджрдж рд╕реЗ рд╣рдо рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд╕рдордп рдХрд╛ рдбреЗрдЯрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░рддреЗ рд╣реИрдВ рдФрд░ рдЗрд╕реЗ рд╣рдорд╛рд░реЗ onRecodedAction рдлрд╝рдВрдХреНрд╢рди рдкрд░ рднреЗрдЬрддреЗ рд╣реИрдВред рд╣рдорд╛рд░реЗ рджреНрд╡рд╛рд░рд╛ рд░рд┐рдХреЙрд░реНрдб рдХрд┐рдП рдЧрдП рдСрдбрд┐рдпреЛ рдХреЛ рдФрд░ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рдЗрд╕реЗ рдбреЗрдЯрд╛ рдореЗрдВ рдмрджрд▓рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рдЗрд╕рдХреЗ рд▓рд┐рдП, рдореИрдВрдиреЗ рдЕрдЧрд▓рд╛ рд╡рд┐рд╕реНрддрд╛рд░ рддреИрдпрд╛рд░ рдХрд┐рдпрд╛ред

PCMBuffer рдХреЛ рдбреЗрдЯрд╛ рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрд┐рдд рдХрд░рдиреЗ рдХрд╛ рдЙрджрд╛рд╣рд░рдг:

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

рдкреНрд▓реЗрдмреИрдХ рдХреЛ рдСрдбрд┐рдпреЛ рдорд┐рд▓рд╛


рдСрдбрд┐рдпреЛ рдХреЛ рдЪрд▓рд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рд╣реА рдлреНрд░реЗрдорд╡рд░реНрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк, рдХреБрдЫ рднреА рдЬрдЯрд┐рд▓ рдирд╣реАрдВ рд╣реИ, рд╕рдВрдХреНрд╖реЗрдк рдореЗрдВ, рд╣рдо рд╕рд┐рд░реНрдл рдПрдХ рдиреЛрдб рдмрдирд╛рддреЗ рд╣реИрдВ рдФрд░ рдЗрд╕реЗ рдЗрдВрдЬрди рдХреЛ рдЕрд╕рд╛рдЗрди рдХрд░рддреЗ рд╣реИрдВ, рдлрд┐рд░ рдЕрдкрдиреЗ рдбреЗрдЯрд╛ рдХреЛ рдкреАрд╕реАрдПрдордмреАрдлрд╝рд░ рдореЗрдВ рд╡рд╛рдкрд╕ рдХрдирд╡рд░реНрдЯ рдХрд░рддреЗ рд╣реИрдВ рдФрд░ рдкреНрд▓реЗрдмреИрдХ рдХреЗ рд▓рд┐рдП рд╣рдорд╛рд░реЗ рдиреЛрдб рдХреЛ рджреЗрддреЗ рд╣реИрдВред

рдкреНрд▓реЗрдмреИрдХ рдЙрджрд╛рд╣рд░рдг:

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

PCMBuffer рдФрд░ рд╣рдорд╛рд░реЗ AVAudioFormat рдХреЛ рдСрдбрд┐рдпреЛ рдЪрд▓рд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдбреЗрдЯрд╛ рд╡рд╛рдкрд╕ рдЕрдиреБрд╡рд╛рдж рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрджрд╛рд╣рд░рдг рдПрдХреНрд╕рдЯреЗрдВрд╢рди:

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

рдбрд┐рд╡рд╛рдЗрд╕ рд╕реЗ рдбрд┐рд╡рд╛рдЗрд╕ рдореЗрдВ рд░рд┐рдХреЙрд░реНрдб рдХрд┐рдП рдЧрдП рдСрдбрд┐рдпреЛ рдЯреНрд░рд╛рдВрд╕рдлрд░ рдХрд░реЗрдВ


рдЦреИрд░, рдЖрдЦрд┐рд░рдХрд╛рд░, рд╣рдо рд╕рдмрд╕реЗ рдорд╣рддреНрд╡рдкреВрд░реНрдг рдмрд╛рдд рдкрд░ рдЖрддреЗ рд╣реИрдВ - рдорд▓реНрдЯреАрдкрд┐рдпрд░рдХрдиреЗрдХреНрдЯрд┐рд╡рд┐рдЯреА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдбрд┐рд╡рд╛рдЗрд╕ рд╕реЗ рдбрд┐рд╡рд╛рдЗрд╕ рдореЗрдВ рд░рд┐рдХреЙрд░реНрдб рдХрд┐рдП рдЧрдП рдСрдбрд┐рдпреЛ рдХрд╛ рд╕реНрдерд╛рдирд╛рдВрддрд░рдг ред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рдПрдХ MCPeerID рдСрдмреНрдЬреЗрдХреНрдЯ (рд╣рдорд╛рд░реЗ рдбрд┐рд╡рд╛рдЗрд╕ рдХреЛ рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХрд░рдирд╛ рд╣реЛрдЧрд╛) рдФрд░ рдХреНрд▓рд╛рд╕ MCNearbyServiceAdvertiser рдФрд░ MCNearbyServiceBrowser рдХреЗ рджреЛ рдЗрдВрд╕реНрдЯреЗрдВрд╕реЗрд╕ рдмрдирд╛рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ , рдЬрд┐рдирдХрд╛ рдЙрдкрдпреЛрдЧ рдЙрдкрдХрд░рдгреЛрдВ рдХреЛ рдЦреЛрдЬрдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ рдФрд░ рддрд╛рдХрд┐ рдЕрдиреНрдп рдбрд┐рд╡рд╛рдЗрд╕ рд╣рдореЗрдВ рдвреВрдВрдв рд╕рдХреЗрдВ (рдЕрдиреНрдп рдЙрдкрдХрд░рдгреЛрдВ рд╕реЗ рдХрдиреЗрдХреНрд╢рди рдЕрдиреБрд░реЛрдз рднреА рд╕реНрд╡реАрдХрд╛рд░ рдХрд░реЗрдВ)ред рд╣рдо рдПрдХ рд╕рддреНрд░ рднреА рдмрдирд╛рддреЗ рд╣реИрдВ рдЬрд┐рд╕рдХреА рдорджрдж рд╕реЗ рд╣рдо рд░рд┐рдХреЙрд░реНрдб рдХрд┐рдП рдЧрдП рдСрдбрд┐рдпреЛ рдХреЛ рдкреНрд░рд╕рд╛рд░рд┐рдд рдХрд░реЗрдВрдЧреЗ рдФрд░ рд╣рдорд╛рд░реЗ рдЙрдкрдХрд░рдгреЛрдВ рдХреЛ "рд╣реЗрд░рдлреЗрд░" рдХрд░реЗрдВрдЧреЗред

рдбреЗрдЯрд╛ рд╕рдВрдЪрд╛рд░рд┐рдд рдХрд░рдиреЗ рдФрд░ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрджрд╛рд╣рд░рдг рд╡рд░реНрдЧ:

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

рдореИрдВрдиреЗ рдЕрдкрдиреЗ рдЖрд╡реЗрджрди рдХреЗ рд╕рднреА рдХреЛрдб рдХреЛ рдЕрдкрд▓реЛрдб рдирд╣реАрдВ рдХрд░рдиреЗ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛, рдХреНрдпреЛрдВрдХрд┐ рдореЗрд░реА рд░рд╛рдп рдореЗрдВ рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХрд╛ рдмрд╣реБрдд рд╕рд╛рд░ рдкрд░реНрдпрд╛рдкреНрдд рд╣реИ, рдЬреИрд╕рд╛ рдХрд┐ рдЙрджрд╛рд╣рд░рдг рдореЗрдВ рджрд┐рдЦрд╛рдпрд╛ рдЧрдпрд╛ рд╣реИред рдореИрдВ рдЗрд╕ рдмрд╛рдд рд╕реЗ рд╕рд╣рдордд рд╣реВрдВ рдХрд┐ рдХреБрдЫ рдЪреАрдЬреЗрдВ рд╣реИрдВ рдЬрд┐рдиреНрд╣реЗрдВ рдЕрд▓рдЧ рддрд░реАрдХреЗ рд╕реЗ рд▓рд╛рдЧреВ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдореИрдВрдиреЗ рдЗрд╕ рдорд╛рдорд▓реЗ рдореЗрдВ рдЬрд┐рд╕ рджреГрд╖реНрдЯрд┐рдХреЛрдг рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдереА, рдЙрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ред

MultipeerConnectivity рдХреЗ рдЙрдкрдпреЛрдЧ рдХреЗ рджреМрд░рд╛рди, рдХреБрдЫ рдирдХрд╛рд░рд╛рддреНрдордХ рдкрд╣рд▓реБрдУрдВ рдХреА рдкрд╣рдЪрд╛рди рдХреА рдЧрдИ рдереА, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдХрдиреЗрдХреНрд╢рди рдХреА рджреВрд░реА, рдЗрд╕рд▓рд┐рдП рдореИрдВ рдЗрд╕ рдбреЗрдЯрд╛ рдЯреНрд░рд╛рдВрд╕рдлрд░ рджреГрд╖реНрдЯрд┐рдХреЛрдг рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рдЕрдиреБрд╢рдВрд╕рд╛ рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реВрдВ рдпрджрд┐ рдЖрдкрдХреЛ рдирд┐рд░рдВрддрд░ рдЖрдзрд╛рд░ рдкрд░ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд╕рдордп рдСрдбрд┐рдпреЛ рдХреЗ рд╕рдорд╛рди рдХреБрдЫ рдкреНрд░рд╕рд╛рд░рд┐рдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред

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


All Articles