我正在尝试在 iOS 上实现 NDI 音频接收器。在 iOS 模拟器上听起来很棒,但在 iPad(iPad Air 2?)上有一些卡顿和故障。我很确定这与缓冲区播放有关,但不确定如何修复它或出了什么问题。
这是我用于播放的 NDI 管理器类
import Foundation
import AVFoundation
class NDIManager {
static let shared = NDIManager()
private init() {}
private var ndiFinderInstance: OpaquePointer?
private var ndiReceiverInstance: OpaquePointer?
private var audioEngine = AVAudioEngine()
private var playerNode = AVAudioPlayerNode()
private var format: AVAudioFormat!
private var buffers: [AVAudioPCMBuffer] = []
private var isPlayerStarted = false
private let bufferQueue = DispatchQueue(label: "com.ndimanager.bufferQueue")
private var lastResetTime: TimeInterval = 0
private let bufferQueueLimit = 50 // Increase limit for smoother playback
// Setup NDI Finder
func setupNDIFinder() -> Bool {
cleanupFinder()
var finderConfig = NDIlib_find_create_t()
finderConfig.show_local_sources = true
ndiFinderInstance = NDIlib_find_create_v2(&finderConfig)
return ndiFinderInstance != nil
}
// List available NDI sources
func listNDISources() -> [NDIlib_source_t] {
guard let finder = ndiFinderInstance else { return [] }
var sourceCount: UInt32 = 0
let sourcesPointer = NDIlib_find_get_current_sources(finder, &sourceCount)
guard let sources = sourcesPointer else { return [] }
return Array(UnsafeBufferPointer(start: sources, count: Int(sourceCount)))
}
// Automatically connect to "vMix Audio - Headphones" source if available
public func connectToHeadphones() {
if !setupNDIFinder() {
print("Failed to set up NDI finder.")
return
}
let targetName = "vMix Audio - Headphones"
DispatchQueue.global().async {
while true {
let sources = self.listNDISources()
if sources.isEmpty {
print("No NDI sources found. Retrying...")
sleep(1)
continue
}
for source in sources {
let sourceName = String(cString: source.p_ndi_name)
print("Available source: \(sourceName)")
if sourceName.contains(targetName) {
DispatchQueue.main.async {
print("Found matching NDI source: \(sourceName)")
if self.setupNDIReceiver(withSource: source) {
print("Successfully connected to NDI source: \(sourceName)")
} else {
print("Failed to connect to NDI source: \(sourceName)")
}
}
return
}
}
print("No NDI source with name containing '\(targetName)' found. Retrying...")
sleep(1)
}
}
}
// Setup NDI receiver with the given source
private func setupNDIReceiver(withSource source: NDIlib_source_t) -> Bool {
cleanupReceiver()
var recvConfig = NDIlib_recv_create_v3_t()
recvConfig.source_to_connect_to = source
recvConfig.color_format = NDIlib_recv_color_format_BGRX_BGRA
recvConfig.bandwidth = NDIlib_recv_bandwidth_highest
recvConfig.allow_video_fields = true
ndiReceiverInstance = NDIlib_recv_create_v3(&recvConfig)
guard ndiReceiverInstance != nil else {
print("Failed to create NDI receiver.")
return false
}
setupAudioEngine()
DispatchQueue.global().async {
self.collectAndPlayAudioFrames()
}
return true
}
func setupAudioEngine() {
format = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 48000, channels: 2, interleaved: false)
audioEngine.attach(playerNode)
audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: format)
do {
try audioEngine.start()
print("Audio engine started.")
} catch {
print("Audio engine failed to start: \(error)")
}
}
private func collectAndPlayAudioFrames() {
guard let receiver = ndiReceiverInstance else {
print("NDI receiver instance is nil.")
return
}
while true {
var audioFrame = NDIlib_audio_frame_v2_t()
let captureResult = NDIlib_recv_capture_v2(receiver, nil, &audioFrame, nil, 5000)
if captureResult == NDIlib_frame_type_audio {
let pcmData = Data(bytes: audioFrame.p_data!, count: Int(audioFrame.no_samples * audioFrame.no_channels) * MemoryLayout<Float>.size)
streamPCMChunk(pcmData, isFirst: buffers.isEmpty)
NDIlib_recv_free_audio_v2(receiver, &audioFrame)
}
}
}
func streamPCMChunk(_ floatPCMData: Data, isFirst: Bool = false) {
bufferQueue.async { [weak self] in
guard let self = self else { return }
// Reset buffers only when absolutely necessary
let currentTime = Date().timeIntervalSince1970
if isFirst && !self.isPlayerStarted && (currentTime - self.lastResetTime > 2.0) {
self.resetBuffers()
self.lastResetTime = currentTime
}
guard let buffer = self.createPCMBuffer(from: floatPCMData) else {
print("Failed to create PCM buffer")
return
}
// Add buffer to queue if below the limit
if self.buffers.count < self.bufferQueueLimit {
self.buffers.append(buffer)
} else {
print("Buffer queue limit reached; dropping buffer.")
}
if !self.isPlayerStarted {
print("Starting playback.")
self.playerNode.play()
self.isPlayerStarted = true
}
if self.buffers.count == 1 {
self.scheduleNextBuffer()
}
}
}
private func scheduleNextBuffer() {
bufferQueue.async { [weak self] in
guard let self = self else { return }
guard !self.buffers.isEmpty else {
self.playerNode.stop()
self.isPlayerStarted = false
return
}
let buffer = self.buffers.removeFirst()
self.playerNode.scheduleBuffer(buffer) {
self.scheduleNextBuffer()
}
}
}
private func createPCMBuffer(from data: Data) -> AVAudioPCMBuffer? {
let frameCount = data.count / MemoryLayout<Float>.size / Int(format.channelCount)
guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(frameCount)) else {
return nil
}
buffer.frameLength = AVAudioFrameCount(frameCount)
data.withUnsafeBytes { floatBytes in
if let floatPointer = floatBytes.baseAddress?.assumingMemoryBound(to: Float.self) {
buffer.floatChannelData?[0].assign(from: floatPointer, count: frameCount)
buffer.floatChannelData?[1].assign(from: floatPointer.advanced(by: frameCount), count: frameCount)
}
}
return buffer
}
private func resetBuffers() {
bufferQueue.async { [weak self] in
self?.buffers.removeAll()
self?.playerNode.stop()
self?.playerNode.reset()
self?.isPlayerStarted = false
}
}
func cleanupFinder() {
if let finder = ndiFinderInstance {
NDIlib_find_destroy(finder)
ndiFinderInstance = nil
}
}
func cleanupReceiver() {
if let receiver = ndiReceiverInstance {
NDIlib_recv_destroy(receiver)
ndiReceiverInstance = nil
}
audioEngine.stop()
playerNode.stop()
isPlayerStarted = false
}
}
这是 iOS 模拟器的一些控制台
AddInstanceForFactory: No factory registered for id <CFUUID 0x60000262c8e0> F8BB1C28-BAE8-11D6-9C31-00039315CD46
AudioConverter.cpp:1007 Failed to create a new in process converter -> from 1 ch, 48000 Hz, Float32 to 0 ch, 0 Hz, with status -50
AudioConverter.cpp:1007 Failed to create a new in process converter -> from 1 ch, 48000 Hz, Float32 to 0 ch, 0 Hz, with status -50
AudioConverter.cpp:1007 Failed to create a new in process converter -> from 1 ch, 48000 Hz, Float32 to 0 ch, 0 Hz, with status -50
AudioConverter.cpp:1007 Failed to create a new in process converter -> from 1 ch, 48000 Hz, Float32 to 0 ch, 0 Hz, with status -50
AQMEIO_HAL.cpp:893 kAudioDevicePropertyMute returned err 2003332927
Audio engine started.
Successfully connected to NDI source: JUDITHHABERF36A (vMix Audio - Headphones)
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Buffers reset and playerNode stopped.
Starting playerNode playback.
Buffer is empty; stopping playback.
Finished playing buffer. Scheduling next buffer.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Buffers reset and playerNode stopped.
Starting playerNode playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Requesting XML data from vMix...
Not connected or output stream unavailable
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
这是物理 iPad 的控制台
Found matching NDI source: JUDITHHABERF36A (vMix Audio - Headphones)
Audio engine started.
Successfully connected to NDI source: JUDITHHABERF36A (vMix Audio - Headphones)
Requesting XML data from vMix...
Not connected or output stream unavailable
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Buffers reset and playerNode stopped.
Starting playerNode playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Buffer is empty; stopping playback.
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Finished playing buffer. Scheduling next buffer.
Buffer is empty; stopping playback.
Buffer is empty; stopping playback.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Starting playerNode playback.
Buffers reset and playerNode stopped.
Buffer is empty; stopping playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
Starting playerNode playback.
Audio frame received: sample_rate = 48000, channels = 2, samples = 960
更新该建议有效!这是更新的缓冲区和流代码
func streamPCMChunk(_ floatPCMData: Data, isFirst: Bool = false) {
bufferQueue.async { [weak self] in
guard let self = self else { return }
// Reset buffers only once at the start of the stream
if isFirst && !self.isPlayerStarted {
self.resetBuffers()
}
// Create PCM buffer
guard let buffer = self.createPCMBuffer(from: floatPCMData) else {
print("Failed to create PCM buffer")
return
}
self.buffers.append(buffer)
// Start playback when enough buffers are preloaded
if !self.isPlayerStarted && self.buffers.count >= self.buffersToScheduleAtOnce {
self.playerNode.play()
self.isPlayerStarted = true
print("Player started with \(self.buffers.count) preloaded buffers.")
}
// Schedule buffers if the player is active
if self.isPlayerStarted {
self.scheduleNextBuffer()
}
}
}
private func scheduleNextBuffer() {
bufferQueue.async { [weak self] in
guard let self = self else { return }
// Ensure there are buffers to schedule
guard !self.buffers.isEmpty else {
print("Buffer underflow: no buffers to schedule.")
return
}
// Remove excess buffers if the queue is too large
if self.buffers.count > self.maxBufferQueueSize {
let excess = self.buffers.count - self.maxBufferQueueSize
self.buffers.removeFirst(excess)
print("Removed \(excess) excess buffers. Remaining queue length: \(self.buffers.count)")
}
// Schedule multiple buffers
let buffersToSchedule = min(self.buffersToScheduleAtOnce, self.buffers.count)
for _ in 0..<buffersToSchedule {
let buffer = self.buffers.removeFirst()
self.playerNode.scheduleBuffer(buffer) {
self.bufferQueue.async {
self.scheduleNextBuffer()
}
}
}
print("Scheduled \(buffersToSchedule) buffers for playback. Remaining queue length: \(self.buffers.count)")
}
}
private func createPCMBuffer(from data: Data) -> AVAudioPCMBuffer? {
let frameCount = data.count / MemoryLayout<Float>.size / Int(format.channelCount)
guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(frameCount)) else {
return nil
}
buffer.frameLength = AVAudioFrameCount(frameCount)
data.withUnsafeBytes { floatBytes in
if let floatPointer = floatBytes.baseAddress?.assumingMemoryBound(to: Float.self) {
buffer.floatChannelData?[0].assign(from: floatPointer, count: frameCount)
buffer.floatChannelData?[1].assign(from: floatPointer.advanced(by: frameCount), count: frameCount)
}
}
return buffer
}