Derived from: public BFile
Declared in: <media/SoundFile.h>
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. Notably absent from the list of a BSoundFile's talents is the ability to play itself and to record into itself. You can play a BSoundFile's data, but this requires the assistance of a BAudioSubscriber (as explained later). Currently, you can't record into a BSoundFile.
To use a BSoundFile, you set its ref (using the methods that are described in the BFile class), and then you open the file through a call to Open(). Since you can't record into a BSoundFile, you almost always open such files in B_READ_ONLY mode. None of the BSoundFile-defined functions work on an unopened file.
The BSoundFile class understands AIFF, WAVE, and "standard" UNIX sound files ( .snd and .au). When you tell a BSoundFile object to open its file, the object figures out the format of the file--you can't force it to assume a particular format. If it encounters a file that's in a format that it doesn't understand ("unknown" format), it assumes that the file contains 44.1 kHz, 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 retrieve the file format from an open BSoundFile through the FileFormat() function.
Note: 8-bit WAVE data is, by definition, unsigned. However, when you read such data (through the ReadFrames() function, which will be discussed later), it's automatically shifted so that it is signed. This automatic conversion allows an 8-bit WAVE file to be mixed with other sound sources.
After opening your BSoundFile, you can ask for the parameters of its data by calling the various parameter-retrieving functions (SamplingRate() , ChannelCount(), SampleSize() , and so on). There's also a set of parameter-setting functions ( SetSamplingRate(), SetChannelCount(), SetSampleSize(), ...), but 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 the sort of data that it contains so other objects that act on your BSoundFile will interpret the data correctly. This should only be necessary if the file format is unknown.
For example, let's say you have your own sound file format. Your format defines a header that lists the usual information--the size of the samples, where the data starts, and so on. BSoundFile won't recognize your format, of course, but through a combination of the Read() function (so you can read the header yourself) and the sample parameter-setting functions defined by BSoundFile, you can tell the object what sort of data it contains.
If you're creating your own BSoundFile-derived class to encapsulate your own sound file format, you would put the header-reading code in your implementation of the Open() function. For example:
long MySoundFile::Open(long mode) { long result; if ((result = BSoundFile::Open(mode)) < B_NO_ERROR) return result; /* ReadHeader() is assumed to be implemented * by MySoundFile--it isn't a BSoundFile function. */ if (FileFormat() == B_UNKNOWN_FILE) result = ReadHeader()); return result; }
By invoking the BSoundFile version of Open(), you allow your object to represent the standard file formats in addition to your own. (Keep in mind that BSoundFile sets the file format in its Open() implementation.)
There are two methods for playing a sound file:
A demonstration of the second approach is given below. To understand the example, you must be familiar with the subscription and stream-entering mechanisms described in the BSubscriber class.
In this example, we show how to read data from a BSoundFile and add it to the DAC stream for playback. In addition, we'll allow dynamic amplitude control of the sound. For the sake of brevity, we'll restrict the example to 16-bit data.
First, we define an object called SoundPlayer that will be used to coordinate the Media Kit objects. Notice that SoundPlayer needn't derive from a Kit class:
class SoundPlayer : public BObject { public: long SetSoundFile(record_ref ref); void Play(void); void SetAmpScale(double value); private: static bool _play_back(void *arg, char *sound, long size); bool Playback(short *sound, long sample_count); BAudioSubscriber *a_sub; BSoundFile s_file; char transfer_buf[B_PAGE_SIZE]; double amp_scale; };
There are three public functions: SetSoundFile() let's you set the soundfile that you want to play, Play() plays it, and SetAmpScale() will control the amplitude. In this implementation, the file is always allowed to play to completion --aborting the playback is left as an exercise for the reader.
The private _play_back() function will be the BAudioSubscriber's literal stream function. Playback() will be called from within _play_back() ; it will do the actual stream work. The private transfer_buf will be used to transfer data between the file and the audio stream ( a page at a time), and amp_scale will hold the amplitude scaling value.
In the implementation of SetSoundFile(), we set the BSoundFile's ref and open the file...
long SoundPlayer::SetSoundFile(record_ref ref) { /* Set the BSoundFile's ref and open the object. */ s_file.SetRef(ref); s_file.Open(B_READ_ONLY); if (s_file.Error() < B_NO_ERROR) return B_ERROR; /* Check for 16-bit data (given in bytes). */ if (s_file.SampleSize() != 2) return B_ERROR;
...and then we create the BAudioSubscriber and subscribe it to the DAC stream and set the stream's sample parameters to match the data that's in the file:
a_sub = new BAudioSubscriber("SoundFile Player"); if(!a_sub->Subscribe(B_DAC_STREAM, B_SHARED_SUBSCRIBER_ID, FALSE) < B_NO_ERROR) return B_ERROR; a_sub->SetSamplingRate(s_file.SamplingRate()); a_sub->SetDACSampleInfo(s_file.SampleSize(), s_file.CountChannels(), s_file.ByteOrder(), s_file.SampleFormat());
Next, we set the size of the stream's buffers to match that of our transfer buffer. The arguments to SetStreamBuffers() are buffer size, buffer count. The buffer count we use here (8, the same as the Audio Server default) is unimportant in this example:
a_sub->SetStreamBuffers(B_PAGE_SIZE, 8);
Finally, we initialize the amp scaler and return:
amp_scale = 1.0; return B_NO_ERROR; }
By setting the DAC stream's sample info and buffer size as shown in here, we make the stream function's job quite a bit easier--it won't have to convert the samples as it reads them from the file, or keep track of how many samples it has read. However, you should be aware that some other BAudioSubscriber could come along and reset the DAC stream at any time, thus screwing up the playback. For now, we'll live with the danger.
The Play() function enters the BAudioSubscriber into the DAC stream. This causes buffers to be sent to the stream function, which we'll implement in the next section.
void SoundPlayer::Play(void) { a_sub->EnterStream(NULL, /* no neighbor */ TRUE, /* head of the stream */ this, /* arg for the stream function */ _play_back, /* the stream function */ NULL, /* no completion function */ TRUE); /* run in the background */ }
While we're at it, we'll implement the SetAmpScale() function:
void SoundPlayer::SetAmpScale(double scale) { amp_scale = min(1.0, max(0.0, scale)); }
Now comes the fun part. First we implement the literal stream function, _play_back():
bool SoundPlayer::_play_back(void *arg, char *sound, long size) { return (((SoundPlayer *)arg)->Playback((short *)sound, size/2)); }
As _play_back() receives buffers from the DAC stream, it forwards them (cast as 16-bit data) to the guts of the operation, Playback(). At each invocation, Playback() reads the correct number of frames from the sound file, scales their amplitudes, and adds the samples into the DAC stream buffer. First, we set up some variables:
bool SoundPlayer::Playback(short *sound, long sample_count) { long frames_read, counter; long channel_count = s_file.CountChannels(); long frame_count = sample_count / channel_count; short *tb_ptr = (short *)transfer_buf;
Now we read frame_count sample frames from the file and place them in the transfer buffer. (We should check to make sure that the transfer buffer can accommodate the number of frames read--but, for this example, we'll assume that the stream's buffer size hasn't changed since we set it to be the same size as the transfer buffer.) If ReadFrames() returns less than the number of frames that we asked for, we're at the end of the file. ReadFrames() returns the number of frames that it actually read, or an error code (as usual, a negative number) if something went wrong:
frames_read = s_file.ReadFrames(transfer_buf, frame_count); if (frames_read <= 0) return FALSE;
Finally, we get to write into the sound buffer. We loop over the samples in the transfer buffer, scale each by the amp_scale value, and then write the scaled value into the sound buffer:
for (counter = 0; counter < frames_read; counter++) { *sound++ += *tb_ptr++ * amp_scale; /* left or mono */ if (channel_count == 2) *sound++ += *tb_ptr++ * amp_scale; /* right */ }
Once again we examine the frames_read count. If it's less than what we expected to have read, we've reached the end of the file, and so return FALSE. Otherwise we return TRUE:
if (frames_read < frame_count) return FALSE; else return TRUE; }
Obviously, this example is neither robust nor efficient. In particular, the file-reading mechanism should probably read more than one page at a time--if you were to play more than a couple files simultaneously with this code, the constant file seeking could cause your hard disk to burn a hole right through to Australia. Or to California, if you live in Perth. The point of this exercise was to demonstrate the basic procedures of playing a sound file.
BSoundFile(void) BSoundFile(record_ref ref)
Creates and returns a new BSoundFile object. The first version of the constructor must be followed by a call to SetRef() .
virtual ~BSoundFile(void)
Closes the BSoundFile's sound file and destroys the object. The data in the sound file isn't affected.
long CountFrames(void)
Returns the number of frames of sound that are in the object's file. If the object's file isn't open, this returns B_ERROR.
long FileFormat(void)
Returns a constant that identifies the type of sound file that this object is associated with. Currently, three types of sound files are recognized: B_AIFF_FILE, B_WAVE_FILE , B_UNIX_FILE and B_UNKNOWN_FILE. AIFF is the Apple-defined sound format, WAVE is a popular PC format, the B_UNIX_FILE constant represents the sound file format that's used on many UNIX-based computers. B_UNKNOWN_FILE is returned for all other formats.
B_UNKNOWN_FILE isn't as useless as it sounds: Any file that is so identified is considered to contain "raw" sound data. You can accept the default values of the data format parameters (see SamplingRate() for a list of these values), or you can shape the data into a recognizable format by setting the data format parameters directly, through calls to SetSamplingRate(), SetChannelCount(), and so on. In this case, you'll need to position the frame pointer to the first frame--in other words, you have to read past the file's header, if any--yourself. Thus primed, subsequent calls to ReadFrames() will read the proper sequences of samples.
If the BSoundFile isn't open, this returns B_ERROR .
long FramesRemaining(void)
Returns the number of unread frames in the file, or B_ERROR if the object isn't open.
virtual long ReadFrames(char *buffer, long frameCount)
Reads (as many as) frameCount frames of data into buffer . The function returns the number of frames that were actually read and increments the frame pointer by that amount. When you hit the end of the file, the function returns 0.
Note that buffer shouldn't be the sound buffer that's passed to you in a stream function. If you read directly into a stream function's sound buffer, you'll be clobbering the data that's already there. If you're calling ReadFrames() from within a stream function, you must first read into a "transfer buffer", and then add the contents of this buffer into the sound buffer.
If the BSoundFile object isn't open, this returns B_ERROR .
long SamplingRate(void) long CountChannels(void) long SampleSize(void) long FrameSize(void) long ByteOrder(void) long SampleFormat(void)
These functions return information about the format of the data that's found in the object's sound file:
These functions returns default values if the object isn't associated with a file. The defaults are:
If the BSoundFile object isn't open, these functions return B_ERROR.
virtual long SeekToFrame(ulong index) long FrameIndex(void)
Theses function set and return the location of the "frame pointer." The frame pointer points to the next frame that will be read from the file. The first frame in a file is frame 0.
If you try to set the frame pointer to a location that's outside the bounds of the data, the pointer is set to the frame at the nearest extreme.
If the BSoundFile object isn't open, this returns B_ERROR .
virtual long SetSamplingRate(long samplingRate) virtual long SetChannelCount(long channelCount) virtual long SetSampleSize(long bytesPerSample) virtual long SetByteOrder(long byteOrder) virtual long SetSampleFormat(long sampleFormat)
If the file format of your BSoundFile is B_UNKNOWN_FILE, you can use these functions to tell the object how to interpret the format of its data. These functions don't change the actual data--neither as it's represented within the object, nor as it resides in the file --they simply prime the object for subsequent reads of the data.
The candidate values for the functions are:
Each function returns the value that was actually implanted. If the BSoundFile object isn't
open, they return B_ERROR.
The Be Book, HTML Edition, for Developer Release 8 of the Be Operating System.
Copyright © 1996 Be, Inc. All rights reserved.
Be, the Be logo, BeBox, BeOS, BeWare, and GeekPort are trademarks of Be, Inc.
Last modified September 6, 1996.