类介绍 RenderDelayBuffer、RenderDelayBufferImpl
RenderDelayBuffer
用于render_buffer的维护
这个类具有以下公共成员函数和纯虚函数:
-
Create()
:静态函数,用于创建RenderDelayBuffer
对象。 -
Reset()
:重置缓冲区的对齐。 -
Insert()
:将块插入到缓冲区中,并返回一个表示缓冲事件的枚举值。 -
PrepareCaptureProcessing()
:基于指定的缓冲延迟更新缓冲区,并返回一个表示特殊事件发生的枚举值。 -
HandleSkippedCaptureProcessing()
:在未调用PrepareCaptureProcessing()
的捕获块上调用。 -
AlignFromDelay()
:设置缓冲延迟,并返回一个布尔值,指示延迟是否发生了变化。 -
AlignFromExternalDelay()
:从最近报告的外部延迟设置缓冲延迟。 -
Delay()
:获取缓冲延迟。 -
MaxDelay()
:获取最大缓冲延迟。 -
GetRenderBuffer()
:返回用于回声去除器的渲染缓冲区。 -
GetDownsampledRenderBuffer()
:返回下采样的渲染缓冲区。 -
DelayEstimatorOffset()
:返回可在延迟缓冲区中发生的最大非因果偏移量。 -
SetAudioBufferDelay()
:提供一个可选的音频缓冲延迟的外部估计。 -
HasReceivedBufferDelay()
:返回是否通过SetAudioBufferDelay
接收到了外部的延迟估计。
该类还包含了一个嵌套的枚举类BufferingEvent
,用于表示缓冲事件的不同类型。该类似乎属于webrtc
命名空间
kNone
:表示没有发生特殊事件。
kRenderUnderrun
:表示渲染缓冲区发生了不足的情况,即需要更多的渲染数据以应对实时处理需求。
kRenderOverrun
:表示渲染缓冲区发生了溢出的情况,即渲染数据超过了实时处理的需求。
kApiCallSkew
:表示API调用的时间不匹配,即API调用的时间与实际数据处理的时间发生了偏差。
RenderDelayBufferImpl
继承自RenderDelayBuffer,除了override父类方法外,
类的私有成员变量和私有方法如下:
私有成员变量:
-
instance_count_
:一个静态整数,用于跟踪RenderDelayBufferImpl
类的实例数。 -
data_dumper_
:一个指向ApmDataDumper
的唯一指针,用于数据记录和调试。 -
optimization_
:一个Aec3Optimization
类型的常量,表示AEC3的优化选项。 -
config_
:一个EchoCanceller3Config
类型的常量,表示回声消除器的配置。 -
update_capture_call_counter_on_skipped_blocks_
:一个bool
值,表示是否在跳过的块上更新捕获调用计数器。 -
render_linear_amplitude_gain_
:一个float
值,表示渲染线性幅度增益。 -
delay_log_level_
:一个rtc::LoggingSeverity
类型的常量,表示延迟日志级别。 -
down_sampling_factor_
:一个size_t
值,表示下采样因子。 -
sub_block_size_
:一个int
值,表示子块的大小。 -
blocks_
:一个BlockBuffer
对象,表示音频块的缓冲区。 -
spectra_
:一个SpectrumBuffer
对象,表示频谱的缓冲区。 -
ffts_
:一个FftBuffer
对象,表示FFT的缓冲区。 -
delay_
:一个absl::optional<size_t>
对象,表示延迟值的可选项。 -
echo_remover_buffer_
:一个RenderBuffer
对象,表示回声移除器的缓冲区。 -
low_rate_
:一个DownsampledRenderBuffer
对象,表示降采样的渲染缓冲区。 -
render_mixer_
:一个AlignmentMixer
对象,用于混合渲染音频。 -
render_decimator_
:一个Decimator
对象,用于渲染音频的抽取和降采样。 -
fft_
:一个Aec3Fft
对象,用于快速傅里叶变换。 -
render_ds_
:一个std::vector<float>
,用于存储降采样后的渲染音频。 -
buffer_headroom_
:一个int
值,表示缓冲区的保留头空间。 -
last_call_was_render_
:一个bool
值,表示上一次调用是否为渲染调用。 -
num_api_calls_in_a_row_
:一个int
值,表示连续的API调用次数。 -
max_observed_jitter_
:一个int
值,表示最大观察到的抖动。 -
capture_call_counter_
:一个int64_t
值,表示捕获调用的计数器。 -
render_call_counter_
:一个int64_t
值,表示渲染调用的计数器。 -
render_activity_
:一个bool
值,表示渲染活动状态。 -
render_activity_counter_
:一个size_t
值,表示渲染活动计数器。 -
external_audio_buffer_delay_
:一个absl::optional<int>
对象,表示外部音频缓冲区的延迟。 -
external_audio_buffer_delay_verified_after_reset_
:一个bool
值,表示在重置后是否验证了外部音频缓冲区的延迟。 -
min_latency_blocks_
:一个size_t
值,表示最小延迟块数。 -
excess_render_detection_counter_
:一个size_t
值,表示超出渲染的检测计数器。
私有成员方法:
-
MapDelayToTotalDelay
:将延迟映射到总延迟的私有方法。 -
ComputeDelay
:计算延迟的私有方法。 -
ApplyTotalDelay
:应用总延迟的私有方法。 -
InsertBlock
:插入音频块到缓冲区的私有方法。 -
DetectActiveRender
:检测活动渲染的私有方法。 -
DetectExcessRenderBlocks
:检测超出渲染块的私有方法。 -
IncrementWriteIndices
:增加写索引的私有方法。 -
IncrementLowRateReadIndices
:增加低速率读索引的私有方法。 -
IncrementReadIndices
:增加读索引的私有方法。 -
RenderOverrun
:检测渲染超限的私有方法。 -
RenderUnderrun
:检测渲染不足的私有方法。
RenderDelayBufferImpl::Reset()
该方法用于重置缓冲区的延迟,并清除报告的延迟。
代码逻辑如下:
-
重置一些状态变量,包括
last_call_was_render_
(上次调用是否为渲染)、num_api_calls_in_a_row_
(连续的 API 调用次数)、min_latency_blocks_
(最小延迟块数)、excess_render_detection_counter_
(渲染超限检测计数器)。 -
读取位置更新:将低采样率缓冲区的读索引(
low_rate_.read
)初始化为写索引(low_rate_.write
)的前一个子块位置 -
检查是否存在外部音频缓冲区的延迟,并计算合适的初始化延迟audio_buffer_delay_to_set:如果存在外部音频缓冲区延迟(
external_audio_buffer_delay_
),且大于3,设置的延迟值取决于外部音频缓冲区延迟减去一个头部余量(headroom,2),同时,确保设置的延迟不超过最大延迟值。否则,设置为1 -
调用ApplyTotalDelay(audio_buffer_delay_to_set)设置延时; delay_ = ComputeDelay();
-
调用ComputeDelay()计算总延时delay_ ;
-
将
external_audio_buffer_delay_verified_after_reset_
置为false
,表示在重置后需要重新验证外部音频缓冲区延迟。 -
如果不存在外部音频缓冲区延迟估计,则使用默认延迟值作为初始延迟,并将渲染缓冲区延迟设置为默认延迟。
-
通过
AlignFromDelay
方法设置的延迟值。
该方法的作用是重置缓冲区的状态和延迟设置,以便在重新开始音频处理时,缓冲区的状态和延迟被正确初始化。根据是否存在外部音频缓冲区延迟估计,采取不同的延迟设置策略,以确保音频处理的准确性和一致性。
RenderDelayBufferImpl::Insert()
该方法接收block作为参数,将其插入渲染缓冲区,修改render_buffer, write_index并返回缓冲事件。
代码逻辑如下:
-
首先,增加渲染调用计数器
render_call_counter_
。 -
如果存在延迟值
delay_
,则进行以下操作:a. 检查上一次调用是否为渲染调用。如果不是,将
last_call_was_render_
设置为true
,并将num_api_calls_in_a_row_
设置为1。b. 如果上一次调用是渲染调用,则将
num_api_calls_in_a_row_
加1,并检查是否超过了max_observed_jitter_
(之前观察到的最大抖动)。如果超过了最大抖动,更新max_observed_jitter_
的值,并记录日志。这段代码与PrepareCaptureProcessing的计算代码呼应,max_observed_jitter_代表连续插入(渲染或捕获)block的数量;
-
更新render_buffer各个index,如下:
-
low_rate.write -= sub_block_size(16)
-
blocks_.write += 1
-
spectra_.write -= 1
-
ffts_.write -= 1
-
调用RenderOverrun(),判断是否渲染超限(RenderOverrun),是则将事件设置为
BufferingEvent::kRenderOverrun
;否则,事件设置为BufferingEvent::kNone
。 -
检测并更新渲染活动状态render_activity。如果渲染活动状态之前为非活动状态(
render_activity_
为false
),且此render block能量大于150 * 150 * 64,则计数器render_activity_counter_
+1;render_activity_counter累计到20,render_activity置true; -
调用RenderDelayBufferImpl::InsertBlock() 插入当前block;
-
如果事件不是
BufferingEvent::kNone
,则进行重置(Reset)操作。 -
返回事件。
RenderDelayBufferImpl::InsertBlock()
这段代码是RenderDelayBufferImpl
类中的InsertBlock
方法的实现。该方法将一个块(block)插入到渲染缓冲区中。
代码逻辑如下:
-
获取相关缓冲区的引用,包括
blocks_
(渲染缓冲区)、low_rate_
(低采样率缓冲区)、render_ds_
(渲染下采样缓冲区)、ffts_
(FFT缓冲区)、spectra_
(频谱缓冲区)。 -
获取渲染缓冲区块的频带数目(
num_bands
)和渲染通道数目(num_render_channels
)。 -
使用嵌套循环遍历块的频带和渲染通道,并将块的数据复制到渲染缓冲区blocks_中。
-
如果渲染线性幅度增益(
render_linear_amplitude_gain_
)不等于1.0,对渲染缓冲区中的数据进行线性幅度增益处理。 -
进行渲染混音和下采样操作,将下采样结果存储在
render_ds_
缓冲区中,并将下采样结果的逆序复制到低采样率缓冲区(low_rate_
)的相应位置。 -
对渲染缓冲区
blocks_
(的第一个通道进行快速傅里叶变换(FFT)和频谱计算,将结果存储在FFT缓冲区和频谱缓冲区中。
RenderDelayBufferImpl::PrepareCaptureProcessing()
该方法用于准备渲染缓冲区以处理另一个捕获块。
代码逻辑如下:
-
首先,初始化一个缓冲事件(
event
)为BufferingEvent::kNone
,表示没有缓冲事件发生。 -
增加捕获调用计数器(
capture_call_counter_
)。 -
如果存在延迟(
delay_
):-
如果上次调用是渲染(
last_call_was_render_
为true
),则将last_call_was_render_
设为false
,表示当前调用是捕获。 -
否则,增加连续 API 调用次数
num_api_calls_in_a_row_
-
如果连续 API 调用次数超过了最大观察到的抖动值(
max_observed_jitter_
),更新最大观察到的抖动值,并记录日志。
-
这段代码与Insert的计算代码呼应,max_observed_jitter_代表连续插入(渲染或捕获)block的数量;
-
-
判断kRenderOverrun,检测与捕获块相比,是否存在过多的渲染块。如果存在过多的渲染块,则执行以下操作:
-
记录日志指示检测到过多的渲染块。
-
调用
Reset
方法重置缓冲区。 -
将缓冲事件设置为
BufferingEvent::kRenderOverrun
,表示发生了渲染超限事件。
-
-
判断是否渲染不足(RenderUnderrun),若是,执行以下操作:
-
记录日志指示检测到渲染缓冲区不足。
-
增加读索引。IncrementReadIndices,low_rate不增加。
-
如果存在延迟且延迟值大于0,则减少延迟值。
-
将缓冲事件设置为
BufferingEvent::kRenderUnderrun
,表示发生了渲染不足事件。
-
-
否则,增加渲染缓冲区和频谱缓冲区的读索引。
-
low_rate_.read -= 16;
-
blocks_.read += 1
-
spectra_.read -= 1
-
ffts_.read -= 1
-
将当前render_activity_设置到回声去除器,设置完后更新 render_activity:
-
如果存在渲染活动(
render_activity_
为true
),则将渲染活动计数器(render_activity_counter_
)重置为0,并将渲染活动状态(render_activity_
)设为false
。 -
与Insert函数对应维护render_activity_,该状态设置到回声去除器后没有使用。
-
-
返回缓冲事件。
RenderDelayBufferImpl::RenderOverrun()
该方法用于在block insert的时候,检查渲染缓冲区是否发生了(kRenderOverrun)超限事件,当发生渲染超限(kRenderOverrun)时,允许发生超限,并执行重置reset操作。
代码逻辑很简单:
-
如果低采样率缓冲区(
low_rate_
)的读索引(read
)等于写索引(write
),或者渲染延迟缓冲区(blocks_
)的读索引等于写索引,则返回true
,表示发生了渲染超限。 -
如果上述条件不满足,则返回
false
,表示没有发生渲染超限。
kRenderOverrun指的是插入的渲染数据比接收到的捕获数据更多而导致的情况。
渲染超限的情况可能发生在以下情形下:
-
渲染数据的流速(插入速率)存在一段时间持续快于捕获数据的流速(读取速率),导致渲染缓冲区的写索引超过了读索引,若继续写入会覆盖数据。
RenderDelayBufferImpl::DetectExcessRenderBlocks()
该方法用于在处理捕获块数据时候,检测是否存在过多的渲染块(kRenderOverrun),当发生渲染超限(kRenderOverrun)时,执行重置reset操作。
代码逻辑如下:
每处理250个块(1s)进入一次判断
判断这段时间里的low_rate_最小延迟块数是否高于阈值max_allowed_excess_render_blocks=8。
如果满足条件,则表示存在过多的渲染块。如果检测到过多的渲染块,返回true
不满足则,返回false
RenderDelayBufferImpl::RenderUnderrun()
该方法用于检测渲染缓冲区是否发生了kRenderUnderrun代码逻辑很简单:
-
检查低速率(low_rate_)结构体中的读指针(read)和写指针(write)是否相等。
-
如果读指针和写指针相等,则表示发生了kRenderUnderrun,即读取速度比写入速度更快,导致无法获得足够的渲染数据。
RenderDelayBufferImpl::ApplyTotalDelay()
该方法根据总延迟设置blocks.read 、spectra.read 、 ffts_.read 读索引的位置。
代码逻辑如下:传入delay
-
使用日志记录当前应用的延迟值。
-
更新渲染缓冲区(
blocks_
)、频谱缓冲区(spectra_
)和FFT缓冲区(ffts_
)的读索引位置,注意下采样的读索引并未发生改变。-
blocks.read = blocks.write - delay
-
spectra.read = spectra.write + delay
-
ffts.read = ffts.write + delay
根据预估的时延值,将几个缓冲区对应的读指针设置得到对应位置,以便后续做回声去除的时候,读取的数据已经是实现对齐的;
-
RenderDelayBufferImpl::ComputeDelay()
该方法用于计算延迟(不包括呼叫抖动)。
代码逻辑如下:
-
首先,获取当前缓冲区的延迟块数(latency_blocks),调用
BufferLatency
方法来计算。 -
接下来,计算内部延迟(internal_delay)。如果频谱缓冲区的读索引(spectra.read)大于等于写索引(spectra.write),则内部延迟等于读索引减去写索引。否则,内部延迟等于频谱缓冲区的大小加上读索引再减去写索引。
-
最后,返回内部延迟internal_delay减去延迟块数latency_blocks的结果作为计算得到的延迟值。
该方法的作用是计算渲染缓冲区的内部延迟(不包括呼叫抖动),通过读索引和写索引之间的差值来确定延迟。通过减去延迟块数,可以得到实际的延迟值。这个延迟值可以用于音频处理的时间对齐和同步操作。
RenderDelayBufferImpl::BufferLatency()
该方法用于计算缓冲区中的延迟(未读取的子块数)。
代码逻辑如下:
-
计算延迟的样本数(latency_samples)。通过(l.buffer.size() + l.read - l.write) % l.buffer.size();得到未读取的样本数。
-
将延迟的样本数除以子块的大小(
sub_block_size_
),得到延迟的子块数(latency_blocks)。 -
返回延迟的子块数作为计算得到的缓冲区延迟值。
RenderDelayBufferImpl::AlignFromDelay
这段代码是RenderDelayBufferImpl
类中的AlignFromDelay
方法的实现。该方法用于设置渲染延迟,并返回一个布尔值,指示延迟是否发生了变化。
-
external_audio_buffer_delay_verified_after_reset_ reset后是否经过音频缓冲区延迟验证
-
external_audio_buffer_delay_ 设置进去的delay值,以block为单位
-
如果在重置后尚未进行外部音频缓冲区延迟验证(
external_audio_buffer_delay_verified_after_reset_
为假),且存在外部音频缓冲区延迟(external_audio_buffer_delay_
为真)和当前已估计的延迟(delay_
为真):-
第一次计算得出估计值,与输入的估计值对比,并判断外部估计值是否正确;
-
-
更新当前估计值,将指定的延迟值赋给
delay_
。 -
计算总延时,调用MapDelayToTotalDelay(该函数调用ComputeDelay,计算buffer内的时延,与前面delay_ 相加得到总延时返回)计算总延迟(
total_delay
),并将其限制在(<=153)允许的范围内,因此,可以得出结论,aec3的滤波器时延估计值最大为512ms,加上内部延时,aec3最多能处理 153*4 = 612ms的时延; -
将总延迟应用到缓冲区中,即调用
ApplyTotalDelay
方法根据总延迟blocks.read 、spectra.read 、 ffts_.read 读index; -
返回
true
,表示延迟发生了变化。
BufferingEvent介绍,包括kNone、kRenderUnderrun、kRenderOverrun、kApiCallSkew
enum class BufferingEvent {
kNone,
kRenderUnderrun,
kRenderOverrun,
kApiCallSkew
}
kNone
kRenderOverrun: 播放帧过多——对应外部可能发生的:录音阻塞,录音停止,(网络不佳时,播放瞬时来了很多帧,可能),作了Reset处理将delay相关的变量全部重置,重新匹配,影响不会很大;
-
置位的地方: 当执行RenderDelayBufferImpl::Insert(),向render_buffer插入block时,会执行RenderOverrun()判断,在移动write指针后,判断low_rate_ 和blocks_ 的read和write指针是否相等(相等说明已经套圈了),以此来判断render_buffer是否发生了kRenderOverrun事件(相等了就是发生了kRenderOverrun);
-
如果发生了kRenderOverrun,会执行RenderDelayBufferImpl::Reset();并且在PrepareCaptureProcessing时,会delay_controller_也会reset。
-
RenderDelayBufferImpl::PrepareCaptureProcessing()时,为处理捕获数据作准备。也会执行DetectExcessRenderBlocks判断此时renderbuffer是否发生kRenderOverrun,这个每1s才会检测一次,且不会改变render_event_ 的取值。只是当检测到的时候,同样会执行RenderDelayBufferImpl::Reset(),但是significant_candidate_found_标志位会保留;
-
发生了kRenderOverrun也同样会执行匹配滤波器等后续步骤;
kRenderUnderrun:播放帧太少——对应外部事件:播放阻塞、播放停止
-
这个只会在RenderDelayBufferImpl::PrepareCaptureProcessing()时检测,检测方法为low_rate_ read和write指针是否相等
如果发生了kRenderUnderrun,则不会挪动low_rate_ 的read指针,因为没有新的播放帧可以用了;并且delay_controller_也会reset。
发生了kRenderOverrun也同样会执行匹配滤波器等后续步骤;