线性预测模型卷积

信息处理 过滤器 声音的 卷积 线性预测
2022-02-19 03:24:36

不小心在一般区问了这个问题,被告知在这里问,所以...

我一直在尝试开发一种轻量级、解码速度相对较快的声音压缩格式,用于我的游戏项目(不需要完美再现,所以我只使用 16 位数据)。

这个想法是将声音数据分成 14 个样本帧,并使用线性预测通过仅存储残差来减少数据大小。为了使其更轻,然后通过降低其精度将残差量化为每个样本 4 位,以便将它们存储为缩放残差,其中比例由帧头决定。为了降低信号噪声,生成了 16 个最适合文件中信号的线性预测模型。

每帧最终为 64 位(4 位用于 LP 模型选择,4 位用于残差比例(2^n,因此仅存储 n),以及 14*4 位用于残差数据)。

我一直在做的只是为每一帧求解 Yule-Walker 方程以获得系数,然后将它们保存在一个数组中。在计算了所有帧的系数之后,我尝试根据每个 LP 模型的欧几里德距离创建 16 个系数并将它们平均出来。也就是说...

Zero-out target buffer

For i = 1, i <= 16, i++
  Create and zero-out a temporary buffer for i LP models

  For each saved LP model
    Find the model in the original target buffer [up to i] with the smallest Euclidean distance between it and this model
    Accumulate the coefficients to the temporary buffer

  Average out the temporary buffer and store it to the final LP model array [again up to i]

正如预期的那样,这根本不会产生很好的结果,因为我认为它类似于统一颜色量化——它与实际数据几乎没有关系。

在谷歌搜索了几个月后,我唯一能想到的就是“稀疏卷积”,但我不完全确定 LPC 是什么意思,或者它是否是我所追求的。

给定这些参数(即:生成 16 个 LPC 模型,假设帧长度为 14 个样本,这些模型将最大限度地减少残差),你会怎么做呢?

2个回答

平均 AR 系数是一项有风险的业务——这可能会导致相应多项式的零点位置发生剧烈变化,从而导致不稳定!

如果我要解决这个问题,我会尝试的第一件事是:

  • 计算频谱图(短期傅里叶变换)
  • 使用对数上的欧几里得距离在码本大小为 16 的光谱切片上运行 VQ
  • 一旦你有 16 个光谱模板,将它们转换回自相关函数,为 16 个模板求解 yule-walker,你就得到了 16 个模型。

或者另一种“安全”的方式:

  • 对于每一帧,提取 AR 系数。
  • 将它们转换为反射系数或任何其他替代表示,例如可以安全插值的线谱对。
  • 使用 16 码本大小在这些上运行 VQ 以获得 16 个模型。
  • 转换回 AR 系数。

但我真的怀疑这是一条值得探索的道路。我的两个直觉是:

  • 如果一个音频文件足够长且足够多样化,那么您从中获得的 16 个专用模型与您在大量音频集合上一劳永逸地运行该程序而获得的 16 个“通用”模型并没有太大的不同文件。
  • 从大量音频文件中学习 16 个模型可能会为您提供 FLAC(多项式插值器)中使用的那种幼稚的“固定预测器”。

一个相关的结果是,在足够长的音频剪辑上对 mel 频谱运行 KLT 将产生与 DCT 基础非常相似的东西——这就是为什么像 mp3 或 AAC 这样的音频编解码器不使用按文件优化的转换——DCT是一个很好的通用转换,使用通用转换的优点是它们甚至不需要任何辅助信息。这就是激发我直觉的原因 - 您正在查看一个大型数据集(整个音频文件),16 很小。您宁愿考虑 16 个良好的通用预测器并使用它们,而不是尝试优化每个文件的 16 个预测器。

好的,我终于开始编写代码并对其进行测试。结果?太棒了=D

我按照 pichenettes 的原始建议将系数存储为 RFC 而不是 LPC,然后执行矢量量化(我尝试了使用 15 个“全局”预测器和 1 个块预测器的想法。结果没有我想象的那么好,即使有许多不同的预测变量和精度 =/)。

就个人而言,我认为最困难的部分是试图弄清楚如何安全地插入数据,而我对 DSP 的影响缺乏深入的了解。但是,在被指出正确的方向之后,我很高兴地说,结果正是我在将近一年前开始时所希望的(是的,我一直在尝试这样做很长时间 =P) .

稍微棘手的另一部分是编写具有可接受结果的快速矢量量化。虽然我可能可以用质心等正确的方式完成它。但我的做法略有不同(我不确定如何显示/隐藏预先格式化的文本块,所以如果你不想看,只需阅读粗体线我写的 C++ 代码来解释):

1)生成第一个系数集作为所有未处理系数的平均值(一种量化训练)

// Quantized coefficient sets
double VQCoef[CodebookSize][Order];

// For all original coefficients...
for(int i=0; i < NumberOfCoef; i++) {
  // For each coefficient in the set...
  for(int j=0; j < Order; j++) {
    // Accumulate
    VQCoef[0][j] += Coef[i][j];
  }
}

// Average out coefficients
// For each coefficient in the set...
for(int i=0; i < Order; i++) {
  // Normalize [clamped to (-1.0,+1.0) but omitted for brevity]
  VQCoef[0][i] /= (double)NumberOfCoef;
}

2) 根据最后一个稍微偏移的系数集计算下一个 2^n 系数集

// For each final coefficient set...
for(int i=0; i < CodebookSize;) {
  // Quantization buffer. Counter and accumulator.
  int    VQCnt[CodebookSize];        memset(VQCnt, 0, sizeof(VQCnt));
  double VQAcc[CodebookSize][Order]; memset(VQAcc, 0, sizeof(VQAcc));

  // Estimate the next set by slightly offsetting the original data
  for(int j=0; j < i; j++) for(int k=0; k < Order; k++) VQCoef[i+j][k] = VQCoef[j][k] + 0.01;
}

// Now we have double the amount of coefficients, so adjust 'i' accordingly
i *= 2;

3)遍历未处理的系数列表并找到具有最接近相关性(点积)的目标系数。找到后,将数据累积到临时缓冲区中

// For each original coefficient...
for(int i=0; i < NumCoef; i++) {
  // Find target coefficient set with best correlation
  int    BestPos = 0;       // Best target [reset when code runs]
  double BestDst = -1.0e99; // Best distance [just a really large negative value]
  for(int j=0; j < CodebookSize; j++) {
    // Get correlation between the original coefficient set and the tested target
    double Dist = NormalizedDotProduct(Coef[i], VQCoef[j], Order);

    // If the correlation was better, save this as the target
    if(Dist > BestDst) {
      BestPos = j;
      BestDst = Dist;
    }
  }

  // Now that we know the target, save it to the accumulation buffer
  VQCnt[BestPos]++;
  for(int j=0; j < Order; j++) VQAcc[BestPos][j] += Coef[i][j];
}

4)当这批系数集完成后,将其平均以找到新的集

// For all sets done up to now...
for(int i=0; i < CoefSetsDone; i++) {
  // Fetch count/normalization coefficient. If zero sets, don't bother doing this set
  double Cnt = VQCnt[i];
  if(Cnt != 0.0) {
    // We have sets, so average the coefficients out
    for(int j=0; j < Order; j++) {
      VQCoef[i][j] /= Cnt; // [again, this is clamped to (-1.0,+1.0)]
    }
  }
}

重复步骤 2-4,直到我得到我想要的 2^n 组,此时我只需将反射系数转换为 LP 系数就完成了。=)

虽然我打算使用它的项目是非常长期的(想想十多年),你能同意我把声音设计的关键部分归功于你吗?