从捕获的PCM样本数据生成WAV文件

4

我有几个GB的野外捕获的样本数据,使用NI数据采集模块以48ksps的速率。我想从这些数据创建一个WAV文件。

我之前用MATLAB加载数据,将其归一化为16位PCM范围,然后将其写成WAV文件。但是MATLAB会因为它在内存中处理所有事情而无法处理文件大小。

我最好使用C ++或C(C#是一个选项),或者如果有现有的实用程序,我会使用它。是否有一种简单的方法(即现有库)可以获取原始PCM缓冲区,指定采样率,位深度,并将其打包成WAV文件?

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

我知道我可以根据格式规范从头开始做这件事,但如果可能的话,我不想重新发明轮子,或者花时间修复这个问题。


你的原始数据每个样本有多少比特,需要将其重新采样为44.1ksps吗?WAV格式支持48ksps。 - MusiGenesis
WAV文件格式:http://ccrma.stanford.edu/courses/422/projects/WaveFormat/ - Dan
谢谢大家。48Ksps应该被保留。NI捕获的数据是浮点电压,但最初采样时为14位。我使用16位来保持信号完整性。这不是直接的编程问题,尽管我使用编程来解决问题。数据是嵌入式信号处理应用程序的测试集(真正的编程问题)。我只需要一种便携式的方法来重新创建原始信号。最后,我只是将样本重新缩放为+/- 1.0而不是电压测量,并使用sox直接从浮点数据创建wav文件。 - Clifford
6个回答

3

有趣的是,我发现stackoverflow对代码的解析有一个bug,它不支持像下面看到的行尾的\字符,很遗憾

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

终于我可以添加注释了,感谢并修复了代码中的错误,我有很多C代码要在这里发布。 - Mandrake
已修复错误,并注意到在某些情况下处理wav文件的Microsoft工具可能从位置60开始,因此请查找wav文件上的“数据”位置以在正确位置启动。同时确认wav文件在位置44之后有有效数据。 - Mandrake

2
我认为你可以使用 libsox 来完成这个任务。

看起来就是我需要的。希望我能够在不用污染我的电脑安装Cygwin的情况下构建它。我宁愿使用Linux虚拟机! - Clifford
预编译的二进制文件依赖于Cygwin;您是否真正需要一个C库,或者仅从命令行调用sox就足够了? - Christoph
目前,直接调用sox是我首选的选项。 - Clifford
谢谢,最终我使用了sox.exe来达到我需要的效果。 - Clifford

1
我之前在Mathworks的文件交换网站上发现了一个名为WAVAPPEND的函数。虽然我从未使用过它,因此不确定它是否适用于你所尝试的操作,但或许对你有所帮助。

谢谢,我认为这将来会很有用。 - Clifford

1

好的...我来晚了5年...但是我只是为自己做了这个并想把解决方案分享出来!

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

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

2)我修改了wavwrite函数中的y变量,将其从包含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);

with this:

% 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数据的函数。将其转换为嵌套函数,以便它可以将您的y memmap变量作为全局变量读取,而不是将值传递给函数并占用您的RAM,然后您可以进行一些更改,例如:

替换写入wav数据的行:

如果(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变量中的数据复制到wav文件中。
4) 就是这样!您可以将其余的代码保留不变,因为它会为您编写头文件。以下是使用此函数编写大型双通道wav文件的示例:
wavwriteModified({'c:\wavFileinputCh1' 'c:\wavFileinputCh2'},44100,16,'c:\output2ChanWavFile');

我可以验证这种方法是可行的,因为我刚刚使用我的编辑过的wavwrite函数写了一个800mB的4通道wav文件,而对于我来说,Matlab通常会在写大于200mb的wav文件时抛出“内存不足”的错误。

0

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

有许多库可以进行此类转换,但我不确定它们是否能处理您所说的巨大数据量。即使它们可以,您可能仍然需要编程工作来向这些库提供较小的原始数据块。

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


0

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

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


这些是从RF接收器采样的基带信号,使用模拟数据采集模块进行采样。需要保留直流偏移,这不能通过麦克风输入或其他交流耦合音频输入来完成。数据已经存在于浮点电压测量值中。问题是如何将现有数据打包成一种形式,以便将其重放到RF调制器中,以进行基带解码器的可重复测试。最终文件比原始文件小得多,因为原始数据是双精度浮点数,而转换后的数据是16位PCM。 - Clifford
我只是指出示例包含了从原始PCM数据构建WAV文件的代码,而不是建议你从设备中捕获。 - Larry Osterman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接