The Media Kit: BSoundFile

Derived from: none

Declared in: be/media/SoundFile.h

Library: libmedia.so


Overview

BSoundFile objects give you access to files that contain sound data. The BSoundFile functions let you examine the format of the data in the sound file, read the data, and position a "frame pointer" in the file. BSoundFile does not, however, include functions for playing from and recording into a sound file. You can play a BSoundFile's data, or write data into a BSoundFile, but you'll have to set up code to do this yourself, using a BSubscriber object and a BDACStream object.

To use a BSoundFile, you set its entry_ref, and then you open the file using the SetTo() call (there's also a version of the BSoundFile constructor that does this when the object is constructed). If you want to read the sound file (to play it, for example), open the file with B_READ_ONLY access. To write into the sound file (while recording, perhaps), use B_WRITE_ONLY access. B_READ_WRITE access is not allowed.


Sound File Formats

The BSoundFile class understands AIFF, WAVE, and "standard" UNIX sound files (.snd and .au). When you give BSoundFile a reference to a file, it opens the file and determines the format of the data it contains--you can't force it to assume a particular format. If the file is not in a format it understands ("unknown" format), it assumes that the file contains 44.1kHz, 16-bit stereo data, and that the file doesn't have a header (it assumes that the entire file is sound data). The admission of the unknown format means that any file can act as sound data. BSoundFile doesn't know the meaning of "inappropriate data."

The file formats are represented by the constants B_AIFF_FILE, B_WAVE_FILE, B_UNIX_FILE, and B_UNKNOWN_FILE. You can determine the format of the file represented by a BSoundFile object by using its FileFormat() function.

8-bit WAVE data is, by definition, unsigned. When you use the ReadFrames() function to read data in this format, you must be sure to convert each sample into signed format before manipulating it further, since all other sound formats supported by BeOS are signed (including both the ADC and DAC streams).

To convert an 8-bit WAVE sample into a signed 8-bit sample, just subtract 128 from the sample.


Sound Data Parameters

Once you've opened your BSoundFile, you can ask for information about the format of the data by calling the various format-retrieving functions (SamplingRate(), CountChannels(), SampleSize(), and so forth). There are also functions for setting these parameters (SetSamplingRate(), SetChannelCount(), SetSampleSize(), and so on). Note that these functions don't actually modify the data in the file (or in the BSoundFile object). They simply set the object's impression of what sort of data is in the file. This should only be neecessary if the file format is unknown, or if you're creating a new BSoundFile object for a sound you're preparing to save to disk.


Playing a Sound File

There are two methods for playing a sound file:

The next section provides a demonstration of the second approach. To follow the example, you should first familiarize yourself with the features of the BSubscriber and BDACStream classes.


An Example

In this example, we show how to read data from a BSoundFile and add it to the DAC stream for playback. To keep the example brief, this class only plays 16-bit sounds, so that we don't have to handle converting data from 8-bit formats.

First, we define a class called SoundPlayer that will be used to coordinate the Media Kit objects (BDACStream, BSubscriber, and BSoundFile). Notice that in this example we don't derive from any of these Media Kit classes, although we could.

   class SoundPlayer {
      public:
         status_t         SetSoundFile(entry_ref *ref);
         void         Play(void);
         void         Stop(void);
      
      private:
         static bool      _play_back(void *userData,
                              char *buffer,
                              size_t count,
                              void *header);
         bool         Playback(char *buffer, size_t count);
         
         BDACStream         stream;
         BSubscriber         subscriber;
         BSoundFile         soundFile;
         char         transfer_buf[B_PAGE_SIZE];
   };

There are three public functions: SetSoundFile() lets you specify the file you want to play, Play() starts playing the sound file, and Stop() immediately stops the playback.

The private _play_back() function is the stream function that's passed to EnterStream(). It will call Playback() to do the actual stream manipulations. The private transfer_buf will be used when we read data from the audio stream; once it's read into the buffer, we can mix it into the sound stream (a page at a time).

Preparing for Playback

The SetSoundFile() function handles setting the BSoundFile to reference the desired file. It also checks to be sure the file is one that our code is capable of playing (our example only plays 16-bit sounds):

   status_t SoundPlayer::SetSoundFile(entry_ref *ref) {
      status_t   err;
      err = soundFile.SetTo(ref, B_READ_ONLY);
      if (err == B_OK) {
         if (soundFile.SampleSize() != 2) {
            err = B_ERROR;      // Reject file if not 16-bit stereo
         }

If the sample size is in fact 16 bits, we continue by subscribing to the DAC stream and setting the stream's sampling rate to match that of the file we're playing. We also set the size of the stream's buffers to match the size of our transfer buffer. We specify eight stream buffers (the default for the Audio Server). Finally, if an error occurred at any point during this function, we return an appropriate error code. Otherwise, B_OK is returned.

         else {
            err = subscriber.Subscribe(&stream);
            if (err == B_OK) {
               stream.SetSamplingRate(soundFile.SamplingRate());
               stream.SetStreamBuffers(B_PAGE_SIZE, 8);
            }
         }
      }
      return err;
   }

Note that we're making a lot of assumptions in this example--assumptions we really shouldn't make. We're assuming that nobody changes the size of the stream buffers behind our back, and that nobody changes the sampling rate of the stream. In a real-world application, these are not safe assumptions to make, and you have to be prepared for the possibility that these parameters might be changed while your sound is playing.

Entering the Stream

The Play() function enters the BSubscriber into the DAC stream. This causes buffers to begin arriving at the stream function, which we'll implement in a moment.

   void SoundPlayer::Play(void) {
         subscriber.EnterStream(NULL, false, this, _play_back,
                           NULL, true);
   }

Reading and Playing the File

Now we get to the really fun part. First, let's implement the literal stream function, _play_back().

   bool SoundPlayer::_play_back(void *userData, char *buffer,
                         size_t count, void *header) {
      return (((SoundPlayer *) userData)->Playback(buffer, count));
   }

As _play_back() receives buffers from the DAC stream, it forwards them to the Playback() function to do the real work. At each invokation, Playback() reads the appropriate number of frames from the file and mixes the samples into the DAC stream buffer. First we establish our local variables and create pointers to the transfer_buf and the stream buffer, cast to pointers to 16-bit integers.

   bool SoundPlayer::Playback(char *buffer, size_t count) {
      int32         frameCount;      // Number of frames to mix
      int32         framesRead;      // Number of frames read from disk
      int32         channelCount;   // Number of channels in sound
      int32         counter;      // Loop counter for mixing
      int16         *soundData;      // Pointer to the sample to mix
      int16         *tbuf;         // Short pointer to transfer buffer
      int32         sample;         // Temporary value of sample while mixing
      
      soundData = (int16 *) buffer;
      tbuf = (int16 *) transfer_buf;

Then we figure out how many frames of data will fit in the stream buffer by dividing count (the number of bytes in the buffer) by 4 (the size of a 16-bit stereo frame of sound). We also determine the number of channels in the sound we're playing, since we'll need to know that when we mix the sound into the stream buffer. Then we read the next batch of frames from the file into memory.

ReadFrames() returns as a result the number of frames of sound data read from the file. If this value is less than we requested, the batch of frames read is the last batch in the file. If it returns zero, there were no frames left to read. A negative result means an error occurred. If the result is either zero or negative, we exit, returning FALSE so that we are removed from the stream.

      frameCount = count/4;
      channelCount = soundFile.CountChannels();
   
      framesRead = soundFile.ReadFrames(transfer_buf, frameCount);
      
      if (framesRead <= 0) {
         return false;         // Either error or done with file
      }

Now it's time to mix the sound into the sound buffer. For each sample (both left and right channels), we do the following:

We only mix the right channel if it exists in the sound we're playing. If it doesn't, we skip over it in the stream buffer, leaving the original sample data intact.

      counter = 0;
      do {
         sample = *soundData + *tbuf++;      // Add the old and the new
         if (sample > 32767) {            // Is the result too high?
            sample = 32767;
         }
         else if (sample < -32768) {         // How about too low?
            sample = 32768;
         }
         *soundData++ = sample;            // Now save the clipped value
         if (channelCount == 2) {         // If there's another channel,
            sample = *soundData + *tbuf++;   //  do the same thing again
            if (sample > 32767) {
               sample = 32767;
            }
            else if (sample < -32768) {
               sample = 32768;
            }
            *soundData++ = sample;
         }
         else {
            soundData++;               // Just skip the right channel
         }
      } while (++counter < framesRead);
   
      // If we're done, return false.  Otherwise,
      // return true.
      
      if (framesRead < frameCount) {
         return false;
      }
      return true;
   }

Clearly, this example isn't as robust or efficient as it could be. If you're writing code to play sound files, the following should be considered:


Constructor and Destructor


BSoundFile()


      BSoundFile()
      BSoundFile(const entry_ref *soundRef, uint32 openMode)

Creates and returns a new BSoundFile object, which represents a sound file. The first form of the constructor must be followed by a call to SetTo(). In the second form of the constructor, you establish a reference to a sound file to open and the file open mode. The open mode can be either B_READ_ONLY or B_WRITE_ONLY; you can't open a sound file for read/write access.

After constructing your BSoundFile object, you should call InitCheck() to determine if any errors occurred.


~BSoundFile()


      virtual ~BSoundFile()

Closes the sound file and destroys the BSoundFile object.


Member Functions


ByteOrder(), SetByteOrder()


      int32 ByteOrder() const

      virtual int32 SetByteOrder(int32 newByteOrder)

ByteOrder() returns the byte ordering of the sound data. If the data is in a big-endian format, this function returns B_BIG_ENDIAN; otherwise, B_LITTLE_ENDIAN is returned. The default value for new or uninitialized sound files is B_BIG_ENDIAN.

SetByteOrder() lets you change the byte order of for the sound file to newByteOrder. It returns newByteOrder.

These functions never fail.


CompressionName(), SetCompressionName()


      char *CompressionName() const

      virtual char *SetCompressionName(char *newCompressionName)

CompressionName() returns the name of the compression format used in the sound file object. The default for new or uninitialized sound files is NULL, since there is no compression.

SetCompressionName() currently does nothing, and returns NULL.

The pointer returned to you by these functions belongs to the BSoundFile object; you must not change or delete it.

This function never fails.


CompressionType(), SetCompressionType()


      int32 CountChannels() const

      virtual int32 SetCompressionType(int32 newCompressionType)

CompressionType() returns the type of compression used for the sound file object. The default for new or uninitialized sound files is -1 (no compression).

SetCompressionType() currently does nothing and returns 0.

These functions never fail.


CountChannels(), SetChannelCount()


      int32 CountChannels() const

      virtual int32 SetChannelCount(int32 newChannelCount)

CountChannels() returns the number of channels in the sound file object. For stereo sound files, this is 2; for monaural sound files, this is 1. The default for new or uninitialized sound files is 2.

SetChannelCount() lets you change the number of channels in the sound file to newChannelCount; it returns the new value.

The channel count can be thought of as not only the number of audio channels in the sound, but as the number of audio samples in a single frame of audio data; see FrameSize() and the Overview for a more in-depth discussion of this relationship.

These functions never fail.

See also: CountFrames(), FrameSize()


CountFrames(), SetFrameCount()


      off_t CountFrames() const

      virtual off_t SetFrameCount(off_t newFrameCount)

CountFrames() returns the number of frames in the sound file object. For new or uninitialized sound files, this is 0.

SetFrameCount() lets you set the number of frames in the sound file to newFrameCount, and returns the new count.

These functions never fail.


FileFormat(), SetFileFormat()


      int32 FileFormat() const

      virtual int32 SetFileFormat(int32 newFormat)

These functions get and set the file format of the sound file object. The format can be one of the following:

FileFormat() returns the current file format; for new or uninitialized sound files, the default format is B_UNKNOWN_FILE.

SetFileFormat() sets the format of the file to newFormat and returns newFormat.

These functions never fail.


FrameSize()


      int32 FrameSize() const

Returns the number of bytes needed to represent a single frame of sound data. A frame is a single "instant" of sound, and consists of the data necessary to represent a single sound sample for all channels in the sound.

For example, if the sound is 8-bit monaural, the frame size will be 1 byte. For 16-bit stereo (the default for new or uninitialized files), the frame size will be 4 bytes. The frame size is determined through the following complicated mathematical formula:

frameSize = sampleSize * channelCount

Because this is very technically challenging, you should always use the FrameSize() function to determine the size of a frame (additionally, this protects you from your code breaking on odd file formats). This function never fails.


FrameIndex()


      off_t FrameIndex() const

FrameIndex() returns the current frame offset within the sound file; this is the frame number that will be read the next time ReadFrames() is called or written the next time WriteFrames() is called.

This function never fails.


FramesRemaining()


      off_t FramesRemaining() const

FramesRemaining() returns the number of frames left to access in the file; this is the difference between the highest frame number in the file and FrameIndex().

This function never fails.


InitCheck()


      status_t InitCheck() const

Reports the result of the BSoundFile constructor. You should call this after constructing the object, to ensure that no errors occurred.

RETURN CODES


IsCompressed(), SetIsCompressed()


      bool IsCompressed() const

      virtual bool SetIsCompressed(bool newIsCompressed)

IsCompressed() returns TRUE if the sound file's data is compressed, or FALSE otherwise. For new or uninitialized sound files, this is FALSE.

SetIsCompressed() currently does nothing and always returns FALSE.

These functions never fail.


ReadFrames()


      size_t ReadFrames(char *buffer, size_t frameCount)

Reads frames of audio data into the specified buffer. Up to frameCount frames of data are read; fewer frames will be read if the end of the sound file is reached.

Once the frames have been read into the buffer, this function returns the number of frames of audio data read into memory.

If an error occurs while reading the data, a negative value will be returned; this value will be one of the return values specified below.

RETURN CODES

Values 0 and greater: the number of frames successfully read.
B_BAD_FILE. An error occurred while reading the file.


SampleFormat(), SetSampleFormat()


      int32 SampleFormat() const

      virtual int32 SetSampleFormat(int32 newFormat)

SampleFormat() returns sound file's sample format; for new or uninitialized files, this is B_LINEAR_SAMPLES. The possible sample formats are:

SetSampleFormat() sets the sample format of a sound sample to newFormat and returns newFormat to the caller.

These functions never fail.


SampleSize(), SetSampleSize()


      int32 SampleSize() const

      virtual int32 SetSampleSize(int32 newSampleSize)

SampleSize() returns the number of bytes used to represent each sample in the sound file; this is 1 for 8-bit sound, and 2 for 16-bit sound; the default for new or uninitialized sound files is 2.

SetSampleSize() lets you change the sound sample size to newSampleSize, and returns the new size.

The sample size is used to determine the size of a frame of sound data; see FrameSize() and the Overview for details on how this relationship works.

See also: CountChannels(), FrameSize()

These functions never fail.


SamplingRate(), SetSamplingRate


      int32 SamplingRate() const

      virtual int32 SetSamplingRate(int32 newRate)

SamplingRate() returns the sampling rate of the sound file object, in frames per second; for new or uninitialized files, this is 44,100 Hz.

SetSamplingRate() sets the sampling rate of the file to newRate, and returns the new value.

These functions never fail.


SeekToFrame()


      virtual off_t SeekToFrame(off_t frame)

Sets the current frame to the specified number. This is the next frame of audio data that will be read from using ReadFrames() or written to using WriteFrames().

The new current frame number will be returned (it will be different from the value you passed in only if there are fewer than frame frames in the file, in which case the number of the last frame in the file will be returned.

If an error occurs, a negative value will be returned; this value will be one of the return values specified below.

RETURN CODES

Values 0 and greater: the number of frames successfully written.
B_BAD_FILE. An error occurred while writing the file.


SetByteOrder() see ByteOrder()


SetChannelCount() see CountChannels()


SetCompressionName() see SetCompressionName()


SetCompressionType() see CompressionType()


SetDataLocation()


      virtual off_t SetDataLocation(off_t dataOffset)

Lets you set the offset, in bytes, into an audio frame at which the actual sound data is stored. This allows you to deal with audio formats in which each frame of audio has a header or other data added to the frame. This function never fails.


SetFileFormat() see FileFormat()


SetFrameCount() see CountFrames()


SetIsCompressed() see IsCompressed()


SetSampleFormat() see SampleFormat()


SetSampleSize() see SampleSize()


SetSamplingRate() see SamplingRate()


SetSamplingRate() see SamplingRate()


SetTo()


      status_t SetTo(const entry_ref *soundRef, uint32 openMode)

Sets the current file and open mode referred to by the BSoundFile object and prepares the file for reading or writing, as appropriate:

openMode must be either B_READ_ONLY or B_WRITE_ONLY; read/write access is not permitted and attempting to open the file in this mode will result in a B_BAD_VALUE error.

RETURN CODES


WriteFrames()


      size_t WriteFrames(char *buffer, size_t frameCount)

Writes frames of audio data from the specified buffer into the sound file. frameCount frames of data are written; fewer frames will be written only if an error occurs.

Once the frames have been written into the file, this function returns the number of frames of audio data successfully written.

If an error occurs while writing the data, a negative value will be returned; this value will be one of the return values specified below.

RETURN CODES

Values 0 and greater: the number of frames successfully written.
B_BAD_FILE. An error occurred while writing the file.






The Be Book, in lovely HTML, for BeOS Release 3.

Copyright © 1998 Be, Inc. All rights reserved.

Last modified March 26, 1998.