[SOLVED] How to form Wav header ?



  • I have some QAudioDevice with it's properties and need to make a Wav header using the settings of my device. I know that format of data is wave in a-law, mu-law or PCM - the raw data without any compression, also I know that it is mono(but I actualy can get it from QAudioDevice format). So I need to fiil all the fields of Wav header:"Your text to link here...":http://www.sonicspot.com/guide/wavefiles.html . Also I dont know now the file size beacouse I just starting to it's writing. So can anyone show me how to get all of this fields from my QAudioDevice format or some other method in Qt.



  • What do you need the wav headers for? If you just want to record from you device to a wav file, let Qt handle it.



  • I want to open this file by right codec. So as I know the wav file has all needed information(like bitrate, num of chanels, freeq, sample rate, codec etc) in its header after this header goes data. So the task is to add header is start of file and then write data.



  • You could write the header, put 0 instead of the data size, write the audio data.
    When you are done recording, use QFile::seek to go back where the sizes should be and write them.

    Qt Mobility has some code in their test suite to write the wav header for PCM audio:
    http://qt.gitorious.org/qt-mobility/qt-mobility/blobs/master/tests/auto/qaudiooutput/
    But their function writeDataLength (to write the size after the audio data length is known) seems to be missing some code to write the RIFF header length (fileSize - 8) at position 4.



  • So, here what I done:
    Creating file here...
    @void AudioOutput::createAudioOutput(AudioBuffer * audioBuffer)
    {
    m_audioBuffer = audioBuffer;//new AudioBuffer(m_settings,this);
    m_audioOutput = new QAudioOutput(m_settings,this);

    QString fileName = QString("C:/AudiofromStation(%1,%2,%3).wav")
    .arg(m_audioOutput->format().frequency())
    .arg(m_audioOutput->format().channelCount())
    .arg(m_audioOutput->format().sampleSize());

    m_File = new QFile(fileName);
    m_File->open(QIODevice::WriteOnly);

    }@
    Start playing...
    @void AudioOutput::start() {
    m_audioBuffer->start();
    m_File->open(QIODevice::Append);
    m_output = m_audioOutput->start();
    }@
    Writing data to file and playing it on audio device...
    @void AudioOutput::write( const char *data, qint64 len )
    {
    if (m_audioBuffer)
    {
    m_audioBuffer->writeData(data, len);
    m_File->write(data, len);
    }
    }
    @
    or
    @
    void AudioOutput::write(const QByteArray & array) {
    if (m_audioBuffer)
    {
    m_audioBuffer->writeData(array,sizeof(array));
    m_File->write(array,sizeof(array));
    }
    }@
    Stop playing and recording, and calling write header function...
    @void AudioOutput::stop()
    {
    m_audioOutput->stop();
    writeWavHeader(m_File);
    m_File->close();
    // m_audioBuffer->clear();
    // m_audioBuffer->stop();
    m_output = NULL;
    }@
    And write header function copied from here :http://qt.gitorious.org/qt-mobility/qt-mobility/blobs/master/tests/auto/qaudiooutput/
    @void AudioOutput::writeWavHeader( QFile * file )
    {
    QAudioFormat format = m_audioOutput->format();
    qint64 dataLength = file->size() - HeaderLength;
    CombinedHeader header;

    memset(&header, 0, HeaderLength);

    // RIFF header
    if (format.byteOrder() == QAudioFormat::LittleEndian)
    memcpy(header.riff.descriptor.id,"RIFF",4);
    else
    memcpy(header.riff.descriptor.id,"RIFX",4);
    qToLittleEndian<quint32>(quint32(dataLength + HeaderLength - 8),
    reinterpret_cast<unsigned char*>(&header.riff.descriptor.size));
    memcpy(header.riff.type, "WAVE",4);

    // WAVE header
    memcpy(header.wave.descriptor.id,"fmt ",4);
    qToLittleEndian<quint32>(quint32(16),
    reinterpret_cast<unsigned char*>(&header.wave.descriptor.size));
    qToLittleEndian<quint16>(quint16(1),
    reinterpret_cast<unsigned char*>(&header.wave.audioFormat));
    qToLittleEndian<quint16>(quint16(format.channels()),
    reinterpret_cast<unsigned char*>(&header.wave.numChannels));
    qToLittleEndian<quint32>(quint32(format.frequency()),
    reinterpret_cast<unsigned char*>(&header.wave.sampleRate));
    qToLittleEndian<quint32>(quint32(format.frequency() * format.channels() * format.sampleSize() / 8),
    reinterpret_cast<unsigned char*>(&header.wave.byteRate));
    qToLittleEndian<quint16>(quint16(format.channels() * format.sampleSize() / 8),
    reinterpret_cast<unsigned char*>(&header.wave.blockAlign));
    qToLittleEndian<quint16>(quint16(format.sampleSize()),
    reinterpret_cast<unsigned char*>(&header.wave.bitsPerSample));

    // DATA header
    memcpy(header.data.descriptor.id,"data",4);
    qToLittleEndian<quint32>(quint32(dataLength),
    reinterpret_cast<unsigned char*>(&header.data.descriptor.size));

    file->write(reinterpret_cast<const char *>(&header), HeaderLength);

    }@

    But I can't play it with the help of Winamp or WindowsMedia player. So thats mean that the header is in wrong format or I done somthing wrong. When I open my file in audio editor with right codec and settings it's looks like I have no header at start, but have some addition data at end. (so, maybe it's my header ?).



  • Why are you using QAudioOutput ?

    You should write the header in the file before starting the recording, and go back at the beginning of the file after ending the recording to edit the 2 length fields.

    Or you could derive from QFile, and reimplement open and close to do just that. And pass that object to QAudioInput::start(QIODevice*):
    @class WavPcmFile : public QFile {
    public:
    WavPcmFile(const QString & name, const QAudioFormat & format, QObject *parent = 0);
    bool open();
    void close();

    private:
    void writeHeader();
    bool hasSupportedFormat();
    QAudioFormat format;
    };@

    @
    WavPcmFile::WavPcmFile(const QString & name, const QAudioFormat & format_, QObject *parent_)
    : QFile(name, parent_), format(format_)
    {
    }

    bool WavPcmFile::hasSupportedFormat()
    {
    return (format.sampleSize() == 8
    && format.sampleType() == QAudioFormat::UnSignedInt)
    || (format.sampleSize() > 8
    && format.sampleType() == QAudioFormat::SignedInt
    && format.byteOrder() == QAudioFormat::LittleEndian);
    }

    bool WavPcmFile::open()
    {
    if (!hasSupportedFormat()) {
    setErrorString("Wav PCM supports only 8-bit unsigned samples "
    "or 16-bit (or more) signed samples (in little endian)");
    return false;
    } else {
    if (!QFile::open(ReadWrite | Truncate))
    return false;
    writeHeader();
    return true;
    }
    }

    void WavPcmFile::writeHeader()
    {
    QDataStream out(this);
    out.setByteOrder(QDataStream::LittleEndian);

    // RIFF chunk
    out.writeRawData("RIFF", 4);
    out << quint32(0); // Placeholder for the RIFF chunk size (filled by close())
    out.writeRawData("WAVE", 4);

    // Format description chunk
    out.writeRawData("fmt ", 4);
    out << quint32(16); // "fmt " chunk size (always 16 for PCM)
    out << quint16(1); // data format (1 => PCM)
    out << quint16(format.channelCount());
    out << quint32(format.sampleRate());
    out << quint32(format.sampleRate() * format.channelCount()
    * format.sampleSize() / 8 ); // bytes per second
    out << quint16(format.channelCount() * format.sampleSize() / 8); // Block align
    out << quint16(format.sampleSize()); // Significant Bits Per Sample

    // Data chunk
    out.writeRawData("data", 4);
    out << quint32(0); // Placeholder for the data chunk size (filled by close())

    Q_ASSERT(pos() == 44); // Must be 44 for WAV PCM
    }

    void WavPcmFile::close()
    {
    // Fill the header size placeholders
    quint32 fileSize = size();

    QDataStream out(this);
    // Set the same ByteOrder like in writeHeader()
    out.setByteOrder(QDataStream::LittleEndian);
    // RIFF chunk size
    seek(4);
    out << quint32(fileSize - 8);

    // data chunk size
    seek(40);
    out << quint32(fileSize - 44);

    QFile::close();
    }@

    And you use the class like this:
    @// Starts the recording
    WavPcmFile *m_file = new WavPcmFile("Filename.wav", m_audioInput->format(), this);
    if(m_file.open()) {
    m_audioInput->start(m_file);
    } else {
    // Error
    }

    // Stops the recording
    m_audioInput->stop();
    m_file->close();
    @

    Edit: Fixed the close() byte order error as indicated by tiho_d.



  • It's solved by adding m_File->write("0x55",44); in this code:

    @void AudioOutput::createAudioOutput(AudioBuffer * audioBuffer)
    {
    m_audioBuffer = audioBuffer;//new AudioBuffer(m_settings,this);
    m_audioOutput = new QAudioOutput(m_settings,this);

    QString fileName = QString("C:/AudiofromStation(%1,%2,%3).wav")
    .arg(m_audioOutput->format().frequency())
    .arg(m_audioOutput->format().channelCount())
    .arg(m_audioOutput->format().sampleSize());

    m_File = new QFile(fileName);
    m_File->open(QIODevice::WriteOnly);
    m_File->write("0x55",44); // Here we fill 0x55 our for reserving place for future header

    connect(m_audioOutput,SIGNAL(notify()),SLOT(status()));
    connect(m_audioOutput,SIGNAL(stateChanged(QAudio::State)),SLOT(state(QAudio::State)));
    }@

    And here I do seek to 0 and write header:
    @void AudioOutput::stop()
    {
    m_audioOutput->stop();
    m_File->seek(0);
    writeWavHeader(m_File);
    m_File->close();
    // m_audioBuffer->clear();
    // m_audioBuffer->stop();
    m_output = NULL;
    }@

    Thanks everyone.



  • @
    m_File->write("0x55",44); // Here we fill 0x55 our for reserving place for future header
    @
    The string "0x55" isn't 44 bytes long, so it will probably crash randomly at some point...

    You can instead create a buffer with QByteArray:
    @
    m_File->write(QByteArray(44, '\x55'));
    @



  • Thanks, alexisdm now It's works without crashes.



  • Hi guys. I used alexisdm's code and it worked beautifully. Thank You, alexisdm.
    I found a bug inside the WavPcmFile::close() function though. You need to set the correct ByteOrder on the out stream inside WavPcmFile::close() function too, just as you set inside the WavPcmFile::writeHeader() function. If you don't set the right ByteOrder the written value is not the expected one. Here comes the fixed close() function for completeness:
    @
    void WavPcmFile::close()
    {
    // Fill the header size placeholders
    quint32 fileSize = size();

    QDataStream out(this);
    // Set the same ByteOrder like in writeHeader()
    out.setByteOrder(QDataStream::LittleEndian);
    // RIFF chunk size
    seek(4);
    out << quint32(fileSize - 8);

    // data chunk size
    seek(40);
    out << quint32(fileSize - 44);

    QFile::close();
    }
    @
    Thanks again for your code sample alexisdm!



  • Hey, i did recording from microphone using qt in .wav file.
    i am able to play this file in media playe.but when i added this file in matlab for wavread this is giving a error invalid chunk size .....
    and throgh matlab code i fixed the chunk size of my .wav file .
    the matlab code is given below....
    function wavchunksizefix( test13)
    d = dir('test13.wav');
    fileSize = d.bytes
    fid=fopen('test13.wav','r+','l');
    fseek(fid,4,-1);
    fwrite(fid,fileSize-8,'uint32');
    fseek(fid,40,-1);
    fwrite(fid,fileSize-44,'uint32');
    fclose(fid);

    but i want to fix this problem throgh qt code.
    plzzz give me some hint.



  • [[blank-post-content-placeholder]]



  • @alexisdm Hello friend, i tried to use your class for recording wav file but i didnt understand "QAudioFormat & format_" part of your function.

    u use "m_audioInput->format()" in WavPcmFile *m_file = new WavPcmFile("Filename.wav", m_audioInput->format(), this);

    but i didnt understand what is m_audioInput and where u use it?

    is it QAudioInput? and how i can configure format type in program.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.