如何将字节数组中的pcm样本转换为-1.0至1.0及以后的浮点数?

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

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


答案 1

你问两个问题:

  1. 如何将采样从22kHz降至8kHz?

  2. 如何从浮点数 [-1,1] 转换为 16 位整数并返回?

请注意,这个问题已经更新,表明#1在其他地方得到了处理,但我会留下我的答案的这一部分,以防它帮助别人。

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

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

对信号进行重新采样的一个非常好的方法是使用多相滤波器。但是,即使对于在信号处理方面有经验的人来说,这也相当复杂。您还有其他几个选项:

  • 使用实现高质量重采样的库,如库采样
  • 做一些快速而肮脏的事情

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

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

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

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

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

2. 如何从浮点数 [-1,1] 转换为 16 位整数和回?

这已经是由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,我认为我们已经确定情况并非如此,因此这个答案很好。

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

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


答案 2

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;
}