31

我使用的重采样算法需要包含-1.0 到 1.0范围内的输入样本的浮点数组。音频数据是16 位PCM,采样率为 22khz

我想将音频从 22khz 下采样到 8khz,如何将字节数组中的样本表示为浮点数>= -1 和 <= 1并返回到字节数组?

4

2 回答 2

60

你问两个问题:

  1. 如何从 22kHz 下采样到 8kHz?

  2. 如何从 float [-1,1] 转换为 16 位 int 并返回?

请注意,问题已更新以表明 #1 已在其他地方得到处理,但我将保留我的答案的那一部分,以防它帮助其他人。

1. 如何从 22kHz 下采样到 8kHz?

一位评论者暗示这可以通过 FFT 解决。这是不正确的(重采样的一个步骤是过滤。我提到为什么不在这里使用 FFT 进行过滤,以防您感兴趣:http ://blog.bjornroche.com/2012/08/when-to-not-use -fft.html )。

对信号重新采样的一种非常好的方法是使用多相滤波器。然而,这相当复杂,即使对于有信号处理经验的人来说也是如此。您还有其他几种选择:

  • 使用实现高质量重采样的库,例如libsamplerate
  • 做一些又快又脏的事

听起来您已经采用了第一种方法,这很棒。

快速而肮脏的解决方案听起来不会那么好,但是由于您要降低到 8 kHz,我猜音质不是您的首要任务。一种快速而肮脏的选择是:

  • 对信号应用低通滤波器。尽可能多地去除 4 kHz 以上的音频。您可以使用此处描述的过滤器(尽管理想情况下您想要比这些过滤器更陡峭的东西,但它们至少总比没有好)。
  • 从原始信号中选择每 2.75 个样本以产生新的重采样信号。当您需要非整数样本时,请使用线性插值。如果您需要线性插值方面的帮助,请尝试此处

这种技术对于语音应用来说应该绰绰有余。但是,我没有尝试过,所以我不确定,所以我强烈建议使用别人的库。

如果您真的想实现自己的高质量采样率转换,例如多相滤波器,您应该研究它,然后在https://dsp.stackexchange.com/上提出任何问题,而不是在这里。

2. 如何将float [-1,1] 转换为16-bit int 并返回?

这已经由 c.fogelklou 开始了,但让我来润色一下。

首先,16 位整数的范围是 -32768 到 32767(通常 16 位音频是有符号的)。要从 int 转换为 float,请执行以下操作:

float f;
int16 i = ...;
f = ((float) i) / (float) 32768
if( f > 1 ) f = 1;
if( f < -1 ) f = -1;

您通常不需要做额外的“边界”(事实上,如果您真的使用 16 位整数,则不需要),但如果您出于某种原因有一些 >16 位整数,则可以这样做。

要转换回来,请执行以下操作:

float f = ...;
int16 i;
f = f * 32768 ;
if( f > 32767 ) f = 32767;
if( f < -32768 ) f = -32768;
i = (int16) f;

在这种情况下,通常需要注意超出范围的值,尤其是大于 32767 的值。您可能会抱怨这会在 f = 1 时引入一些失真。这个问题引起了激烈的争论。有关对此的一些(不完整)讨论,请参阅此博客文章

这不仅仅是“足以胜任政府工作”。换句话说,它可以正常工作,除非您担心最终音质。由于您要使用 8kHz,我认为我们已经确定情况并非如此,所以这个答案很好。

但是,为了完整起见,我必须补充一点:如果您试图保持绝对原始,请记住这种转换会引入失真。为什么?因为从 float 转换为 int 时的错误与信号相关。事实证明,该错误的相关性很糟糕,您实际上可以听到它,即使它非常小。(幸运的是,它足够小,对于语音和低动态范围音乐之类的东西并不重要)要消除此错误,您必须在从浮点数到整数的转换中使用称为抖动的东西。同样,如果这是您关心的事情,请研究它并在https://dsp.stackexchange.com/上提出相关的具体问题,而不是在这里。

您可能还对我关于数字音频编程基础的演讲中的幻灯片感兴趣,其中有一张关于这个主题的幻灯片,尽管它基本上说的是同样的事情(甚至可能比我刚才说的要少):http://blog .bjornroche.com/2011/11/slides-from-fundamentals-of-audio.html

于 2013-02-26T16:44:29.633 回答
18

16 位 PCM 有一个范围 - 32768 到 32767。因此,将每个 PCM 样本乘以 (1.0f/32768.0f) 到一个新的浮点数组中,并将其传递给您的重新采样。

重新采样后返回浮动,乘以 32768.0,饱和(剪切范围之外的任何内容 - 32768 到 32767),舍入(或 Björn 提到的抖动),然后再转换回短。

使用没有位错误的乘法显示正向和反向转换的测试代码:

// PcmConvertTest.cpp : Defines the entry point for the console application.
//

#include <assert.h>
#include <string.h>
#include <stdint.h>
#define SZ 65536
#define MAX(x,y) ((x)>(y)) ? (x) : (y)
#define MIN(x,y) ((x)<(y)) ? (x) : (y)
int main(int argc, char* argv[])
{
  int16_t *pIntBuf1 = new int16_t[SZ];
  int16_t *pIntBuf2 = new int16_t[SZ];
  float   *pFloatBuf = new float[SZ];

  // Create an initial short buffer for testing
  for( int i = 0; i < SZ; i++) {
    pIntBuf1[i] = (int16_t)(-32768 + i);
  }

  // Convert the buffer to floats. (before resampling)
  const float div = (1.0f/32768.0f);
  for( int i = 0; i < SZ; i++) {
    pFloatBuf[i] = div * (float)pIntBuf1[i];
  }

  // Convert back to shorts
  const float mul = (32768.0f);
  for( int i = 0; i < SZ; i++) {
    int32_t tmp = (int32_t)(mul * pFloatBuf[i]);
    tmp = MAX( tmp, -32768 ); // CLIP < 32768
    tmp = MIN( tmp, 32767 );  // CLIP > 32767
    pIntBuf2[i] = tmp;
  }

  // Check that the conversion went int16_t to float and back to int for every PCM value without any errors.
  assert( 0 == memcmp( pIntBuf1, pIntBuf2, sizeof(int16_t) * SZ) );

  delete pIntBuf1;
  delete pIntBuf2;
  delete pFloatBuf;
  return 0;
}
于 2013-02-26T14:01:22.953 回答