2

我在读取 16 位 .wav 文件时遇到问题。我已阅读标题信息,但是,转换似乎不起作用。

例如,在 Matlab 中,如果我读入波形文件,我会得到以下类型的数据:

 -0.0064, -0.0047,  -0.0051, -0.0036, -0.0046, -0.0059,  -0.0051

但是,在我的 C++ 程序中,返回以下内容:

0.960938, -0.00390625, -0.949219, -0.00390625, -0.996094, -0.00390625

我需要以相同的方式表示数据。现在,对于8 bit.wav 文件,我执行了以下操作:

uint8_t c;

for(unsigned i=0; (i < size); i++)
{
    c = (unsigned)(unsigned char)(data[i]);
    double t = (c-128)/128.0;
    rawSignal.push_back(t);
}

但是,当我为 16 位执行此操作时,这很有效:

uint16_t c;

for(unsigned i=0; (i < size); i++)
{
   c = (signed)(signed char)(data[i]);
   double t = (c-256)/256.0;
   rawSignal.push_back(t);
}

不起作用并显示输出(上图)。

我遵循此处找到的标准

数组在哪里,我可能data只是在转换错误,但似乎无法找出在哪里。有人有什么建议吗?charrawSignalstd::vector<double>

谢谢

编辑:

这是现在显示的内容(在图表中):

在此处输入图像描述

这是它应该显示的内容:

在此处输入图像描述

4

5 回答 5

6

这里有几个问题:

  • 8 位 wav 是无符号的,但 16 位 wav 是有符号的。因此,Carl 和 Jay 的答案中给出的减法步骤是不必要的。我认为他们只是从您的代码中复制的,但他们是错误的。
  • 16 位波的范围从 -32,768 到 32,767,而不是从 -256 到 255,这使得您使用的乘法无论如何都不正确。
  • 16 位 wav 是 2 个字节,因此您必须读取两个字节才能制作一个样本,而不是一个。您似乎一次阅读一个字符。当您读取字节时,如果您的本地字节序不是小字节序,则可能必须交换它们。

假设一个小端架构,你的代码看起来更像这样(非常接近卡尔的答案):

for (int i = 0; i < size; i += 2)
{
    int c = (data[i + 1] << 8) | data[i];
    double t = c/32768.0;
    rawSignal.push_back(t);
}

对于大端架构:

for (int i = 0; i < size; i += 2)
{
    int c = (data[i] << 8) | data[i+1];
    double t = c/32768.0;
    rawSignal.push_back(t);
}

该代码未经测试,所以如果它不起作用,请LMK。

于 2013-09-12T21:44:03.307 回答
1

(首先是关于 little-endian/big-endian-ness。WAV 只是一种容器格式,其中编码的数据可以是无数种格式。大多数编解码器都是无损的(MPEG Layer-3 aka MP3,是的,流可以“打包”成 WAV、各种 CCITT 和其他编解码器)。您假设您处理某种 PCM 格式,您可以在其中看到 RAW 格式的实际波形,没有对其进行无损转换。字节序取决于在产生流的编解码器上 。RIFF WAV 文件中是否保证格式参数的字节序?

如果一个 PCM 样本是线性比例采样整数,或者它背后有一些缩放、对数比例或其他变换,这也是一个问题。我遇到的常规 PCM wav 文件是简单的线性比例样本,但我不在录音或制作行业工作。

因此,您的解决方案的路径:

  1. 确保您处理的是常规 16 位 PCM 编码的 RIFF WAV 文件。
  2. 读取流时,始终一次读取两个字节(char)并将两个字符转换为 16 位短。人们在我面前展示了这一点。
  3. 您显示的波形清楚地表明您没有很好地估计频率(或者您只有一个单声道而不是立体声)。因为采样率(44.1kHz、22KHz、11KHz、8kHz 等)与分辨率(8 位、16 位、24 位等)同样重要。也许在第一种情况下,您有立体声数据。你可以把它读成单声道,你可能不会注意到它。在第二种情况下,如果您有单声道数据,那么您将在读取数据的中途用完样本。根据您的图表,这似乎是发生的事情。谈到另一个原因:较低的采样分辨率(16 位也较低)通常与较低的采样率配对。因此,如果您的输入数据是录制时间,并且您认为您有一个 22kHz 的数据,但实际上只有 11kHz,那么您将再次从实际样本中用完一半并读入内存垃圾。所以其中之一。

确保你解释和处理你的循环迭代器变量和大小。似乎大小告诉你有多少字节。您将拥有正好一半的短整数样本。请注意,Bjorn 的解决方案因此正确地将 i 增加了 2。

于 2013-09-12T23:58:59.633 回答
1

我的工作代码是

int8_t* buffer = new int8_t[size];
/*
  HERE buffer IS FILLED
*/
for (int i = 0; i < size; i += 2)
{
    int16_t c = ((unsigned char)buffer[i + 1] << 8) | (unsigned char)buffer[i];
    double t = c/32768.0;
    rawSignal.push_back(t);
}
于 2015-12-30T18:50:48.293 回答
0

16 位数量为您提供从 -32,768 到 32,767 的范围,而不是从 -256 到 255(这只是 9 位)。利用:

for (int i = 0; i < size; i += 2)
{
    c = (data[i + 1] << 8) + data[i]; // WAV files are little-endian
    double t = (c - 32768)/32768.0;
    rawSignal.push_back(t);
}
于 2013-09-12T18:10:59.567 回答
0

你可能想要更多这样的东西:

uint16_t c;
for(unsigned i=0; (i < size); i++)
{
   // get a 16 bit pointer to the array
   uint16_t* p = (uint16_t*)data;
   // get the i-th element
   c = *( p + i );
   // convert to signed? I'm guessing this is what you want
   int16_t cs = (int16_t)c;
   double t = (cs-256)/256.0;
   rawSignal.push_back(t);
}

您的代码将 8 位值转换为有符号值,然后将其写入无符号变量。你应该看看它,看看它是否是你想要的。

于 2013-09-12T19:10:18.840 回答