4

我使用 NI 数据采集模块以 48ksps 的速度在“现场”捕获了几 Gb 的样本数据。我想从这些数据中创建一个 WAV 文件。

我之前使用 MATLAB 加载数据,将其标准化为 16 位 PCM 范围,然后将其写为 WAV 文件。然而,MATLAB 在文件大小上犹豫不决,因为它在“内存中”执行所有操作。

理想情况下,我会在 C++ 或 C 中执行此操作(C# 是一个选项),或者如果有一个现有的实用程序,我会使用它。是否有一种简单的方法(即现有库)来获取原始 PCM 缓冲区、指定采样率、位深度并将其打包成 WAV 文件?

为了处理大型数据集,它需要能够以块的形式附加数据,因为不一定可以将整个数据集读入内存。

我知道我可以使用格式规范从头开始执行此操作,但我不想重新发明轮子,或者如果我能提供帮助,我不想花时间修复错误。

4

6 回答 6

3

有趣的是,我在stackoverflow parse of code上发现了一个错误,它不支持如下所示的行尾的\字符,悲伤

//stolen from OGG Vorbis pcm to wav conversion rountines, sorry
#define VERSIONSTRING "OggDec 1.0\n"

static int quiet = 0;
static int bits = 16;
static int endian = 0;
static int raw = 0;
static int sign = 1;
unsigned char headbuf[44];  /* The whole buffer */







#define WRITE_U32(buf, x) *(buf)     = (unsigned char)((x)&0xff);\
                          *((buf)+1) = (unsigned char)(((x)>>8)&0xff);\
                          *((buf)+2) = (unsigned char)(((x)>>16)&0xff);\
                          *((buf)+3) = (unsigned char)(((x)>>24)&0xff);

#define WRITE_U16(buf, x) *(buf)     = (unsigned char)((x)&0xff);\
                          *((buf)+1) = (unsigned char)(((x)>>8)&0xff);

/*
 * Some of this based on ao/src/ao_wav.c
 */
static int
write_prelim_header (FILE * out, int channels, int samplerate)
{

  int knownlength = 0;

  unsigned int size = 0x7fffffff;
  // int channels = 2;
  // int samplerate = 44100;//change this to 48000
  int bytespersec = channels * samplerate * bits / 8;
  int align = channels * bits / 8;
  int samplesize = bits;

  if (knownlength)
    size = (unsigned int) knownlength;

  memcpy (headbuf, "RIFF", 4);
  WRITE_U32 (headbuf + 4, size - 8);
  memcpy (headbuf + 8, "WAVE", 4);
  memcpy (headbuf + 12, "fmt ", 4);
  WRITE_U32 (headbuf + 16, 16);
  WRITE_U16 (headbuf + 20, 1);  /* format */
  WRITE_U16 (headbuf + 22, channels);
  WRITE_U32 (headbuf + 24, samplerate);
  WRITE_U32 (headbuf + 28, bytespersec);
  WRITE_U16 (headbuf + 32, align);
  WRITE_U16 (headbuf + 34, samplesize);
  memcpy (headbuf + 36, "data", 4);
  WRITE_U32 (headbuf + 40, size - 44);

  if (fwrite (headbuf, 1, 44, out) != 44)
    {
      printf ("ERROR: Failed to write wav header: %s\n", strerror (errno));
      return 1;
    }

  return 0;
}

static int
rewrite_header (FILE * out, unsigned int written)
{
  unsigned int length = written;

  length += 44;

  WRITE_U32 (headbuf + 4, length - 8);
  WRITE_U32 (headbuf + 40, length - 44);
  if (fseek (out, 0, SEEK_SET) != 0)
    {
      printf ("ERROR: Failed to seek on seekable file: %s\n",
          strerror (errno));
      return 1;
    }

  if (fwrite (headbuf, 1, 44, out) != 44)
    {
      printf ("ERROR: Failed to write wav header: %s\n", strerror (errno));
      return 1;
    }
  return 0;
}
于 2009-10-21T13:55:40.767 回答
2

我认为您可以为此使用libsox

于 2009-09-22T13:26:05.010 回答
1

不久前,我在 Mathworks 的文件交换网站上遇到了一个名为WAVAPPEND的函数。我从来没有使用过它,所以我不确定它是否有效或是否适合您尝试做的事情,但也许它对您有用。

于 2009-09-22T18:33:55.110 回答
1

好的......我在这里迟到了 5 年......但我只是为自己做了这个,并想把解决方案放在那里!

在 matlab 中编写大型 wav 文件时,我遇到了内存不足的问题。我通过编辑 matlab wavwrite 函数解决了这个问题,因此它使用memmap而不是存储在 RAM 上的变量从硬盘驱动器中提取数据,然后将其保存为新函数。这将为您省去很多麻烦,因为您在从头开始编写 wav 文件时不必担心处理标题,并且您不需要任何外部应用程序。

1) 键入edit wavwrite以查看函数的代码,然后将其副本另存为新函数。

2) 我y将函数中的变量wavwrite从包含 wav 数据的数组修改为单元数组,其中字符串指向保存在硬盘驱动器上的每个通道的数据的位置。当然,首先用于fwrite将您的 wav 数据存储在硬盘上。在函数开始时,我将存储的文件位置y转换为 memmap 变量,并定义通道数和样本数,如下所示:

替换这些行:

% If input is a vector, force it to be a column:
if ndims(y) > 2,
  error(message('MATLAB:audiovideo:wavwrite:invalidInputFormat'));
end
if size(y,1)==1,
   y = y(:);
end
[samples, channels] = size(y);

有了这个:

% get num of channels
channels = length(y);

%Convert y from strings pointing to wav data to mammap variables allowing access to the data
for i  = 1:length(y)
   y{i} = memmapfile(y{i},'Writable',false,'Format','int16');
end
samples = length(y{1}.Data);

3) 现在你可以编辑私有函数了write_wavedat(fid,fmt)。这是写入 wav 数据的函数。把它变成一个嵌套函数,这样它就可以将你的ymemmap 变量作为全局变量读取,而不是将值传递给函数并吃掉你的 RAM,然后你可以做一些这样的改变:

替换写入 wav 数据的行:

if (fwrite(fid, reshape(data',total_samples,1), dtype) ~= total_samples), error(message('MATLAB:audiovideo:wavewrite:failedToWriteSamples')); end

有了这个:

%Divide data into smaller packets for writing
       packetSize = 30*(5e5); %n*5e5 = n Mb of space required
       packets = ceil(samples/packetSize);

       % Write data to file!
       for i=1:length(y)
           for j=1:packets
               if j == packets
                    fwrite(fid, y{i}.Data(((j-1)*packetSize)+1:end), dtype);
               else
                    fwrite(fid, y{i}.Data(((j-1)*packetSize)+1:j*packetSize), dtype);
               end
               disp(['...' num2str(floor(100*((i-1)*packets + j)/(packets*channels))) '% done writing file...']);
           end
       end

这会将数据从每个 memmap 变量增量复制到 wavfile

4)应该是这样!您可以保留其余代码,因为它会为您编写标题。下面是如何使用此函数编写大型 2 通道 wav 文件的示例:

wavwriteModified({'c:\wavFileinputCh1' 'c:\wavFileinputCh2'},44100,16,'c:\output2ChanWavFile');

我可以验证这种方法是否有效,因为我刚刚使用我编辑的 wavwrite 函数编写了一个 800mB 4 通道 wav 文件,而当 matlab 为我编写大于 200mb 的 wav 文件时通常会抛出out of memmory错误。

于 2015-07-05T16:37:44.370 回答
0

C# 将是一个不错的选择。FileStreams 易于使用,可用于读取和写入块中的数据。此外,读取 WAV 文件头是一项相对复杂的任务(您必须搜索 RIFF 块等),但编写它们是小菜一碟(您只需填写一个头结构并将其写入文件开头)。

有许多库可以进行这样的转换,但我不确定它们能否处理您所说的巨大数据量。即使他们这样做了,您可能仍然需要做一些编程工作才能将较小的原始数据块提供给这些库。

对于编写自己的方法,归一化并不难,甚至从 48ksps 重新采样到 44.1ksps 也相对简单(假设您不介意线性插值)。您可能还可以更好地控制输出,因此创建一组较小的 WAV 文件而不是一个巨大的文件会更容易。

于 2009-09-22T13:46:29.777 回答
0

当前的 Windows SDK 音频捕获示例从麦克风捕获数据并将捕获的数据保存到 .WAV 文件。该代码远非最佳,但它应该可以工作。

请注意,RIFF 文件(.WAV 文件是 RIFF 文件)的大小限制为 4G。

于 2009-09-25T04:52:29.700 回答