Search code examples
c++audiowavendianness

Correct reading of samples from .wav file


I am trying to read correctly a WAVE file, PCM, mono, 16 bits (2 bytes per sample). I have managed to read the header. The problem is reading (writing) the data part.

As far as I understand the 16-bit samples in the data chunk are little-endian, and "split" into two chunks of 8 bits each. So for me a way to read the correct data should be:

  1. Read file and put chunks into two differentint8_t variables (or a std::vector<int8_t>..)
  2. In some way "join" these two variables to make a int16_t and being able to process it.

The problem is I have no idea on how to deal with the little-endianness and the fact that these samples aren't unsigned, so I can't use the << operator.

This is one of the test I've done, without success:

int8_t buffer[], firstbyte,secondbyte;
int16_t result;
std::vector<int16_t> data;
while(Read bytes and put them in buffer){
for (int j=0;j<bytesReadFromTheFile;j+=2){
                    firstbyte = buffer[j];
                    secondbyte = buffer[j+1];
                    result = (firstbyte);
                    result = (result << 8)+secondbyte; //shift first byte and add second
                    data.push_back(result);
                }
}

To be more verbose, I am using this code found online and created a class starting from it (The process is the same, but the Class configuration is very long and has many features that aren't useful for this problem):

#include <iostream>
#include <string>
#include <fstream>
#include <cstdint>

using std::cin;
using std::cout;
using std::endl;
using std::fstream;
using std::string;

typedef struct  WAV_HEADER
{
    /* RIFF Chunk Descriptor */
    uint8_t         RIFF[4];        // RIFF Header Magic header
    uint32_t        ChunkSize;      // RIFF Chunk Size
    uint8_t         WAVE[4];        // WAVE Header
    /* "fmt" sub-chunk */
    uint8_t         fmt[4];         // FMT header
    uint32_t        Subchunk1Size;  // Size of the fmt chunk
    uint16_t        AudioFormat;    // Audio format 1=PCM,6=mulaw,7=alaw,     257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM
    uint16_t        NumOfChan;      // Number of channels 1=Mono 2=Sterio
    uint32_t        SamplesPerSec;  // Sampling Frequency in Hz
    uint32_t        bytesPerSec;    // bytes per second
    uint16_t        blockAlign;     // 2=16-bit mono, 4=16-bit stereo
    uint16_t        bitsPerSample;  // Number of bits per sample
    /* "data" sub-chunk */
    uint8_t         Subchunk2ID[4]; // "data"  string
    uint32_t        Subchunk2Size;  // Sampled data length
} wav_hdr;

// Function prototypes
int getFileSize(FILE* inFile);

int main(int argc, char* argv[])
{
    wav_hdr wavHeader;
    int headerSize = sizeof(wav_hdr), filelength = 0;

    const char* filePath;
    string input;
    if (argc <= 1)
    {
        cout << "Input wave file name: ";
        cin >> input;
        cin.get();
        filePath = input.c_str();
    }
    else
    {
        filePath = argv[1];
        cout << "Input wave file name: " << filePath << endl;
    }

    FILE* wavFile = fopen(filePath, "r");
    if (wavFile == nullptr)
    {
        fprintf(stderr, "Unable to open wave file: %s\n", filePath);
        return 1;
    }

    //Read the header
    size_t bytesRead = fread(&wavHeader, 1, headerSize, wavFile);
    cout << "Header Read " << bytesRead << " bytes." << endl;
    if (bytesRead > 0)
    {
        //Read the data
        uint16_t bytesPerSample = wavHeader.bitsPerSample / 8;      //Number     of bytes per sample
        uint64_t numSamples = wavHeader.ChunkSize / bytesPerSample; //How many samples are in the wav file?
        static const uint16_t BUFFER_SIZE = 4096;
        int8_t* buffer = new int8_t[BUFFER_SIZE];
        while ((bytesRead = fread(buffer, sizeof buffer[0], BUFFER_SIZE / (sizeof buffer[0]), wavFile)) > 0)
        {
            * /** DO SOMETHING WITH THE WAVE DATA HERE **/ *
            cout << "Read " << bytesRead << " bytes." << endl;
        }
        delete [] buffer;
        buffer = nullptr;
        filelength = getFileSize(wavFile);

        cout << "File is                    :" << filelength << " bytes." << endl;
        cout << "RIFF header                :" << wavHeader.RIFF[0] << wavHeader.RIFF[1] << wavHeader.RIFF[2] << wavHeader.RIFF[3] << endl;
        cout << "WAVE header                :" << wavHeader.WAVE[0] << wavHeader.WAVE[1] << wavHeader.WAVE[2] << wavHeader.WAVE[3] << endl;
        cout << "FMT                        :" << wavHeader.fmt[0] << wavHeader.fmt[1] << wavHeader.fmt[2] << wavHeader.fmt[3] << endl;
        cout << "Data size                  :" << wavHeader.ChunkSize << endl;

        // Display the sampling Rate from the header
        cout << "Sampling Rate              :" << wavHeader.SamplesPerSec << endl;
        cout << "Number of bits used        :" << wavHeader.bitsPerSample << endl;
        cout << "Number of channels         :" << wavHeader.NumOfChan << endl;
        cout << "Number of bytes per second :" << wavHeader.bytesPerSec << endl;
        cout << "Data length                :" << wavHeader.Subchunk2Size << endl;
        cout << "Audio Format               :" << wavHeader.AudioFormat << endl;
        // Audio format 1=PCM,6=mulaw,7=alaw, 257=IBM Mu-Law, 258=IBM A-Law, 259=ADPCM

        cout << "Block align                :" << wavHeader.blockAlign << endl;
        cout << "Data string                :" << wavHeader.Subchunk2ID[0] << wavHeader.Subchunk2ID[1] << wavHeader.Subchunk2ID[2] << wavHeader.Subchunk2ID[3] << endl;
    }
    fclose(wavFile);
    return 0;
}

// find the file size
int getFileSize(FILE* inFile)
{
    int fileSize = 0;
    fseek(inFile, 0, SEEK_END);

    fileSize = ftell(inFile);

    fseek(inFile, 0, SEEK_SET);
    return fileSize;
}

The problem is in the /** DO SOMETHING WITH THE WAVE DATA HERE **/ . I have no Idea on how to get the sample value.


Solution

  • I'm a Java programmer, not C++, but I've dealt with this often.

    The PCM data is organized by frame. If it's mono, little-endian, 16-bit the first byte will be the lower half of the value, and the second byte will be the upper and include the sign bit. Big-endian will reverse the bytes. If it's stereo, a full frame (I think it's left then right but I'm not sure) is presented intact before moving on to the next frame.

    I'm kind of amazed at all the code being shown. In Java, the following suffices for PCM encoded as signed values:

    public short[] fromBufferToPCM(short[] audioPCM, byte[] buffer)
    {
        for (int i = 0, n = buffer.length; i < n; i += 2)
        {
            audioPCM[i] = (buffer[i] & 0xff) | (buffer[i + 1] << 8);
        }
    
        return audioBytes;
    }
    

    IDK how to translate that directly to C++, but we are simply OR-ing together the two bytes with the second one first being shifted 8 places to the left. The pure shift picks up the sign bit. (I can't recall why the & 0xff was included--I wrote this a long while back and it works.)

    Curious why so many answers are in the comments and not posted as answers. I thought comments were for requests to clarify the OP's question.