webrtc降噪模块NS源码解析(1)

前言:

大家好,在上一篇文章里面,我们已经用webrtc的降噪模块,对噪声做了一个降噪处理,算是在rk3568上跑通了webrtc的ns模块,今天我们继续来学习webrtc的ns模块里面的细节实现,所以我们需要深度研究里面的源码学习。

webrtc的ns模块:

先来看一下数据流到ns处理的流程:

那么我们先不管增益和回声消除,核心来看一下降噪模块:

go 复制代码
// 用于对输入信号做降噪(Noise Suppression, NS)的核心类。
// 典型位置:WebRTC 音频处理链路(APM)里,通常在 AEC(回声消除) 前后之一执行:
// - Analyze() 常用于"只分析统计量",可在 AEC 前做,避免分析到 AEC 的 comfort noise。
// - Process() 才会真正修改音频数据,执行降噪。
class NoiseSuppressor {
 public:
  // 构造函数:
  // config          : NS 的配置(强度、模式、是否启用某些策略等)
  // sample_rate_hz  : 采样率(8k/16k/32k/48k... 会影响频带数、上频带处理等)
  // num_channels    : 通道数(1=mono, 2=stereo...)
  NoiseSuppressor(const NsConfig& config,
                  size_t sample_rate_hz,
                  size_t num_channels);

  // 禁止拷贝:因为内部持有大量状态/堆内存,拷贝成本大且容易引入状态错误。
  NoiseSuppressor(const NoiseSuppressor&) = delete;
  NoiseSuppressor& operator=(const NoiseSuppressor&) = delete;

  // 只分析输入信号(不修改音频内容)。
  // 注释里提到:通常在 AEC 前调用,避免把 AEC 产生的 comfort noise 当成噪声/语音去统计。
  //
  // Analyze() 的典型职责:
  // 1) 计算频谱/能量特征
  // 2) 更新噪声估计(NoiseEstimator)
  // 3) 更新语音存在概率(SpeechProbabilityEstimator)
  // 4) 更新上一帧频谱等历史状态
  void Analyze(const AudioBuffer& audio);

  // 对音频做真正的降噪处理(会就地修改 audio)。
  //
  // Process() 的典型职责:
  // 1) FFT(含 overlap 拼帧)
  // 2) 根据噪声估计 + 语音概率计算 Wiener 增益(或其他增益曲线)
  // 3) 频域乘增益抑制噪声
  // 4) IFFT + overlap-add 合成输出
  void Process(AudioBuffer* audio);

  // 指定"采集端输出是否会被使用"。
  // 目的:当输出不会被用到(例如静音、端点 mute),NS 可以关闭/简化部分耗时步骤降低 CPU。
  // 注意:一般仍要维护部分状态,避免恢复输出时瞬态不稳定(爆音、增益突变等)。
  void SetCaptureOutputUsage(bool capture_output_used) {
    capture_output_used_ = capture_output_used;
  }

 private:
  // ======= 维度与配置参数 =======

  // 频带数(band 的概念通常与采样率相关):
  // - 在 WebRTC 中,高采样率(32k/48k)往往会拆"低频带 + 高频带(upper band)"进行处理/对齐。
  // - num_bands_ 用于控制滤波器组状态、上频带增益数组等的大小。
  const size_t num_bands_;

  // 通道数:每个通道都有独立的噪声估计、语音概率估计、Wiener滤波状态等。
  const size_t num_channels_;

  // 抑制参数(由 NsConfig 转换/预计算得到):
  // 通常包含:
  // - 不同频段的目标抑制量(dB)
  // - 最小增益限制(避免"全静音/水下音")
  // - 语音存在时的保真策略(减少语音失真)
  // - 过渡平滑、攻击/释放时间常数等
  const SuppressionParams suppression_params_;

  // 已分析的帧计数:
  // - 初始 -1 表示"尚未开始/尚未初始化到稳定状态"
  // - 某些算法会在前 N 帧做 warm-up(例如先估噪,再逐步启用强抑制)
  int32_t num_analyzed_frames_ = -1;

  // FFT 工具对象(NS 的核心是频域处理):
  // - 内部会用固定 FFT 大小 kFftSize
  // - 以及 kFftSizeBy2Plus1(实信号 FFT 的半谱大小 N/2+1)
  NrFft fft_;

  // 输出是否被使用(用于降耗/跳过部分处理)
  bool capture_output_used_ = true;

  // ======= 每通道状态(独立状态机) =======
  struct ChannelState {
    // 每通道的状态依赖 suppression_params 和 num_bands(例如不同 band 的延迟线长度等)
    ChannelState(const SuppressionParams& suppression_params, size_t num_bands);

    // 语音概率估计器:
    // - 输出每个频点/每个 band 的"像语音的概率"
    // - 常用特征:SNR、谱平坦度、谱变化率、历史平滑等
    // - 用于避免把语音也当噪声压得太狠
    SpeechProbabilityEstimator speech_probability_estimator;

    // Wiener 滤波器状态:
    // - Wiener 增益常见形式:G(k) = SNR / (SNR + 1)
    // - 实际会结合语音概率做保真(比如语音概率高 -> 增益更接近1)
    // - wiener_filter 通常保存平滑状态、上一帧增益等
    WienerFilter wiener_filter;

    // 噪声估计器:
    // - 跟踪每个频点的噪声功率谱密度(noise PSD)
    // - 在无语音段快速更新,在语音段慢更新或冻结,避免把语音统计进噪声
    NoiseEstimator noise_estimator;

    // 上一帧"分析用"的频谱(半谱大小 N/2+1):
    // - 用于平滑、计算谱变化、语音概率特征、突变检测等
    std::array<float, kFftSizeBy2Plus1> prev_analysis_signal_spectrum;

    // Analyze() 的分析端 overlap 记忆:
    // - 因为 NS 通常是 10ms 一帧输入(kNsFrameSize),
    //   但 FFT 窗口 kFftSize 往往更大,需要把上一帧尾巴与当前帧拼成 extended_frame。
    // - 这个数组保存"上一帧剩余那部分"。
    std::array<float, kFftSize - kNsFrameSize> analyze_analysis_memory;

    // Process() 的分析端 overlap 记忆(与 Analyze 分开存):
    // - Analyze 和 Process 可能在 pipeline 的不同位置被调用,
    //   两条路径的状态不能互相踩。
    std::array<float, kOverlapSize> process_analysis_memory;

    // Process() 的合成端 overlap 记忆:
    // - IFFT 回来的时域信号,需要 overlap-add 合成连续输出,
    //   这里保存合成时的重叠部分。
    std::array<float, kOverlapSize> process_synthesis_memory;

    // 处理延迟线(可能是:每个 band 一条或若干条):
    // - 在多 band、上频带对齐、或某些平滑/决策需要 lookback 时使用
    // - vector<array<kOverlapSize>> 表明:延迟线长度可变(由 num_bands 或配置决定)
    std::vector<std::array<float, kOverlapSize>> process_delay_memory;
  };

  // ======= 滤波器组/FFT 临时工作区 =======
  struct FilterBankState {
    // FFT 复数频谱的实部缓存
    std::array<float, kFftSize> real;

    // FFT 复数频谱的虚部缓存
    std::array<float, kFftSize> imag;

    // 拼好的扩展时域帧(窗函数/overlap 后的输入),用于 FFT
    std::array<float, kFftSize> extended_frame;
  };

  // ======= 堆上缓存(避免实时处理中频繁分配) =======

  // 滤波器组状态(可能按:通道×band 或 band 数分配,具体看实现)
  std::vector<FilterBankState> filter_bank_states_heap_;

  // 上频带增益缓存:
  // - 当采样率较高时(如 32k/48k),NS 可能对高频段采用独立增益或从低频外推。
  // - 这里存每个频点/每个 band 的 upper band 增益。
  std::vector<float> upper_band_gains_heap_;

  // 滤波前能量/功率缓存:
  // - 用于计算 SNR、语音概率、做能量门限、或用于调节增益/抑制策略
  std::vector<float> energies_before_filtering_heap_;

  // 增益调整项缓存:
  // - Wiener gain 计算出来后,可能还会做额外修正:
  //   * 最小增益限制
  //   * 语音存在时减轻抑制(保真)
  //   * 不同频段不同曲线(低频保留更多、噪声集中频段压得更狠)
  std::vector<float> gain_adjustments_heap_;

  // 每个通道一个 ChannelState(unique_ptr:避免拷贝大对象,明确所有权)
  std::vector<std::unique_ptr<ChannelState>> channels_;

  // ======= 关键私有方法:多通道 Wiener filter 聚合 =======
  // 将多个通道的 Wiener filter 合并为"一个最终滤波器":
  // - 多通道(立体声)时常用来保持左右一致性(避免左右声道音色/噪声底不同)
  // - 也可能用于某些共享处理路径(降低计算或稳定决策)
  //
  // filter 是输出参数:长度 kFftSizeBy2Plus1 的半谱增益曲线
  void AggregateWienerFilters(
      rtc::ArrayView<float, kFftSizeBy2Plus1> filter) const;
};

从上面我们可以总结一下:NoiseSuppressor 是 WebRTC 的实时频域降噪器:它对每个通道维护噪声估计、语音概率和 Wiener 增益等状态,提供 Analyze(只统计)与 Process(真正降噪)两阶段接口,并支持多通道滤波器聚合和静音降算力。

下面为了更加详细的学习,我们先从NoiseSuppressor构造函数来学习源码开始:

作用:根据采样率和通道数,提前分配并初始化所有降噪所需的状态、缓存和子模块,避免在实时处理过程中动态分配内存:

go 复制代码
NoiseSuppressor::NoiseSuppressor(const NsConfig& config,
                                 size_t sample_rate_hz,
                                 size_t num_channels)
    // ======= 1. 根据采样率计算频带数 =======
    // WebRTC 在高采样率(32k/48k)时,会把信号拆成多个 band
    //(低频带 + 上频带)分别处理
    : num_bands_(NumBandsForRate(sample_rate_hz)),

      // ======= 2. 保存通道数 =======
      num_channels_(num_channels),

      // ======= 3. 从配置生成抑制参数 =======
      // suppression_params_ 是 NS 实际算法用的参数集合
      // 通常包含:
      //   - target_level(目标抑制量)
      //   - 最小增益
      //   - 平滑系数
      //   - 语音存在时的保真策略
      suppression_params_(config.target_level),

      // ======= 4. 预分配滤波器组/FFT 工作区 =======
      // NumChannelsOnHeap() 通常返回:
      //   num_channels * num_bands
      // 或
      //   num_channels
      // 具体取决于实现
      // 目的:每个通道/频带都有独立 FFT 缓存
      filter_bank_states_heap_(NumChannelsOnHeap(num_channels_)),

      // ======= 5. 预分配上频带增益缓存 =======
      // 高频段可能采用独立增益或从低频外推
      // 这里提前分配,避免 Process() 里 new
      upper_band_gains_heap_(NumChannelsOnHeap(num_channels_)),

      // ======= 6. 预分配滤波前能量缓存 =======
      // 用于:
      //   - 计算 SNR
      //   - 语音概率估计
      //   - 决策是否冻结噪声估计
      energies_before_filtering_heap_(NumChannelsOnHeap(num_channels_)),

      // ======= 7. 预分配增益调整缓存 =======
      // Wiener 增益计算完后,还会做额外修正
      // 这里提前准备好数组
      gain_adjustments_heap_(NumChannelsOnHeap(num_channels_)),

      // ======= 8. 为每个通道预留 ChannelState 指针槽位 =======
      // 注意:这里只是 resize vector,还没 new ChannelState
      channels_(num_channels_) {
          // ======= 9. 为每个通道创建独立的 ChannelState =======
  for (size_t ch = 0; ch < num_channels_; ++ch) {
    channels_[ch] =
        std::make_unique<ChannelState>(suppression_params_, num_bands_);
  }
}

上面代码中有一个函数NumBandsForRate,他是一个"采样率 → band 数量"的映射函数;band(频带)是 WebRTC NS 内部的处理单元数,不是指真正的滤波器组频段,而是逻辑处理分块,也就是这段代码的核心作用:

  • NumBandsForRate() 根据采样率把音频拆成每 16kHz 一个逻辑 band(16k=1,32k=2,48k=3),让 WebRTC NS 对不同频段采用不同的降噪策略,兼顾音质与性能。
sample_rate_hz num_bands 含义
16000 1 只有低频带
32000 2 低频 + 高频
48000 3 低频 + 中频 + 高频
  • 16k:只处理 0~8k(语音主频)

  • 32k:band0:0~8k ; band1:8~16k

  • 48k:band0:0~8k;band1:8~16k;band2:16~24k

注意: 每 16kHz 采样率增加一个 band,所以这里对于采样率为44.1kHz就不行,无法整除16,如果要处理的话,只能先重采样来进行处理了。

这里还有一个配置降噪等级的参数:

go 复制代码
// Config struct for the noise suppressor
struct NsConfig {
  enum class SuppressionLevel { k6dB, k12dB, k18dB, k21dB };
  SuppressionLevel target_level = SuppressionLevel::k12dB;
};
枚举值 含义 实际效果
k6dB 轻度降噪 几乎不损音质,噪声残留多
k12dB 中等降噪(默认) 平衡点
k18dB 强降噪 噪声明显减少,但可能轻微失真
k21dB 极强降噪 最狠,容易水下音

我们再看一下函数NumChannelsOnHeap:

go 复制代码
// Maximum number of channels for which the channel data is stored on
// the stack. If the number of channels are larger than this, they are stored
// using scratch memory that is pre-allocated on the heap. The reason for this
// partitioning is not to waste heap space for handling the more common numbers
// of channels, while at the same time not limiting the support for higher
// numbers of channels by enforcing the channel data to be stored on the
// stack using a fixed maximum value.
constexpr size_t kMaxNumChannelsOnStack = 2;

// Chooses the number of channels to store on the heap when that is required due
// to the number of channels being larger than the pre-defined number
// of channels to store on the stack.
size_t NumChannelsOnHeap(size_t num_channels) {
  return num_channels > kMaxNumChannelsOnStack ? num_channels : 0;
}

作用:通过"最多 2 通道用栈、超过 2 通道才用堆"的方式,兼顾了实时性能、内存效率和通道扩展性,是 WebRTC 实时音频模块的经典内存管理设计。

好了,构造函数解析完,我们现在来看一下NoiseSuppressor::Analyze内部实现细节:

go 复制代码
void NoiseSuppressor::Analyze(const AudioBuffer& audio) {
  // ============================
  // [阶段 0] 每帧分析开始:让噪声估计器进入"分析阶段准备"状态
  // ============================
  // 作用:为本帧噪声估计更新做准备(例如清空/复位一些临时变量,切换状态机阶段等)
  for (size_t ch = 0; ch < num_channels_; ++ch) {
    channels_[ch]->noise_estimator.PrepareAnalysis();
  }

  // ============================
  // [阶段 1] 检测"全零帧"(zero frame)
  // ============================
  // 为什么要检测?
  // - 实际系统里,麦克风 mute / 通道未连接 / 上游直接置零,会出现连续很多"全 0"帧。
  // - 如果在全 0 帧时继续更新"噪声统计/语音阈值",统计量会被拉向"零信号",
  //   导致后续一旦有真实信号进来,算法会误判"一切都是语音",从而降噪失效,
  //   并且需要很长时间重新学习噪声底。
  bool zero_frame = true;

  for (size_t ch = 0; ch < num_channels_; ++ch) {
    // 取出本通道的 band0(第 0 个频带)的 10ms 时域数据。
    // split_bands_const(ch)[0] 是 band0 的 float 数组(长度 kNsFrameSize)
    rtc::ArrayView<const float, kNsFrameSize> y_band0(
        &audio.split_bands_const(ch)[0][0], kNsFrameSize);

    // 计算"扩展帧"的能量:当前 10ms + 上一帧遗留的 overlap memory(analyze_analysis_memory)
    // 注意:这里不是只算当前 y_band0 能量,而是算"拼接后的 extended frame 能量"
    // 这样可以更可靠地判断是否真的是"全静音/全0"
    float energy = ComputeEnergyOfExtendedFrame(
        y_band0, channels_[ch]->analyze_analysis_memory);

    // 只要任何一个通道的能量 > 0,就不是全零帧
    if (energy > 0.f) {
      zero_frame = false;
      break;
    }
  }

  if (zero_frame) {
    // ============================
    // 全零帧:直接返回,不更新任何统计量
    // ============================
    // 注释解释的核心点:
    // - 若用全0信号更新统计,会把各种阈值/均值拉向 0
    // - 一旦信号恢复,算法会把一切当作语音(speech)
    // - 降噪效果消失,并且需要较长时间重新"学会噪声是什么"
    return;
  }

  // ============================
  // [阶段 2] 更新分析帧计数 num_analyzed_frames_
  // ============================
  // 只有"真正分析过的帧"才计数:zero_frame 会被跳过。
  // 初始值可能是 -1,这里确保递增后不会一直为负。
  if (++num_analyzed_frames_ < 0) {
    num_analyzed_frames_ = 0;
  }

  // ============================
  // [阶段 3] 对每个通道做频域分析与统计更新(不修改音频)
  // ============================
  for (size_t ch = 0; ch < num_channels_; ++ch) {
    // ch_p:当前通道的状态(噪声估计/语音概率/维纳滤波/历史频谱/overlap memory 等)
    std::unique_ptr<ChannelState>& ch_p = channels_[ch];

    // 取本通道 band0 的 10ms 输入帧
    rtc::ArrayView<const float, kNsFrameSize> y_band0(
        &audio.split_bands_const(ch)[0][0], kNsFrameSize);

    // ----------------------------
    // 3.1 构造 extended frame(扩展帧)并加窗
    // ----------------------------
    // 为什么要 extended frame?
    // - NS 以 10ms 为"处理步长",但 FFT 往往需要更长窗口 kFftSize(例如 256/512/1024)
    // - 因此必须把上一帧尾巴(overlap)与当前帧拼接成 kFftSize 长度的 FFT 输入
    // - analyze_analysis_memory 保存的是上一帧保留下来的那一段,用于 overlap 拼接
    std::array<float, kFftSize> extended_frame;
    FormExtendedFrame(y_band0, ch_p->analyze_analysis_memory, extended_frame);

    // ApplyFilterBankWindow:对时域帧乘窗函数(如 Hann/Hamming 等)
    // 作用:
    // - 降低频谱泄漏(spectral leakage)
    // - 让频域幅度更稳定,便于做噪声估计/语音概率估计
    ApplyFilterBankWindow(extended_frame);

    // ----------------------------
    // 3.2 FFT -> 频谱(real/imag),再计算幅度谱 |X(k)|
    // ----------------------------
    // real/imag:FFT 输出复数频谱的实部和虚部
    std::array<float, kFftSize> real;
    std::array<float, kFftSize> imag;
    fft_.Fft(extended_frame, real, imag);

    // signal_spectrum:幅度谱(通常只取半谱 N/2+1,因为输入是实信号)
    // kFftSizeBy2Plus1 = kFftSize/2 + 1
    std::array<float, kFftSizeBy2Plus1> signal_spectrum;
    ComputeMagnitudeSpectrum(real, imag, signal_spectrum);

    // ----------------------------
    // 3.3 计算一些统计特征(能量、谱总和)
    // ----------------------------
    // signal_energy:这里通过频域功率(real^2 + imag^2)求平均,作为整体能量特征
    float signal_energy = 0.f;
    for (size_t i = 0; i < kFftSizeBy2Plus1; ++i) {
      signal_energy += real[i] * real[i] + imag[i] * imag[i];
    }
    signal_energy /= kFftSizeBy2Plus1;

    // signal_spectral_sum:幅度谱的总和(一个粗粒度的谱强度特征)
    float signal_spectral_sum = 0.f;
    for (size_t i = 0; i < kFftSizeBy2Plus1; ++i) {
      signal_spectral_sum += signal_spectrum[i];
    }

    // ----------------------------
    // 3.4 噪声估计器:PreUpdate(更新前)
    // ----------------------------
    // PreUpdate 常用于:
    // - 根据当前观测谱做预测/预处理
    // - 更新一些内部平滑统计量
    // - 为后续 SNR 计算提供"当前噪声谱候选"
    ch_p->noise_estimator.PreUpdate(num_analyzed_frames_, signal_spectrum,
                                    signal_spectral_sum);

    // ----------------------------
    // 3.5 计算 SNR(先验/后验)
    // ----------------------------
    // post_snr:后验 SNR,通常基于当前观测谱与噪声谱直接计算(当前帧的即时SNR)
    // prior_snr:先验 SNR,通常用"决策导向(decision-directed)"方法平滑:
    //            prior_snr ≈ α * (上一帧增益^2 * 上一帧SNR) + (1-α) * max(post_snr-1, 0)
    // 这样做的好处:
    // - prior_snr 更平滑,避免增益抖动导致"音乐噪声(musical noise)"
    // - 对语音概率估计也更稳定
    std::array<float, kFftSizeBy2Plus1> post_snr;
    std::array<float, kFftSizeBy2Plus1> prior_snr;

    ComputeSnr(
        // 上一帧/当前的 Wiener filter(或增益曲线),用于 decision-directed 的 prior SNR 估计
        ch_p->wiener_filter.get_filter(),

        // 上一帧分析阶段存下来的幅度谱(历史观测)
        ch_p->prev_analysis_signal_spectrum,

        // 当前帧幅度谱(当前观测)
        signal_spectrum,

        // 上一帧噪声谱估计
        ch_p->noise_estimator.get_prev_noise_spectrum(),

        // 当前帧噪声谱估计
        ch_p->noise_estimator.get_noise_spectrum(),

        // 输出:先验/后验 SNR
        prior_snr, post_snr);

    // ----------------------------
    // 3.6 语音概率估计器更新(SpeechProbabilityEstimator)
    // ----------------------------
    // 目的:估计"这一帧/这一频点像语音的概率"
    // - 语音概率高:后续会减少噪声估计更新(避免把语音统计进噪声)
    // - 语音概率高:后续 Process 里也通常会更保真(增益更接近 1)
    //
    // conservative_noise_spectrum:更保守的噪声谱(常用于防止低估噪声)
    // 低估噪声会导致 SNR 假性偏高,从而抑制不足/判语音不准。
    ch_p->speech_probability_estimator.Update(
        num_analyzed_frames_, prior_snr, post_snr,
        ch_p->noise_estimator.get_conservative_noise_spectrum(),
        signal_spectrum, signal_spectral_sum, signal_energy);

    // ----------------------------
    // 3.7 噪声估计器:PostUpdate(更新后)
    // ----------------------------
    // 根据语音概率来决定噪声谱如何更新:
    // - 语音概率低:快速更新噪声谱(环境噪声变化能跟上)
    // - 语音概率高:慢更新或冻结(避免把语音当噪声学进去)
    ch_p->noise_estimator.PostUpdate(
        ch_p->speech_probability_estimator.get_probability(), signal_spectrum);

    // ----------------------------
    // 3.8 保存当前幅度谱(供下一帧 Analyze 或 Process 使用)
    // ----------------------------
    // 注释说:让 Process() 可用。
    // 常见用法:
    // - Process 阶段可能用这个"最近分析谱"来做平滑、估计 prior SNR、或者辅助决策。
    std::copy(signal_spectrum.begin(), signal_spectrum.end(),
              ch_p->prev_analysis_signal_spectrum.begin());
  }
}

channels_[ch]->noise_estimator.PrepareAnalysis();这个是噪声估计器,对应在NoiseSuppressor里面的ChannelState里面:

对应刚才的NoiseEstimator::PrepareAnalysis():

go 复制代码
void NoiseEstimator::PrepareAnalysis() {
  std::copy(noise_spectrum_.begin(), noise_spectrum_.end(),
            prev_noise_spectrum_.begin());
}

PrepareAnalysis() 的作用是把"上一帧噪声谱"保存下来,作为本帧噪声估计、SNR 计算和语音概率估计的历史参考,这是保证降噪算法稳定与平滑的关键一步。

上面这里会涉及到一个什么是 "zero frame(全零帧)",为什么要跳过?

zero frame 指这帧输入全是 0(或几乎全 0),常见原因:

  • 设备 mute

  • 上游没数据,填 0

  • 通道断开

  • AEC/AGC pipeline 某阶段"关断输出"

  • 如果继续更新统计量,会让:

  • 噪声底 → 逼近 0

  • 里面还涉及到其他相关类方法的源码,我们在下一期内容继续详细解读。

结果:当信号恢复时,算法会误判"这肯定是语音",噪声估计不敢更新,降噪失效,恢复需要很久。

什么是 "extended frame(扩展帧)" / overlap memory?

NS 通常每次拿 10ms 帧(kNsFrameSize),但 FFT 需要更长窗口 kFftSize。 所以要拼:

go 复制代码
extended_frame = [上一帧尾巴 (memory)] + [当前帧 (10ms)]

analyze_analysis_memory 保存"上一帧尾巴",每帧都滚动更新。

整个流程如下:

里面还涉及到其他相关类方法的源码,我们在下一期内容继续详细。

相关推荐
任小栗1 天前
【实战干货】Vue3 + WebRTC + SIP + AI 实现全自动语音接警系统(远程流获取+实时ASR+TTS回播)
人工智能·webrtc
runner365.git2 天前
如何使用RTCPilot--跨平台WebRTC开源服务
webrtc·音视频开发
runner365.git2 天前
RTC实现VoiceAgent(二)
大模型·webrtc·实时音视频·voiceagent
runner365.git3 天前
WebRTC实现VoiceAgent智能体
webrtc
runner365.git3 天前
RTCPilot的信令流程
webrtc·音视频开发
runner365.git3 天前
如何使用RTCPilot配置一个集群RTC服务
webrtc·实时音视频·音视频开发
深念Y4 天前
从WebSocket到WebRTC,豆包级实时语音交互背后的技术演进
websocket·网络协议·实时互动·webrtc·语音识别·实时音视频
AI视觉网奇6 天前
webrtc 硬编码
ffmpeg·webrtc
REDcker6 天前
WebRTC 接收端音频流畅低延迟播放:原理与源码对照(NetEQ / Opus)
音视频·webrtc
SUNNY_SHUN6 天前
LiveKit Agents:基于WebRTC的实时语音视频AI Agent框架(9.9k Star)
人工智能·github·webrtc