繁体   English   中英

从捕获的PCM样本数据中获取WAV文件

[英]WAV file from captured PCM sample data

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

我之前使用MATLAB加载数据,将其标准化为16位PCM范围,然后将其写为WAV文件。 然而,MATLAB对文件大小进行了抨击,因为它完成了“内存中”的所有操作。

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

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

我知道我可以使用格式规范从头开始这样做,但我不想重新发明轮子,或者如果我可以帮助它花时间修复bug。

有意思的是,我发现了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;
}

我认为你可以使用libsox

刚才在Mathworks的文件交换站点上遇到了一个名为WAVAPPEND的函数。 我从来没有使用它,所以我不确定它是否有效或适合你想要做的事情,但也许它对你有用。

好的......我在这里已经晚了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);

有了这个:

% 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数据的行:

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');

我可以验证这种方法的作品,我只是写了800MB的4声道的WAV文件,我的编辑wavwrite功能,当MATLAB通常会引发out of memmory错误写WAV文件较大然后200MB我。

C#将是一个很好的选择。 FileStreams易于使用,可用于以块的形式读取和写入数据。 此外,读取WAV文件头是一项相对复杂的任务(您必须搜索RIFF块等),但编写它们就是蛋糕(您只需填写标题结构并将其写入文件的开头)。

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM