iOS 模拟器上的 NDI 音频播放很棒,但 iPad 出现故障且断断续续

问题描述 投票:0回答:1

我正在尝试在 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
    }
ios swift avaudioplayer core-audio ndi
1个回答
0
投票

您会遇到音频丢失的情况,因为您播放的内容太“接近”(一个缓冲区的价值)到可用内容的末尾,并且播放器耗尽了音频数据。

相反,请等到您有更多音频,例如一秒钟,然后安排更多,甚至全部。

这称为“缓冲”。如果您曾经在线观看过视频,您将会经历过缓冲,甚至可能会看到超出当前播放位置的小条中的缓冲区大小。

© www.soinside.com 2019 - 2024. All rights reserved.