0

I am currently using the C++ to implement my code. The idea is I want to read a wav file through a program and then output its waveform on the screen. I have already found some references there:C++ Reading the Data part of a WAV file. and here is my code of reading the audio file. I have no idea of how to generate the waveform .

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

using namespace std;
using std::string;
using std::fstream;



typedef struct  WAV_HEADER
{
   /* for the part of RIEF Chunk Descriptor */

   uint8_t             RIFF[4];        // RIFF Header      Magic header
   uint32_t            ChunkSize;      // RIFF Chunk Size  
   uint8_t             WAVE[4];        // WAVE Header  

   /* for the part of "fmt" subChunk */    
   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

   /* For the part of "data" subChunk */      
   uint8_t              Subchunk2ID[4]; // "data"  string   
   uint32_t             Subchunk2Size;  // Sampled data length    

}wav_hdr; 


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;
 }
Issac ZHANG
  • 1
  • 1
  • 1
  • 1
    wav file already contains an uncompressed waveform, there is no need to generate anything – user7860670 Jun 18 '18 at 09:34
  • If it's not necessary to write from scratch there should already exist some good .wav read/write libraries out there. – Carl Jun 18 '18 at 09:36
  • You are going to need some kind of graphics or `GUI` library to do the displaying. That library will no doubt come with instructions on how to draw stuff, etc... – Galik Jun 18 '18 at 09:59
  • you can use OpenGL to draw the audio curve ... its not trival however very rewarding to master the art of graphics – Scott Stensland Jun 18 '18 at 14:26
  • I can't simply use some existing library codes to read the whole media file or output it. The question only require us to use some basic operations like DrawLine(x,y), – Issac ZHANG Jun 18 '18 at 18:22
  • The waveform drawing part is actually not that difficult. It's the preparing of the data for drawing (averaging the samples and finding peaks) that is the most difficult part here. – dsp_user Jun 19 '18 at 07:02
  • @Carl Sometimes the question is not about putting it in production, but rather understanding how it works. – Dragas Apr 18 '19 at 07:44

1 Answers1

1

I would have left this as a comment with code gists and not an answer since it doesn't answer your question directly but I don't have enough reputation for this.

I did what you're trying to do with Qt, most of my code comes from Qt's documentation. You need to find the peak value of the samples and then draw that. Let me share some code that could give you an idea on how you might want to do it.

void Waveform::appendSamples()
{
    buffer = audioDecoder->read();
    qreal peak = getPeakValue(buffer.format());
    const qint16 *data = buffer.constData<qint16>();
    int count = buffer.sampleCount() / 2;
    for (int i = 0; i < count; i += 1200){ // I want 40 samples per second currently assuming 48kHz
        double val = data[i]/peak;         
        samples.append(val * 300); // *300 for scaling
    }
}

qreal Waveform::getPeakValue(const QAudioFormat &format)
{
    qreal ret(0);
    if (format.isValid()){
        switch (format.sampleType()) {
            case QAudioFormat::Unknown:
            break;
            case QAudioFormat::Float:
                if (format.sampleSize() != 32)
                    ret = 0;
                else
                    ret = 1.00003;
            break;
            case QAudioFormat::SignedInt:
                if (format.sampleSize() == 32)
                    ret = INT_MAX;
                else if (format.sampleSize() == 16)
                    ret = SHRT_MAX;
                else if (format.sampleSize() == 8)
                    ret = CHAR_MAX;
                break;
            case QAudioFormat::UnSignedInt:
                if (format.sampleSize() == 32)
                    ret = UINT_MAX;
                else if (format.sampleSize() == 16)
                    ret = USHRT_MAX;
                else if (format.sampleSize() == 8)
                    ret = UCHAR_MAX;
            break;
        default:
            break;
        }
    }
    return ret;
}

For the drawing part in Qt (probably not useful to you)

QSGNode* WaveformRenderer::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
    QSGSimpleRectNode *n = static_cast<QSGSimpleRectNode *>(oldNode);

    if(!n){
        n = new QSGSimpleRectNode();
        n->setColor(Qt::red);
    }

    QVector<double> samples = wav->getSamples(); //retrieve the samples

    int numberOfSamples = samples.size();

    setItemWidth(qreal(numberOfSamples * 2)); //signal stuff for qml

    for(int i = 0; i < numberOfSamples; ++i){
        QSGSimpleRectNode *temp = new QSGSimpleRectNode();
        temp->setColor(Qt::green);
        temp->setRect(i * 2, height()/2 - samples[i], 2, samples[i] * 2);
        n->appendChildNode(temp);
    }
    return n;
}
ALDSMQJF
  • 19
  • 5