August 12, 2020
Hot Topics:

PortAudio: Portable Audio Processing for All Platforms

  • By Victor Volkman
  • Send Email »
  • More Articles »
 28 #define FUZZ(x)
 30 static int gNumNoInputs = 0;
 31 /* This routine will be called by the PortAudio engine
    /* when audio is needed.
 32 ** It may be called at interrupt level on some machines, so
    ** don't do anything that could mess up the system, like
 33 ** calling malloc() or free().
 34 */
 35 static int fuzzCallback( const void *inputBuffer,
                             void *outputBuffer,
 36                          unsigned long framesPerBuffer,
 37                          const PaStreamCallbackTimeInfo* timeInfo,
 38                          PaStreamCallbackFlags statusFlags,
 39                          void *userData )
 40 {
 41    SAMPLE *out = (SAMPLE*)outputBuffer;
 42    const SAMPLE *in = (const SAMPLE*)inputBuffer;
 43    unsigned int i;
 45    if( inputBuffer == NULL ) {
 46       for( i=0; i<framesPerBuffer; i++ )
 47          {
 48             *out++ = 0;   /* left  - silent */
 49             *out++ = 0;   /* right - silent */
 50          }
 51       gNumNoInputs += 1;
 52    } else {
 53       for( i=0; i<framesPerBuffer; i++ )
 54          {
 55             *out++ = FUZZ(*in++);   /* left  - distorted */
 56             *out++ = *in++;         /* right - clean */
 57          }
 58       }
 60       return paContinue;
 61 }

The PortAudio system is designed to work in a near real-time environment, thus the use of callback functions. The fuzzCallback() function sends an input buffer, output buffer, number of frames (for example, samples), time sequence, buffer status flags, and a pointer to a user-defined storage area. A frame in an input or output buffer contains a complete set of samples for all channels involved (in this case, two for stereo). The program has as many tuples as specified by the incoming frameCount (which may be zero); you've asked for 64 samples (FRAMES_PER_BUFFER).

Although this example uses two-channel audio, you can set up any number of channels. The fuzzCallback() function generates an empty buffer in the case of "no input." If you do have input, you fuzz the left channel (zero) and copy the input clean on the right channel (one). If your distortion was sensitive in the time domain, you could use the timeInfo struct to retrieve the following times in seconds:

  1. When the first sample of the input buffer was received at the audio input
  2. When the first sample of the output buffer will begin being played at the audio output
  3. When the stream callback was called
 63 /*************************************************************/
 64 int main(void)
 65 {
 66    PaStreamParameters inputP, outputP;
 67    PaStream *stream;
 68    PaError err;
 70    err = Pa_Initialize();
 71    if( err != paNoError ) goto error;
 74    inputP.device = Pa_GetDefaultInputDevice();
       /* default input device */
 75    inputP.channelCount = 2;    /* stereo input */
 76    inputP.sampleFormat = PA_SAMPLE_TYPE;
 77    inputP.suggestedLatency = Pa_GetDeviceInfo( inputP.device )
 78    inputP.hostApiSpecificStreamInfo = NULL;
 80    outputP.device = Pa_GetDefaultOutputDevice();
       /* default output device */
 81    outputP.channelCount = 2;    /* stereo output */
 82    outputP.sampleFormat = PA_SAMPLE_TYPE;
 83    outputP.suggestedLatency = Pa_GetDeviceInfo( outputP.device )
 84    outputP.hostApiSpecificStreamInfo = NULL;
 86    err = Pa_OpenStream(
 87       &stream,
 88       &inputP,
 89       &outputP,
 90       SAMPLE_RATE,
 92       0,     /* paClipOff, */
                 /* we won't output out of range samples so don't
                  * bother clipping them */
 93       fuzzCallback,
 94       NULL );
 95    if( err != paNoError ) goto error;

Initializing PA and opening the stream are next. Pa_Initialize() must of course be the first PortAudio call your application uses, just as Pa_Terminate() is the last. After that, you need to set up the parameters of your input streams and output streams. The default input device is usually Microsoft Sound Mapper, which flows from the line-in input of your soundcard (or equivalent). Other possible inputs might be your modem input, CD audio, or other things depending on drivers and hardware. You also could create sophisticated callback algorithms where you mix multiple channels down to one channel or vice-versa.

Finally, you are ready to call Pa_OpenStream() and get the streams ready for immediate use. Because latency is always your enemy, separate opening the stream from starting the stream. The input and output channels must agree to the same sample rate (in this case, CD quality 44100Hz) and the same number of samples per buffer-load.

 97    err = Pa_StartStream( stream );
 98    if( err != paNoError ) goto error;
100    printf("Hit ENTER to stop program.\n");
101    getchar();
102    err = Pa_CloseStream( stream );
103    if( err != paNoError ) goto error;
105    printf("Finished. gNumNoInputs = %d\n", gNumNoInputs );
106    Pa_Terminate();
107    return 0;
109 error:
110    Pa_Terminate();
111    fprintf( stderr, "An error occurred while using the
                         portaudio stream\n" );
112    fprintf( stderr, "Error number: %d\n", err );
113    fprintf( stderr, "Error message: %s\n",
                Pa_GetErrorText( err ) );
114    return -1;
115 }

At first glance, the remainder of the program may leave you scratching your head. The Pa_StartStream() calls a platform-specific function to get a thread going, which begins callbacks immediately. The Win32 implementations all eventually call CreateThread(), although to me the WDMKS code seems a lot simpler than the Win MME version. The two ways to get out of the callback loop are returning a value of 1 or calling Pa_CloseStream().

Get Creative

Your creativity is the limit to what you can do with PortAudio: convert data streams from one format to another in real time, simulate surround sound or other sophisticated multi-channel audio, or even create performance-quality effects. Best of all, you aren't overcommitted to any platform, which makes PortAudio my choice for open source audio projects.

About the Author

Victor Volkman has been writing for C/C++ Users Journal and other programming journals since the late 1980s. He is a graduate of Michigan Tech and a faculty advisor board member for Washtenaw Community College CIS department. Volkman is the editor of numerous books, including C/C++ Treasure Chest and is the owner of Loving Healing Press. He can help you in your quest for open source tools and libraries; just drop an e-mail to sysop@HAL9K.com.

Page 2 of 2

This article was originally published on March 10, 2006

Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date