Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use AudioQueue to play a sound for Mac OSX in C++

Tags:

c++

macos

I am trying to play a sound on OSX, from a buffer (eg: Equivalent of Windows "PlaySound" function).

I have put together some C++ code to play audio with AudioQueue (as it is my understanding that this is the easiest way to play audio on OSX).

However, no sound is ever generated, and the audio callback function is never called.

Does anybody know what I'm doing wrong, or does anyone have a simple C/C++ example of how to play a sound on OSX?


#include 
#include 

#define BUFFER_COUNT 3
static struct AQPlayerState {
    AudioStreamBasicDescription   desc;
    AudioQueueRef                 queue;
    AudioQueueBufferRef           buffers[BUFFER_COUNT];
    unsigned buffer_size;
} state;

static void audio_callback (void *aux, AudioQueueRef aq, AudioQueueBufferRef bufout)
{
    printf("I never get called!\n");
#define nsamples 4096
    short data[nsamples];
    for (int i=0;imAudioDataByteSize = nsamples * sizeof(short) * 1;

    assert(bufout->mAudioDataByteSize mAudioData, data, bufout->mAudioDataByteSize);

    AudioQueueEnqueueBuffer(state.queue, bufout, 0, NULL);
}

void audio_init()
{
    int i;

    bzero(&state, sizeof(state));

    state.desc.mFormatID = kAudioFormatLinearPCM;
    state.desc.mFormatFlags = kAudioFormatFlagIsSignedInteger   | kAudioFormatFlagIsPacked;
    state.desc.mSampleRate = 44100;
    state.desc.mChannelsPerFrame = 1;
    state.desc.mFramesPerPacket = 1;
    state.desc.mBytesPerFrame = sizeof (short) * state.desc.mChannelsPerFrame;
    state.desc.mBytesPerPacket = state.desc.mBytesPerFrame;
    state.desc.mBitsPerChannel = (state.desc.mBytesPerFrame*8)/state.desc.mChannelsPerFrame;
    state.desc.mReserved = 0;

    state.buffer_size = state.desc.mBytesPerFrame * state.desc.mSampleRate;

    if (noErr != AudioQueueNewOutput(&state.desc, audio_callback, 0, NULL, NULL, 0, &state.queue)) {
    printf("audioqueue error\n");
    return;
    }

    // Start some empty playback so we'll get the callbacks that fill in the actual audio.
    for (i = 0; i mAudioDataByteSize = state.buffer_size;
    AudioQueueEnqueueBuffer(state.queue, state.buffers[i], 0, NULL);
    }
    if (noErr != AudioQueueStart(state.queue, NULL)) printf("AudioQueueStart failed\n");
    printf("started audio\n");
}


int main() {
    audio_init();
    while (1) {
    printf("I can't hear anything!\n");
    }
}
like image 321
user598524 Avatar asked Feb 01 '11 14:02

user598524


2 Answers

REFS:

  • developer.apple.com Audio Queue Services Programming Guide: Playing Audio
  • developer.apple.com Audio Queue Services Reference
  • where to start with audio synthesis on iPhone Answer by Andy J Buchanan

Note that I had to explicitly set mAudioDataByteSize to the size I allocated. In the docs they mention that it is initially set to zero, and that's what I found. The docs don't say why but I suspect it's to allow for variable size buffers or something?

/* Ben's Audio Example for OSX 10.5+ (yeah Audio Queue)
     Ben White, Nov, 2011 

Makefile:


example: example.c
        gcc -o $@ $< -Wimplicit -framework AudioToolbox \
                -framework CoreFoundation -lm

*/

#include "AudioToolbox/AudioToolbox.h"

typedef struct {
  double phase, phase_inc;
  int count;
} PhaseBlah;


void callback (void *ptr, AudioQueueRef queue, AudioQueueBufferRef buf_ref)
{
  OSStatus status;
  PhaseBlah *p = ptr;
  AudioQueueBuffer *buf = buf_ref;
  int nsamp = buf->mAudioDataByteSize / 2;
  short *samp = buf->mAudioData;
  int ii;
  printf ("Callback! nsamp: %d\n", nsamp);
  for (ii = 0; ii < nsamp; ii++) {
    samp[ii] = (int) (30000.0 * sin(p->phase));
    p->phase += p->phase_inc;
    //printf("phase: %.3f\n", p->phase);
  }
  p->count++;
  status = AudioQueueEnqueueBuffer (queue, buf_ref, 0, NULL);
  printf ("Enqueue status: %d\n", status);
}


int main (int argc, char *argv[])
{
  AudioQueueRef queue;
  PhaseBlah phase = { 0, 2 * 3.14159265359 * 450 / 44100 };
  OSStatus status;
  AudioStreamBasicDescription fmt = { 0 };
  AudioQueueBufferRef buf_ref, buf_ref2;

  fmt.mSampleRate = 44100;
  fmt.mFormatID = kAudioFormatLinearPCM;
  fmt.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
  fmt.mFramesPerPacket = 1;
  fmt.mChannelsPerFrame = 1; // 2 for stereo
  fmt.mBytesPerPacket = fmt.mBytesPerFrame = 2; // x2 for stereo
  fmt.mBitsPerChannel = 16;

  status = AudioQueueNewOutput(&fmt, callback, &phase, CFRunLoopGetCurrent(),
                  kCFRunLoopCommonModes, 0, &queue);

  if (status == kAudioFormatUnsupportedDataFormatError) puts ("oops!");
  else printf("NewOutput status: %d\n", status);

  status = AudioQueueAllocateBuffer (queue, 20000, &buf_ref);
  printf ("Allocate status: %d\n", status);

  AudioQueueBuffer *buf = buf_ref;
  printf ("buf: %p, data: %p, len: %d\n", buf, buf->mAudioData, buf->mAudioDataByteSize);
  buf->mAudioDataByteSize = 20000;

  callback (&phase, queue, buf_ref);

  status = AudioQueueAllocateBuffer (queue, 20000, &buf_ref2);
  printf ("Allocate2 status: %d\n", status);

  buf = buf_ref2;
  buf->mAudioDataByteSize = 20000;

  callback (&phase, queue, buf_ref2);

  status = AudioQueueSetParameter (queue, kAudioQueueParam_Volume, 1.0);
  printf ("Volume status: %d\n", status);

  status = AudioQueueStart (queue, NULL);
  printf ("Start status: %d\n", status);

  while (phase.count < 15)
    CFRunLoopRunInMode (
        kCFRunLoopDefaultMode,
        0.25, // seconds
        false // don't return after source handled
    );

  return 0;
}
like image 105
bw1024 Avatar answered Nov 17 '22 02:11

bw1024


Based somewhat on the answer by bw1024, I created this complete ogg vorbis player with libvorbisfile.

It expands on the previous answer, demonstrates how to use a function to fill in the audio buffers (as in, doesn't generate the sound by itself) and adds end-of-playback detection with an event listener callback.

The code itself is heavily commented, which hopefully explains everything that needs to be explained.

I tried to keep it as close to "production quality" of both Audio Queues and libvorbisfile, so it contains "real" error conditions and checks for exceptional circumstances; such as variable sample rate in the vorbis file, which it can't handle.

I hope none of the noise distracts from its value as a sample.

// vorplay.c - by Johann `Myrkraverk' Oskarsson <[email protected]>

// In the interest of example code, it's explicitly licensed under the 
// WTFPL, see the bottom of the file or http://www.wtfpl.net/ for details.

#include <pthread.h> // For pthread_exit().

#include <vorbis/vorbisfile.h>

#include <AudioToolbox/AudioToolbox.h>

#include <stdio.h> 

// This is a complete example of an Ogg Vorbis player based on the vorbisfile
// library and the audio queue API in OS X.

// It can be either taken as an example of how to use libvorbisfile, or
// audio queue programming.

// There are many "magic number" constants in the code, and understanding
// them requires looking up the relevant documentation.  Some, such as
// the number of buffers in the audio queue and the size of each buffer
// are the result of experimentation.  A "real application" may benefit
// from allowing the user to tweak these, in order to resolve audio stutters.

// Error handling is done very simply in order to focus on the example code
// while still resembling "production code."  Here, we use the 

//     if ( status = foo() ) { ... } 

// syntax for error checking. The assignment in if()s is not an error.
// If your compiler is complaining, you can use its equivalent of the
// GCC switch -Wno-parentheses to silence it.

// Assuming you'll want to use libvorbisfile from mac ports, you can
// compile it like this.

// gcc -c -I/opt/local/include \
//     vorplay.c \
//     -Wno-parentheses

// And link with

// gcc -o vorplay vorplay.o \
//     -L/opt/local/lib -lvorbisfile \
//     -framework AudioToolbox

// The start/stop listener...
void listener( void *vorbis, AudioQueueRef queue, AudioQueuePropertyID id )
{
  // Here, we're only listening for start/stop, so don't need to check
  // the id; it's always kAudioQueueProperty_IsRunning in our case.

  UInt32 running = 0;
  UInt32 size = sizeof running;
/*   OggVorbis_File *vf = (OggVorbis_File *) vorbis; */
  OSStatus status = -1;

  if ( status = AudioQueueGetProperty( queue, id, &running, &size ) ) {
    printf( "AudioQueueGetProperty status = %d; running = %d\n", 
        status, running );
    exit( 1 );
  }

  if ( !running ) {
    // In a "real example" we'd clean up the vf pointer with ov_clear() and
    // the audio queue with AudioQueueDispose(); however, the latter is 
    // better not called from within the listener function, so we just
    // exit normally.
    exit( 0 );
    // In a "real" application, we might signal the termination with
    // a pthread condition variable, or something similar, instead;
    // where the waiting thread would call AudioQueueDispose().  It is 
    // "safe" to call ov_clear() here, but there's no point.
  }
}

// The audio queue callback...
void callback( void *vorbis, AudioQueueRef queue, AudioQueueBufferRef buffer )
{
  OggVorbis_File *vf = (OggVorbis_File *) vorbis;
  int section = 0;
  OSStatus status = -1;

  // The parameters here are congruent with our format specification for
  // the audio queue.  We read directly into the audio queue buffer.
  long r = ov_read( vf, buffer->mAudioData, buffer->mAudioDataBytesCapacity, 
            0, 2, 1, &section );


  // As an extra precaution, check if the current buffer is the same sample
  // rate and channel number as the audio queue.
  {
    vorbis_info *vinfo = ov_info( vf, section );

    if ( vinfo == NULL ) {
      printf( "ov_info status = NULL\n" );
      exit( 1 );
    }

    AudioStreamBasicDescription description;
    UInt32 size = sizeof description;
    if ( status = AudioQueueGetProperty( queue, 
                     kAudioQueueProperty_StreamDescription,
                     &description,
                     &size ) ) {
      printf( "AudioQueueGetProperty status = %d\n", status );
      exit( 1 );
    }

    // If we were using some other kind of audio playback API, such as OSS4
    // we could simply change the sample rate and channel number on the fly.
    // However, with an audio queue, we'd have to use a different
    // one, afaict; so we don't handle it at all in this example.

    if ( vinfo->rate != description.mSampleRate ) {
      printf( "We don't handle changes in sample rate.\n" );
      exit( 1 );
    }

    if ( vinfo->channels != description.mChannelsPerFrame ) {
      printf( "We don't handle changes in channel numbers.\n" );
      exit( 1 );
    }
  }

  // The real "callback"...

  if ( r == 0 ) { // No more data, stop playing.

    // Flush data, to make sure we play to the end.
    if ( status = AudioQueueFlush( queue ) ) {
      printf( "AudioQueueFlush status = %d\n", status );
      exit( 1 );
    }

    // Stop asynchronously.
    if ( status = AudioQueueStop( queue, false ) ) { 
      printf( "AudioQueueStop status = %d\n", status );
      exit( 1 );
    }

  } else if ( r < 0 ) { // Some error?

    printf( "ov_read status = %ld\n", r );
    exit( 1 );

  } else { // The normal course of action.

    // ov_read() may not return exactly the number of bytes we requested.
    // so we update the buffer size per call.
    buffer->mAudioDataByteSize = r;

    if ( status = AudioQueueEnqueueBuffer( queue, buffer, 0, 0 ) ) {
      printf( "AudioQueueEnqueueBuffer status = %d, r = %ld\n", status, r );
      exit( 1 );
    }

  }
}

int main( int argc, char *argv[] )
{
  // The very simple command line argument check.
  if ( argc != 2 ) {
    printf( "Usage: vorplay <file>\n" );
    exit( 1 );
  }

  FILE *file = fopen( argv[ 1 ], "r" );

  if ( file == NULL ) {
    printf( "Unable to open input file.\n" );
    exit( 1 );
  }

  OggVorbis_File vf;
  // Using OV_CALLBACKS_DEFAULT means ov_clear() will close the file.
  // However, this particular example doesn't use that function.
  // A typical place for it might be the listener(), when we stop
  // playing.
  if ( ov_open_callbacks( file, &vf, 0, 0, OV_CALLBACKS_DEFAULT ) ) {
    printf( "ov_open_callbacks() failed.  Not an Ogg Vorbis file?\n" );
    exit( 1 );
  }

  // For the sample rate and channel number in the audio.
  vorbis_info *vinfo = ov_info( &vf, -1 );
  if ( vinfo == NULL ) {
    printf( "ov_info status = NULL\n" );
    exit( 1 );
  }

  // The audio queue format specification.  This structure must be set 
  // to values congruent to the ones we use with ov_read().
  AudioStreamBasicDescription format = { 0 };
  // First, the constants.  The format is quite hard coded, both here
  // and in the calls to ov_read().
  format.mFormatID = kAudioFormatLinearPCM;
  format.mFormatFlags = 
    kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
  format.mFramesPerPacket = 1;
  format.mBitsPerChannel = 16;
  // Load the sample rate and channel number from the vorbis file.
  format.mSampleRate = vinfo->rate;
  format.mChannelsPerFrame = vinfo->channels;
  // The number of bytes depends on the channel number.
  format.mBytesPerPacket = 
    format.mBytesPerFrame = 2 * vinfo->channels; // times two, for 16bit

  OSStatus status = -1;
  AudioQueueRef queue;

  // Create the audio queue with the desired format.  Notice that we
  // use the OggVorbis_File pointer as the data far the callback.
  if ( status = AudioQueueNewOutput( &format, callback,
                     &vf, NULL, NULL, 0, &queue ) ) {
    printf( "AudioQueueNewOutput status = %d\n", status );
    exit( 1 );
  }

  // For me distortions happen with 3 buffers; hence the magic number 5.
  AudioQueueBufferRef buffers[ 5 ]; 
  for ( int i = 0; i < sizeof buffers / sizeof (AudioQueueBufferRef); i++ ) {
    // For each buffer...

    // The size of the buffer is a magic number.  4096 is good enough, too.
    if ( status = AudioQueueAllocateBuffer( queue, 8192, &buffers[ i ] ) ) {
      printf( "AudioQueueAllocateBuffer status = %d\n", status );
      exit( 1 );
    }

    // Enqueue buffers, before play.  According to the process outlined 
    // in the Audio Queue Services Programming Guide, we must do this
    // before calling AudioQueueStart() and it's simplest to do it like
    // this.
    callback( &vf, queue, buffers[ i ] ); 
  }

  // We set the volume to maximum; even though the docs say it's the
  // default.
  if ( status = AudioQueueSetParameter( queue, 
                    kAudioQueueParam_Volume, 1.0 ) ) {
    printf( "AudioQueueSetParameter status = %d\n", status );
    exit( 1 );
  }

  // Here, we might want to call AudioQueuePrime if we were playing one
  // of the supported compressed formats.  However, since we only have
  // raw PCM buffers to play, I don't see the point.  Maybe playing will
  // start faster with it, after AudioQueueStart() but I still don't see
  // the point for this example; if there's a delay, it'll happen anyway.

  // We add a listener for the start/stop event, so we know when to call
  // exit( 0 ) and terminate the application.  We also give it the vf 
  // pointer, even though it's not used in our listener().
  if ( status = AudioQueueAddPropertyListener( queue, 
                           kAudioQueueProperty_IsRunning,
                           listener,
                           &vf ) ) {
    printf( "AudioQueueAddPropertyListener status = %d\n", status );
    exit( 1 );
  }

  // And then start to play the file.
  if ( status = AudioQueueStart( queue, 0 ) ) {
    printf( "AudioQueueStart status = %d\n", status );
    exit( 1 );
  }

  // Work's For Me[tm].  This trick to make sure the process doesn't
  // terminate before the song has played "works for me" on 
  // OS X 10.10.3.  If you're going to use this same trick in production
  // code, you might as well turn off the joinability of the main thread,
  // with pthread_detach() and also make sure no callback or listener is
  // using data from the stack.  Unlike this example.
  pthread_exit( 0 );

  return 0; // never reached, left intact in case some compiler complains.
}


//             DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
//                     Version 2, December 2004
// 
//          Copyright (C) 2015 Johann `Myrkraverk' Oskarsson
//                    <[email protected]>
// 
//  Everyone is permitted to copy and distribute verbatim or modified
//  copies of this license document, and changing it is allowed as long
//  as the name is changed.
// 
//             DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
//    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
// 
//   0. You just DO WHAT THE FUCK YOU WANT TO.
like image 2
Johann Oskarsson Avatar answered Nov 17 '22 02:11

Johann Oskarsson