Как использовать AudioQueue для воспроизведения звука для Mac OSX в C ++ - PullRequest
5 голосов
/ 01 февраля 2011

Я пытаюсь воспроизвести звук на OSX из буфера (например: Эквивалентная функция Windows "PlaySound").

Я собрал код C ++ для воспроизведения звука с AudioQueue (как естьнасколько я понимаю, это самый простой способ воспроизведения звука на OSX).

Однако звук никогда не генерируется, а функция обратного вызова аудио никогда не вызывается.

Кто-нибудь знает, что я?я делаю неправильно, или у кого-нибудь есть простой пример C / C ++, как воспроизводить звук на 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");
    }
}

Ответы [ 2 ]

10 голосов
/ 21 ноября 2011

REFS:

  • developer.apple.com Руководство по программированию служб Audio Queue: воспроизведение аудио
  • developer.apple.com Справочник по службам аудио-очереди
  • с чего начать синтез звука на iPhone Ответ Энди Дж. Бьюкенена

Обратите внимание, что мне пришлось явно установить mAudioDataByteSize в размер, который я выделил. В документах они упоминают, что изначально он установлен на ноль, и это то, что я нашел. В документах не говорится, почему, но я подозреваю, что это разрешает использование буферов переменного размера или что-то в этом роде?

/* 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;
}
2 голосов
/ 10 июня 2015

Основываясь на ответе bw1024, я создал этот полный проигрыватель ogg vorbis с помощью libvorbisfile.

Он расширяет предыдущий ответ, демонстрирует, как использовать функцию для заполнения аудио-буферов (например, сам по себе не генерирует звук) и добавляет обнаружение окончания воспроизведения с помощью обратного вызова слушателя событий.

Сам код сильно прокомментирован, что, надеюсь, объясняет все, что нужно объяснить.

Я пытался сохранить его как можно ближе к «качеству продукции» как Audio Queues, так и libvorbisfile, чтобы он содержал «реальные» условия ошибки и проверки на исключительные обстоятельства; например, переменная частота дискретизации в файле vorbis, которую он не может обработать.

Я надеюсь, что ни один из шумов не отвлекает от его значения в качестве образца.

// vorplay.c - by Johann `Myrkraverk' Oskarsson <johann@myrkraverk.com>

// 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
//                    <johann@myrkraverk.com>
// 
//  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.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...