Search code examples
iosswiftaudiounitplayingaugraph

Play stereo sound in AudioUnit using AUGraph in IOS


I want to play stereo sounds using Audiounit using AUGraph in ios but the problem that I am facing is "My sound is playing fast when i want to play in stereo mode on 48000 samplings rate".But it works fine for mono sounds(Single channel).

Here is my code.

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
    }
    
}

In this class, I am just playing wave files of stereo sounds. Any help in this regard will be highly appreciated.


Solution

  • There is no mistake in my Player code that is written up there. But the main reason for playing audio in fast mode was that while inserting my data into the player, I was giving stereo data but treated it as mono(As I was filling only half the number of frames). I multiplied the number of frames with the number of channels while allocating the memory and it did the trick.