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

Я пытаюсь выполнить простое воспроизведение из функции файла, и, похоже, моя функция обратного вызова никогда не вызывается. На самом деле это не имеет смысла, потому что все OSStatus возвращаются 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 - моя структура пользовательских данных (определенная по типу, чтобы я мог использовать ее как свойство класса), а player - статический экземпляр управляющего класса Objective-C. Если потребуется какой-либо другой код, дайте мне знать. Я немного не в себе. Вывод любого из приведенных здесь чисел дает правильный результат, включая функции в bufferCallback, когда я сам вызываю его в цикле выделения. После этого он просто никогда не вызывается. Метод запуска возвращается, и ничего не происходит.

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

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

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

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


person Radiodef    schedule 17.10.2013    source источник
comment
Понятия не имею, почему это не работает. Но почему вы не используете AudioUnit?   -  person Aliaksandr Andrashuk    schedule 31.10.2013
comment
@AliaksandrAndrashuk А AudioUnits не только для эффектов? Разумно ли их использовать для потоковой передачи из файла?   -  person Radiodef    schedule 31.10.2013
comment
да. Вы можете настроить AUFilePlayer - ›RemoteIO (в AUGraph) для воспроизведения файла.   -  person Aliaksandr Andrashuk    schedule 02.11.2013
comment
попробуйте вручную установить (большой) размер буфера   -  person HaneTV    schedule 04.11.2013


Ответы (2)


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

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

Что касается вашего кода, то на первый взгляд он кажется законным. Однако я отмечу две вещи: 1. Кажется, вы выделяете буферы со СЛИШКОМ МАЛЕНЬКИМ размером буфера. Я заметил, что 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 ничего не воспроизводит с меньшими размерами буфера, я провел тест, чтобы увидеть, вызывается ли мой обратный вызов вообще, даже когда нет звука. Ответ заключается в том, что буферы вызываются все время, пока AudioQueues воспроизводится и ему нужны данные.

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

Так почему не воспроизводится звук?

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

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

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

Потом до меня дошло ...

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

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

person RoyGal    schedule 01.12.2013
comment
кажется, что вы выделяете буферы со СЛИШКОМ МАЛЕНЬКИМ размером буфера Я позволяю AudioFileGetProperty заполнять его. Я видел примеры, которые показывают это, а именно собственный SpeakHere от Apple, поэтому я не понимаю, почему это должно быть проблема. В принципе, ты думаешь, я должен это переделать? Вы проверили возвращенные свойства? ASBD возвращает правильную информацию о файле. Вот что меня озадачивает в искаженном звуке. - person Radiodef; 21.12.2013
comment
Я не могу сказать, что это именно ваша проблема, но я тестировал разные размеры буфера и смог воспроизвести все, от отсутствия звука до искаженного звука в различной степени с очень маленькими буферами, до идеального воспроизведения звука с достаточно большим буфером. Все остальное вроде нормально. Это, конечно, зависит от вашего оборудования. Мое оборудование старое и, вероятно, давится быстрее, чем новые Mac. - person RoyGal; 26.01.2014

Я решил взглянуть на это еще раз и смог решить эту проблему, увеличив буферы. Я принял ответ @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;
}
person Radiodef    schedule 14.02.2014