# The Media Kit: BDACStream

Derived from: BAbstractBufferStream

Declared in: be/media/AudioStream.h

Library: libmedia.so

## Overview

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:

- Mixing additional sounds into the block.

- Filtering the sound. Noise-removal, volume and pitch control, echo, and other effects can be added to sounds in the stream.

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.

### Controlling the Hardware

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.

#### Volume and Muting

To set the volume level of a particular sound device, use the `SetVolume()` function. This function accepts three parameters:

- A constant that identifies the device you want to control.

- A float representing the new volume for the left channel of the device.

- A float representing the new volume for the right channel of the device.

The possible device constants are:

- `B_CD_THROUGH` CD throughput.
- `B_LINE_IN_THROUGH` Line in throughput.
- `B_ADC_IN` ADC input.
- `B_LOOPBACK` Audio loopback
- `B_DAC_OUT` DAC output
- `B_MASTER_OUT` Master output.
- `B_SPEAKER_OUT` Speaker output.

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.

#### Sampling Rate

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:

- 44,100 bytes/sec * 2 bytes/sample * 2 channels * 60 sec/min = 10,584,000 bytes (10 MB)

If you use the 22,050 byte/sec sampling rate, you use half as much memory:

- 22,050 bytes/sec * 2 bytes/sample * 2 channels * 60 sec/min = 5,292,000 bytes (5 MB)

You can, by the way, save even more memory by dropping to 8-bit audio:

- 22,050 bytes/sec * 1 byte/sample * 2 channels * 60 sec/min = 2,646,000 bytes (2.5 MB)

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(&currentRate);

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.

### Wading Into the Stream

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.

#### Managing the Buffers

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.

- By decreasing the size and/or number of buffers, you can decrease the maximum latency of the stream (the time it takes for a buffer to get from one end of the stream to the other). However, if you go too far in this direction, you run the risk of falling out of real time.

- By increasing the buffer size and count, you help ensure the real-time integrity of the stream, but you increase its maximum latency.

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.

### An Example

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.

## Constructor and Destructor

### BDACStream()

          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:

### ~BDACStream()

          virtual ~BDACStream()

Destroys the BDACStream object. Don't do this if a BSubscriber is currently subscribed to the stream.

## Member Functions

### EnableDevice(), IsDeviceEnabled()

          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:

- `B_CD_THROUGH` CD throughput.
- `B_LINE_IN_THROUGH` Line in throughput.
- `B_ADC_IN` ADC input.
- `B_LOOPBACK` Audio loopback
- `B_DAC_OUT` DAC output
- `B_MASTER_OUT` Master output.
- `B_SPEAKER_OUT` Speaker output.

You don't have to have already subscribed to the stream to use these functions.

RETURN CODES

``

- B_OK. The operation was a success.
- `B_BAD_REPLY`. Unable to determine or change the output device.
- `B_BAD_VALUE`. Invalid device specified.
- `B_SERVER_NOT_FOUND`. The Audio Server is not running.

### GetVolume(), SetVolume()

          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

``

- B_OK. The operation was a success.
- `B_BAD_REPLY`. Unable to determine or change the volume.
- `B_BAD_VALUE`. Invalid device specified.
- `B_SERVER_NOT_FOUND`. The Audio Server is not running.

### IsDeviceEnabled() see EnableDevice()

### SamplingRate(), SetSamplingRate()

          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

- `B_OK`. The operation was a success.
- `B_BAD_REPLY`. Unable to determine or change the current sampling rate.
- `B_SERVER_NOT_FOUND`. The Audio Server is not running.

### SetSamplingRate() see SamplingRate()

### SetStreamBuffers()

          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:

- By decreasing the size and/or number of buffers, you can decrease the maximum latency of the stream (the time it takes for a buffer to get from one end of the stream to the other). However, if you go too far in this direction, you run the risk of falling out of real time.

- By increasing the buffer size and count, you help ensure the real-time integrity of the stream, but you increase its maximum latency.

RETURN CODES

- `B_OK`. The operation was a success.
- `B_BAD_REPLY`. Unable to change the buffer settings.
- `B_SERVER_NOT_FOUND`. The Audio Server is not running.

### SetVolume() see GetVolume()

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

Copyright © 1998 Be, Inc. All rights reserved.

Last modified March 26, 1998.