2013-10-17 18 views
12

Próbuję wykonać proste odtwarzanie z funkcji pliku i wygląda na to, że moja funkcja wywołania zwrotnego nigdy nie jest wywoływana. To naprawdę nie ma sensu, ponieważ wszystkie OSStatuses wracają 0 i inne liczby również wydają się poprawne (jak pakiety wyjściowe odczytują wskaźnik z AudioFileReadPackets).Funkcja wywołania zwrotnego CoreAudio AudioQueue nigdy nie została wywołana, nie zgłoszono błędów

Oto konfiguracja:

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); 

(Nie pokazano gdzie jestem sprawdzając wartość stat pomiędzy funkcjami, to po prostu wraca normalny).

a funkcja oddzwaniania:

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; 
} 

Gdzie jest moja aStreamData struct dane użytkownika (typedefed więc można go używać jako właściwość klasy), a gracz jest statyczna instancja kontrolnego Objective-C c dziewczyna. Jeśli chcesz otrzymać inny kod, daj nam znać. Trochę jestem na końcu mojego dowcipu. Drukowanie dowolnych liczb tutaj podanych daje prawidłowy wynik, w tym funkcje w funkcji wywołania bufora, gdy sam je nazywam w pętli alokacji. Po prostu nigdy nie zostanie wywołany. Metoda uruchamiania powraca i nic się nie dzieje.

Również anegdotycznie używam urządzenia peryferyjnego (MBox Pro 3) do odtwarzania dźwięku, który CoreAudio uruchamia tylko, gdy ma zamiar wyprowadzić. IE, jeśli uruchomię iTunes lub coś takiego, głośniki pojawiają się słabo i jest dioda LED, która przechodzi od migania do bryły. Urządzenie uruchamia się tak, jak to robi, więc CA zdecydowanie coś robi. (Oczywiście próbowałem go również z wbudowanym dźwiękiem w komputerze Macbookowym).

Czytałem inne rozwiązania problemów, które brzmią podobnie i nie działają. Rzeczy takie jak korzystanie z wielu buforów, które teraz robię i nie wydaje się, by robiły jakąkolwiek różnicę.

W zasadzie zakładam, że robię coś oczywiście nie tak, ale nie wiem, co to może być. Przeczytałem odpowiednią dokumentację, przyjrzałem się dostępnym przykładom kodu i przetrząsałem sieć w poszukiwaniu odpowiedzi. Wygląda na to, że to wszystko, co muszę zrobić, i powinno po prostu pójść.

Co najmniej mogę zrobić coś, co mogę zbadać?

+0

Nie mam pojęcia, dlaczego to nie działa. Ale dlaczego nie używasz AudioUnit? –

+0

@AleksandrAndrashuk Czy AudioUnits to nie tylko efekty? Czy rozsądnie jest używać ich do przesyłania strumieniowego z pliku? – Radiodef

+0

tak. Możesz ustawić AUFilePlayer -> RemoteIO (w AUGraph), aby odtworzyć plik. –

Odpowiedz

2

Moja pierwsza odpowiedź nie była wystarczająco dobra, więc skompilowałem minimalny przykład, który będzie odtwarzał 2-kanałowy, 16-bitowy plik wave.

Główna różnica w stosunku do kodu polega na tym, że wykonałem odsłuchiwanie obiektu dla rozpoczęcia i zakończenia odtwarzania.

Co do kodu, na pierwszy rzut oka wydaje się być uzasadniony. Chciałbym jednak zwrócić uwagę na dwie rzeczy: 1. Wygląda na to, że alokujesz bufory o zbyt małym rozmiarze bufora. Zauważyłem, że AudioQueues nie będą odtwarzane, jeśli bufory są zbyt małe, co wydaje się pasować do twojego problemu. 2. Czy zweryfikowaliście zwrócone właściwości?

Powrót na moim przykładzie kodu:

Wszystko jest trudne kodowane, więc to nie jest dokładnie to dobra praktyka, kodowanie, ale to pokazuje, w jaki sposób można to zrobić.

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 

Wreszcie, chciałbym to rozeznanie zrobiłem dzisiaj przetestować odporność AudioQueues.

Zauważyłem, że jeśli zrobisz zbyt małe bufory AudioQueue, to nie będzie ono w ogóle grać. To sprawiło, że trochę się pobawiłem, żeby zobaczyć, dlaczego nie gra.

Jeśli spróbuję rozmiaru bufora, który może pomieścić tylko 150 próbek, nie otrzymam żadnego dźwięku.

Jeśli spróbuję rozmiaru bufora, który może pomieścić 175 próbek, odtwarza całą piosenkę, ale z Dużym zniekształceniem. 175 oznacza odrobinę mniej niż 4 ms dźwięku.

AudioQueue ciągle prosi o nowe bufory, o ile nadal dostarczasz bufory. Niezależnie od tego, czy AudioQueue rzeczywiście odtwarza bufory, czy nie.

Jeśli podasz bufor o rozmiarze 0, bufor zostanie utracony i zostanie zwrócony błąd kAudioQueueErr_BufferEmpty dla tego żądania kolejkowania kolejek. Nigdy nie zobaczysz, że AudioQueue poprosi Cię o ponowne wypełnienie tego bufora. Jeśli tak się stało w przypadku ostatniej opublikowanej kolejki, AudioQueue przestanie prosić o wypełnienie kolejnych buforów. W takim przypadku nie będzie słychać więcej dźwięku dla tej sesji.

Aby zobaczyć, dlaczego AudioQueues nie odtwarza niczego z mniejszymi rozmiarami bufora, wykonałem test, aby sprawdzić, czy mój oddzwaniany jest w ogóle wywoływany, nawet gdy nie ma dźwięku. Odpowiedź brzmi, że bufory są wywoływane przez cały czas, dopóki AudioQueues gra i potrzebuje danych.

Więc jeśli nadal ładujesz bufory do kolejki, żaden bufor nie zostanie utracony. To się nie zdarza. O ile oczywiście nie ma błędu.

Dlaczego więc nie słychać dźwięku?

Przetestowałem, czy "AudioQueueEnqueueBuffer()" zwrócił żadnych błędów. To nie mialo miejsca. Żadnych innych błędów w mojej rutynie gry. Dane zwrócone z odczytu z pliku również są dobre.

Wszystko jest w normie, bufory są dobre, dane ponownie zgromadzone są dobre, po prostu nie ma dźwięku.

Tak więc moim ostatnim testem było powolne zwiększanie rozmiaru bufora, dopóki nie usłyszałem niczego. W końcu usłyszałem słabe i sporadyczne zniekształcenie.

Potem przyszedł do mnie ...

Wydaje się, że problem leży że system stara się utrzymać strumień zsynchronizowane z czasem więc jeśli enqueue dźwięku, a czas dla audio chciałeś gra minęła, po prostu pominie tę część bufora. Jeśli rozmiar bufora staje się zbyt mały, coraz więcej danych jest pomijanych lub pomijanych, aż system audio zsynchronizuje się ponownie. Które nigdy nie jest, jeśli rozmiar bufora jest zbyt mały. (Możesz usłyszeć to jako zniekształcenie, jeśli wybierzesz rozmiar bufora, który jest wystarczająco duży, aby obsługiwać ciągłe odtwarzanie.)

Jeśli o tym pomyślisz, jest to jedyny sposób, w jaki kolejka audio może działać, ale jest to dobre uświadomienie sobie, kiedy jesteś nieświadomy jak ja i "odkryć", jak to naprawdę działa.

+0

* "wydaje się, że przydzielasz bufory z ZA MAŁO rozmiaru bufora" * Pozwalam, aby AudioFileGetProperty wypełniało to. Widziałem przykłady, które pokazują to, a mianowicie własna SpeakHere firmy Apple, więc Nie rozumiem, dlaczego powinien to być problem. Zasadniczo myślisz, że powinienem to zmienić? * "Czy zweryfikowaliście zwrócone właściwości?" * ASBD zwraca poprawne informacje o pliku. Właśnie to mnie dręczy z powodu zniekształconego dźwięku. – Radiodef

+0

Nie mogę powiedzieć, że to jest dokładnie twój problem, ale testowałem różne rozmiary buforów i byłem w stanie wyprodukować wszystko, od braku dźwięku, do zniekształconego dźwięku w różnym stopniu z naprawdę małymi buforami, do perfekcyjnego odtwarzania dźwięku z odpowiednio dużym buforem . Wszystko inne wydaje się normalne. Jest to oczywiście zależne od sprzętu. Mój sprzęt jest stary i prawdopodobnie dławi się szybciej niż nowsze komputery Mac. – RoyGal

2

Postanowiłem spojrzeć na to jeszcze raz i udało mi się go rozwiązać, zwiększając bufory. Zaakceptowałem odpowiedź @RoyGal, ponieważ była to ich sugestia, ale chciałem podać rzeczywisty kod, który działa, ponieważ myślę, że inni mają ten sam problem (pytanie ma kilka ulubionych, które nie są mnie w tej chwili).

Jedno Próbowałem robił większy rozmiar pakietu:

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

to nie działa: powoduje AudioQueuePrime się niepowodzeniem z komunikatem AudioConverterNew returned -50. Chyba chce mFramesPerPacket być 1 dla PCM.

(Próbowałem również ustawienie właściwości kAudioQueueProperty_DecodeBufferSizeFrames które nie wydają się nic zrobić Nie wiem, co to za.).

Rozwiązaniem wydaje się być jedynie przydzielić bufora (ów) z określonym rozmiarze:

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

Rozmiar musi być wystarczająco duży. Uważam, że magiczna liczba:

mBytesPerPacket * 1024/N_BUFFERS 

(Gdzie N_BUFFERS jest liczbą buforów i powinna być > 1 lub odtwarzanie nie jest płynne.)

Tutaj jest MCVE wykazując problemu i rozwiązanie:

#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; 
} 
Powiązane problemy