音频流格式和数据类型与 Core Audio 的混淆
Confusion With Audio Stream Formats and Data Types with Core Audio
我正在使用 Core Audio(带有 swift 包装器)播放一些音频样本(用于记录冲动的短刺激)。我坚持使用核心音频而不是较新的 AVFoundation,因为我需要一些严格的时间和多设备输入,而较新的框架目前还没有涵盖(我通过苹果代码请求让他们告诉我我必须使用核心音频)。
我现在已经使用以下方法创建了一个非常简单的正弦波:
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)
}
}
当以采样率(在我的例子中为 44,100Hz)播放时,这会为 1000Hz 的正弦波创建一个浮点(我认为是 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,我会听到我的正弦波,但我也会听到很多谐波(噪音)。看来这不是正确的格式。
如果我将每组帧复制到另一个数组中以测试写入的内容是否正确,则输出与输入刺激匹配,因此没有丢失样本。
如果我去查看混音器单元的 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!);
所以这告诉了我一些事情。它需要两个通道,并且是交错的(因为不存在非交错标志)。在我的回调函数中,我将帧跨 2 以仅使用样本填充第一个通道。如果我改为从 1 开始播放,则写入音频并播放到右侧。
采样率是正确的,但是比特率是 16(Float 不是),我可以看到 'isSignedInteger' 有一个标志,所以这需要不同的格式。
所以现在,我尝试使用以下方法将浮点数组转换为 Int16:
for i in 0..<sineArray.count{
sineArray[i] = Int16.init((32767 * sweepSamples[i]))
}
然而,这仍然会导致不同的噪音,尽管有所不同。如果我检查数组,我可以确认结果是有符号的 int16,落在数据范围内。
我看不出如何以核心音频期望看到的格式来表示这些数据。我尝试将格式标志更改为 kAudioFormatFlagIsFloat 但仍然没有成功。
给定您的 [Float] 数据,而不是 kAudioFormatFlagIsSignedInteger 和每个通道 16 位,您可能想使用 kAudioFormatFlagIsFloat 和 32 位(每个数据包和帧 8 个字节)。
请注意,对于所有最近的 iOS 设备,本机音频格式为 32 位浮点数,而不是 16 位整数,使用本机(硬件?)采样率 48000,而不是 44100。
此外,请注意,Apple 建议不要在音频回调上下文中使用 Swift(请参阅 2017 年或 2018 年 WWDC 音频会议),因此您的音频单元渲染回调可能应该调用 C 函数来完成所有工作(任何接触 ioData 或 inRefCon 的东西)。
您可能还需要检查以确保您的数组索引没有超出数组范围。
我正在使用 Core Audio(带有 swift 包装器)播放一些音频样本(用于记录冲动的短刺激)。我坚持使用核心音频而不是较新的 AVFoundation,因为我需要一些严格的时间和多设备输入,而较新的框架目前还没有涵盖(我通过苹果代码请求让他们告诉我我必须使用核心音频)。
我现在已经使用以下方法创建了一个非常简单的正弦波:
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)
}
}
当以采样率(在我的例子中为 44,100Hz)播放时,这会为 1000Hz 的正弦波创建一个浮点(我认为是 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,我会听到我的正弦波,但我也会听到很多谐波(噪音)。看来这不是正确的格式。
如果我将每组帧复制到另一个数组中以测试写入的内容是否正确,则输出与输入刺激匹配,因此没有丢失样本。
如果我去查看混音器单元的 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!);
所以这告诉了我一些事情。它需要两个通道,并且是交错的(因为不存在非交错标志)。在我的回调函数中,我将帧跨 2 以仅使用样本填充第一个通道。如果我改为从 1 开始播放,则写入音频并播放到右侧。
采样率是正确的,但是比特率是 16(Float 不是),我可以看到 'isSignedInteger' 有一个标志,所以这需要不同的格式。
所以现在,我尝试使用以下方法将浮点数组转换为 Int16:
for i in 0..<sineArray.count{
sineArray[i] = Int16.init((32767 * sweepSamples[i]))
}
然而,这仍然会导致不同的噪音,尽管有所不同。如果我检查数组,我可以确认结果是有符号的 int16,落在数据范围内。
我看不出如何以核心音频期望看到的格式来表示这些数据。我尝试将格式标志更改为 kAudioFormatFlagIsFloat 但仍然没有成功。
给定您的 [Float] 数据,而不是 kAudioFormatFlagIsSignedInteger 和每个通道 16 位,您可能想使用 kAudioFormatFlagIsFloat 和 32 位(每个数据包和帧 8 个字节)。
请注意,对于所有最近的 iOS 设备,本机音频格式为 32 位浮点数,而不是 16 位整数,使用本机(硬件?)采样率 48000,而不是 44100。
此外,请注意,Apple 建议不要在音频回调上下文中使用 Swift(请参阅 2017 年或 2018 年 WWDC 音频会议),因此您的音频单元渲染回调可能应该调用 C 函数来完成所有工作(任何接触 ioData 或 inRefCon 的东西)。
您可能还需要检查以确保您的数组索引没有超出数组范围。