You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

macOS下如何从仅含输出通道的虚拟设备获取音频

音频捕获问题:仅输出虚拟设备无法捕获音频流

当虚拟设备同时具备输入和输出通道时,我可以通过以下代码捕获音频流并转发至耳机,但将虚拟设备设置为仅输出模式后,无法捕获音频流:

init() {
        
        let allDevices = simply.allDevices
        for item in allDevices {
            if item.name == loopback_name{
                loopbackDevice = item
            }
            if item.name == m2_output_name{
                M2OutDevice = item
            }
        }
                
        var inputAcd = AudioComponentDescription()
        inputAcd.componentType = kAudioUnitType_Output;
        inputAcd.componentSubType = kAudioUnitSubType_HALOutput;
        inputAcd.componentManufacturer = kAudioUnitManufacturer_Apple;
        let comp =  AudioComponentFindNext(nil, &inputAcd);
        AudioComponentInstanceNew(comp!, &inputUnit)
        AudioUnitInitialize(inputUnit!)
        
        
        var enableFlag = UInt32(1)
        var disableFlag = UInt32(0)
        let inputBus:UInt32 = 1
        let outputBus:UInt32 = 0
        checkErr(inputUnit!.setPropertyEnableIO(&enableFlag, .input,inputBus))
        checkErr(inputUnit!.setPropertyEnableIO(&disableFlag, .output,outputBus))
        
        var deviceid = loopbackDevice!.id
        checkErr(inputUnit!.setPropertyCurrentDevice(&deviceid,.output))

        var streamFormat1 = AudioStreamBasicDescription()
        checkErr(inputUnit!.getPropertyStreamFormat(&streamFormat1,.output,inputBus))
        var streamFormat2 = AudioStreamBasicDescription()
        checkErr(inputUnit!.getPropertyStreamFormat(&streamFormat2,.input,inputBus))
        debugPrint(streamFormat1,"===
",streamFormat2)
        inputUnit!.setPropertyStreamFormat(&streamFormat1, .output, inputBus)
        inputUnit!.setPropertyStreamFormat(&streamFormat1, .input, inputBus)

        
        var bufferSizeFrames : UInt32 = 0
        checkErr(inputUnit!.getPropertyBufferFrameSize(&bufferSizeFrames, .global))
        
        inputUnit!.setInputBuffer(&inputBuffer, streamFormat1, bufferSizeFrames)
        ringBuffer = CircularBuffer<Int32>(channelCount: Int(streamFormat2.mChannelsPerFrame),
                                       capacity: Int(bufferSizeFrames) * 2048)
        var callback = AURenderCallbackStruct()
        callback.inputProc = inputRenderCallback
        callback.inputProcRefCon = Unmanaged.passUnretained(self).toOpaque()

       AudioUnitSetProperty(inputUnit!,kAudioOutputUnitProperty_SetInputCallback,kAudioUnitScope_Input, inputBus,
            &callback,UInt32(MemoryLayout<AURenderCallbackStruct>.size))
        
        checkErr(NewAUGraph(&graph))
        checkErr(AUGraphInitialize(graph!))
        var outNode :AUNode = 0
        checkErr(graph!.getNode(&outNode))
        var mixerNode :AUNode = 0
        checkErr(graph!.getNode(&mixerNode,.mixer,.stereoMixer))
        
        checkErr(AUGraphOpen(graph!))
        checkErr(graph!.getUnit(outNode , &outputUnit))
        checkErr(graph!.getUnit(mixerNode, &mixerUnit))
        checkErr(AUGraphConnectNodeInput(graph!, mixerNode, 0, outNode, 0))

        var outputCallback = AURenderCallbackStruct()
        outputCallback.inputProc = outputRenderCallBack
        outputCallback.inputProcRefCon = Unmanaged.passUnretained(self).toOpaque()
        AUGraphSetNodeInputCallback(graph!, mixerNode, 0, &outputCallback)
        
        deviceid = M2OutDevice!.id
        outputUnit!.setPropertyCurrentDevice(&deviceid,.output)
        
        AudioOutputUnitStart(inputUnit!)
        checkErr(AUGraphStart(graph!))

    }
    
    // MARK: - inputUnit单元获取音频写入环形缓冲区 -
    let inputRenderCallback: AURenderCallback = {
      (inRefCon: UnsafeMutableRawPointer,
       ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
       inTimeStamp:  UnsafePointer<AudioTimeStamp>,
       inBusNumber: UInt32,
       inNumberFrames: UInt32,
       ioData: UnsafeMutablePointer<AudioBufferList>?) -> OSStatus in
        let inSelf = Unmanaged<IXIAudioTestOutputHelper>.fromOpaque(inRefCon).takeUnretainedValue()
        AudioUnitRender(inSelf.inputUnit!,
                        ioActionFlags,
                        inTimeStamp,
                        inBusNumber,
                        inNumberFrames,
                        inSelf.inputBuffer!.unsafeMutablePointer);
        let start = inTimeStamp.pointee.mSampleTime.int64Value
        let end = start + Int64(inNumberFrames)
        if (inSelf.inputLatestSampleTime == -1) {
            inSelf.inputLatestSampleTime = start
          return noErr
        }
        inSelf.inputLatestSampleTime = start
        _ = inSelf.ringBuffer!.write(from: inSelf.inputBuffer!.unsafeMutablePointer, start: start, end: end)
        debugPrint("inputRenderCallback")

      return noErr
    }
    
    let outputRenderCallBack: AURenderCallback = {
      (inRefCon: UnsafeMutableRawPointer,
       ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
       inTimeStamp:  UnsafePointer<AudioTimeStamp>,
       inBusNumber: UInt32,
       inNumberFrames: UInt32,
       ioData: UnsafeMutablePointer<AudioBufferList>?) -> OSStatus in
        let inSelf = Unmanaged<IXIAudioTestOutputHelper>.fromOpaque(inRefCon).takeUnretainedValue()
        let sampleTime = inTimeStamp.pointee.mSampleTime
        
        if (inSelf.outputLatestSampleTime == -1) {
            inSelf.outputLatestSampleTime = sampleTime.int64Value
            inSelf.computeOffset()
          return noErr
        } else {
            inSelf.outputLatestSampleTime = sampleTime.int64Value
        }
        let from = Int64(sampleTime + inSelf.sampleOffset - inSelf.safetyOffset)
        let to = from + Int64(inNumberFrames)

        _ = inSelf.ringBuffer!.read(into: ioData!, from: from, to: to)
      return noErr
    }

我尝试禁用inputUnit的输入、启用输出,并使用kAudioUnitProperty_SetRenderCallback捕获音频流但未成功,还出现错误提示ioData.mNumberBuffers=2, ASBD::NumberChannelStreams(output.GetStreamFormat())=1,表明捕获的音频为交错格式,而我设置的inputBuffer为非交错格式;设置非交错缓冲区后,捕获的音频流为空,相关代码如下:

var enableFlag = UInt32(1)
        var disableFlag = UInt32(0)
        let inputBus:UInt32 = 1
        let outputBus:UInt32 = 0
        checkErr(inputUnit!.setPropertyEnableIO(&disableFlag, .input,inputBus))
        checkErr(inputUnit!.setPropertyEnableIO(&enableFlag, .output,outputBus))
        
        var deviceid = loopbackDevice!.id
        checkErr(inputUnit!.setPropertyCurrentDevice(&deviceid,.output))

        var streamFormat1 = AudioStreamBasicDescription()
        checkErr(inputUnit!.getPropertyStreamFormat(&streamFormat1,.output,outputBus))
        var streamFormat2 = AudioStreamBasicDescription()
        checkErr(inputUnit!.getPropertyStreamFormat(&streamFormat2,.input,outputBus))
        debugPrint(streamFormat1,"===
",streamFormat2)
        inputUnit!.setPropertyStreamFormat(&streamFormat2, .output, outputBus)
        inputUnit!.setPropertyStreamFormat(&streamFormat2, .input, outputBus)

        
        var bufferSizeFrames : UInt32 = 0
        checkErr(inputUnit!.getPropertyBufferFrameSize(&bufferSizeFrames, .global))
        
        inputUnit!.setInputBuffer(&inputBuffer, streamFormat2, bufferSizeFrames)
        ringBuffer = CircularBuffer<Int32>(channelCount: Int(streamFormat2.mChannelsPerFrame),
                                       capacity: Int(bufferSizeFrames) * 2048)

        
        checkErr(NewAUGraph(&graph))
        checkErr(AUGraphInitialize(graph!))
        var outNode :AUNode = 0
        checkErr(graph!.getNode(&outNode))
        var mixerNode :AUNode = 0
        checkErr(graph!.getNode(&mixerNode,.mixer,.stereoMixer))
        
        checkErr(AUGraphOpen(graph!))
        checkErr(graph!.getUnit(outNode , &outputUnit))
        checkErr(graph!.getUnit(mixerNode, &mixerUnit))
        checkErr(AUGraphConnectNodeInput(graph!, mixerNode, 0, outNode, 0))

        var callback = AURenderCallbackStruct()
        callback.inputProc = inputRenderCallback
        callback.inputProcRefCon = Unmanaged.passUnretained(self).toOpaque()

        AudioUnitSetProperty(mixerUnit!,kAudioUnitProperty_SetRenderCallback,kAudioUnitScope_Output, 0,
                &callback,UInt32(MemoryLayout<AURenderCallbackStruct>.size))
        
        deviceid = M2OutDevice!.id
        outputUnit!.setPropertyCurrentDevice(&deviceid,.output)
        
        AudioOutputUnitStart(inputUnit!)
        checkErr(AUGraphStart(graph!))

内容的提问来源于stack exchange,提问作者user22997196

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 13:37:02