Я успешно настроил основы Multipeer Connectivity, и мои два устройства распознают и соединяются друг с другом как одноранговые узлы. Однако у меня возникают проблемы с потоковой передачей и воспроизведением звука с использованием AVAudioPlayerNode
. У меня есть функция StartRecording()
, вызываемая при переключении кнопки, и она должна открывать и закрывать OutputStream.
В общем, я понимаю, что вам нужно создать AVAudioEngine
, присоединить AVAudioPlayerNode
, подключить AVAudioPlayerNode
для основного микшера или выходного узла (?). Затем с отправляющего устройства запустите outputStream
и installTap()
на inputNode
Audio Engine, который будет непрерывно отправлять байты, преобразованные из AVAudioPCMBuffer
.
В функциях Multipeer, один раз поток данные получены, я установил ViewController
inputStream
, который, я думаю, обрабатывается с использованием функций StreamDelegate
. Входящие байты должны быть обработаны (если есть место). Код кажется функциональным, но .hasBytesAvailable
редко вызывается. У меня также включен доступ к микрофону на обоих устройствах.
Поскольку весь код в этом посте трудно уместить, я создал publi c gist с соответствующими файлами. Вот соответствующие части из 3 основных классов: ViewController.swift
, MultipeerHandler.swift
, StreamHelper.swift
:
В ViewController.swift
:
override func viewDidLoad() {
super.viewDidLoad()
configureAudioSession()
audioEngine = AVAudioEngine()
inputNode = audioEngine.inputNode
audioFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 8000, channels: 1, interleaved: false)
multipeerSetup()
viewSetup()
}
private func configureAudioSession() {
do {
try audioSession.setCategory(.playAndRecord, options: .defaultToSpeaker)
try audioSession.setMode(.voiceChat)
try audioSession.setActive(true)
audioSession.requestRecordPermission() { [unowned self] (allowed: Bool) -> Void in
DispatchQueue.main.async {
if allowed {
print("allowed")
}
}
}
} catch { }
}
@objc func startRecording() throws {
// start streaming
if !(self.isRecording) {
audioEngine.stop()
let inputFormat = inputNode.inputFormat(forBus: 0)
audioEngine.attach(audioPlayer)
audioEngine.connect(audioPlayer, to: mainMixer, format: audioFormat)
// audioEngine.connect(audioPlayer, to: audioPlayer.outputNode, format: inputFormat)
do {
if multipeerHandler.session.connectedPeers.count > 0 {
if outputStream != nil {
outputStream = nil
}
outputStream = try multipeerHandler.session.startStream(withName: "voice", toPeer: multipeerHandler.session.connectedPeers[0])
outputStream.schedule(in: RunLoop.main, forMode: .default)
outputStream.delegate = self
outputStream.open()
inputNode.installTap(onBus: 0, bufferSize: AVAudioFrameCount(inputFormat.sampleRate/10), format: inputFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
let convertedFrameCount = AVAudioFrameCount((Double(buffer.frameLength) / inputFormat.sampleRate) * inputFormat.sampleRate)
guard let pcmBuffer = AVAudioPCMBuffer(pcmFormat: inputFormat, frameCapacity: convertedFrameCount) else {
print("cannot make pcm buffer")
return
}
print("\(#line)")
let bytes = StreamHelper.copyAudioBufferBytes(pcmBuffer)
if(self.outputStream.hasSpaceAvailable){
self.outputStream.write(bytes, maxLength: bytes.count)
print("\(#line)")
}
}
audioEngine.prepare()
try audioEngine.start()
} else {
print("no peers to connect to")
}
} catch let error {
print(error.localizedDescription)
}
self.isRecording = true
} else {
// stop streaming
inputNode.removeTap(onBus: 0)
self.isRecording = false
}
}
В MultipeerHelper.swift
:
func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {
print("didReceiveStream")
if streamName == "voice" {
print(#line)
viewController.inputStream = stream
viewController.inputStream.delegate = viewController
viewController.inputStream.schedule(in: RunLoop.main, forMode: .default)
viewController.inputStream.open()
}
}
Также важно отметить, что я нашел много похожих постов, но они несколько старые. Вот некоторые источники, которые я использовал, чтобы понять, что происходит:
Еще раз, я опубликовал publi c gist для этого запроса. Любая помощь будет принята с благодарностью.