BTR
BTR

Reputation: 5200

Problem writing binary data with ofstream

Hey all, I'm writing an application which records microphone input to a WAV file. Previously, I had written this to fill a buffer of a specified size and that worked fine. Now, I'd like to be able to record to an arbitrary length. Here's what I'm trying to do:

It kind of works in that the files are coming out to the correct length (header and file size and are correct). However, the data is wonky as hell. I can make out a semblance of what I said -- and the timing is correct -- but there's this repetitive block of distortion. It basically sounds like only half the data is getting into the file.

Here are some variables the code uses (in header)

// File writing
ofstream mFile;
WAVFILEHEADER mFileHeader;
int16_t * mPcmBuffer;
int32_t mPcmBufferPosition;
int32_t mPcmBufferSize;
uint32_t mPcmTotalSize;
bool mRecording;

Here is the code that prepares the file:

// Start recording audio
void CaptureApp::startRecording()
{

    // Set flag
    mRecording = true;

    // Set size values
    mPcmBufferPosition = 0;
    mPcmTotalSize = 0;

    // Open file for streaming
    mFile.open("c:\my.wav", ios::binary|ios::trunc);

}

Here's the code that receives the buffer. This assumes the incoming data is correct -- it should be, but I haven't ruled out that it isn't.

// Append file buffer to output WAV
void CaptureApp::writeData()
{

    // Update header with new PCM length
    mPcmBufferPosition *= sizeof(int16_t);
    mPcmTotalSize += mPcmBufferPosition;
    mFileHeader.bytes = mPcmTotalSize + sizeof(WAVFILEHEADER);
    mFileHeader.pcmbytes = mPcmTotalSize;
    mFile.seekp(0);
    mFile.write(reinterpret_cast<char *>(&mFileHeader), sizeof(mFileHeader));

    // Append PCM data
    if (mPcmBufferPosition > 0)
    {
        mFile.seekp(mPcmTotalSize - mPcmBufferPosition + sizeof(WAVFILEHEADER));
        mFile.write(reinterpret_cast<char *>(&mPcmBuffer), mPcmBufferPosition);
    }

    // Reset file buffer position
    mPcmBufferPosition = 0;

}

And this is the code that closes the file:

// Stop recording
void CaptureApp::stopRecording()
{

    // Save remaining data
    if (mPcmBufferSize > 0) 
        writeData();

    // Close file
    if (mFile.is_open())
    {
        mFile.flush();
        mFile.close();
    }

    // Turn off recording flag
    mRecording = false;

}

If there's anything here that looks like it would result in bad data getting appended to the file, please let me know. If not, I'll triple check the input data (in the callback). This data should be good, because it works if I copy it to a larger buffer (eg, two minutes) and then save that out.

Upvotes: 0

Views: 2623

Answers (4)

BTR
BTR

Reputation: 5200

Shoot, sorry -- had a late night of work and am a bit off today. I forgot to show y'all the actual callback. This is it:

// Called when buffer is full
void CaptureApp::onData(float * data, int32_t & size)
{

    // Check recording flag and buffer size
    if (mRecording && size <= BUFFER_LENGTH)
    {

        // Save the PCM data to file and reset the array if we 
        // don't have room for this buffer
        if (mPcmBufferPosition + size >= mPcmBufferSize) 
            writeData();

        // Copy PCM data to file buffer
        copy(mAudioInput.getData(), mAudioInput.getData() + size, mPcmBuffer + mPcmBufferPosition);

        // Update PCM position
        mPcmBufferPosition += size;

    }

}

Will try y'alls advice and report.

Upvotes: 0

Daniel Mošmondor
Daniel Mošmondor

Reputation: 19956

I won't debug your code, but will try to give you checklist of the things you can try to check and determine where's the error:

  • always have referent recorder or player handy. It can be something as simple as Windows Sound Recorder, or Audacity, or Adobe Audition. Have a recorder/player that you are CERTAIN that will record and play files correctly.
  • record the file with your app and try to play it with reference player. Working?
  • try to record the file with reference recorder, and play it with your player. Working?
  • when you write SOUND data to the WAV file in your recorder, write it to one extra file. Open that file in RAW mode with the player (Windows Sound Recorder won't be enough here). Does it play correctly?
  • when playing the file in your player, and writing to the soundcard, write output to the RAW file, to see if you are playing the data correctly at all or you have soundcars issues. Does it play correctly?

Try all this, and you'll have much better idea of where something went wrong.

Upvotes: 0

Jason Williams
Jason Williams

Reputation: 57892

The most likely reason is you're writing from the address of the pointer to your buffer, not from the buffer itself. Ditch the "&" in the final mFile.write. (It may have some good data in it if your buffer is allocated nearby and you happen to grab a chunk of it, but that's just luck that your write hapens to overlap your buffer)

In general, if you find yourself in this sort of situation, you could try to think how you can test this code in isolation from the recording code: Set up a buffer that has the values 0..255 in it, and then set your "chunk size" to 16 and see if it writes out a continuous sequence of 0..255 across 16 separate write operations. That will quickly verify if your buffering code is working or not.

Upvotes: 1

Pawel Zubrycki
Pawel Zubrycki

Reputation: 2713

I am just wondering, how

void CaptureApp::writeData()
{
    mPcmBufferPosition *= sizeof(int16_t); // mPcmBufferPosition = 0, so 0*2 = 0;

// (...)
    mPcmBufferPosition = 0;

}

works (btw. sizeof int16_t is always 2). Are you setting mPcmBufferPosition somewhere else?

void CaptureApp::writeData()
{

    // Update header with new PCM length
    long pos = mFile.tellp();
    mPcmBufferBytesToWrite *= 2;
    mPcmTotalSize += mPcmBufferBytesToWrite;
    mFileHeader.bytes = mPcmTotalSize + sizeof(WAVFILEHEADER);
    mFileHeader.pcmbytes = mPcmTotalSize;

    mFile.seekp(0);
    mFile.write(reinterpret_cast<char *>(&mFileHeader), sizeof(mFileHeader));
    mFile.seekp(pos);

    // Append PCM data
    if (mPcmBufferBytesToWrite > 0)    
        mFile.write(reinterpret_cast<char *>(mPcmBuffer), mPcmBufferBytesToWrite);
}

Also mPcmBuffer is a pointer, so don't know why you use & in write.

Upvotes: 2

Related Questions