菜鸟教程小白 发表于 2022-12-13 12:11:42

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


                                            <p><p>这是在 iPhone 6s 和 6s+ 推出后我的应用程序中出现的问题,我几乎可以肯定这是因为新型号的内置麦克风在 48kHz 录制时卡住了(您可以阅读更多关于这个<a href="https://developer.apple.com/forums/thread/22197#72961" rel="noreferrer noopener nofollow">here</a>)。澄清一下,这对于我测试过的以前的手机型号从来都不是问题。我将在下文详细介绍我的音频引擎实现以及不同点的不同结果,具体取决于手机型号。</p>
<p>所以这就是发生的事情 - 当我的代码在以前的设备上运行时,我在 AVCaptureDevice 返回的每个 CMSampleBuffer 中获得一致数量的音频样本,通常是 1024 个样本。我的音频单元图的渲染回调为 1024 帧提供了适当的缓冲区。一切都很好,听起来很棒。</p>
<p>然后 Apple 不得不制造这款该死的 iPhone 6s(开个玩笑,这太棒了,这个 bug 刚刚进入我的脑海),现在我得到了一些非常不一致和令人困惑的结果。 AVCaptureDevice 现在在捕获 940 或 941 个样本之间变化,并且渲染回调现在开始在第一次调用时创建一个具有 940 或 941 个样本帧空间的缓冲区,但随后立即开始将其在后续调用中保留的空间增加到 1010、1012、或 1024 个样本帧,然后停留在那里。它最终保留的空间因 session 而异。老实说,我不知道这个渲染回调如何确定它为渲染准备了多少帧,但我猜它与渲染回调打开的音频单元的采样率有关。</p >
<p>无论设备是什么,CMSampleBuffer 的格式都以 44.1kHz 的采样率出现,所以我猜测在我什至从 AVCaptureDevice 上接收到 CMSampleBuffer 之前就会发生某种隐式采样率转换6秒。唯一的区别是 6s 的首选硬件采样率为 48kHz,而早期版本为 44.1kHz。</p>
<p>我了解到,对于 6s,您确实必须准备好为返回的不同数量的样本腾出空间,但是我在上面描述的那种行为正常吗?如果是,如何调整我的渲染周期来处理这个问题?</p>
<p>如果您想进一步了解,下面是处理音频缓冲区的代码:</p>
<p>音频样本缓冲区,即 <a href="https://developer.apple.com/documentation/coremedia/cmsamplebuffer-u71" rel="noreferrer noopener nofollow">CMSampleBufferRefs</a> ,通过麦克风进来<a href="https://developer.apple.com/documentation/avfoundation/avcapturedevice" rel="noreferrer noopener nofollow">AVCaptureDevice</a>并发送到我的音频处理函数,该函数对捕获的名为 audioBuffer 的 CMSampleBufferRef 执行以下操作</p>
<pre><code>CMBlockBufferRef buffer = CMSampleBufferGetDataBuffer(audioBuffer);
      
CMItemCount numSamplesInBuffer = CMSampleBufferGetNumSamples(audioBuffer);
AudioBufferList audioBufferList;
      
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(audioBuffer,
                                                            NULL,
                                                            &amp;audioBufferList,
                                                            sizeof(audioBufferList),
                                                            NULL,
                                                            NULL,
                                                            kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
                                                            &amp;buffer
                                                            );

self.audioProcessingCallback(&amp;audioBufferList, numSamplesInBuffer, audioBuffer);
CFRelease(buffer);
</code></pre>
<p>这是将音频样本放入 AudioBufferList 并将其连同样本数量和保留的 CMSampleBuffer 一起发送到我用于音频处理的以下函数。 TL;DR 以下代码设置音频图中的一些音频单元,使用 CMSampleBuffer 的格式设置输入的 ASBD,通过转换器单元、newTimePitch 单元和另一个转换器单元运行音频样本。然后,我使用从 CMSampleBufferRef 接收到的样本数量在输出转换器单元上启动渲染调用,并将渲染的样本放回 AudioBufferList 以便随后写入电影文件,更多关于下面的音频单元渲染回调。 </p>
<pre><code>movieWriter.audioProcessingCallback = {(audioBufferList, numSamplesInBuffer, CMSampleBuffer) -&gt; () in

      var ASBDSize = UInt32(sizeof(AudioStreamBasicDescription))
      self.currentInputAudioBufferList = audioBufferList.memory
      
      let formatDescription = CMSampleBufferGetFormatDescription(CMSampleBuffer)
      let sampleBufferASBD = CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription!)
      
      if (sampleBufferASBD.memory.mFormatID != kAudioFormatLinearPCM) {
            print(&#34;Bad ASBD&#34;)
      }
      
      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(&#34;New IN ASBD: \(self.currentInputASBD)&#34;)
            
            // set the ASBD for converter in&#39;s input to currentInputASBD
            var err = AudioUnitSetProperty(self.converterInAudioUnit,
                kAudioUnitProperty_StreamFormat,
                kAudioUnitScope_Input,
                0,
                &amp;self.currentInputASBD,
                UInt32(sizeof(AudioStreamBasicDescription)))
            self.checkErr(err, &#34;Set converter in&#39;s input stream format&#34;)
            
            // Set currentOutputASBD to the in/out format for newTimePitch unit
            err = AudioUnitGetProperty(self.newTimePitchAudioUnit,
                kAudioUnitProperty_StreamFormat,
                kAudioUnitScope_Input,
                0,
                &amp;self.currentOutputASBD,
                &amp;ASBDSize)
            self.checkErr(err, &#34;Get NewTimePitch ASBD stream format&#34;)
            
            print(&#34;New OUT ASBD: \(self.currentOutputASBD)&#34;)
            
            //Set the ASBD for the convert out&#39;s input to currentOutputASBD
            err = AudioUnitSetProperty(self.converterOutAudioUnit,
                kAudioUnitProperty_StreamFormat,
                kAudioUnitScope_Input,
                0,
                &amp;self.currentOutputASBD,
                ASBDSize)
            self.checkErr(err, &#34;Set converter out&#39;s input stream format&#34;)
            
            //Set the ASBD for the converter out&#39;s output to currentInputASBD
            err = AudioUnitSetProperty(self.converterOutAudioUnit,
                kAudioUnitProperty_StreamFormat,
                kAudioUnitScope_Output,
                0,
                &amp;self.currentInputASBD,
                ASBDSize)
            self.checkErr(err, &#34;Set converter out&#39;s output stream format&#34;)
            
            //Initialize the graph
            err = AUGraphInitialize(self.auGraph)
            self.checkErr(err, &#34;Initialize audio graph&#34;)
            
            self.checkAllASBD()
            
      }
      
      self.currentSampleTime += Double(numSamplesInBuffer)
      
      var timeStamp = AudioTimeStamp()
      memset(&amp;timeStamp, 0, sizeof(AudioTimeStamp))
      timeStamp.mSampleTime = self.currentSampleTime
      timeStamp.mFlags = AudioTimeStampFlags.SampleTimeValid
      
      var flags = AudioUnitRenderActionFlags(rawValue: 0)
      
      err = AudioUnitRender(self.converterOutAudioUnit,
            &amp;flags,
            &amp;timeStamp,
            0,
            UInt32(numSamplesInBuffer),
            audioBufferList)
      self.checkErr(err, &#34;Render Call on converterOutAU&#34;)
      
    }
</code></pre>
<p>AudioUnitRender 调用到达输入转换器单元后调用的 Audio Unit Render Callback 如下</p>
<pre><code>func pushCurrentInputBufferIntoAudioUnit(inRefCon : UnsafeMutablePointer&lt;Void&gt;, ioActionFlags : UnsafeMutablePointer&lt;AudioUnitRenderActionFlags&gt;, inTimeStamp : UnsafePointer&lt;AudioTimeStamp&gt;, inBusNumber : UInt32, inNumberFrames : UInt32, ioData : UnsafeMutablePointer&lt;AudioBufferList&gt;) -&gt; OSStatus {

let bufferRef = UnsafeMutablePointer&lt;AudioBufferList&gt;(inRefCon)
ioData.memory = bufferRef.memory
print(inNumberFrames);

return noErr
}
</code></pre>
<p>废话,这是一个巨大的脑残,但我真的很感谢<strong>任何</strong>的帮助。如果您需要任何其他信息,请告诉我。</p></p>
                                    <br><hr><h1><strong>Best Answer-推荐答案</ strong></h1><br>
                                            <p><p>通常,您可以通过将传入的样本放入一个无锁的圆形 fifo 中来处理缓冲区大小的细微变化(但输入和输出的采样率是恒定的),并且在您获得全尺寸 block 加上潜在的一些安全填充以覆盖 future 的尺寸抖动。</p>

<p>大小的变化可能与采样率转换器比率(不是简单的倍数)、所需的重采样滤波器以及重采样过程所需的任何缓冲有关。</p>

<p>1024 * (44100/48000) = 940.8</p>

<p>因此,速率转换可以解释 940 和 941 样本之间的抖动。如果硬件始终以 48 kHz 的固定速率输出 1024 个样本 block ,并且您需要尽快将该 block 重新采样到 44100 以进行回调,则转换后的样本的一小部分最终只需要在某些输出回调中输出.</p></p>
                                   
                                                <p style="font-size: 20px;">关于ios - 在音频单元渲染周期中处理不同数量的样本,我们在Stack Overflow上找到一个类似的问题:
                                                        <a href="https://stackoverflow.com/questions/34030626/" rel="noreferrer noopener nofollow" style="color: red;">
                                                                https://stackoverflow.com/questions/34030626/
                                                        </a>
                                                </p>
                                       
页: [1]
查看完整版本: ios - 在音频单元渲染周期中处理不同数量的样本