2011-02-01 55 views
5

我試圖在OSX上從緩衝區播放聲音(例如:Windows「PlaySound」功能的等價物)。如何使用AudioQueue在C++中爲Mac OSX播放聲音

我已經把一些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"); 
    } 
} 

回答

9

REFS:

請注意,我必須明確地將mAudioDataByteSize設置爲大小I alloca特德。 在文檔中他們提到它最初設置爲零,這就是我發現的。 文檔沒有說明爲什麼,但我懷疑它是允許可變大小的緩衝區或什麼?

/* 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

由bw1024答案有點基礎,我創造了這個完整的Ogg Vorbis播放器libvorbisfile。

它擴展了以前的答案,演示瞭如何使用函數來填充音頻緩衝區(如,不會自己生成聲音),並在事件偵聽器回調中添加重放結束檢測。

代碼本身受到很多評論,希望能夠解釋需要解釋的所有內容。

我試圖保持它與音頻隊列和libvorbisfile的「製作質量」接近,因此它包含「真實」的錯誤條件並檢查特殊情況;例如vorbis文件中的變量採樣率,它無法處理。

我希望所有的噪音都不會影響它作爲樣本的價值。

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