阻止从文件流式传输时的音频失真(八度)

Audio distortion when block streaming from a file (Octave)


我正在设置一个简单的音频 IO 系统,它通过从已存储在内存中的文件一次调用一个块来模拟 'real-time block processing'。


此刻,我有一个简单的脚本,它从文件中检索数据,然后进入 while 循环,一次提取一个块,并提供 600 Hz 的一阶巴特沃斯低通滤波器(框架设置去测试)。然后将每个块处理并添加到另一个声明在 while 循环范围之外的数组中,以便处理后的数据可以在完成后写入 wave 文件。


为了过滤数据,我使用 Octave signal pkg 生成系数(butter), and then the built in filter 函数应用 IIR 滤波器。

问题是,如果我不应用过滤器影响,即输入 = 输出,音频听起来完全一样。但是,如果我每次调用一个块时都应用一个滤波器,则会产生一个振铃,使信号严重失真。


请参阅以下脚本进行设置(目前仅处理单声道音频)。

# Reset
close all; clear all;

# Audio file path
fileName = 'test.wav';

# Init routines
[x,fs] = audioread(fileName);
xlen = length(x);
[dim1,dim2] = size(x);
y = zeros(dim1,dim2);
[b,a] = butter(1, (600./(fs*0.5)));    
index = 1;
blockSize = 256;

# Enter process loop
while(index + blockSize < xlen)

  # Extract one block
  audioBlock(:,1) = x(index : index + blockSize - 1, 1);

  # Do process
  outAudioBlock = filter(b,a,audioBlock);

  # Store output block
  y(index : index + blockSize - 1, 1) = outAudioBlock(:);

  # Update index 
  index += blockSize;

endwhile

# Write to outputs
audiowrite('processed.wav', y, fs);
audiowrite('processed1.wav', filter(b,a,y), fs);

第二个 audiowrite 只是一个示例,它确认在一次调用中过滤整个音频数据不会产生失真,而块过滤会产生明显的数字失真。


作为旁注:

我还尝试使用不同的滤波技术,包括频域乘法加窗,然后 ifft 返回(使用八度 fftfilt & 仅使用 fft)以及时域卷积并创建重叠相加方法。当应用 FIR 滤波器而不是使用 IIR 系数时,也会出现相同的效果。

我也知道这个例子忽略了最后一个块左右的音频,但对于这个用例,我不关心最后一个块的零填充。


我不确定我错过了什么;有什么想法吗?

编辑 1:我的想法是尽可能不使用频域处理(只是时域 IIR/FIR 过滤),但我研究了频域乘法以查看是否存在类似的失真结果发生了(它确实发生了)。

这很可能是边缘效应。您将因果 IIR 滤波器应用于 audioBlock。为了计算第一个样本,状态被初始化为全零。如果我没记错的话,这相当于假设第一个样本之前的信号全为零。这可能会产生不连续性,这将影响块开头的一定数量的样本。因为您使用 IIR 滤波器,所以这种影响可能会持续很长时间。在这方面使用 FIR 滤波器更安全。

让我们假设 margin 个样本受到影响。您可以按如下方式修改代码以将信号扩展该数量并防止失真:

while(index + blockSize < xlen)

  % Extract one block
  if index==1
    audioBlock = x(index : index + blockSize - 1);
  else
    audioBlock = x(index - margin : index + blockSize - 1);
  end

  % Do process
  outAudioBlock = filter(b,a,audioBlock);

  % Store output block
  if index==1
    y(index : index + blockSize - 1) = outAudioBlock;
  else
    y(index : index + blockSize - 1) = outAudioBlock(margin+1:end);
  end

  % Update index 
  index += blockSize;

end

(免责声明:我这里没有安装octave,而且我的MATLAB副本没有信号处理工具箱,所以我无法测试上面的代码。)


不请自来的建议:

  • 你的数据都是一维的,使用一维(线性)索引。它效率更高,输入时间更短。 (见我上面的代码。)

  • 提取新的信号位时不要audioBlock(:,1) =。只需将结果分配给变量即可。它 很多,如果信号大小发生变化并且您忘记重置变量,也不会出现问题。

  • 不要以 close all; clear all; 开头。相反,请在脚本顶部写入 [​​=15=]。这会将脚本转换为一个函数,这意味着它有自己的工作区。这是一种更安全的工作方式,因为您不会不小心在脚本中使用现有变量,也不会不小心删除基础工作区中的任何内容。

  • 我用了 end 而不是 endwhile。这是一样的,但也适用于 MATLAB。没有理由不使用最便携的选项。

  • 我用了 % 而不是 #。同样,相同但便携。请注意 SO 语法突出显示如何与 % 一起使用,而不是 #! :)