我正在使用Core Audio(带有快速包装器)播放一些音频样本(用于记录冲动的简短刺激)。我坚持使用核心音频,而不是较新的AVFoundation,因为我需要一些严格的时间安排,而且还需要多设备输入,而新的框架目前还没有涵盖(我经历了Apple代码请求,告诉他们我必须使用核心音频)。
我现在使用:
创建了一个非常简单的正弦波。func createSine()->[Float]{
var timeArray = makeArray(from: ((1.0/Float(sampleRate))*((-0.5)*Float(kernelLength))), to: ((1.0/Float(sampleRate))*((0.5)*Float(kernelLength))), increment: 1/sampleRate)
var sineArray = Array(repeating:0, count: timeArray.count)
for i in 0..<timeArray.count {
let x = 2 * Float.pi * 1000 * testTimeArray[i]
sineArray[i] = cos(x)
}
}
这会在sampleRate(在我的情况下为44,100Hz)中播放时,为1000Hz的正弦波创建一个Float(我相信是32位)数组。
如果我将其写入到wav文件中并进行播放,则会以预期的方式创建音调。
但是,我实际上想在应用程序中触发此声音。我已经安装了AUGraph并用音频单元填充了它。我创建了一个AURenderCallback,它在混音器的输入上被调用。每次,此输入需要信号调用此回调函数。
let genCallback: AURenderCallback = { (
inRefCon,
ioActionFlags,
inTimeStamp,
inBusNumber,
frameCount,
ioData) -> OSStatus in
let audioObject = unsafeBitCast(inRefCon, to: AudioEngine.self)
for buffer in UnsafeMutableAudioBufferListPointer(ioData!) {
var frames = buffer.mData!.assumingMemoryBound(to: Float.self)
var j = 0
for i in stride(from: 0, to: Int(frameCount), by: 2) {
frames[i] = Float((audioObject.Stimulus[j + audioObject.stimulusReadIndex]))
j += 1
}
audioObject.stimulusReadIndex += Int(frameCount/2)
}
}
return noErr;
}
其中audioObject.Stimulus是我的SineArray,而audioObject.stimulusReadIndex只是一个计数器,用于记住已在数组中读取的内容。
现在,这是我遇到麻烦的地方。如果启动AUGraph,我会听到正弦波,但也会听到很多谐波(噪声)。看来这不是正确的格式。
如果我将每组帧复制到另一个Array中以测试所写的内容正确无误,那么输出将与输入刺激匹配,因此不会丢失样本。
如果我去查看我的混音器单元的AudioStreamBasicDescription(因为它正在调用渲染回调,那么我将得到以下内容:
var audioFormat = AudioStreamBasicDescription()
audioFormat.mSampleRate = 44100.00;
audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 2;
audioFormat.mBitsPerChannel = 16;
audioFormat.mBytesPerPacket = 4;
audioFormat.mBytesPerFrame = 4;
audioFormat.mReserved = 0;
status = AudioUnitSetProperty(mixerUnit!,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
1,
&stimFormat,
UInt32(MemoryLayout<AudioStreamBasicDescription>.size));
checkStatus(status: status!);
所以这告诉了我几件事。它期望有两个通道,并且是交错的(因为不存在Non Interleaved Flag)。在我的回调函数中,我将帧跨步2来仅用样本填充第一个通道。如果我改从1开始,然后播放音频,则将其写入并播放到右侧。
采样率是正确的,但是比特率是16(Float不是),我可以看到有一个'isSignedInteger'标志,因此期望使用不同的格式。
所以现在,我尝试使用以下方法将我的Float Array转换为Int16:
for i in 0..<sineArray.count{
sineArray[i] = Int16.init((32767 * sweepSamples[i]))
}
尽管如此,这仍然会导致不同的噪声。如果检查数组,则可以确认结果是否在数据范围内签名为int16。
我看不到如何以核心音频期望的格式表示此数据。我尝试将格式标志更改为kAudioFormatFlagIsFloat,但仍然没有运气。
答案 0 :(得分:0)
鉴于您的[Float]数据,而不是kAudioFormatFlagIsSignedInteger和每个通道16位,您可能希望使用kAudioFormatFlagIsFloat和32位(每个数据包和帧8个字节)。
请注意,对于所有最新的iOS设备,本机音频格式均为32位浮点数,而不是16位int,使用本机(硬件?)采样率为48000,而不是44100。
此外,请注意,Apple建议不要在音频回调上下文中使用Swift(请参阅有关音频的2017或2018 WWDC会话),因此您的Audio Unit渲染回调可能应调用C函数来完成所有工作(任何触摸ioData或inRefCon)。
您可能还需要检查以确保数组索引不超出数组范围。