user8064607
user8064607

Reputation:

C++ RIFF WAVE reader is painfully slow

So I've never really worked with binary files before and I'm new to C++. I wanted to read a wav file and output its data section into a txt (seperating the value of each sample with a comma). I also managed to read in the header section, but this code is not important here so I'll not include it.

My wav file stores data in the IEEE 754 standard (floating point numbers) at 32bps. I first read the entire wav file into a char vector and try to work with that afterwards. The output of the program is what I expect it to be, I can play the sound back in Python by reading in the txt without issues. The program is just horribly slow (it takes several minutes for a wav file that is a couple seconds long).

This is wavReader.cpp:

#include "stdafx.h"
#include "wavFile.h"
#include <fstream>
#include <iostream>
#include <vector>

int main()
{
    std::ifstream file("file.wav", std::ios::binary);
    std::vector<char> buffer((
        std::istreambuf_iterator<char>(file)),
        (std::istreambuf_iterator<char>()));
    std::cout << "Loading complete!\n";

    WavFile wavFile = setWavFile(buffer);

    return 0;
}

This is wavFile.h:

#pragma once
#include <iostream>
#include <vector>

struct WavFile
{
    uint32_t dataSize;
};

WavFile setWavFile(std::vector<char> buffer);
uint32_t getUint32(std::vector<char> buffer, std::vector<char>::iterator it);

This is wavFile.cpp:

#include "stdafx.h"
#include "WavFile.h"
#include <fstream>

WavFile setWavFile(std::vector<char> buffer) {

    WavFile wavFile;
    std::vector<char>::iterator it = buffer.begin();

    // Beginning of data chunk is marked with "data"
    it += 4;
    while (*(it - 4) != 'd' ||
        *(it - 3) != 'a' ||
        *(it - 2) != 't' ||
        *(it - 1) != 'a')
        it++;

    wavFile.dataSize = getUint32(buffer, it), it += 4;
    std::ofstream output("data.txt");

    while (it != buffer.end())
    {
        char outputChar[4];
        for (int i = 0; i < 4; (i++, it++))
            outputChar[i] = *it;
        char* outputStr = outputChar;
        char** outputStrPtr = &outputStr;
        float** outputPtr = reinterpret_cast<float**>(outputStrPtr);
        output << **outputPtr << ", ";
        std::cout << static_cast<double>(std::distance(buffer.begin(), it)) * 100 / wavFile.dataSize << "\%\n";
    }

    return wavFile;
}

uint32_t getUint32(std::vector<char> buffer, std::vector<char>::iterator it)
{
    char outputChar[4];
    for (int i = 0; i < 4; (i++, it++))
        outputChar[i] = *it;
    char* outputStr = outputChar;
    char** outputStrPtr = &outputStr;
    uint32_t** outputPtr = reinterpret_cast<uint32_t**>(outputStrPtr);
    return **outputPtr;
}

I made the program print the progress to the console. Note that this only works with wav files that have one channel and store the samples in IEEE 754 standard. You can find the file I used here. I'm just a hobby programmer, so forgive me that I have no clue what makes my program this slow... Is it the vector iteration? Or is it the kinda messy variable declarations with reinterpret_cast?

Upvotes: 0

Views: 285

Answers (2)

user7860670
user7860670

Reputation: 37582

You are reading it completely wrong way. Wave files have RIFF format. Each file consists of RIFF file header and a sequence of chunks.

#include <Windows.h> // for DWORD
#include <MMReg.h> // for PCMWAVEFORMATPCMWAVEFORMAT and FORCC

struct t_RiffFileHeader
{
    ::FOURCC m_riff;      // must be 'R', 'I', 'F', 'F'
    ::DWORD  m_file_size; // should be less than or equal to the total file zize
    ::FOURCC m_formtype;  // must be 'W', 'A', 'V', 'E'
};
static_assert(12 == sizeof(t_RiffFileHeader), "");

So you first read 12 bytes of this riff header and verify that it is correct.

size_t remaining_bytes_count(buffer.size());
const char * p_cursor(buffer.data());
if(remaining_bytes_count <= sizeof(t_RiffFileHeader))
{
    exit(1);
}
const t_RiffFileHeader & riff_header(*reinterpret_cast< const t_RiffFileHeader * >(reinterpret_cast< uintptr_t >(p_cursor)));
if(static_cast< size_t >(riff_header.m_file_size) < sizeof(t_RiffChunkHeader))
{
    exit(1);
}
p_cursor += sizeof(t_RiffFileHeader);
remaining_bytes_count -= sizeof(t_RiffFileHeader);

Then you proceed to read chunks.

struct t_RiffChunkHeader
{
    ::FOURCC m_id;        
    ::DWORD  m_chunk_content_size;
};
static_assert(8 == sizeof(t_RiffFileHeader), "");

You read chunk header and then read m_chunk_content_size bytes of data based on chunk id:

  • 'f', 'm', 't', ' ' chunk is typically first and should contain PCMWAVEFORMAT structure describing wav data;
  • 'd', 'a', 't', 'a' chunk contains audio data;
  • other chunks can be skipped.

If you are done with reading of the last chunk but file end is not reached yet then most likely there is another RIFF file attached. Because of 32-bit length limitation large files are constructed by concatenating several smaller RIFF files.

Upvotes: 0

geza
geza

Reputation: 29970

Maybe the progress printing slows it down? You print a lot. Maybe you can try to print only when the integral value of the percent changes, like:

int lastPercent = -1;

loop {
  ...
  float percent = ...;
  int integralPercent = (int)percent;
  if (integralPercent!=lastPercent) {
    lastPercent = integralPercent;
    // print percent here
  }
}

Upvotes: 0

Related Questions