2011-01-20 2 views
0

Mon application dispose d'un rappel d'enregistrement audio appelé par l'infrastructure RemoteIO AudioUnit. L'AudioUnit appelle le rappel dans un thread différent du thread principal, il n'a donc pas de pool de libération automatique. Dans ce rappel, je fais ce qui suit:Fuite de mémoire dans le fil de rappel audio (iOS)

  • Alloc un tampon pour les échantillons enregistrés. Pour appeler ce tampon, appelez AudioUnitRender.
  • Ouvrez un fichier pour la consignation à l'aide de freopen.
  • Appelez une méthode de traitement audio.
  • En fonction de l'audio, mettez à jour l'interface utilisateur du thread principal en utilisant performSelectorOnMainThread (envoyé au contrôleur de vue).

En outre, j'ai une fonction appelée testFilter() qui fait une analyse comparative, que j'appelle à viewDidLoad du contrôleur de vue, avant la session audio est initialisé, donc avant le rappel audio est appelé pour la première fois. Dans cette fonction, j'alloue un buffer (en utilisant malloc/free) et appelle la même méthode de traitement audio que j'ai mentionnée ci-dessus.

Maintenant, le problème est (sur l'appareil, et non le simulateur):

  • Si je commente l'appel à testFilter(), je ne reçois pas de messages associés-fuites de mémoire.
  • Si je n'appelle testFilter(), je commence à avoir un tas de messages à partir de la fonction de rappel audio (Les 5 premiers messages sont mes journaux de la méthode testFilter()):

2011-01-20 23: 05: 10,358 TimeKeeper [389: 307] tampon d'initialisation ...

2011-01-20 23: 05: 10,693 TimeKeeper [389: 307] fait ...

2011-01-20 23:05 : 10.696 TimeKeeper [389: 307] tampon de traitement ...

2011-01-20 23: 05: 15,772 TimeKeeper [389: 307] fait ...

2011-01-20 23: 05: 15,775 TimeKeeper [389: 307] temps écoulé 5,073843

2011-01-20 23: 05: 16,319 TimeKeeper [389: 660F] * __NSAutoreleaseNoPool(): Object 0x137330 de __NSCFData de classe autoreleased sans piscine en place - juste une fuite

2011-01-20 23:05: 16.327 TimeKeeper [389: 660f] * __NSAutoreleaseNoPool(): Objet 0x1373a0 de la classe __NSCFData auto-libéré sans pool en place - juste fuite

et ainsi de suite. Le rappel a un fil différent, comme on peut le voir dans le journal.

Pourquoi ces avertissements ne se produisent-ils que si j'appelle une fonction appelée et terminée avant même l'initialisation de la session audio? Comment puis-je détecter la fuite?

Annexe

Les méthodes pertinentes:

void testFilter() { 
#if TARGET_IPHONE_SIMULATOR == 0 
    freopen([@"/tmp/console.log" cStringUsingEncoding:NSASCIIStringEncoding],"a",stderr); 
#endif 
    int bufSize = 2048; 
    int numsec=100; 
    OnsetDetector * onsetDetector = [[OnsetDetector alloc] init]; 
    AudioSampleDataType *buffer = (AudioSampleDataType*) malloc (44100*numsec * sizeof(AudioSampleDataType)); // numsec seconds of audio @44100 

    AudioBufferList bufferList; 
    bufferList.mNumberBuffers = 1; 
    bufferList.mBuffers[0].mData = buffer; 
    bufferList.mBuffers[0].mDataByteSize = sizeof (AudioSampleDataType) * bufSize; 
    bufferList.mBuffers[0].mNumberChannels = 1; 

    //--- init buffer 
    NSLog(@"\n\n---***---***---"); 
    NSLog(@"initializing buffer..."); 
    for (int i = 0; i < 44100*numsec; ++i) { 
     *(buffer+i) = (AudioSampleDataType)rand(); 
    } 
    NSLog(@"done..."); 

    NSLog(@"processing buffer..."); 
    CFAbsoluteTime t0 = CFAbsoluteTimeGetCurrent(); 
    for (int i = 0; (i+1)*bufSize < 44100*numsec; ++i) { 
     bufferList.mBuffers[0].mData = buffer + i * bufSize; 
     [onsetDetector process:&bufferList]; 
    } 
    CFAbsoluteTime t1 = CFAbsoluteTimeGetCurrent(); 
    NSLog(@"done..."); 
    NSLog(@"elapsed time %1.6f",(double)(t1-t0)); 
    free(buffer); 
    [onsetDetector release]; 
} 

Et:

OSStatus recordingCallback (void *inRefCon, 
          AudioUnitRenderActionFlags *ioActionFlags, 
          const AudioTimeStamp *inTimeStamp, 
          UInt32 inBusNumber, 
          UInt32 inNumberFrames, 
          AudioBufferList *ioData) { 

    AudioBufferList bufferList; 

    // redundant 
    SInt16 *buffer = (SInt16 *)malloc (sizeof (AudioSampleDataType) * inNumberFrames); 
    bufferList.mNumberBuffers = 1; 
    bufferList.mBuffers[0].mData = buffer; 
    bufferList.mBuffers[0].mDataByteSize = sizeof (AudioSampleDataType) * inNumberFrames; 
    bufferList.mBuffers[0].mNumberChannels = 1; 

    ioData = &bufferList; 

    // Obtain recorded samples 
    OSStatus status; 

    MainViewController *mainViewController = (MainViewController *)inRefCon; 
    AudioManager  *audioManager  = [mainViewController audioManager]; 
    SampleManager  *sampleManager  = [audioManager sampleManager]; 

    status = AudioUnitRender([audioManager audioUnit], 
          ioActionFlags, 
          inTimeStamp, 
          inBusNumber, //1 
          inNumberFrames, 
          ioData); 

#if TARGET_IPHONE_SIMULATOR == 0 
    freopen([@"/tmp/console.log" cStringUsingEncoding:NSASCIIStringEncoding],"a",stdout); 
#endif 

    // send to onset detector 
    SInt32 onset = [[audioManager onsetDetector] process:ioData]; 
    if (onset > 0) { 
     NSLog(@"onset - %ld\n", sampleManager.recCnt + onset); 

     //--- updating the UI - must be done on main thread 
     [mainViewController performSelectorOnMainThread:@selector(tapButtonPressed) withObject:nil waitUntilDone:NO]; 
     [mainViewController performSelectorOnMainThread:@selector(onsetLedOn) withObject:nil waitUntilDone:NO]; 
    } 

    sampleManager.recCnt += inNumberFrames; 

    free(buffer); 
    return noErr; 
} 

Répondre

0

Semble que le problème a été causé par freopen non suivi de fclose. Je passe stderr en tant que 3ème argument à freopen, de sorte que les messages de journal sont redirigés vers un fichier. Si j'appelle fclose à la fin du rappel, aucune erreur de fuite de mémoire n'apparaît.

Certaines questions doivent encore une réponse si:

  • Pourquoi utiliser autorelease piscine n'a pas résolu le problème? Je suppose que c'est parce que freopen est une fonction C, donc pas de messages autorelease sont envoyés de toute façon.
  • La seule situation dans laquelle les messages d'erreur sont jetés est:
    • freopen est utilisé dans une fonction sur le thread principal, sans fclose
    • freopen est utilisé dans un rappel audio sur un thread différent, sans fclose.

C'est, si freopen est utilisé uniquement dans le fil de rappel audio (et non sur le thread principal), apparaissent aucune erreur.

Modifier

Un autre comportement étrange est que même si ne pas utiliser fclose dans les deux fils, mais je passe stdout sur le thread principal et stderr dans le fil de rappel audio, je reçois des messages d'erreur. Je pense que cela a quelque chose à voir avec la façon dont NSLog est appelée quand il y a plus d'un thread. Toute idée à ce sujet est la bienvenue.

+0

Ma conclusion finale de tout ce mal de tête n'utilise pas 'freopen' et' NSLog' du tout, mais pour implémenter ma propre classe de journalisation qui utilisera la sortie de fichier ('fprintf' de C ou une classe IO de fichier Cocoa) –

2

Le message d'erreur "juste fuite" est ce que vous obtenez où votre méthode (ou une autre méthode vos appels de méthode) essaie de autorelease un objet lorsqu'il n'y a pas de pool de libération automatique en place. Sans pool, il n'y a rien à faire pour savoir quels objets doivent être libérés, et donc ils ne font que fuir.

Sur le thread principal, un tel pool est automatiquement créé et géré pour vous, mais sur les threads secondaires, vous devez le faire vous-même.

Ce que vous devez faire est de modifier vos fonctions pour allouer un pool:

void myFunction() { 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 

    // do stuff here 

    [pool release]; 
} 

Et cela fera que les messages d'erreur disparaissent, et corriger les fuites.

+0

Merci. Je sais que les messages d'erreur disparaissent, mais je n'arrive pas à comprendre deux choses: 1. Quel objet tente-t-on d'auto-libérer, et 2. Pourquoi ces messages n'apparaissent que si j'appelle une fonction sur le fil principal, avant même que le fil audio ne soit créé. –

+0

** Mise à jour ** Même si je mets toutes les commandes de rappel dans un pool autorelease (et appelez [drain de la piscine] à la fin), les messages d'erreur se produisent encore. D'autres idées? –