音频流格式和数据类型与 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 的东西)。

您可能还需要检查以确保您的数组索引没有超出数组范围。