音频队列回调未被调用

3

基本上,我想要播放一些音频文件(主要是mp3和caf格式)。但回调函数从来没有被调用过。只有当我调用它们以预先填充队列时才会调用。

这是我的数据结构:

struct AQPlayerState
{
    CAStreamBasicDescription        mDataFormat;
    AudioQueueRef                   mQueue;
    AudioQueueBufferRef             mBuffers[kBufferNum];
    AudioFileID                     mAudioFile;
    UInt32                          bufferByteSize;
    SInt64                          mCurrentPacket;
    UInt32                          mNumPacketsToRead;
    AudioStreamPacketDescription   *mPacketDescs;
    bool                            mIsRunning;
};

这是我的回调函数:

static void HandleOutputBuffer (void *aqData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
{
    NSLog(@"HandleOutput");
    AQPlayerState *pAqData = (AQPlayerState *) aqData;

    if (pAqData->mIsRunning == false) return;

    UInt32 numBytesReadFromFile;
    UInt32 numPackets = pAqData->mNumPacketsToRead;
    AudioFileReadPackets (pAqData->mAudioFile,
                          false,
                          &numBytesReadFromFile,
                          pAqData->mPacketDescs,
                          pAqData->mCurrentPacket,
                          &numPackets,
                          inBuffer->mAudioData);
    if (numPackets > 0) {    
        inBuffer->mAudioDataByteSize = numBytesReadFromFile;
        AudioQueueEnqueueBuffer (pAqData->mQueue,
                                 inBuffer,
                                 (pAqData->mPacketDescs ? numPackets : 0),
                                 pAqData->mPacketDescs);
        pAqData->mCurrentPacket += numPackets;
    } else {
//        AudioQueueStop(pAqData->mQueue, false);
//        AudioQueueDispose(pAqData->mQueue, true);
//        AudioFileClose (pAqData->mAudioFile);
//        free(pAqData->mPacketDescs);
//        free(pAqData->mFloatBuffer);
        pAqData->mIsRunning = false;
    }
}

以下是我的方法:

- (void)playFile
{
    AQPlayerState aqData;

    // get the source file
    NSString *p = [[NSBundle mainBundle] pathForResource:@"1_Female" ofType:@"mp3"];
    NSURL *url2 = [NSURL fileURLWithPath:p];
    CFURLRef srcFile = (__bridge CFURLRef)url2;

    OSStatus result = AudioFileOpenURL(srcFile, 0x1/*fsRdPerm*/, 0/*inFileTypeHint*/, &aqData.mAudioFile);
    CFRelease (srcFile);

    CheckError(result, "Error opinning sound file");

    UInt32 size = sizeof(aqData.mDataFormat);
    CheckError(AudioFileGetProperty(aqData.mAudioFile, kAudioFilePropertyDataFormat, &size, &aqData.mDataFormat),
               "Error getting file's data format");

    CheckError(AudioQueueNewOutput(&aqData.mDataFormat, HandleOutputBuffer, &aqData, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &aqData.mQueue),
               "Error AudioQueueNewOutPut");

    // we need to calculate how many packets we read at a time and how big a buffer we need
    // we base this on the size of the packets in the file and an approximate duration for each buffer
    {
        bool isFormatVBR = (aqData.mDataFormat.mBytesPerPacket == 0 || aqData.mDataFormat.mFramesPerPacket == 0);

        // first check to see what the max size of a packet is - if it is bigger
        // than our allocation default size, that needs to become larger
        UInt32 maxPacketSize;
        size = sizeof(maxPacketSize);
        CheckError(AudioFileGetProperty(aqData.mAudioFile, kAudioFilePropertyPacketSizeUpperBound, &size, &maxPacketSize),
                   "Error getting max packet size");

        // adjust buffer size to represent about a second of audio based on this format
        CalculateBytesForTime(aqData.mDataFormat, maxPacketSize, 1.0/*seconds*/, &aqData.bufferByteSize, &aqData.mNumPacketsToRead);

        if (isFormatVBR) {
            aqData.mPacketDescs = new AudioStreamPacketDescription [aqData.mNumPacketsToRead];
        } else {
            aqData.mPacketDescs = NULL; // we don't provide packet descriptions for constant bit rate formats (like linear PCM)
        }

        printf ("Buffer Byte Size: %d, Num Packets to Read: %d\n", (int)aqData.bufferByteSize, (int)aqData.mNumPacketsToRead);
    }

    // if the file has a magic cookie, we should get it and set it on the AQ
    size = sizeof(UInt32);
    result = AudioFileGetPropertyInfo(aqData.mAudioFile, kAudioFilePropertyMagicCookieData, &size, NULL);

    if (!result && size) {
        char* cookie = new char [size];
        CheckError(AudioFileGetProperty(aqData.mAudioFile, kAudioFilePropertyMagicCookieData, &size, cookie),
                   "Error getting cookie from file");
        CheckError(AudioQueueSetProperty(aqData.mQueue, kAudioQueueProperty_MagicCookie, cookie, size),
                   "Error setting cookie to file");
        delete[] cookie;
    }

    aqData.mCurrentPacket = 0;
    for (int i = 0; i < kBufferNum; ++i) {
        CheckError(AudioQueueAllocateBuffer (aqData.mQueue,
                                             aqData.bufferByteSize,
                                             &aqData.mBuffers[i]),
                   "Error AudioQueueAllocateBuffer");

        HandleOutputBuffer (&aqData,
                            aqData.mQueue,
                            aqData.mBuffers[i]);
    }

    // set queue's gain

    Float32 gain = 1.0;
    CheckError(AudioQueueSetParameter (aqData.mQueue,
                                       kAudioQueueParam_Volume,
                                       gain),
               "Error AudioQueueSetParameter");

    aqData.mIsRunning = true;
    CheckError(AudioQueueStart(aqData.mQueue,
                               NULL),
               "Error AudioQueueStart");

}

当我按下播放按钮时,输出如下:

Buffer Byte Size: 40310, Num Packets to Read: 38
HandleOutput start
HandleOutput start
HandleOutput start

我曾尝试将CFRunLoopGetCurrent()替换为CFRunLoopGetMain(),并将CFRunLoopCommonModes替换为CFRunLoopDefaultMode,但没有效果。
当我启动队列时,应该立即开始播放预先缓冲的数据吧? 但是,当我启动队列时,没有回调函数被触发。 我做错了什么?感谢任何想法。
1个回答

4
您所要做的基本上是使用音频队列进行基本的音频播放示例。在详细查看您的代码以查看缺少什么之前(这可能需要一段时间),我更愿意向您推荐遵循此基本示例代码中的步骤,该示例代码完全执行了您正在执行的操作(不包括那些并不真正相关的额外内容..例如,为什么要尝试增加音频增益?)
在其他地方,您正在尝试使用音频单元播放音频。音频单元比基本音频队列播放更复杂,如果您对音频队列非常熟悉,则不建议尝试它们。但是,您可以查看此示例项目以获取有关音频队列的基本示例。
总的来说,在iOS中进行核心音频编程时,最好花时间学习基本示例并逐步提高自己的水平。在线教程的问题在于它们添加了额外的内容,并经常将其与obj-c代码混合使用。而Core Audio是纯C代码(即额外内容不会为学习过程增加任何内容)。如果您尚未阅读过Learning Core Audio这本书,我强烈建议您阅读。所有示例代码都可以在线获得,但也可以从存储库克隆以方便使用。这就是我学习核心音频的方式。它需要时间 :)

终于搞定了!天啊 :D 文件播放器的例子有一些我忽略的东西。 - Skiny
@Skiny 嘿,伙计,很高兴它能正常工作。当涉及到iOS的核心音频时,它是一种不同的动物.. 它需要大量的培训和阅读.. 再次强烈建议您阅读这本书..并使用其中的示例..它们可以拯救生命。 - abbood
强调不够:好的,这点非常重要 :) - Skiny

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接