Функция обратного вызова CoreAudio AudioQueue никогда не вызывалась, ошибок не сообщалось

Я пытаюсь сделать простое воспроизведение из функциональности файла, и кажется, что моя функция обратного вызова никогда не вызывается. Это не имеет никакого смысла, потому что все OSStatuses возвращаются 0, и другие числа также кажутся правильными (например, выходные пакеты считывают указатель из AudioFileReadPackets).

Вот настройка:

OSStatus stat;

stat = AudioFileOpenURL(
    (CFURLRef)urlpath, kAudioFileReadPermission, 0, &aStreamData->aFile
);

UInt32 dsze = 0;
stat = AudioFileGetPropertyInfo(
    aStreamData->aFile, kAudioFilePropertyDataFormat, &dsze, 0
);

stat = AudioFileGetProperty(
    aStreamData->aFile, kAudioFilePropertyDataFormat, &dsze, &aStreamData->aDescription
);

stat = AudioQueueNewOutput(
    &aStreamData->aDescription, bufferCallback, aStreamData, NULL, NULL, 0, &aStreamData->aQueue
);

aStreamData->pOffset = 0;

for(int i = 0; i < NUM_BUFFERS; i++) {
    stat = AudioQueueAllocateBuffer(
        aStreamData->aQueue, aStreamData->aDescription.mBytesPerPacket, &aStreamData->aBuffer[i]
    );

    bufferCallback(aStreamData, aStreamData->aQueue, aStreamData->aBuffer[i]);
}

stat = AudioQueuePrime(aStreamData->aQueue, 0, NULL);
stat = AudioQueueStart(aStreamData->aQueue, NULL);

(Не показано, где я проверяю значение stat между функциями, он просто возвращается в нормальное состояние.)

И функция обратного вызова:

void bufferCallback(void *uData, AudioQueueRef queue, AudioQueueBufferRef buffer) {
    UInt32 bread = 0;
    UInt32 pread = buffer->mAudioDataBytesCapacity / player->aStreamData->aDescription.mBytesPerPacket;

    OSStatus stat;

    stat = AudioFileReadPackets(
        player->aStreamData->aFile, false, &bread, NULL, player->aStreamData->pOffset, &pread, buffer->mAudioData
    );

    buffer->mAudioDataByteSize = bread;

    stat = AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);

    player->aStreamData->pOffset += pread;
}

Где aStreamData - это моя структура данных пользователя (typedefed, поэтому я могу использовать ее как свойство класса), а игрок является статическим экземпляром класса Objective-C. Если какой-либо другой код нужен, пожалуйста, дайте мне знать. Я немного в своем уме. Печать любого из приведенных здесь чисел дает правильный результат, включая функции в bufferCallback, когда я сам его вызываю в цикле выделения. После этого он никогда не будет вызван. Метод запуска возвращается и ничего не происходит.

Кроме того, я использую периферийное устройство (MBox Pro 3) для воспроизведения звука, который CoreAudio загружает только тогда, когда он собирается выводить. IE, если я запустил iTunes или что-то в этом роде, динамики слегка покачиваются, и есть светодиод, который идет от моргания до твердого. Устройство загружается, как это делает CA, безусловно, что-то делает. (Также я, конечно, пробовал его с встроенным звуком Macbook без устройства.)

Я читал другие решения проблем, которые кажутся похожими, и они не работают. Такие вещи, как использование нескольких буферов, которые я делаю сейчас, и, похоже, не имеют никакого значения.

В основном я предполагаю, что я что-то явно ошибаюсь, но не уверен, что это может быть. Я прочитал соответствующую документацию, посмотрел доступные примеры кода и немного вычислил сеть для ответов, и кажется, что это все, что мне нужно сделать, и это должно просто пойти.

По крайней мере, есть ли что-нибудь еще, что я могу сделать, чтобы исследовать?

Ответ 1

Мой первый ответ был недостаточно хорош, поэтому я составил минимальный пример, который будет воспроизводить 2-канальный 16-битный волновой файл.

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

Что касается вашего кода, он кажется на первый взгляд законным. Две вещи я укажу, хотя: 1. Кажется, вы выделяете буферы с TOO SMALL размером буфера. Я заметил, что AudioQueues не будет воспроизводиться, если буферы слишком малы, что, похоже, соответствует вашей проблеме. 2. Вы подтвердили возвращаемые свойства?

Вернуться к примеру моего кода:

Все жестко закодировано, поэтому это не совсем хорошая практика кодирования, но показывает, как вы можете это сделать.

AudioStreamTest.h

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>

uint32_t bufferSizeInSamples;
AudioFileID file;
UInt32 currentPacket;
AudioQueueRef audioQueue;
AudioQueueBufferRef buffer[3];
AudioStreamBasicDescription audioStreamBasicDescription;

@interface AudioStreamTest : NSObject

- (void)start;
- (void)stop;

@end

AudioStreamTest.m

#import "AudioStreamTest.h"

@implementation AudioStreamTest

- (id)init
{
    self = [super init];
    if (self) {
        bufferSizeInSamples = 441;

        file = NULL;
        currentPacket = 0;

        audioStreamBasicDescription.mBitsPerChannel = 16;
        audioStreamBasicDescription.mBytesPerFrame = 4;
        audioStreamBasicDescription.mBytesPerPacket = 4;
        audioStreamBasicDescription.mChannelsPerFrame = 2;
        audioStreamBasicDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
        audioStreamBasicDescription.mFormatID = kAudioFormatLinearPCM;
        audioStreamBasicDescription.mFramesPerPacket = 1;
        audioStreamBasicDescription.mReserved = 0;
        audioStreamBasicDescription.mSampleRate = 44100;
    }

    return self;
}

- (void)start {
    AudioQueueNewOutput(&audioStreamBasicDescription, AudioEngineOutputBufferCallback, (__bridge void *)(self), NULL, NULL, 0, &audioQueue);

    AudioQueueAddPropertyListener(audioQueue, kAudioQueueProperty_IsRunning, AudioEnginePropertyListenerProc, NULL);

    AudioQueueStart(audioQueue, NULL);
}

- (void)stop {
    AudioQueueStop(audioQueue, YES);
    AudioQueueRemovePropertyListener(audioQueue, kAudioQueueProperty_IsRunning, AudioEnginePropertyListenerProc, NULL);
}

void AudioEngineOutputBufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
    if (file == NULL) return;

    UInt32 bytesRead = bufferSizeInSamples * 4;
    UInt32 packetsRead = bufferSizeInSamples;
    AudioFileReadPacketData(file, false, &bytesRead, NULL, currentPacket, &packetsRead, inBuffer->mAudioData);
    inBuffer->mAudioDataByteSize = bytesRead;
    currentPacket += packetsRead;

    if (bytesRead == 0) {
        AudioQueueStop(inAQ, false);
    }
    else {
        AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
    }
}

void AudioEnginePropertyListenerProc (void *inUserData, AudioQueueRef inAQ, AudioQueuePropertyID inID) {
    //We are only interested in the property kAudioQueueProperty_IsRunning
    if (inID != kAudioQueueProperty_IsRunning) return;

    //Get the status of the property
    UInt32 isRunning = false;
    UInt32 size = sizeof(isRunning);
    AudioQueueGetProperty(inAQ, kAudioQueueProperty_IsRunning, &isRunning, &size);

    if (isRunning) {
        currentPacket = 0;

        NSString *fileName = @"/Users/roy/Documents/XCodeProjectsData/FUZZ/03.wav";
        NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: fileName];
        AudioFileOpenURL((__bridge CFURLRef) fileURL, kAudioFileReadPermission, 0, &file);

        for (int i = 0; i < 3; i++){
            AudioQueueAllocateBuffer(audioQueue, bufferSizeInSamples * 4, &buffer[i]);

            UInt32 bytesRead = bufferSizeInSamples * 4;
            UInt32 packetsRead = bufferSizeInSamples;
            AudioFileReadPacketData(file, false, &bytesRead, NULL, currentPacket, &packetsRead, buffer[i]->mAudioData);
            buffer[i]->mAudioDataByteSize = bytesRead;
            currentPacket += packetsRead;

            AudioQueueEnqueueBuffer(audioQueue, buffer[i], 0, NULL);
        }
    }

    else {
        if (file != NULL) {
            AudioFileClose(file);
            file = NULL;

            for (int i = 0; i < 3; i++) {
                AudioQueueFreeBuffer(audioQueue, buffer[i]);
                buffer[i] = NULL;
            }
        }
    }
}

-(void)dealloc {
    [super dealloc];
    AudioQueueDispose(audioQueue, true);
    audioQueue = NULL;
}

@end

Наконец, я хочу включить некоторые исследования, которые я сделал сегодня, чтобы проверить надежность AudioQueues.

Я заметил, что если вы делаете слишком маленькие буферы AudioQueue, он вообще не будет воспроизводиться. Это заставило меня немного поиграть, чтобы понять, почему он не играет.

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

Если я попробую размер буфера, который может содержать 175 образцов, он воспроизводит всю песню, но с большим искажением. 175 составляет чуть менее 4 мс аудио.

AudioQueue продолжает запрашивать новые буферы, пока вы продолжаете подавать буферы. Это независимо от того, что AudioQueue фактически воспроизводит ваши буферы или нет.

Если вы предоставите буфер размером 0, буфер будет потерян, и для этого запроса очереди очереди будет возвращена ошибка kAudioQueueErr_BufferEmpty. Вы никогда не увидите, что AudioQueue попросит вас снова заполнить этот буфер. Если это произошло для последней очереди, которую вы отправили, AudioQueue перестанет просить вас заполнить все буферы. В этом случае вы не услышите больше звука для этого сеанса.

Чтобы узнать, почему AudioQueues не играет ничего с меньшими размерами буфера, я сделал тест, чтобы узнать, вызван ли мой callback даже при отсутствии звука. Ответ заключается в том, что буферы вызываются все время, пока звуковые сигналы воспроизводятся и нуждаются в данных.

Итак, если вы продолжаете подавать буферы в очередь, ни один буфер не будет потерян. Этого не происходит. Если, конечно, нет ошибки.

Итак, почему звук не звучит?

Я протестировал, чтобы увидеть, если 'AudioQueueEnqueueBuffer()' возвратил любые ошибки. Это не так. Никаких других ошибок в моей игровой программе тоже нет. Данные, возвращенные из чтения из файла, также хороши.

Все нормально, буферы хороши, данные re-enqueued хороши, звук просто отсутствует.

Итак, мой последний тест состоял в том, чтобы медленно увеличить размер буфера, пока я ничего не услышал. Наконец я услышал слабое и спорадическое искажение.

Тогда он пришел ко мне...

Похоже, что проблема заключается в том, что система пытается синхронизировать поток во времени, поэтому, если вы вставляете аудио, и время для аудио, которое вы хотели воспроизвести, прошло, оно просто пропустит эту часть буфера, Если размер буфера становится слишком маленьким, все больше и больше данных отбрасываются или пропускаются до тех пор, пока аудиосистема не будет снова синхронизирована. Это никогда не будет, если размер буфера слишком мал. (Вы можете услышать это как искажение, если вы выбрали размер буфера, который достаточно велик для поддержки непрерывного воспроизведения.)

Если вы думаете об этом, это единственный способ работы звуковой очереди, но это хорошая реализация, когда вы бессовестны, как я, и "открываете", как это работает.

Ответ 2

Я решил снова взглянуть на это и смог его решить, сделав буферы более крупными. Я принял ответ от @RoyGal, так как это было их предложение, но я хотел предоставить фактический код, который работает, так как я предполагаю, что у других есть одна и та же проблема (у вопроса есть несколько фаворитов, которые на данный момент меня нет).

Одна вещь, которую я пробовал, заключалась в увеличении размера пакета:

aData->aDescription.mFramesPerPacket = 512; // or some other number
aData->aDescription.mBytesPerPacket = (
    aData->aDescription.mFramesPerPacket * aData->aDescription.mBytesPerFrame
);

Это НЕ работает: это приводит к ошибке AudioQueuePrime с сообщением AudioConverterNew -50. Я предполагаю, что он хочет mFramesPerPacket быть 1 для PCM.

(Я также попытался установить свойство kAudioQueueProperty_DecodeBufferSizeFrames, которое, похоже, ничего не делало. Не уверен, для чего это.)

Похоже, что решение состоит только в распределении буфера (ов) с указанным размером:

AudioQueueAllocateBuffer(
    aData->aQueue,
    aData->aDescription.mBytesPerPacket * N_BUFFER_PACKETS / N_BUFFERS,
    &aData->aBuffer[i]
);

И размер должен быть достаточно большим. Я нашел магическое число:

mBytesPerPacket * 1024 / N_BUFFERS

(Где N_BUFFERS - количество буферов и должно быть > 1 или воспроизведение прерывисто.)

Вот MCVE, демонстрирующий проблему и решение:

#import <Foundation/Foundation.h>

#import <AudioToolbox/AudioToolbox.h>
#import <AudioToolbox/AudioQueue.h>
#import <AudioToolbox/AudioFile.h>

#define N_BUFFERS 2
#define N_BUFFER_PACKETS 1024

typedef struct AStreamData {
    AudioFileID aFile;
    AudioQueueRef aQueue;
    AudioQueueBufferRef aBuffer[N_BUFFERS];
    AudioStreamBasicDescription aDescription;
    SInt64 pOffset;
    volatile BOOL isRunning;
} AStreamData;

void printASBD(AudioStreamBasicDescription* desc) {
    printf("mSampleRate = %d\n", (int)desc->mSampleRate);
    printf("mBytesPerPacket = %d\n", desc->mBytesPerPacket);
    printf("mFramesPerPacket = %d\n", desc->mFramesPerPacket);
    printf("mBytesPerFrame = %d\n", desc->mBytesPerFrame);
    printf("mChannelsPerFrame = %d\n", desc->mChannelsPerFrame);
    printf("mBitsPerChannel = %d\n", desc->mBitsPerChannel);
}

void bufferCallback(
    void *vData, AudioQueueRef aQueue, AudioQueueBufferRef aBuffer
) {
    AStreamData* aData = (AStreamData*)vData;

    UInt32 bRead = 0;
    UInt32 pRead = (
        aBuffer->mAudioDataBytesCapacity / aData->aDescription.mBytesPerPacket
    );

    OSStatus stat;

    stat = AudioFileReadPackets(
        aData->aFile, false, &bRead, NULL, aData->pOffset, &pRead, aBuffer->mAudioData
    );
    if(stat != 0) {
        printf("AudioFileReadPackets returned %d\n", stat);
    }

    if(pRead == 0) {
        aData->isRunning = NO;
        return;
    }

    aBuffer->mAudioDataByteSize = bRead;

    stat = AudioQueueEnqueueBuffer(aQueue, aBuffer, 0, NULL);
    if(stat != 0) {
        printf("AudioQueueEnqueueBuffer returned %d\n", stat);
    }

    aData->pOffset += pRead;
}

AStreamData* beginPlayback(NSURL* path) {
    static AStreamData* aData;
    aData = malloc(sizeof(AStreamData));

    OSStatus stat;

    stat = AudioFileOpenURL(
        (CFURLRef)path, kAudioFileReadPermission, 0, &aData->aFile
    );
    printf("AudioFileOpenURL returned %d\n", stat);

    UInt32 dSize = 0;

    stat = AudioFileGetPropertyInfo(
        aData->aFile, kAudioFilePropertyDataFormat, &dSize, 0
    );
    printf("AudioFileGetPropertyInfo returned %d\n", stat);

    stat = AudioFileGetProperty(
        aData->aFile, kAudioFilePropertyDataFormat, &dSize, &aData->aDescription
    );
    printf("AudioFileGetProperty returned %d\n", stat);

    printASBD(&aData->aDescription);

    stat = AudioQueueNewOutput(
        &aData->aDescription, bufferCallback, aData, NULL, NULL, 0, &aData->aQueue
    );
    printf("AudioQueueNewOutput returned %d\n", stat);

    aData->pOffset = 0;

    for(int i = 0; i < N_BUFFERS; i++) {
        // change YES to NO for stale playback
        if(YES) {
            stat = AudioQueueAllocateBuffer(
                aData->aQueue,
                aData->aDescription.mBytesPerPacket * N_BUFFER_PACKETS / N_BUFFERS,
                &aData->aBuffer[i]
            );
        } else {
            stat = AudioQueueAllocateBuffer(
                aData->aQueue,
                aData->aDescription.mBytesPerPacket,
                &aData->aBuffer[i]
            );
        }

        printf(
            "AudioQueueAllocateBuffer returned %d for aBuffer[%d] with capacity %d\n",
            stat, i, aData->aBuffer[i]->mAudioDataBytesCapacity
        );

        bufferCallback(aData, aData->aQueue, aData->aBuffer[i]);
    }

    UInt32 numFramesPrepared = 0;
    stat = AudioQueuePrime(aData->aQueue, 0, &numFramesPrepared);
    printf("AudioQueuePrime returned %d with %d frames prepared\n", stat, numFramesPrepared);

    stat = AudioQueueStart(aData->aQueue, NULL);
    printf("AudioQueueStart returned %d\n", stat);

    UInt32 pSize = sizeof(UInt32);
    UInt32 isRunning;
    stat = AudioQueueGetProperty(
        aData->aQueue, kAudioQueueProperty_IsRunning, &isRunning, &pSize
    );
    printf("AudioQueueGetProperty returned %d\n", stat);

    aData->isRunning = !!isRunning;
    return aData;
}

void endPlayback(AStreamData* aData) {
    OSStatus stat = AudioQueueStop(aData->aQueue, NO);
    printf("AudioQueueStop returned %d\n", stat);
}

NSString* getPath() {
    // change NO to YES and enter path to hard code
    if(NO) {
        return @"";
    }

    char input[512];
    printf("Enter file path: ");
    scanf("%[^\n]", input);

    return [[NSString alloc] initWithCString:input encoding:NSASCIIStringEncoding];
}

int main(int argc, const char* argv[]) {
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

    NSURL* path = [NSURL fileURLWithPath:getPath()];
    AStreamData* aData = beginPlayback(path);

    if(aData->isRunning) {
        do {
            printf("Queue is running...\n");
            [NSThread sleepForTimeInterval:1.0];
        } while(aData->isRunning);
        endPlayback(aData);
    } else {
        printf("Playback did not start\n");
    }

    [pool drain];
    return 0;
}