这是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 的帮助。如果您需要任何其他信息,请与我们联系。
答案 0 :(得分:4)
通常,您可以通过将传入的样本放入无锁的圆形fifo中来处理缓冲区大小的微小变化(但是输入和输出的常量采样率),并且不会从该圆形FIFO中移除任何样本块,直到您有全尺寸块加上可能的一些安全填充,以覆盖未来的大小抖动。
尺寸的变化可能与采样率转换器比率不是简单的倍数,所需的重采样滤波器以及重采样过程所需的任何缓冲有关。
1024 *(44100/48000)= 940.8
因此速率转换可以解释940和941样本之间的抖动。如果硬件始终以48 kHz的固定速率发送1024个样本的块,并且您需要将该块重新采样到44100以便尽快回调,那么转换样本的一小部分最终只需要输出一些输出回调。