Writing to wav file


Results 1 to 5 of 5

Thread: Writing to wav file

  1. #1
    Join Date
    Sep 2003
    Location
    Rochester, MN
    Posts
    3,604

    Writing to wav file

    This topic would probably be better suited to a more multimedia specific forum, but I'm not sure where a good one would be. What I'm trying to do is write a 3-5 second silent wav file (don't really care that much about the format or anything, I just need some slience) from my program. It works okay, but when I try to play the wav file from XMMS, the seconds increment way to fast. I assume that this is because I made a mistake in formatting the file, but I can't seem to figure out what it is. While the wav file works for what I need it for, it concerns me that it is not working exactly the way it should. I'm afraid that maybe for other people using my program it might not work, depending on what versions of audio software they are using. The code is included below, so if anyone who knows something about wav files could take a quick look and see if there's anything obviously wrong I would appreciate it.

    void PlayFile::createDummy()
    {
    // open the file
    FILE *fp = fopen(QDir::homeDirPath() + "/.qui/dummy.wav", "wb");

    // write the file header
    unsigned long wholeLength = 400036;
    unsigned long chunkLength = 16;
    unsigned short formatType = 1;
    unsigned short numChannels = 1;
    unsigned long sampleRate = 44100;
    unsigned long bytesPerSecond = 44100;
    unsigned short bytesPerSample = 1;
    unsigned short bitsPerChannel = 8;
    unsigned long dataLength = 400000;

    if (fputs("RIFF", fp) == EOF
    || fwrite(&wholeLength, sizeof(wholeLength), 1, fp) != 1
    || fputs("WAVE", fp) == EOF
    || fputs("fmt ", fp) == EOF
    || fwrite(&chunkLength, sizeof(chunkLength), 1, fp) != 1
    || fwrite(&formatType, sizeof(formatType), 1, fp) != 1
    || fwrite(&numChannels, sizeof(numChannels), 1, fp) != 1
    || fwrite(&sampleRate, sizeof(sampleRate), 1, fp) != 1
    || fwrite(&bytesPerSecond, sizeof(bytesPerSecond), 1, fp) != 1
    || fwrite(&bytesPerSample, sizeof(bytesPerSample), 1, fp) != 1
    || fwrite(&bitsPerChannel, sizeof(bitsPerChannel), 1, fp) != 1
    || fputs("data", fp) == EOF
    || fwrite(&dataLength, sizeof(dataLength), 1, fp) != 1)
    {
    cout << "Problem writing dummy.wav, QUI may not behave well\n";
    }

    // Begin writing the blank data
    char* buffer = " ";

    for (int i = 0; i < 400000; i++)
    fwrite(buffer, sizeof(buffer), 1, fp);
    fclose(fp);
    }

    Incidentally, most of this code is taken from an example I found on another site, so it uses C functions like fopen and fputs for file io, which is okay with me, but I'd rather do it with C++ file io if possible, but I'm not sure how. That's not a big deal though. Thanks in advance for any help.

  2. #2
    Join Date
    Jan 2001
    Location
    Somewhere in middle America
    Posts
    164
    All of your numbers look correct.

    A data length of 400000 @ 44100 bytes/sec is around 9sec though.

    That aside.

    RIFF files are little endian.
    So all of your numbers in the header must be stored that way.

    You just need to swap the bytes in the numbers if your platform is big-endian.

    Here is some code I wrote to write the little endian header values, but there are better ways to do it.
    Code:
    ***************************************************
     * pmsint2(FILE * fd, int val)
     * -------------------------------------------------
     * [ Print MicroSoft INT 2-byte ]
     * Write val to fd as a 2-byte little-endian int
     ***************************************************/
    #ifdef __STDC__
    void pmsint2(FILE * fd, int val)
    #else
    void pmsint2(fd, val)
       FILE * fd;
       int val;
    #endif
    {
       fprintf(fd,"%c%c",
              val & 0xFF,
       (val >> 8) & 0xFF );
    }
    
    /***************************************************
     * pmsint4(FILE * fd, int val)
     * -------------------------------------------------
     * [ Print MicroSoft INT 4-byte ]
     * Write val to fd as a 4-byte little-endian int
     ***************************************************/
    #ifdef __STDC__
    void pmsint4(FILE * fd, int val)
    #else
    void pmsint4(fd, val)
       FILE * fd;
       int val;
    #endif
    {
       fprintf(fd,"%c%c%c%c",
                  val & 0xFF,
          (val >>  8) & 0xFF,
          (val >> 16) & 0xFF,
          (val >> 24) & 0xFF );
    My Machine:
    Maytag SAV5905
    710 rpm Stainless Steel Drum
    Dual boot: Gentoo / Tide

  3. #3
    Join Date
    Sep 2003
    Location
    Rochester, MN
    Posts
    3,604
    I thought 400000 seemed a little large, but it seemed to play for about the right amount of time. It's nice to know someone else agrees with my numbers though.

    So if I'm on an Intel pc (little-endian if I remember right), I need to use these functions instead of the fputs and fwrites in my original code? How about if I want to make it compatible with a big-endian machine? Thanks for your help.

  4. #4
    Join Date
    Jan 2001
    Location
    Somewhere in middle America
    Posts
    164
    Intel is little-endian, so fwrite should store the data correctly on an intel machine. However, if you move your code to a big-endian machine it will create bad files.
    Code like I included in my post will work on either big-endian or little-endian because the shifts work the same regardless of endian-ness.
    (Like I said before, there are better ways to do what my code does, but I did it that way back when I wrote it and haven't had a reason to change it)

    I don't see any reason the file would misbehave.

    I don't remember if linear audio in RIFF files is signed or unsigned, but I would guess you would get cleaner sounding silence (no clicks at the start and end) if you set the samples to 0 (if it's signed) or 127 (if it's unsigned).
    Currently setting it to " " will result in a value of 32 (the ASCII value of the space), so there would be a dc offset in the silence.

    Maybe something like...
    Code:
    unsigned char buffer = 127;
    
    for (int i = 0; i < 400000; i++)
    fwrite(&buffer, sizeof(buffer), 1, fp);
    fclose(fp);
    }
    Actually... their may be a problem with your current code.

    fwrite(buffer,sizeof(buffer),1,fp);
    buffer is a pointer to a char string (I believe that would return the size of a pointer 4 bytes).
    Check to see how large your output file is to see if that is a problem.
    My Machine:
    Maytag SAV5905
    710 rpm Stainless Steel Drum
    Dual boot: Gentoo / Tide

  5. #5
    Join Date
    Sep 2003
    Location
    Rochester, MN
    Posts
    3,604
    (Like I said before, there are better ways to do what my code does, but I did it that way back when I wrote it and haven't had a reason to change it)
    Whatever works. I don't see any particular reason to change it either.

    I don't see any reason the file would misbehave.
    Yeah, I don't think it was actually a problem with the file, but rather that XMMS doesn't like one channel wav files. At least changing it to two channel made it work right. Also shortened it to 4.5 seconds, which is about what I was looking for anyway.
    I don't remember if linear audio in RIFF files is signed or unsigned, but I would guess you would get cleaner sounding silence (no clicks at the start and end) if you set the samples to 0 (if it's signed) or 127 (if it's unsigned).
    Unsigned seems to work best. It completely gets rid of the clicks at beginning and end and makes actual silence.

    Currently setting it to " " will result in a value of 32 (the ASCII value of the space), so there would be a dc offset in the silence.
    I thought that might be a problem (and it is, when I listened to the file on headphones I heard squealing). I just forgot that you can assign a character an int value, and I couldn't get the zero character to work.

    Maybe something like...

    code:

    unsigned char buffer = 127;

    for (int i = 0; i < 400000; i++)
    fwrite(&buffer, sizeof(buffer), 1, fp);
    fclose(fp);
    }
    Works much better thanks. I should replace the fwrite here with your functions too right?

    Check to see how large your output file is to see if that is a problem.
    Right again. After the changes my output file got about 75% smaller.

    Thanks so much for your help. I'm pretty sure I would never have come up with most of this stuff on my own.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •