处理音频单元渲染周期中不同数量的样本

时间:2015-12-01 21:34:27

标签: ios iphone audio core-audio

这是iPhone 6s和6s +推出后我的应用程序中出现的一个问题,我几乎肯定是因为新型号的内置麦克风被记录在48kHz(你可以阅读更多关于这个here)。为了澄清这一点,这对于我之前测试的手机型号来说从来都不是问题。我将根据下面的手机型号,逐步介绍我的音频引擎实施以及不同点的不同结果。

所以这里发生了什么 - 当我的代码在以前的设备上运行时,我在AVCaptureDevice返回的每个CMSampleBuffer中获得了一致数量的音频样本,通常是1024个样本。我的音频单元图的渲染回调提供了一个适当的缓冲区,其空间为1024帧。一切都很好,听起来很棒。

然后苹果公司不得不制造这款该死的iPhone 6s(开玩笑,它很棒,这个错误只是让我头疼)现在我得到了一些非常不一致和令人困惑的结果。 AVCaptureDevice现在在捕获940或941个样本之间变化,渲染回调现在开始在第一次调用时为940或941个样本帧创建一个具有空间的缓冲区,但随后立即开始增加它在后续调用中保留的空间,最多为1010,1012,或1024个样本帧,然后停留在那里。它最终保留的空间因会话而异。说实话,我不知道这个渲染回调是如何确定它为渲染准备了多少帧,但我猜测它与渲染回调所在的音频单元的采样率有关。 / p>

无论设备是什么,CMSampleBuffer的格式都是44.1kHz采样率,因此我猜测在我接收CMSampleBuffer之前发生了某种隐式采样率转换。 6s的AVCaptureDevice。唯一的区别是6s的首选硬件采样率为48kHz,而早期版本为44.1kHz。

我已经读过6s你必须准备为返回的不同数量的样本腾出空间,但我上面描述的那种行为是否正常?如果是,我的渲染周期如何定制来处理这个?

以下是处理音频缓冲区的代码,如果您想进一步了解它:

音频样本缓冲区CMSampleBufferRefs通过麦克风AVCaptureDevice进入并发送到我的音频处理函数,该函数对捕获的名为audioBuffer的CMSampleBufferRef执行以下操作

CMBlockBufferRef buffer = CMSampleBufferGetDataBuffer(audioBuffer);

CMItemCount numSamplesInBuffer = CMSampleBufferGetNumSamples(audioBuffer);
AudioBufferList audioBufferList;

CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(audioBuffer,
                                                            NULL,
                                                            &audioBufferList,
                                                            sizeof(audioBufferList),
                                                            NULL,
                                                            NULL,
                                                            kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
                                                            &buffer
                                                            );

self.audioProcessingCallback(&audioBufferList, numSamplesInBuffer, audioBuffer);
CFRelease(buffer);

这是将音频样本放入AudioBufferList并将其与样本数量和保留的CMSampleBuffer一起发送到我用于音频处理的下面的函数。 TL; DR以下代码设置音频图中的一些音频单元,使用CMSampleBuffer的格式设置输入的ASBD,通过转换器单元,newTimePitch单元,然后另一个转换器运行音频样本单元。然后,我在输出转换器单元上启动渲染调用,其中包含从CMSampleBufferRef接收的样本数,并将渲染的样本放回AudioBufferList,随后将其写入影片文件,更多内容将在下面的Audio Unit Render Callback中。

movieWriter.audioProcessingCallback = {(audioBufferList, numSamplesInBuffer, CMSampleBuffer) -> () in

        var ASBDSize = UInt32(sizeof(AudioStreamBasicDescription))
        self.currentInputAudioBufferList = audioBufferList.memory

        let formatDescription = CMSampleBufferGetFormatDescription(CMSampleBuffer)
        let sampleBufferASBD = CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription!)

        if (sampleBufferASBD.memory.mFormatID != kAudioFormatLinearPCM) {
            print("Bad ASBD")
        }

        if(sampleBufferASBD.memory.mChannelsPerFrame != self.currentInputASBD.mChannelsPerFrame || sampleBufferASBD.memory.mSampleRate != self.currentInputASBD.mSampleRate){


            // Set currentInputASBD to format of data coming IN from camera
            self.currentInputASBD = sampleBufferASBD.memory
            print("New IN ASBD: \(self.currentInputASBD)")

            // set the ASBD for converter in's input to currentInputASBD
            var err = AudioUnitSetProperty(self.converterInAudioUnit,
                kAudioUnitProperty_StreamFormat,
                kAudioUnitScope_Input,
                0,
                &self.currentInputASBD,
                UInt32(sizeof(AudioStreamBasicDescription)))
            self.checkErr(err, "Set converter in's input stream format")

            // Set currentOutputASBD to the in/out format for newTimePitch unit
            err = AudioUnitGetProperty(self.newTimePitchAudioUnit,
                kAudioUnitProperty_StreamFormat,
                kAudioUnitScope_Input,
                0,
                &self.currentOutputASBD,
                &ASBDSize)
            self.checkErr(err, "Get NewTimePitch ASBD stream format")

            print("New OUT ASBD: \(self.currentOutputASBD)")

            //Set the ASBD for the convert out's input to currentOutputASBD
            err = AudioUnitSetProperty(self.converterOutAudioUnit,
                kAudioUnitProperty_StreamFormat,
                kAudioUnitScope_Input,
                0,
                &self.currentOutputASBD,
                ASBDSize)
            self.checkErr(err, "Set converter out's input stream format")

            //Set the ASBD for the converter out's output to currentInputASBD
            err = AudioUnitSetProperty(self.converterOutAudioUnit,
                kAudioUnitProperty_StreamFormat,
                kAudioUnitScope_Output,
                0,
                &self.currentInputASBD,
                ASBDSize)
            self.checkErr(err, "Set converter out's output stream format")

            //Initialize the graph
            err = AUGraphInitialize(self.auGraph)
            self.checkErr(err, "Initialize audio graph")

            self.checkAllASBD()

        }

        self.currentSampleTime += Double(numSamplesInBuffer)

        var timeStamp = AudioTimeStamp()
        memset(&timeStamp, 0, sizeof(AudioTimeStamp))
        timeStamp.mSampleTime = self.currentSampleTime
        timeStamp.mFlags = AudioTimeStampFlags.SampleTimeValid

        var flags = AudioUnitRenderActionFlags(rawValue: 0)

        err = AudioUnitRender(self.converterOutAudioUnit,
            &flags,
            &timeStamp,
            0,
            UInt32(numSamplesInBuffer),
            audioBufferList)
        self.checkErr(err, "Render Call on converterOutAU")

    }

AudioUnitRender呼叫到达输入转换器单元时调用的音频单元渲染回调

func pushCurrentInputBufferIntoAudioUnit(inRefCon : UnsafeMutablePointer<Void>, ioActionFlags : UnsafeMutablePointer<AudioUnitRenderActionFlags>, inTimeStamp : UnsafePointer<AudioTimeStamp>, inBusNumber : UInt32, inNumberFrames : UInt32, ioData : UnsafeMutablePointer<AudioBufferList>) -> OSStatus {

let bufferRef = UnsafeMutablePointer<AudioBufferList>(inRefCon)
ioData.memory = bufferRef.memory
print(inNumberFrames);

return noErr
}
Blah,这是一次巨大的脑力转移,但我非常感谢 ANY 的帮助。如果您需要任何其他信息,请与我们联系。

1 个答案:

答案 0 :(得分:4)

通常,您可以通过将传入的样本放入无锁的圆形fifo中来处理缓冲区大小的微小变化(但是输入和输出的常量采样率),并且不会从该圆形FIFO中移除任何样本块,直到您有全尺寸块加上可能的一些安全填充,以覆盖未来的大小抖动。

尺寸的变化可能与采样率转换器比率不是简单的倍数,所需的重采样滤波器以及重采样过程所需的任何缓冲有关。

1024 *(44100/48000)= 940.8

因此速率转换可以解释940和941样本之间的抖动。如果硬件始终以48 kHz的固定速率发送1024个样本的块,并且您需要将该块重新采样到44100以便尽快回调,那么转换样本的一小部分最终只需要输出一些输出回调。