音频流格式和核心音频的数据类型混淆

时间:2019-04-21 16:46:51

标签: swift audio buffer core-audio

我正在使用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,但仍然没有运气。

1 个答案:

答案 0 :(得分:0)

鉴于您的[Float]数据,而不是kAudioFormatFlagIsSignedInteger和每个通道16位,您可能希望使用kAudioFormatFlagIsFloat和32位(每个数据包和帧8个字节)。

请注意,对于所有最新的iOS设备,本机音频格式均为32位浮点数,而不是16位int,使用本机(硬件?)采样率为48000,而不是44100。

此外,请注意,Apple建议不要在音频回调上下文中使用Swift(请参阅有关音频的2017或2018 WWDC会话),因此您的Audio Unit渲染回调可能应调用C函数来完成所有工作(任何触摸ioData或inRefCon)。

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