在 IOS 中使用 AUGraph 在 AudioUnit 中播放立体声

Play stereo sound in AudioUnit using AUGraph in IOS

我想在 ios 中使用 AUGraph 使用 Audiounit 播放立体声,但我面临的问题是“当我想在 48000 上以立体声模式播放时,我的声音播放速度很快采样率”。但它适用于单声道声音(单声道)。

这是我的代码。

import Combine
import AudioUnit
import Foundation
import AudioToolbox
import AVFoundation

@objc protocol AudioUnitDataChannelPlayoutDelegate
{
    func performInput(
        _ ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
        inTimeStamp: UnsafePointer<AudioTimeStamp>,
        inBufNumber: UInt32,
        inNumberFrames: UInt32,
        ioData: UnsafeMutablePointer<AudioBufferList> ) -> OSStatus
}

private let AudioController_InputCallback: AURenderCallback =
{
    (
        inRefCon,
        ioActionFlags,
        inTimeStamp,
        inBufNumber,
        inNumberFrames,
        ioData
    )-> OSStatus in
    
    let delegate = unsafeBitCast(inRefCon, to: AudioUnitDataChannelPlayoutDelegate.self)
    
    let result = delegate.performInput(ioActionFlags,
                                       inTimeStamp: inTimeStamp,
                                       inBufNumber: inBufNumber,
                                       inNumberFrames: inNumberFrames,
                                       ioData: ioData!)
    return noErr
}

class AudioUnitDataChannelPlayout
{
    
    private static var instance: AudioUnitDataChannelPlayout?
    
    public class var sharedInstance: AudioUnitDataChannelPlayout
    {
        if instance == nil
        {
            instance = AudioUnitDataChannelPlayout()
        }
        return instance!
    }
    
    static let audioBuffer = CircularBuffer(size: 25003904)
   
    let kInputBus: UInt32 = 1
    let kOutputBus: UInt32 = 0
    var status: OSStatus?
    var flag: UInt32 = 1
    
    var ioFormat = CAStreamBasicDescription(
        sampleRate: Double(48000.0),
        numChannels: 2,
        pcmf: .int16,
        isInterleaved: true )
    
    var timePitchFormat = CAStreamBasicDescription(
        sampleRate: Double(48000.0),
        numChannels: 1,  // if chnges to 2 nothing happens
        pcmf: .float32,
        isInterleaved: false ) // if change to true then error occurs
    
    var graph: AUGraph?
    var firstConverterNode: AUNode = 0
    var timePitchNode: AUNode = 0
    var secondConverterNode: AUNode = 0
    var outputNode: AUNode = 0
    var firstConverterUnit: AudioUnit?
    var timePitchUnit: AudioUnit?
    var secondConverterUnit: AudioUnit?
    var outputUnit: AudioUnit?
    
    
    init()
    {
        configureAudioSession()
        setupRecordingUnit()
        playAudio()
    }
    
    private func setupRecordingUnit()
    {
        check(error: NewAUGraph(&graph), description: "Failed to create AUGraph")
        
        var output_desc = AudioComponentDescription(
            componentType: OSType(kAudioUnitType_Output),
            componentSubType: OSType(kAudioUnitSubType_VoiceProcessingIO),
            componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
            componentFlags: 0,
            componentFlagsMask: 0 )
        
        var converter_desc = AudioComponentDescription(
            componentType: OSType(kAudioUnitType_FormatConverter),
            componentSubType: OSType(kAudioUnitSubType_AUConverter),
            componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
            componentFlags: 0,
            componentFlagsMask: 0 )
        
        var varispeed_desc = AudioComponentDescription(
            componentType: OSType(kAudioUnitType_FormatConverter),
            componentSubType: OSType(kAudioUnitSubType_NewTimePitch),
            componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
            componentFlags: 0,
            componentFlagsMask: 0 )
        
        check(error: AUGraphAddNode(graph!, &output_desc, &outputNode), description: "Failed to Add Node Audio Node")
        
        check(error: AUGraphAddNode(graph!, &converter_desc, &firstConverterNode), description: "Failed to Add converter node")
        check(error: AUGraphAddNode(graph!, &varispeed_desc, &timePitchNode), description: "Failed to Add Node varispeed Node")
        check(error: AUGraphAddNode(graph!, &converter_desc, &secondConverterNode), description: "Failed to Add converter node")
        
        check(error: AUGraphOpen(graph!), description: "Failed to open AUGraph")
        
        check(error: AUGraphNodeInfo(graph!, outputNode, nil, &outputUnit), description: "Failed to get Info of audioUnit")
        check(error: AUGraphNodeInfo(graph!, firstConverterNode, nil, &firstConverterUnit), description: "Failed to get Info of converterUnit")
        check(error: AUGraphNodeInfo(graph!, timePitchNode, nil, &timePitchUnit), description: "Failed to get Info of VarispeedUnit")
        check(error: AUGraphNodeInfo(graph!, secondConverterNode, nil, &secondConverterUnit), description: "Failed to get Info of converterUnit")
        
        check(error: AudioUnitSetProperty(outputUnit!,kAudioOutputUnitProperty_EnableIO,kAudioUnitScope_Output,kOutputBus,&flag,MemoryLayoutStride.SizeOf32(flag)),
              description: "Failed to set enable IO for Playing.")
        
        check(error: AudioUnitSetProperty(firstConverterUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &ioFormat, MemoryLayoutStride.SizeOf32(ioFormat)), description: "Failed to set property of firstConverter Unit")
        
        check(error: AudioUnitSetProperty(firstConverterUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kOutputBus, &timePitchFormat, MemoryLayoutStride.SizeOf32(timePitchFormat)), description: "Failed to set property of firstConverter Unit 1")
        
        check(error: AudioUnitSetProperty(timePitchUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &timePitchFormat, MemoryLayoutStride.SizeOf32(timePitchFormat)), description: "Failed to set property of varispeed unit")
        
        check(error: AudioUnitSetProperty(timePitchUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kOutputBus, &timePitchFormat, MemoryLayoutStride.SizeOf32(timePitchFormat)), description: "Failed to set property of Varispeed Unit 1")
        
        check(error: AudioUnitSetProperty(secondConverterUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &timePitchFormat, MemoryLayoutStride.SizeOf32(timePitchFormat)), description: "Failed to set property of firstConverter Unit")
        
        check(error: AudioUnitSetProperty(secondConverterUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kOutputBus, &ioFormat, MemoryLayoutStride.SizeOf32(ioFormat)), description: "Failed to set property of firstConverter Unit 1")
        
        
        check(error: AudioUnitSetProperty(outputUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, kOutputBus, &ioFormat, MemoryLayoutStride.SizeOf32(ioFormat)), description: "Failed to set property of Output Unit")
        AUGraphConnectNodeInput(graph!, firstConverterNode, 0, timePitchNode, 0)
        AUGraphConnectNodeInput(graph!, timePitchNode, 0, secondConverterNode, 0)
        AUGraphConnectNodeInput(graph!, secondConverterNode, 0, outputNode, 0)
        
        
        var recordingCallback = AURenderCallbackStruct(
            inputProc: AudioController_InputCallback,
            inputProcRefCon: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) )
        
        check(error: AUGraphSetNodeInputCallback(graph!, firstConverterNode, 0, &recordingCallback), description: "Failed to set inPutCallback")
        
        AUGraphInitialize(graph!)
    }
    
    func check(error: OSStatus, description: String)
    {
        if error != noErr
        {
            fatalError("\(description) : \(error)")
        }
    }
    
    func startSpeakerUnit()
    {
        check(error: AUGraphStart(graph!), description: "Error Failed to start AUGraph")
        print("initializing the speaker unit")
    }
    
    func stopSpeakerUnit()
    {
        check(error: AUGraphStop(graph!), description: "Error Failed to stop AUGraph")
        AudioUnitDataChannelPlayout.instance = nil
    }

    private func playAudio()
    {
        AudioUnitSetParameter(self.timePitchUnit!, kNewTimePitchParam_Rate, kAudioUnitScope_Global,0, AudioUnitParameterValue(1.0), 0)
    }
    
}

extension AudioUnitDataChannelPlayout: AudioUnitDataChannelPlayoutDelegate
{
    func performInput(_ ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>, inTimeStamp: UnsafePointer<AudioTimeStamp>, inBufNumber: UInt32, inNumberFrames: UInt32, ioData: UnsafeMutablePointer<AudioBufferList>) -> OSStatus
    {
        
        let playbackPointer = ioData[0].mBuffers
        
        print("SECOND POINTER: \(playbackPointer)")

        let bytesToCopy = ioData[0].mBuffers.mDataByteSize

        
        var bufferTail = AudioUnitDataChannelPlayout.audioBuffer.getTail()
        
        print(AudioUnitDataChannelPlayout.audioBuffer.getAvailableBytes())

        let bytesToWrite = min(bytesToCopy, AudioUnitDataChannelPlayout.audioBuffer.getAvailableBytes())
        
        memcpy(playbackPointer.mData, bufferTail, Int(bytesToWrite))
        
        print("playing...\(bytesToCopy)")
        AudioUnitDataChannelPlayout.audioBuffer.consume(samples: bytesToWrite)
        
        return noErr
    }
    
}

在这个class中,我只是播放立体声的波形文件。在这方面的任何帮助将不胜感激。

上面写的我的播放器代码没有错误。但在快速模式下播放音频的主要原因是,在将我的数据插入播放器时,我正在提供立体声数据但将其视为单声道(因为我只填充了一半的帧数)。我在分配内存时将帧数乘以通道数,结果成功了。