簡體   English   中英

Stream 使用 Multipeer 連接和 EZAudio 從一台設備到另一台設備的麥克風音頻

[英]Stream Microphone Audio from one device to another using Multipeer connectivy and EZAudio

[TLDR:在嘗試保存流式音頻數據時在 CABufferList.h 上收到斷言失敗(在底部發現錯誤)]

我在使用 Multipeer Connectivity 保存在設備之間流式傳輸的麥克風音頻時遇到問題。 到目前為止,我有兩個設備使用 Multipeer Connectivity 相互連接,並讓它們相互發送消息和流。

最后我有了 StreamDelegate 方法

func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
    
    // create a buffer for capturing the inputstream data
    let bufferSize = 2048
    let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
    defer {
        buffer.deallocate()
    }
    
    var audioBuffer: AudioBuffer!
    var audioBufferList: AudioBufferList!
    
    switch eventCode {
        case .hasBytesAvailable:
            // if the input stream has bytes available
            // return the actual number of bytes placed in the buffer;
            let read = self.inputStream.read(buffer, maxLength: bufferSize)
            if read < 0 {
                //Stream error occured
                print(self.inputStream.streamError!)
            } else if read == 0 {
                //EOF
                break
            }
            
            guard let mData = UnsafeMutableRawPointer(buffer) else { return }
            audioBuffer = AudioBuffer(mNumberChannels: 1, mDataByteSize: UInt32(read), mData: mData)
            audioBufferList = AudioBufferList(mNumberBuffers: 1, mBuffers: audioBuffer)
            let audioBufferListPointer = UnsafeMutablePointer<AudioBufferList>.allocate(capacity: read)
            audioBufferListPointer.pointee = audioBufferList
            
            DispatchQueue.main.async {
                if self.ezRecorder == nil {
                    self.recordAudio()
                }
                
                self.ezRecorder?.appendData(from: audioBufferListPointer, withBufferSize: UInt32(read))
            }
        
            print("hasBytesAvailable \(audioBuffer!)")
        case .endEncountered:
            print("endEncountered")
            if self.inputStream != nil {
                self.inputStream.delegate = nil
                self.inputStream.remove(from: .current, forMode: .default)
                self.inputStream.close()
                self.inputStream = nil
            }
        case .errorOccurred:
            print("errorOccurred")
        case .hasSpaceAvailable:
            print("hasSpaceAvailable")
        case .openCompleted:
            print("openCompleted")
        default:
            break
    }
}

我正在獲取 stream 數據,但是當我嘗試使用 EZRecorder 將其保存為音頻文件時,我收到以下錯誤消息

[default]            CABufferList.h:184   ASSERTION FAILURE [(nBytes <= buf->mDataByteSize) != 0 is false]:

我懷疑當我為 EZRecorder 創建 AudioStreamBasicDescription 時可能會出現錯誤。

我知道這里可能還有其他錯誤,我很感激任何解決錯誤和改進代碼的建議。 謝謝

EZAudio 帶有 TPCircularBuffer - 使用它。

因為將緩沖區寫入文件是一種異步操作,所以這成為我們有一個生產者和一個消費者的循環緩沖區的一個很好的用例。

盡可能使用EZAudioUtilities

更新: EZRecorder write 期望 bufferSize 是要寫入的幀數而不是字節數

所以這樣的事情應該有效:

class StreamDelegateInstance: NSObject {
  private static let MaxReadSize = 2048
  private static let BufferSize = MaxReadSize * 4
  
  private var availableReadBytesPtr = UnsafeMutablePointer<Int32>.allocate(capacity: 1)
  private var availableWriteBytesPtr = UnsafeMutablePointer<Int32>.allocate(capacity: 1)
  
  private var ezRecorder: EZRecorder?
  
  private var buffer =  UnsafeMutablePointer<TPCircularBuffer>.allocate(capacity: 1)
  private var inputStream: InputStream?
  
  init(inputStream: InputStream? = nil) {
    self.inputStream = inputStream
    super.init()
    EZAudioUtilities.circularBuffer(buffer, withSize: Int32(StreamDelegateInstance.BufferSize))
    ensureWriteStream()
  }
  
  deinit {
    EZAudioUtilities.freeCircularBuffer(buffer)
    buffer.deallocate()
    availableReadBytesPtr.deallocate()
    availableWriteBytesPtr.deallocate()
    self.ezRecorder?.closeAudioFile()
    self.ezRecorder = nil
  }
  
  private func ensureWriteStream() {
    guard self.ezRecorder == nil else { return }
    // stores audio to temporary folder
    let audioOutputPath = NSTemporaryDirectory() + "audioOutput2.aiff"
    let audioOutputURL = URL(fileURLWithPath: audioOutputPath)
    print(audioOutputURL)
    
//    let audioStreamBasicDescription = AudioStreamBasicDescription(mSampleRate: 44100.0, mFormatID: kAudioFormatLinearPCM, mFormatFlags: kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked, mBytesPerPacket: 4, mFramesPerPacket: 1, mBytesPerFrame: 4, mChannelsPerFrame: 1, mBitsPerChannel: 32, mReserved: 1081729024)
    
//    EZAudioUtilities.audioBufferList(withNumberOfFrames: <#T##UInt32#>,
//                                     numberOfChannels: 1,
//                                     interleaved: true)
    
    // if you don't need a custom format, consider using EZAudioUtilities.m4AFormat
    let format = EZAudioUtilities.aiffFormat(withNumberOfChannels: 1,
                                             sampleRate: 44800)
    self.ezRecorder = EZRecorder.init(url: audioOutputURL,
                                      clientFormat: format,
                                      fileType: .AIFF)
  }
  
  private func writeStream() {
    let ptr = TPCircularBufferTail(buffer, availableWriteBytesPtr)
    
    // ensure we have non 0 bytes to write - which should always be true, but you may want to refactor things
    guard availableWriteBytesPtr.pointee > 0 else { return }
    let framesToWrite = availableWriteBytesPtr.pointee / 4 // sizeof(float)
    let audioBuffer =  AudioBuffer(mNumberChannels: 1,
                                   mDataByteSize: UInt32(availableWriteBytesPtr.pointee),
                                   mData: ptr)
    let audioBufferList = AudioBufferList(mNumberBuffers: 1, mBuffers: audioBuffer)
    
    self.ezRecorder?.appendData(from: &audioBufferList,
                                withBufferSize: UInt32(framesToWrite))
    
    TPCircularBufferConsume(buffer, framesToWrite * 4)
  }
}

extension StreamDelegateInstance: StreamDelegate {
  
  func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
    switch eventCode {
    case .hasBytesAvailable:
      // if the input stream has bytes available
      // return the actual number of bytes placed in the buffer;
      guard let ptr = TPCircularBufferHead(buffer, availableReadBytesPtr) else {
        print("couldn't get buffer ptr")
        break;
      }
      let bytedsToRead = min(Int(availableReadBytesPtr.pointee), StreamDelegateInstance.MaxReadSize)
      let mutablePtr = ptr.bindMemory(to: UInt8.self, capacity: Int(bytedsToRead))
      let bytesRead = self.inputStream?.read(mutablePtr,
                                             maxLength: bytedsToRead) ?? 0
      if bytesRead < 0 {
        //Stream error occured
        print(self.inputStream?.streamError! ?? "No bytes read")
        break
      } else if bytesRead == 0 {
        //EOF
        break
      }
      
      TPCircularBufferProduce(buffer, Int32(bytesRead))
      
      DispatchQueue.main.async { [weak self] in
        self?.writeStream()
      }
    case .endEncountered:
      print("endEncountered")
      if self.inputStream != nil {
        self.inputStream?.delegate = nil
        self.inputStream?.remove(from: .current, forMode: .default)
        self.inputStream?.close()
        self.inputStream = nil
      }
    case .errorOccurred:
      print("errorOccurred")
    case .hasSpaceAvailable:
      print("hasSpaceAvailable")
    case .openCompleted:
      print("openCompleted")
    default:
      break
    }
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM