
مساء الخير ، عزيزي القارئ! منذ بعض الوقت ، قررت محاولة تسجيل ونقل الصوت المسجل من جهاز لآخر. كوسيلة لنقل الصوت المسجل ، وقع الاختيار على إطار
MultipeerConnectivity . في هذه المقالة سأخبرك بكيفية القيام بذلك.
بادئ ذي بدء ، نحن بحاجة إلى جهازين لتسجيل الصوت وتشغيله. وفقًا لذلك ، نحتاج إلى كتابة فصل لأداء هذه الإجراءات.
تسجيل صوتي في الوقت الفعلي من جهاز
لتسجيل الصوت ، يتم
استخدام AVAudioEngine و
AVAudioMixerNode المعتاد
، والتي يتم توفيرها مع إطار عمل
AVFoundation .
مثال على تسجيل الصوت:
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 } }
تلقى تشغيل الصوت
يتم استخدام نفس الإطار لتشغيل الصوت ، ونتيجة لذلك ، لا يوجد شيء معقد ، باختصار ، نقوم فقط بإنشاء عقدة وتعيينها إلى المحرك ، ثم تحويل بياناتنا مرة أخرى إلى
PCMBuffer ومنحها عقدة للتشغيل.
مثال على التشغيل:
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) }
نقل الصوت المسجل من جهاز إلى جهاز
حسنًا ، أخيرًا ، نأتي إلى أهم شيء - نقل الصوت المسجل من جهاز إلى آخر باستخدام
MultipeerConnectivity . للقيام بذلك ، نحتاج إلى إنشاء كائن
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 ، تم تحديد بعض الجوانب السلبية ، على سبيل المثال ، مسافة الاتصال ، لذلك لا أوصي باستخدام نهج نقل البيانات هذا إذا كنت بحاجة إلى نقل شيء مشابه للصوت في الوقت الحقيقي بشكل مستمر.