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

后端 未结 2 1242
栀梦
栀梦 2021-01-06 01:33

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

2条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2021-01-06 01:42

    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 
    
    // 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  // For pthread_exit().
    
    #include 
    
    #include 
    
    #include  
    
    // 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, §ion );
    
    
      // 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 \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
    //                    
    // 
    //  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.
    

提交回复
热议问题