[英]Recording audio with Audio Unit with files segmented in X number of seconds each
[英]Handle Varying Number of Samples in Audio Unit Rendering Cycle
这是在推出 iPhone 6s 和 6s+ 后出现在我的应用程序中的一个问题,我几乎肯定这是因为新型号的内置麦克风卡在 48kHz 的录制频率上(您可以在此处阅读更多相关信息) )。 澄清一下,这对于我测试过的以前的手机型号从来都不是问题。 我将在下面详细介绍我的音频引擎实现以及不同点的不同结果,具体取决于手机型号。
所以这是发生了什么 - 当我的代码在以前的设备上运行时,我在 AVCaptureDevice 返回的每个 CMSampleBuffer 中获得一致数量的音频样本,通常是 1024 个样本。 我的音频单元图的渲染回调提供了一个适合 1024 帧空间的缓冲区。 一切都很好,听起来很棒。
然后 Apple 不得不去制造这个该死的 iPhone 6s(开玩笑,这很棒,这个错误刚刚出现在我的脑海中),现在我得到了一些非常不一致和令人困惑的结果。 AVCaptureDevice 现在在捕获 940 或 941 个样本之间变化,并且渲染回调现在开始在第一次调用时为 940 或 941 个样本帧创建一个缓冲区,但随后立即开始增加它在后续调用中保留的空间,最多可达 1010、1012、或 1024 个样本帧,然后留在那里。 它最终保留的空间因会话而异。 老实说,我不知道这个渲染回调如何确定它为渲染准备了多少帧,但我猜这与渲染回调打开的音频单元的采样率有关。
无论设备是什么,CMSampleBuffer 的格式都以 44.1kHz 采样率出现,所以我猜在我什至在 6s 上从 AVCaptureDevice 接收 CMSampleBuffer 之前发生了某种隐式采样率转换。 唯一的区别是 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,通过转换器单元、新的时间音调单元和另一个转换器单元运行音频样本。 然后,我使用我从 CMSampleBufferRef 接收到的样本数在输出转换器单元上启动渲染调用,并将渲染的样本放回 AudioBufferList 中,随后写入电影文件,更多关于音频单元渲染回调如下。
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
}
废话,这是一个巨大的大脑转储,但我真的很感激任何帮助。 如果您需要任何其他信息,请告诉我。
通常,您可以通过将传入样本放入无锁循环 fifo 中来处理缓冲区大小的细微变化(但输入和输出恒定的采样率),并且在拥有完整大小的块之前不要从该圆形 fifo 中移除任何样本块加上潜在的一些安全填充来覆盖未来的尺寸抖动。
大小的变化可能与不是简单倍数的采样率转换器比率、所需的重采样滤波器以及重采样过程所需的任何缓冲有关。
1024 * (44100/48000) = 940.8
因此,速率转换可以解释 940 和 941 样本之间的抖动。 如果硬件总是以 48 kHz 的固定速率输出 1024 个样本块,并且您需要尽快将该块重新采样到 44100 以用于回调,那么有一小部分转换后的样本最终只需要在某些输出回调上输出.
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.