Derived from: BAbstractBufferStream
Declared in: be/media/AudioStream.h
Library: libmedia.so
The BDACStream class represents the sound output stream. It's used in tandem with the BSubscriber class to manipulate blocks of audio data flowing through the output stream.
The audio stream is always there, flowing in the background, even if no sounds are being played. The stream consists of blocks of audio data, which flows from one BSubscriber to the next until finally it reaches the output device or devices (the speaker, headphone port, and line out port).
Each subscriber can, if it wishes, change the sound in the blocks of audio data it receives. These changes include, but aren't limited to:
Each subscriber receives a block of sound data which has been processed already by the subscriber behind it. See the BSubscriber class for more information on how this works.
The BDACStream class defines several functions that control the sound output hardware and let you determine the current state of the hardware. These functions can be called even when you haven't subscribed to the stream.
To set the volume level of a particular sound device, use the SetVolume() function. This function accepts three parameters:
The possible device constants are:
All volume levels are floating-point numbers from 0.0 to 1.0, where 0.0 is silent and 1.0 is the loudest. If you're setting the volume of a single-channel device, such as the speaker, the left channel volume is used and the right channel volume is ignored. If you want to set the volume of one channel of a stereo device but leave the other channel's volume unchanged, specify the constant B_NO_CHANGE for the volume of the channel to leave alone.
For example, if you want to change the volume of the CD throughput signal, you could use the following code:
BDACStream stream = BDACStream(); // Set the right channel's volume to 50% and // leave the left channel's volume alone. stream->SetVolume(B_CD_THROUGH, B_NO_CHANGE, 0.5);
Use the GetVolume() function to determine the current volume of a device's left and right channels:
float left, right; bool enable; stream->GetVolume(B_CD_THROUGH, &left, &right, &enable);
This code places the current left channel volume in the variable left, the right channel volume in the variable right, and sets the enable variable to true if the CD throughput device is currently enabled (not muted). You can specify NULL for any of these pointers (left, right, or enable) whose value you don't want to retrieve.
To mute a device, you must disable it using the EnableDevice() function. The first argument is the constant representing the device you want to control, and the second argument is a boolean specifying whether or not the device is currently active. This boolean should be true if the device should be enabled or false if it should be disabled.
To mute a sound (such as the built-in speaker), simply do the following:
stream->EnableDevice(B_SPEAKER_OUT, false);
To unmute the speaker, the following code can be used:
stream->EnableDevice(B_SPEAKER_OUT, true);
The IsDeviceEnabled() function can be used to determine if the device is enabled or not. It returns true if the device is enabled and false otherwise.
Sound is represented digitally by taking regular, periodic samples of a sound and storing those values in memory. The stream of samples comprises the complete sound. For instance, consider the following sound wave:
To represent this sound digitally, we break it up into a finite number of regular, periodic samples. Typically, these samples are represented by 8 or 16 bit integers (16 bit samples provide higher sample resolution, and thus higher sound quality, but double the size of the sound). The value 0 usually represents silence, and numbers increasingly far away specify the amplitude of the sound wave at the time increment represented by the individual sample.
In the diagram above, we've taken periodic samples of the sound. They're very far apart, and we miss some important details. That's because we used a low sampling rate--the samples we took of the sound are so far apart that we lose resolution in the sound. The more samples we use, the less risk there is of missing details in the sound.
Simply put: the higher the sampling rate used to record a sound, the less detail will be lost and the higher the quality of the recording. CD audio uses a sampling rate of 44.1kHz--44,100 samples per second.
However, the higher the sampling rate used, the more memory (and disk space) the sound will require. For instance, one minute of CD-quality audio (44.1kHz, 16-bit stereo) requires 10,584,000 bytes of memory:
If you use the 22,050 byte/sec sampling rate, you use half as much memory:
You can, by the way, save even more memory by dropping to 8-bit audio:
By carefully selecting the sampling rate and sample size (8 or 16 bits), you can compromise between your storage requirements and the quality of your audio.
When you create a sound, you specify the sampling rate at which the sound is sampled. Likewise, when you play back a sound, using the BDACStream class, you need to set the stream to play back the sound at the desired sampling rate (usually the same rate used when recording the sound, but you can get some interesting special effects by varying the output sampling rate).
BDACStream provides two functions for controlling and determining the current sampling rate of the DAC stream.
The SetSamplingRate() function is used to change the sampling rate currently in effect on the DAC stream. If you want to play back sound sampled at 44.1kHz, you would issue the following call, assuming that outStream is a pointer to your BDACStram object:
outStream->SetSamplingRate(44100.0);
You can specify any sampling rate you wish; if the rate you specify isn't supported by the sound hardware of the computer you're running the BeOS on, the closest possible rate will automatically be selected.
To determine the current sampling rate, use the SamplingRate() function:
float currentRate; outStream->SamplingRate(¤tRate);
If the above code were run with the stream set to 44.1kHz, the currentRate variable would contain the value 44100.0 after SamplingRate() returns.
Your application can subscribe to the audio stream and add to or alter the sound flowing through the stream. To do so, you first have to instantiate a BDACStream object to represent the stream itself. Then you have to establish a subscriber, which represents an inlet into the stream:
BDACStream *outStream; BSubscriber *outSubscriber; // Create the output stream and the subscriber. outStream = new BDACStream(); outSubscriber = new BSubscriber("Output_Stream_Name"); // Subscribe to the output stream. outSubscriber->Subscribe(outStream);
This doesn't actually give the subscriber access to the stream, though. When you're ready to begin altering the output stream, you have to enter the stream by calling the BSubscriber's EnterStream() function. When you enter the audio stream, you establish a pointer to a function which receives blocks of audio data as they flow down the stream. That function can insert new data into the stream or alter the data already present in the stream. EnterStream() is described in great detail in the BSubscriber section. An example of how to use the BDACStream class in tandem with BSubscriber is provided in a later section.
If you wish, you can refine the performance of your software by specifying the size and the number of audio buffers in the DAC stream. The Audio Server provides acceptable defaults for both the size and number of buffers in the stream. However, you can change these settings using the SetStreamBuffers() function.
For instance, if your experiments have determined that your code will work most efficiently with six 4k buffers, you would use the following code to establish the new buffers:
outStream->SetStreamBuffers(4096, 6);
This specifies that each of the six buffers in the stream should be 4,096 bytes (4k) long. The change affects all applications using the DAC stream. Since your application may not be alone using the stream, you should keep in mind that other applications may use this call as well, so be sure your code can handle buffers that change in size from one invokation of your stream function to the next.
In this example, we'll create a class that makes sounds played by other programs play quieter than normal (at a third normal volume). This is a fairly trivial example, but it covers all the basics. First, the class definition:
class DimSound : public BDACStream { public: void Start(void); // Start dimming sound void Stop(void); // Cut it out! private: static bool _dim_sound(void *userData, char *buffer, size_t count, void *header); bool DimIt(char *buffer, size_t count); BSubscriber subscriber; };
This class, derived from BDACStream, has public functions to start and stop the sound dimming effect (creatively named Start() and Stop()), as well as the private _dim_sound() function (which is the stream function we'll pass to EnterStream()), and the DimIt() function, which is called by _dim_sound(). In addition, this class has a BSubscriber that will be used to subscribe to the DAC stream.
Since the audio received by the subscriber is always in 16-bit stereo format, we'll define a special structure called a standard_frame that will represent a single frame of audio in this format. Also, we create the STANDARD_FRAME_SIZE constant to represent the size of one of these frames in bytes.
struct standard_frame { int16 left; // Left channel's sample int16 right; // Right channel's sample }; typedef struct standard_frame standard_frame; #define STANDARD_FRAME_SIZE 4
The Start() function, below, simply subscribes to the audio stream and, if successful, enters the stream. Since the DimSound class is derived from BDACStream, we pass a reference to the DimSound object to Subscribe(). Also, we pass a pointer to the DimSound object to EnterStream() as the userData parameter, so that the stream function can access the object. This function would be called when you want to start dimming the volume of sounds.
void DimSound::Start(void) { if (subscriber.Subscribe(this) == B_OK) { subscriber.EnterStream(NULL, false, this, _dim_sound, NULL, true); } }
The Stop() function does the opposite. It exits and unsubscribes from the DAC stream.
void DimSound::Stop(void) { subscriber.ExitStream(false); // Leave the stream subscriber.Unsubscribe(); // And unsubscribe from it }
Now we get to the meat of things. The _dim_sound() function is the static function we passed to EnterStream() as the streamFunction parameter. This function is called once for each block of audio data so we can process the data. In our case, we pass through to the DimIt() function, which does the actual processing. We do this using userData, which is a pointer to the DimSound object itself.
bool DimSound::_dim_sound(void *userData, char *buffer, size_t count, void *header) { return (((DimSound *) userData)->DimIt(buffer, count)); }
DimIt() is where the actual bit-twiddling is done. We begin by establishing a local pointer of type standard_frame * to the audio buffer. Since the count parameter specifies the size of the buffer in bytes, we have to compute the number of frames by dividing by STANDARD_FRAME_SIZE.
Once we've done that, we can loop through each of the frames in the buffer and divide the left and right samples of each frame by three. When we're finished, we return true; if we returned false, we would be automatically exited from the stream.
bool DimSound::DimIt(char *buffer, size_t count) { standard_frame *soundData; // Pointer to the frame to alter int32 frameCount; // Number of frames to manage soundData = (standard_frame *) buffer; frameCount = count/STANDARD_FRAME_SIZE; while (frameCount-- > 0) { soundData->left /= 3; soundData->right /= 3; soundData++; // Move on to the next frame } return true; }
We wrap things up by providing a tiny little main() function for demonstration purposes. It simply starts the dimmer, waits for the user to press the return key, and stops the dimmer. If you want to play with this sample, try launching another program that plays sound (such as CDPlayer) and then run dimmer from a Terminal window.
void main(void) { DimSound dimmer; dimmer.Start(); // Start diming the sound printf("Now dimming sounds. Hit return to stop.\\n"); getchar(); dimmer.Stop(); // Stop diming the sound }
This is a pretty simple example. You can get more ambitious; you can alter the sound in the buffer in any way you desire, including adding new sound data to the stream. There's one important thing to remember, though:
![]() |
Never clear the data already in the buffers you receive! Other programs are counting on you to be polite and leave their sounds alone. If you want to play a sound, add your sound data to the data already in the stream, but don't replace it! |
To properly play a sound, add your sample data to the stream like this:
soundData->left += myNewSample->left; soundData->right += myNewSample->right;
Also, keep in mind before adding samples to the stream that you'll have to convert your data into 16-bit stereo format. If your sound is in 8-bit mono format, for example, you might do something like this:
char *mysoundbuffer; int i; mysoundbuffer = read_sound_buffer(count); while (i < frameCount) { soundData->left += ((int16) mysoundbuffer[i++]) << 8; }
The read_sound_buffer() function's implementation is left as an exercise, but its purpose is to return a pointer to a buffer of count bytes of 8-bit mono sound data. Then, we loop through each frame, taking a byte from the sound data, shifting it up 8 bits (thereby converting it into a 16-bit sample) and adding it to the stream buffer.
You should check for possible clipping problems; this can occur if the sum of the existing sample and your new sample is greater than (or less than) can be represented in a 16-bit signed integer.
BDACStream()
Creates and returns a new BDACStream object, which represents a sound output stream.
After creating a BDACStream, you can use the resulting pointer as the stream parameter for your subscriber's Subscribe() call:
virtual ~BDACStream()
Destroys the BDACStream object. Don't do this if a BSubscriber is currently subscribed to the stream.
status_t EnableDevice(int32 device, bool enable) bool IsDeviceEnabled(int32 device) const
EnableDevice() lets you turn on and off sound output devices, and IsDeviceEnabled() returns the current state of a given output device.
For instance, to mute the external speaker, you would use EnableDevice() as follows:
EnableDevice(B_SPEAKER_OUT, false);
Valid values for device are:
You don't have to have already subscribed to the stream to use these functions.
RETURN CODES
. The operation was a success.
status_t GetVolume(int32 device, float *leftVolume, float *rightVolume, bool *enable) status_t SetVolume(int32 device, float leftVolume, float rightVolume)
This pair of functions lets you get and set the volumes of the left and right channels of a given sound output device.
GetVolume() stores the left and right volumes in the floating-point variables pointed to by leftVolume and rightVolume, and also stores a boolean value in enable signifying whether or not the device is enabled (TRUE if the device is active, FALSE otherwise). You may pass a NULL pointer for any combination of these arguments for any information you don't need reported.
SetVolume() sets the current volumes of the left and right channels for the device to the values specified by leftVolume and rightVolume.
The volume ranges from 0.0 to 1.0, where 0.0 is silent and 1.0 is the maximum volume. If you're setting the volume of a single-channel device (such as the speaker), the left channel is used--the value you pass as the right channel volume is ignored. If you want to set the volume of one channel of a stereo device without changing the value of the other channel, pass the B_NO_CHANGE constant for the no-change channel.
You don't have to have already subscribed to the stream to use these functions. If an error occurs, GetVolume() doesn't change the memory pointed to by leftVolume, rightVolume, and enable.
RETURN CODES
. The operation was a success.
status_t SamplingRate(float *currentRate) status_t SetSamplingRate(float newRate)
These functions allow you to get and set the current sampling rate of the stream.
When calling SetSamplingRate() to set the current sampling rate, the value you specify will automatically be rounded to the nearest value accepted by the hardware on which your application is running.
You don't have to have already subscribed to the stream to use these functions. If an error occurs, SamplingRate() does not alter the memory at pointed to by currentRate.
RETURN CODES
status_t SetStreamBuffers(size_t bufferSize, int32 bufferCount)
Sets the size in bytes and the number of buffers that are used to transport data through the stream. Although it's up to the server to provide reasonable default values, you can fine-tune the performance of the stream by adjusting these values:
RETURN CODES
The Be Book, in lovely HTML, for BeOS Release 3.
Copyright © 1998 Be, Inc. All rights reserved.
Last modified March 26, 1998.