Audio Queue Services Player in Swift isn't calling callback - ios

I've been playing around with Audio Queue Services for about a week and I've written a swift version of from the Apple Audio Queue Services Guide.
I'm recording in Linear PCM and saving to disk with this method:
AudioFileCreateWithURL(url, kAudioFileWAVEType, &format,
AudioFileFlags.dontPageAlignAudioData.union(.eraseFile), &audioFileID)
My AudioQueueOutputCallback isn't being called even though I can verify that my bufferSize is seemingly large enough and that it's getting passed actual data. I'm not getting any OSStatus errors and it seems like everything should work. Theres very little in the way of Swift written AudioServiceQueues and should I get this working I'd be happy to open the rest of my code.
Any and all suggestions welcome!
class SVNPlayer: SVNPlayback {
var state: PlayerState!
private let callback: AudioQueueOutputCallback = { aqData, inAQ, inBuffer in
guard let userData = aqData else { return }
let audioPlayer = Unmanaged<SVNPlayer>.fromOpaque(userData).takeUnretainedValue()
guard audioPlayer.state.isRunning,
let queue = audioPlayer.state.mQueue else { return }
var buffer = inBuffer.pointee // dereference pointers
var numBytesReadFromFile: UInt32 = 0
var numPackets = audioPlayer.state.mNumPacketsToRead
var mPacketDescIsNil = audioPlayer.state.mPacketDesc == nil // determine if the packetDesc
if mPacketDescIsNil {
audioPlayer.state.mPacketDesc = AudioStreamPacketDescription(mStartOffset: 0, mVariableFramesInPacket: 0, mDataByteSize: 0)
}
AudioFileReadPacketData(audioPlayer.state.mAudioFile, false, &numBytesReadFromFile, // read the packet at the saved file
&audioPlayer.state.mPacketDesc!, audioPlayer.state.mCurrentPacket,
&numPackets, buffer.mAudioData)
if numPackets > 0 {
buffer.mAudioDataByteSize = numBytesReadFromFile
AudioQueueEnqueueBuffer(queue, inBuffer, mPacketDescIsNil ? numPackets : 0,
&audioPlayer.state.mPacketDesc!)
audioPlayer.state.mCurrentPacket += Int64(numPackets)
} else {
AudioQueueStop(queue, false)
audioPlayer.state.isRunning = false
}
}
init(inputPath: String, audioFormat: AudioStreamBasicDescription, numberOfBuffers: Int) throws {
super.init()
var format = audioFormat
let pointer = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) // get an unmananged reference to self
guard let audioFileUrl = CFURLCreateFromFileSystemRepresentation(nil,
inputPath,
CFIndex(strlen(inputPath)), false) else {
throw MixerError.playerInputPath }
var audioFileID: AudioFileID?
try osStatus { AudioFileOpenURL(audioFileUrl, AudioFilePermissions.readPermission, 0, &audioFileID) }
guard audioFileID != nil else { throw MixerError.playerInputPath }
state = PlayerState(mDataFormat: audioFormat, // setup the player state with mostly initial values
mQueue: nil,
mAudioFile: audioFileID!,
bufferByteSize: 0,
mCurrentPacket: 0,
mNumPacketsToRead: 0,
isRunning: false,
mPacketDesc: nil,
onError: nil)
var dataFormatSize = UInt32(MemoryLayout<AudioStreamBasicDescription>.stride)
try osStatus { AudioFileGetProperty(audioFileID!, kAudioFilePropertyDataFormat, &dataFormatSize, &state.mDataFormat) }
var queue: AudioQueueRef?
try osStatus { AudioQueueNewOutput(&format, callback, pointer, CFRunLoopGetCurrent(), CFRunLoopMode.commonModes.rawValue, 0, &queue) } // setup output queue
guard queue != nil else { throw MixerError.playerOutputQueue }
state.mQueue = queue // add to playerState
var maxPacketSize = UInt32()
var propertySize = UInt32(MemoryLayout<UInt32>.stride)
try osStatus { AudioFileGetProperty(state.mAudioFile, kAudioFilePropertyPacketSizeUpperBound, &propertySize, &maxPacketSize) }
deriveBufferSize(maxPacketSize: maxPacketSize, seconds: 0.5, outBufferSize: &state.bufferByteSize, outNumPacketsToRead: &state.mNumPacketsToRead)
let isFormatVBR = state.mDataFormat.mBytesPerPacket == 0 || state.mDataFormat.mFramesPerPacket == 0
if isFormatVBR { //Allocating Memory for a Packet Descriptions Array
let size = UInt32(MemoryLayout<AudioStreamPacketDescription>.stride)
state.mPacketDesc = AudioStreamPacketDescription(mStartOffset: 0,
mVariableFramesInPacket: state.mNumPacketsToRead,
mDataByteSize: size)
} // if CBR it stays set to null
for _ in 0..<numberOfBuffers { // Allocate and Prime Audio Queue Buffers
let bufferRef = UnsafeMutablePointer<AudioQueueBufferRef?>.allocate(capacity: 1)
let foo = state.mDataFormat.mBytesPerPacket * 1024 / UInt32(numberOfBuffers)
try osStatus { AudioQueueAllocateBuffer(state.mQueue!, foo, bufferRef) } // allocate the buffer
if let buffer = bufferRef.pointee {
AudioQueueEnqueueBuffer(state.mQueue!, buffer, 0, nil)
}
}
let gain: Float32 = 1.0 // Set an Audio Queue’s Playback Gain
try osStatus { AudioQueueSetParameter(state.mQueue!, kAudioQueueParam_Volume, gain) }
}
func start() throws {
state.isRunning = true // Start and Run an Audio Queue
try osStatus { AudioQueueStart(state.mQueue!, nil) }
while state.isRunning {
CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 0.25, false)
}
CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 1.0, false)
state.isRunning = false
}
func stop() throws {
guard state.isRunning,
let queue = state.mQueue else { return }
try osStatus { AudioQueueStop(queue, true) }
try osStatus { AudioQueueDispose(queue, true) }
try osStatus { AudioFileClose(state.mAudioFile) }
state.isRunning = false
}
private func deriveBufferSize(maxPacketSize: UInt32, seconds: Float64, outBufferSize: inout UInt32, outNumPacketsToRead: inout UInt32){
let maxBufferSize = UInt32(0x50000)
let minBufferSize = UInt32(0x4000)
if state.mDataFormat.mFramesPerPacket != 0 {
let numPacketsForTime: Float64 = state.mDataFormat.mSampleRate / Float64(state.mDataFormat.mFramesPerPacket) * seconds
outBufferSize = UInt32(numPacketsForTime) * maxPacketSize
} else {
outBufferSize = maxBufferSize > maxPacketSize ? maxBufferSize : maxPacketSize
}
if outBufferSize > maxBufferSize && outBufferSize > maxPacketSize {
outBufferSize = maxBufferSize
} else if outBufferSize < minBufferSize {
outBufferSize = minBufferSize
}
outNumPacketsToRead = outBufferSize / maxPacketSize
}
}
My player state struct is :
struct PlayerState: PlaybackState {
var mDataFormat: AudioStreamBasicDescription
var mQueue: AudioQueueRef?
var mAudioFile: AudioFileID
var bufferByteSize: UInt32
var mCurrentPacket: Int64
var mNumPacketsToRead: UInt32
var isRunning: Bool
var mPacketDesc: AudioStreamPacketDescription?
var onError: ((Error) -> Void)?
}

Instead of enqueuing an empty buffer, try calling your callback so it enqueues a (hopefully) full buffer. I'm unsure about the runloop stuff, but I'm sure you know what you're doing.

Related

Stream microphone audio in Swift 5

I have a working connection between 2 IOS-Devices. Sending a live-stream from one camera to the other device works. But now i want to send mic-audio and this does not work. I get no error, but i just receive "click"-noices.
I also can see, that bytes are transmitted, but i do not know, where the failure is.
Below you find the sending and the receiving functions. I also insert the streaming-function, which works fine for transmitting video.
sender:
func recorder() {
let settings : Dictionary = ["AVSampleRateKey" : 44100.0,
"AVNumberOfChannelsKey" : 1,
"AVFormatIDKey" : 1819304813,
"AVLinearPCMIsNonInterleaved" : 0,
"AVLinearPCMIsBigEndianKey" : 0,
"AVLinearPCMBitDepthKey" : 16,
"AVLinearPCMIsFloatKey" : 0]
audioFormat = AVAudioFormat.init(settings: settings)
audioEngine = AVAudioEngine.init()
audioEngine?.inputNode.installTap(onBus: 0, bufferSize: 4410, format: audioEngine?.inputNode.outputFormat(forBus: 0), block: {buffer, when in
let audioBuffer = buffer.audioBufferList.pointee.mBuffers
let data : Data = Data.init(bytes: audioBuffer.mData!, count: Int(audioBuffer.mDataByteSize))
let arraySize = Int(buffer.frameLength)
let samples = Array(UnsafeBufferPointer(start: buffer.floatChannelData![0], count:arraySize))
self.streamData(data: data, len: 4410)
})
// Start audio engine
self.audioEngine?.prepare()
do {
try self.audioEngine?.start()
}
catch {
NSLog("cannot start audio engine")
}
if(self.audioEngine?.isRunning == true){
NSLog("Audioengine is running")
}
}
sender streamData (working fine for e.g. video)
func streamData(data : Data, len : Int)
{
var baseCaseCondition : Bool = false
var _len : Int = len
var _byteIndex : Int = 0
func recursiveBlock(block: #escaping (()->Void)->Void) -> ()->Void {
return { block(recursiveBlock(block: block)) }
}
let aRecursiveBlock :()->Void = recursiveBlock {recurse in
baseCaseCondition = (data.count > 0 && _byteIndex < data.count) ? true : false
if ((baseCaseCondition)) {
_len = (data.count - _byteIndex) == 0 ? 1 : (data.count - _byteIndex) < len ? (data.count - _byteIndex) : len
NSLog("START | byteIndex: %lu/%lu writing len: %lu", _byteIndex, data.count, _len)
var bytes = [UInt8](repeating:0, count:_len)
data.copyBytes(to: &bytes, from: _byteIndex ..< _byteIndex+_len )
_byteIndex += (self.outputStream?.write(&bytes, maxLength: _len))!
NSLog("END | byteIndex: %lu/%lu wrote len: %lu", _byteIndex, data.count, _len)
recurse()
}
}
if (self.outputStream!.hasSpaceAvailable){
aRecursiveBlock();
}
}
receiver:
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
...
case Stream.Event.hasBytesAvailable:
var thePCMBuffer : AVAudioPCMBuffer = AVAudioPCMBuffer.init(pcmFormat: (self.audioEngine?.inputNode.outputFormat(forBus: 0))!, frameCapacity: AVAudioFrameCount(mlen))!
thePCMBuffer.frameLength = thePCMBuffer.frameCapacity
let channels = UnsafeBufferPointer(start: thePCMBuffer.floatChannelData, count: Int(thePCMBuffer.format.channelCount))
_ = mdata?.copyBytes(to: UnsafeMutableBufferPointer(start: channels[0], count: Int(thePCMBuffer.frameLength)))
if((self.player?.isPlaying) != nil){
DispatchQueue.global(qos: .background).async {
// Background Thread
DispatchQueue.main.async {
// Run UI Updates
self.player?.scheduleBuffer(thePCMBuffer, completionHandler: {
NSLog("Scheduled buffer")
NSLog("\(self.player!.isPlaying)")
let arraySize = Int(thePCMBuffer.frameLength)
let samples = Array(UnsafeBufferPointer(start: thePCMBuffer.floatChannelData![0], count:arraySize))
for sample in samples{
NSLog("\(sample)")
}
})
}
}
}
}
mdata = Data.init()
mlen = DATA_LENGTH
}
break;
...
}
Found the solution i guess. I tried with one simulator and one real device. Now i read, that there es a problem because of different sample-rates. Running on 2 devices (or 2 simulators) just works fine.

Swift AudioToolbox not playing back audio file nor filling audio buffer

I am trying to play back an audio file using AudioToolbox. I wrote this and swift based on an old objective C example on the apple website. It compiles and runs, however the callback function never gets triggered once CFRunLoop starts. (It gets called during set up, but I call it manually so that doesn't count.)
My understanding of how this is supposed to work is that when this line is called:
status = AudioQueueNewOutput(&dataFormat, callback, &aqData, CFRunLoopGetCurrent(), commonModes, 0, &queue)
It is supposed to create an AudioQueue object, place it inside CFRunLoop, set the callback function called "callback", then give me back a reference to the queue object. The callback function either lives inside the AudioQueue, which lives inside CFRunLoop, or the callback function lives in CFRunLoop directly. Not sure.
When I'm done setting up I call:
status = AudioQueueStart(aqData.mQueue!, nil)
which "starts" the queue.
Then I call:
repeat {
CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 0.1, false)
}
My understanding is that this is supposed to trigger the audio queue, which in turn calls my callback function. However, from this point on the callback function never gets hit. I and thinking there might be a way to inspect the audio queue, or perhaps inspect CFRunLoop. I might have made a mistake on one of the pointers somewhere.
The that result is the app plays nothing but silence.
let kNumberBuffers = 3;
var aqData = AQPlayerState()
var bufferLength : Float64 = 0.1
func playAudioFileWithToolbox(){
let bundle = Bundle.main
let permissions : AudioFilePermissions = .readPermission
let filePath = bundle.path(forResource: "dreams", ofType: "wav")!
var filePathArray = Array(filePath.utf8)
let filePathSize = filePath.count
let audioFileUrl = CFURLCreateFromFileSystemRepresentation(nil, &filePathArray, filePathSize, false)
var status = AudioFileOpenURL(audioFileUrl!, permissions, kAudioFileWAVEType, &aqData.mAudioFile)
if status != noErr{
print("ErrorOpeningAudioFileUrl")
}
var dataFormatSize:UInt32 = UInt32(MemoryLayout<AudioStreamBasicDescription>.size)
status = AudioFileGetProperty(aqData.mAudioFile!, kAudioFilePropertyDataFormat, &dataFormatSize, &aqData.mDataFormat)
if status != noErr{
print("Error getting AudioStreamBasicDescription")
}
var queue : AudioQueueRef? = aqData.mQueue
var dataFormat = aqData.mDataFormat
let commonModes = CFRunLoopMode.commonModes.rawValue
status = AudioQueueNewOutput(&dataFormat, callback, &aqData, CFRunLoopGetCurrent(), commonModes, 0, &queue)
if status == noErr{
aqData.mQueue = queue
} else {
print("TroubleSettingUpOutputQueue")
}
var maxPacketSize:UInt32 = 0;
var propertySize:UInt32 = UInt32(MemoryLayout.size(ofValue: maxPacketSize))
AudioFileGetProperty(aqData.mAudioFile!, kAudioFilePropertyPacketSizeUpperBound, &propertySize, &maxPacketSize)
var bufferByteSize = aqData.bufferByteSize
DeriveBufferSize(ASBDesc: &dataFormat, maxPacketSize: maxPacketSize, seconds: bufferLength, outBufferSize: &bufferByteSize, outNumPacketsToRead: &aqData.mNumPacketsToRead)
aqData.bufferByteSize = bufferByteSize
let isFormatVBR = aqData.mDataFormat.mBytesPerPacket == 0 || aqData.mDataFormat.mFramesPerPacket == 0
if isFormatVBR{
aqData.mPacketDescs = UnsafeMutablePointer<AudioStreamPacketDescription>.allocate(capacity: Int(aqData.mNumPacketsToRead))
} else {
aqData.mPacketDescs = nil
}
var cookieSize = UInt32(MemoryLayout.size(ofValue: UInt32.self))
let couldNotGetProperty = AudioFileGetPropertyInfo(aqData.mAudioFile!, kAudioFilePropertyMagicCookieData, &cookieSize, nil)
if couldNotGetProperty == 0 && cookieSize > 0{
var magicCookie = UnsafeMutableRawPointer.allocate(byteCount: Int(cookieSize), alignment: MemoryLayout<UInt32>.alignment)
status = AudioFileGetProperty(aqData.mAudioFile!, kAudioFilePropertyMagicCookieData, &cookieSize, &magicCookie)
if status != noErr{
print("Error:Failed to get magic cookie.")
}
AudioQueueSetProperty(aqData.mQueue!, kAudioQueueProperty_MagicCookie, magicCookie, cookieSize)
magicCookie.deallocate()
}
aqData.mCurrentPacket = 0
for i in 0..<kNumberBuffers{
var pointer = aqData.mBuffers?.advanced(by: i)
status = AudioQueueAllocateBuffer(aqData.mQueue!, aqData.bufferByteSize, &pointer)
if status != noErr{
print("Error allocating audio buffer.")
continue
}
var buffer = aqData.mBuffers![i]
callback(&aqData, aqData.mQueue!, &buffer) //I can imagine how this does anything when it is not running
}
//Set Volume
AudioQueueSetParameter(aqData.mQueue!, kAudioQueueParam_Volume, 0.5)//I have way bigger problems
//Start Playing
aqData.mIsRunning = true
status = AudioQueueStart(aqData.mQueue!, nil)
if status != noErr{
print("Error:Failed to start audio queue.")
}
repeat {
CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 0.1, false)
} while aqData.mIsRunning
CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 1, false)
}
private let callback: AudioQueueOutputCallback = { userData, inAQ, inBuffer in
var aqData = userData!.load(as: AQPlayerState.self) // 255
if !aqData.mIsRunning{ return } // 2
var numBytesReadFromFile : UInt32 = 0
var numPackets = aqData.mNumPacketsToRead
AudioFileReadPacketData(aqData.mAudioFile!, false, &numBytesReadFromFile, aqData.mPacketDescs, aqData.mCurrentPacket, &numPackets, inBuffer)
if (numPackets > 0) {
inBuffer.pointee.mAudioDataByteSize = numBytesReadFromFile
let packetCount = aqData.mPacketDescs!.pointee.mVariableFramesInPacket
AudioQueueEnqueueBuffer (
aqData.mQueue!,
inBuffer,
packetCount,
aqData.mPacketDescs
);
aqData.mCurrentPacket += Int64(numPackets)
} else {
AudioQueueStop (aqData.mQueue!,false)
aqData.mIsRunning = false
}
}
func DeriveBufferSize (ASBDesc: inout AudioStreamBasicDescription, maxPacketSize:UInt32, seconds:Float64, outBufferSize: inout UInt32,outNumPacketsToRead: inout UInt32) {
let maxBufferSize = 0x50000
let minBufferSize = 0x4000
if ASBDesc.mFramesPerPacket != 0 {
let numPacketsForTime = ASBDesc.mSampleRate / Float64(ASBDesc.mFramesPerPacket) * seconds
outBufferSize = UInt32(numPacketsForTime) * maxPacketSize
} else { // 9
outBufferSize = max(UInt32(maxBufferSize), maxPacketSize)
}
if outBufferSize > maxBufferSize && outBufferSize > maxPacketSize{ //10
outBufferSize = UInt32(maxBufferSize)
} else if outBufferSize < minBufferSize {
outBufferSize = UInt32(minBufferSize)
}
outNumPacketsToRead = outBufferSize / UInt32(maxPacketSize) // 12
}
}
struct AQPlayerState{
var mDataFormat:AudioStreamBasicDescription = AudioStreamBasicDescription()
var mQueue:AudioQueueRef?
var mBuffers:AudioQueueBufferRef? = UnsafeMutablePointer<AudioQueueBuffer>.allocate(capacity: 3)
var mAudioFile: AudioFileID?
var bufferByteSize:UInt32 = 0
var mCurrentPacket:Int64 = 0
var mNumPacketsToRead:UInt32 = 0
var mPacketDescs : UnsafeMutablePointer<AudioStreamPacketDescription>?
var mIsRunning : Bool = false
init(){
}
}

iOS AudioUnit recording at 8kHz sample rate yields in silence

I'm building a voip application where we need to record audio from the microphone and send it somewhere in 8kHz sample rate.
Right now I'm recording it in default sample rate, which in my case was always 44,1k. This is then manually converted to 8k using this algorithm.
This naive approach results in an "ok" quality but I think it would be much better using the native downsampling capabilities of the AudioUnit.
But when I change the sample rate property on the recording AudioUnit, it outputs just silent frames (~0.0) and I don't know why.
I've extracted the part of the app responsible for the sound.
It should record from microphone -> write to a ring buffer -> playback the data in the buffer:
RecordingUnit:
import Foundation
import AudioToolbox
import AVFoundation
class RecordingUnit : NSObject
{
public static let AudioPacketDataSize = 160
public static var instance: RecordingUnit!
public var micBuffers : AudioBufferList?;
public var OutputBuffer = RingBuffer<Float>(count: 1 * 1000 * AudioPacketDataSize);
public var currentAudioUnit: AudioUnit?
override init()
{
super.init()
RecordingUnit.instance = self
micBuffers = AudioBufferList(
mNumberBuffers: 1,
mBuffers: AudioBuffer(
mNumberChannels: UInt32(1),
mDataByteSize: UInt32(1024),
mData: UnsafeMutableRawPointer.allocate(byteCount: 1024, alignment: 1)))
}
public func start()
{
var acd = AudioComponentDescription(
componentType: OSType(kAudioUnitType_Output),
componentSubType: OSType(kAudioUnitSubType_VoiceProcessingIO),
componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
componentFlags: 0,
componentFlagsMask: 0)
let comp = AudioComponentFindNext(nil, &acd)
var err : OSStatus
AudioComponentInstanceNew(comp!, &currentAudioUnit)
var true_ui32: UInt32 = 1
guard AudioUnitSetProperty(currentAudioUnit!,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
1,
&true_ui32,
UInt32(MemoryLayout<UInt32>.size)) == 0 else {print ("could not enable IO for input "); return}
err = AudioUnitSetProperty(currentAudioUnit!,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output,
0,
&true_ui32,
UInt32(MemoryLayout<UInt32>.size))
guard err == 0 else {print ("could not enable IO for output "); return}
var sampleRate : Float64 = 8000
err = AudioUnitSetProperty(currentAudioUnit!,
kAudioUnitProperty_SampleRate,
kAudioUnitScope_Input,
0,
&sampleRate,
UInt32(MemoryLayout<Float64>.size))
guard err == 0 else {print ("could not set sample rate (error=\(err))"); return}
var renderCallbackStruct = AURenderCallbackStruct(inputProc: recordingCallback, inputProcRefCon: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
err = AudioUnitSetProperty(currentAudioUnit!,
AudioUnitPropertyID(kAudioOutputUnitProperty_SetInputCallback),
AudioUnitScope(kAudioUnitScope_Global),
1,
&renderCallbackStruct,
UInt32(MemoryLayout<AURenderCallbackStruct>.size))
guard err == 0 else {print("could not set input callback"); return}
guard AudioUnitInitialize(currentAudioUnit!) == 0 else {print("could not initialize recording unit"); return}
guard AudioOutputUnitStart(currentAudioUnit!) == 0 else {print("could not start recording unit"); return}
print("Audio Recording started")
}
let recordingCallback: AURenderCallback = { (inRefCon, ioActionFlags, inTimeStamp, inBusNumber, frameCount, ioData ) -> OSStatus in
let audioObject = RecordingUnit.instance!
var err: OSStatus = noErr
guard let au = audioObject.currentAudioUnit else {print("AudioUnit nil (recording)"); return 0}
err = AudioUnitRender(au, ioActionFlags, inTimeStamp, inBusNumber, frameCount, &audioObject.micBuffers!)
let bufferPointer = UnsafeMutableRawPointer(audioObject.micBuffers!.mBuffers.mData)
let dataArray = bufferPointer!.assumingMemoryBound(to: Float.self)
var frames = 0
var sum = Float(0)
for i in 0..<Int(frameCount) {
if dataArray[i] != Float.nan {
sum += dataArray[i]
audioObject.OutputBuffer.write(Float(dataArray[i]))
frames = frames+1
}
}
let average = sum/Float(frameCount)
print("recorded -> \(frames)/\(frameCount) -> average=\(average)")
return 0
}
public func stop()
{
if currentAudioUnit != nil { AudioUnitUninitialize(currentAudioUnit!) }
}
}
PlaybackUnit.swift:
import Foundation
import AudioToolbox
import AVFoundation
class PlaybackUnit : NSObject {
public static var instance : PlaybackUnit!
public var InputBuffer : RingBuffer<Float>?
public var currentAudioUnit: AudioUnit?
override init()
{
super.init()
PlaybackUnit.instance = self
}
public func start()
{
var acd = AudioComponentDescription(
componentType: OSType(kAudioUnitType_Output),
componentSubType: OSType(kAudioUnitSubType_VoiceProcessingIO),
componentManufacturer: OSType(kAudioUnitManufacturer_Apple),
componentFlags: 0,
componentFlagsMask: 0)
let comp = AudioComponentFindNext(nil, &acd)
var err = AudioComponentInstanceNew(comp!, &currentAudioUnit)
guard err == 0 else {print ("could not create a new AudioComponent instance"); return};
//set sample rate
var sampleRate : Float64 = 8000
AudioUnitSetProperty(currentAudioUnit!,
kAudioUnitProperty_SampleRate,
kAudioUnitScope_Input,
0,
&sampleRate,
UInt32(MemoryLayout<Float64>.size))
guard err == 0 else {print ("could not set sample rate "); return};
//register render callback
var outputCallbackStruct = AURenderCallbackStruct(inputProc: outputCallback, inputProcRefCon: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
err = AudioUnitSetProperty(currentAudioUnit!,
AudioUnitPropertyID(kAudioUnitProperty_SetRenderCallback),
AudioUnitScope(kAudioUnitScope_Input),
0,
&outputCallbackStruct,
UInt32(MemoryLayout<AURenderCallbackStruct>.size))
guard err == 0 else {print("could not set render callback"); return}
guard AudioUnitInitialize(currentAudioUnit!) == 0 else {print("could not initialize output unit"); return}
guard AudioOutputUnitStart(currentAudioUnit!) == 0 else {print("could not start output unit"); return}
print("Audio Output started")
}
let outputCallback: AURenderCallback = { (
inRefCon,
ioActionFlags,
inTimeStamp,
inBusNumber,
frameCount,
ioData ) -> OSStatus in
let ins = PlaybackUnit.instance
let audioObject = ins!
var err: OSStatus = noErr
var frames = 0
var average : Float = 0
if var ringBuffer = audioObject.InputBuffer {
var dataArray = ioData!.pointee.mBuffers.mData!.assumingMemoryBound(to: Float.self)
var i = 0
while i < frameCount {
if let v = ringBuffer.read() {
dataArray[i] = v
average += v
} else {
dataArray[i] = 0
}
i += 1
frames += 1
}
}
average = average / Float(frameCount)
print("played -> \(frames)/\(frameCount) => avarage: \(average)")
return 0
}
public func stop()
{
if currentAudioUnit != nil { AudioUnitUninitialize(currentAudioUnit!) }
}
}
ViewController:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var micPermission = false
private var micPermissionDispatchToken = 0
override func viewDidLoad() {
super.viewDidLoad()
let audioSession = AVAudioSession.sharedInstance()
if (micPermission == false) {
if (micPermissionDispatchToken == 0) {
micPermissionDispatchToken = 1
audioSession.requestRecordPermission({(granted: Bool)-> Void in
if granted {
self.micPermission = true
return
} else {
print("failed to grant microphone access!!")
}
})
}
}
if micPermission == false { return }
try! audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try! audioSession.setPreferredSampleRate(8000)
try! audioSession.overrideOutputAudioPort(.speaker)
try! audioSession.setPreferredOutputNumberOfChannels(1)
try! audioSession.setMode(AVAudioSessionModeVoiceChat)
try! audioSession.setActive(true)
let microphone = RecordingUnit()
let speakers = PlaybackUnit()
speakers.InputBuffer = microphone.OutputBuffer
microphone.start()
speakers.start()
}
}

How to play raw audio data from socket in Swift

I need to play raw audio data coming over socket in small chunks. I have read that I suppose to use circular buffer and found few solutions in Objective C, but couldn't made any of them to work, especially in Swift 3.
Can anyone help me?
First you implement ring Buffer like so.
public struct RingBuffer<T> {
private var array: [T?]
private var readIndex = 0
private var writeIndex = 0
public init(count: Int) {
array = [T?](repeating: nil, count: count)
}
/* Returns false if out of space. */
#discardableResult public mutating func write(element: T) -> Bool {
if !isFull {
array[writeIndex % array.count] = element
writeIndex += 1
return true
} else {
return false
}
}
/* Returns nil if the buffer is empty. */
public mutating func read() -> T? {
if !isEmpty {
let element = array[readIndex % array.count]
readIndex += 1
return element
} else {
return nil
}
}
fileprivate var availableSpaceForReading: Int {
return writeIndex - readIndex
}
public var isEmpty: Bool {
return availableSpaceForReading == 0
}
fileprivate var availableSpaceForWriting: Int {
return array.count - availableSpaceForReading
}
public var isFull: Bool {
return availableSpaceForWriting == 0
}
}
After that, you implement Audio Unit like so. ( modify if necessary)
class ToneGenerator {
fileprivate var toneUnit: AudioUnit? = nil
init() {
setupAudioUnit()
}
deinit {
stop()
}
func setupAudioUnit() {
// Configure the description of the output audio component we want to find:
let componentSubtype: OSType
#if os(OSX)
componentSubtype = kAudioUnitSubType_DefaultOutput
#else
componentSubtype = kAudioUnitSubType_RemoteIO
#endif
var defaultOutputDescription = AudioComponentDescription(componentType: kAudioUnitType_Output,
componentSubType: componentSubtype,
componentManufacturer: kAudioUnitManufacturer_Apple,
componentFlags: 0,
componentFlagsMask: 0)
let defaultOutput = AudioComponentFindNext(nil, &defaultOutputDescription)
var err: OSStatus
// Create a new instance of it in the form of our audio unit:
err = AudioComponentInstanceNew(defaultOutput!, &toneUnit)
assert(err == noErr, "AudioComponentInstanceNew failed")
// Set the render callback as the input for our audio unit:
var renderCallbackStruct = AURenderCallbackStruct(inputProc: renderCallback as? AURenderCallback,
inputProcRefCon: nil)
err = AudioUnitSetProperty(toneUnit!,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input,
0,
&renderCallbackStruct,
UInt32(MemoryLayout<AURenderCallbackStruct>.size))
assert(err == noErr, "AudioUnitSetProperty SetRenderCallback failed")
// Set the stream format for the audio unit. That is, the format of the data that our render callback will provide.
var streamFormat = AudioStreamBasicDescription(mSampleRate: Float64(sampleRate),
mFormatID: kAudioFormatLinearPCM,
mFormatFlags: kAudioFormatFlagsNativeFloatPacked|kAudioFormatFlagIsNonInterleaved,
mBytesPerPacket: 4 /*four bytes per float*/,
mFramesPerPacket: 1,
mBytesPerFrame: 4,
mChannelsPerFrame: 1,
mBitsPerChannel: 4*8,
mReserved: 0)
err = AudioUnitSetProperty(toneUnit!,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&streamFormat,
UInt32(MemoryLayout<AudioStreamBasicDescription>.size))
assert(err == noErr, "AudioUnitSetProperty StreamFormat failed")
}
func start() {
var status: OSStatus
status = AudioUnitInitialize(toneUnit!)
status = AudioOutputUnitStart(toneUnit!)
assert(status == noErr)
}
func stop() {
AudioOutputUnitStop(toneUnit!)
AudioUnitUninitialize(toneUnit!)
}
}
This is Fixed values
private let sampleRate = 16000
private let amplitude: Float = 1.0
private let frequency: Float = 440
/// Theta is changed over time as each sample is provided.
private var theta: Float = 0.0
private func renderCallback(_ inRefCon: UnsafeMutableRawPointer,
ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
inTimeStamp: UnsafePointer<AudioTimeStamp>,
inBusNumber: UInt32,
inNumberFrames: UInt32,
ioData: UnsafeMutablePointer<AudioBufferList>) -> OSStatus {
let abl = UnsafeMutableAudioBufferListPointer(ioData)
let buffer = abl[0]
let pointer: UnsafeMutableBufferPointer<Float32> = UnsafeMutableBufferPointer(buffer)
for frame in 0..<inNumberFrames {
let pointerIndex = pointer.startIndex.advanced(by: Int(frame))
pointer[pointerIndex] = sin(theta) * amplitude
theta += 2.0 * Float(M_PI) * frequency / Float(sampleRate)
}
return noErr
}
You need to put data in a Circular buffer and then play the sound.

Translate Objective - C Introduction to AudioUnits into Swift

I alredy managed to translate this code so the render callback gets called:
http://www.cocoawithlove.com/2010/10/ios-tone-generator-introduction-to.html
I'm sure that my render callback method is not right implemented, because I either get no sound at all or I get pretty awful noise from my headphones.
I also don't see a connection between my audioSession in viewDidLoad and the rest of the code.
Is there anyone who can help me out with this?
private func performRender(
inRefCon: UnsafeMutablePointer<Void>,
ioActionFlags: UnsafeMutablePointer<AudioUnitRenderActionFlags>,
inTimeStamp: UnsafePointer<AudioTimeStamp>,
inBufNumber: UInt32,
inNumberFrames: UInt32,
ioData: UnsafeMutablePointer<AudioBufferList>) -> OSStatus
{
// get object
let vc = unsafeBitCast(inRefCon, ViewController.self)
print("callback")
let thetaIncrement = 2.0 * M_PI * vc.kFrequency / vc.kSampleRate
var theta = vc.theta
// var sinValues = [Int32]()
let amplitude : Double = 0.25
let abl = UnsafeMutableAudioBufferListPointer(ioData)
for buffer in abl
{
let val : Int32 = Int32((sin(theta) * amplitude))
// sinValues.append(val)
theta += thetaIncrement
memset(buffer.mData, val, Int(buffer.mDataByteSize))
}
vc.theta = theta
return noErr
}
class ViewController: UIViewController
{
let kSampleRate : Float64 = 44100
let kFrequency : Double = 440
var theta : Double = 0
private var toneUnit = AudioUnit()
private let kInputBus = AudioUnitElement(1)
private let kOutputBus = AudioUnitElement(0)
#IBAction func tooglePlay(sender: UIButton)
{
if(toneUnit != nil)
{
AudioOutputUnitStop(toneUnit)
AudioUnitInitialize(toneUnit)
AudioComponentInstanceDispose(toneUnit)
toneUnit = nil
}
else
{
createToneUnit()
var err = AudioUnitInitialize(toneUnit)
assert(err == noErr, "error initializing audiounit!")
err = AudioOutputUnitStart(toneUnit)
assert(err == noErr, "error starting audiooutput unit!")
}
}
func createToneUnit()
{
var defaultOutputDescription = AudioComponentDescription(
componentType: kAudioUnitType_Output,
componentSubType: kAudioUnitSubType_RemoteIO,
componentManufacturer: kAudioUnitManufacturer_Apple,
componentFlags: 0,
componentFlagsMask: 0)
let defaultOutput = AudioComponentFindNext(nil,&defaultOutputDescription)
let fourBytesPerFloat : UInt32 = 4
let eightBitsPerByte : UInt32 = 8
var err = AudioComponentInstanceNew(defaultOutput, &toneUnit)
assert(err == noErr, "error setting audio component instance!")
var input = AURenderCallbackStruct(inputProc: performRender, inputProcRefCon: UnsafeMutablePointer(unsafeAddressOf(self)))
err = AudioUnitSetProperty(toneUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, kOutputBus, &input, UInt32(sizeof(AURenderCallbackStruct)))
assert(err == noErr, "error setting render callback!")
var streamFormat = AudioStreamBasicDescription(
mSampleRate: kSampleRate,
mFormatID: kAudioFormatLinearPCM,
mFormatFlags: kAudioFormatFlagsNativeFloatPacked,
mBytesPerPacket: fourBytesPerFloat,
mFramesPerPacket: 1,
mBytesPerFrame: fourBytesPerFloat,
mChannelsPerFrame: 1,
mBitsPerChannel: fourBytesPerFloat*eightBitsPerByte,
mReserved: 0)
err = AudioUnitSetProperty(toneUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, kInputBus, &streamFormat, UInt32(sizeof(AudioStreamBasicDescription)))
assert(err == noErr, "error setting audiounit property!")
}
override func viewDidLoad()
{
super.viewDidLoad()
let audioSession = AVAudioSession.sharedInstance()
do
{
try audioSession.setCategory(AVAudioSessionCategoryPlayback)
}
catch
{
print("Audio session setCategory failed")
}
do
{
try audioSession.setPreferredSampleRate(kSampleRate)
}
catch
{
print("Audio session samplerate error")
}
do
{
try audioSession.setPreferredIOBufferDuration(0.005)
}
catch
{
print("Audio session bufferduration error")
}
do
{
try audioSession.setActive(true)
}
catch
{
print("Audio session activate failure")
}
}
vc.theta isn't being incremented
memset only takes a byte's worth of val
the AudioUnit expects floats, but you're storing Int32s
the range of the audio data looks funny too - why not keep it in the range [-1, 1]?
there's no need to constrain theta either, sin can do that fine.
Are you sure this used to work in objective-c?

Resources