C++ reading 16bit Wav file

隐身守侯 提交于 2019-12-19 10:31:04

问题


I'm having trouble reading in a 16bit .wav file. I have read in the header information, however, the conversion does not seem to work.

For example, in Matlab if I read in wave file I get the following type of data:

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

However, in my C++ program the following is returned:

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

I need the data to be represented the same way. Now, for 8 bit .wav files I did the following:

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

This worked, however, when I did this for 16bit:

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

Does not work and shows the output (above).

I'm following the standards found Here

Where data is a char array and rawSignal is a std::vector<double> I'm probably just handing the conversion wrong but cannot seem to find out where. Anyone have any suggestions?

Thanks

EDIT:

This is what is now displaying (In a graph):

This is what it should be displaying:


回答1:


There are a few problems here:

  • 8 bit wavs are unsigned, but 16 bit wavs are signed. Therefore, the subtraction step given in the answers by Carl and Jay are unnecessary. I presume they just copied from your code, but they are wrong.
  • 16 bit waves have a range from -32,768 to 32,767, not from -256 to 255, making the multiplication you are using incorrect anyway.
  • 16-bit wavs are 2 bytes, thus you must read two bytes to make one sample, not one. You appear to be reading one character at a time. When you read the bytes, you may have to swap them if your native endianness is not little-endian.

Assuming a little-endian architecture, your code would look more like this (very close to Carl's answer):

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 a big-endian architecture:

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

That code is untested, so please LMK if it doesn't work.




回答2:


(First of all about little-endian/big-endian-ness. WAV is just a container format, the data encoded in it can be in countless format. Most of the codecs are lossless (MPEG Layer-3 aka MP3, yes, the stream can be "packaged" into a WAV, various CCITT and other codecs). You assume that you deal with some kind of PCM format, where you see the actual wave in RAW format, no lossless transformation was done on it. The endianness depends on the codec, which produced the stream. Is the endianness of format params guaranteed in RIFF WAV files?)

It's also a question if the one PCM sample is in linear scale sampled integer or there some scaling, log scale or other transformation behind it. Regular PCM wav files I encountered were simple linear scale samples, but I'm not working in the audio recording or producing industry.

So a path to your solution:

  1. Make sure that you are dealing with regular 16 bit PCM encoded RIFF WAV file.
  2. While reading the stream, always read two bytes (char) at a time and convert the two chars into a 16 bit short. People showed this before me.
  3. The wave form you show clearly suggest that you either not estimated the frequency well (or you just have one mono channel instead of a stereo). Because the sampling rate (44.1kHz, 22KHz, 11KHz, 8kHz, etc) is just as important as the resolution (8 bit, 16 bit, 24 bit, etc). Maybe in the first case you had a stereo data. You can read it in as mono, you may not notice it. In the second case if you have mono data, then you'll run out of samples half way into reading the data. That's what it seems to happen according to your graphs. Talking about the other cause: the lower sampling resolutions (and 16 bit is also lower) often paired with lower sampling rates. So if your input data is the recording time, and you think you have a 22kHz data, but it's actually just 11kHz, then again you'll run out half way through from the actual samples and read in memory garbage. So either one of these.

Make sure that you interpret and treat your loop iterator variable and the size well. It seems that size tells how many bytes you have. You'll have exactly half as much short integer samples. Notice, that Bjorn's solution correctly increments i by 2 because of that.




回答3:


A 16-bit quantity gives you a range from -32,768 to 32,767, not from -256 to 255 (that's just 9 bits). Use:

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



回答4:


You might want something more like this:

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

Your code converts the 8 bit value to a signed value then writes it into an unsigned variable. You should look at that and see if it's what you want.




回答5:


My working code is

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


来源:https://stackoverflow.com/questions/18771375/c-reading-16bit-wav-file

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!