2011-02-01 15 views
5

Estoy tratando de reproducir un sonido en OSX, desde un búfer (por ejemplo: Equivalente de la función "PlaySound" de Windows).Cómo usar AudioQueue para reproducir un sonido para Mac OSX en C++

He creado un código C++ para reproducir audio con AudioQueue (ya que entiendo que esta es la forma más fácil de reproducir audio en OSX).

Sin embargo, nunca se genera ningún sonido y nunca se llama a la función de devolución de llamada de audio.

¿Alguien sabe lo que estoy haciendo mal, o alguien tiene un simple ejemplo de C/C++ de cómo reproducir un sonido en 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"); 
    } 
} 

Respuesta

9

REFERENCIAS:

  • developer.apple.com Guía de programación de audio Cola Servicios: Reproducción de audio
  • developer.apple.com audio servicios de cola de referencia
  • where to start with audio synthesis on iPhone respuesta por Andy J Buchanan

Tenga en cuenta que tuve que establecer explícitamente mAudioDataByteSize en el tamaño I alloca ted. En los documentos mencionan que inicialmente se establece en cero, y eso es lo que encontré. Los documentos no dicen por qué, pero sospecho que es para permitir el uso de búferes de tamaño variable o algo así.

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

Makefile: 


example: example.c 
     gcc -o [email protected] $< -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

Basado un tanto en la respuesta de bw1024, creé este reproductor completo ogg vorbis con libvorbisfile.

Se expande en la respuesta anterior, muestra cómo utilizar una función para completar los búferes de audio (como en, no genera el sonido por sí mismo) y agrega la detección de fin de reproducción con una devolución de llamada del detector de eventos.

El código en sí es muy comentado, lo que con suerte explica todo lo que necesita ser explicado.

Intenté mantenerlo tan cerca de la "calidad de producción" de Audio Queues y libvorbisfile, por lo que contiene condiciones de error "reales" y verifica si hay circunstancias excepcionales; como la velocidad de muestreo variable en el archivo vorbis, que no puede manejar.

Espero que nada de ruido distraiga su valor como muestra.

// 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. 
Cuestiones relacionadas