2017-09-12 9 views
0

Mon application synthétise l'audio à partir d'une table de recherche. Il lit l'audio avec succès mais se bloque au moment où j'essaie d'arrêter de jouer. La lecture audio n'a besoin de quitter que si elle n'est pas redémarrée. Les conditions requises pour la gestion de l'interruption sont donc fondamentales. J'ai relu le guide de programmation de session audio d'Apple, y compris la section Responding to Interruptions. Cependant la méthode handleAudioSessionInterruption ne semble pas enregistrer une interruption donc il me manque quelque chose.Pourquoi cette session audio ne reconnaît-elle pas une interruption?


EDIT Voir ma réponse. Quand j'ai commencé à travailler sur ce sujet, je ne savais presque rien à propos de NSNotificationCenter, donc je salue toute suggestion d'amélioration. Deux méthodes permettent de configurer la session audio à lire au premier plan.

- (void)setUpAudio 
{ 
    if (_playQueue == NULL) 
    { 
     if ([self setUpAudioSession] == TRUE) 
     { 
      [self setUpPlayQueue]; 
      [self setUpPlayQueueBuffers]; 
     } 
    } 
} 


- (BOOL)setUpAudioSession 
{ 
    BOOL success      = NO; 
    NSError *audioSessionError   = nil; 

    AVAudioSession *session    = [AVAudioSession sharedInstance]; 

    // Set up notifications 

    [[NSNotificationCenter defaultCenter] addObserver:self 
              selector:@selector(handleAudioSessionInterruption:) 
               name:AVAudioSessionInterruptionNotification 
               object:session]; 

    // Set category 

    success        = [session setCategory:AVAudioSessionCategoryPlayback 
                 error:&audioSessionError]; 
    if (!success) 
    { 
     NSLog(@"%@ Error setting category: %@", 
       NSStringFromSelector(_cmd), [audioSessionError localizedDescription]); 

     // Exit early 
     return success; 
    } 

    // Set mode 

    success        = [session setMode:AVAudioSessionModeDefault 
                error:&audioSessionError]; 
    if (!success) 
    { 
     NSLog(@"%@ Error setting mode: %@", 
       NSStringFromSelector(_cmd), [audioSessionError localizedDescription]); 

     // Exit early 
     return success; 
    } 

    // Set some preferred values 

    NSTimeInterval bufferDuration  = .005; // I would prefer a 5ms buffer duration 

    success        = [session setPreferredIOBufferDuration:bufferDuration 
                      error:&audioSessionError]; 
    if (audioSessionError) 
    { 
     NSLog(@"Error %ld, %@ %i", (long)audioSessionError.code, audioSessionError.localizedDescription, success); 
    } 

    double sampleRate     = _audioFormat.mSampleRate; // I would prefer a sample rate of 44.1kHz 

    success        = [session setPreferredSampleRate:sampleRate 
                    error:&audioSessionError]; 
    if (audioSessionError) 
    { 
     NSLog(@"Error %ld, %@ %i", (long)audioSessionError.code, audioSessionError.localizedDescription, success); 
    } 

    success        = [session setActive:YES 
                 error:&audioSessionError]; 
    if (!success) 
    { 
     NSLog(@"%@ Error activating %@", 
       NSStringFromSelector(_cmd), [audioSessionError localizedDescription]); 
    } 

    // Get current values 

    sampleRate       = session.sampleRate; 
    bufferDuration      = session.IOBufferDuration; 
    NSLog(@"Sample Rate:%0.0fHz I/O Buffer Duration:%f", sampleRate, bufferDuration); 

    return success; 
} 

Et voici la méthode qui gère l'interruption lorsque j'appuie sur le bouton d'arrêt. Cependant, il ne répond pas.


EDIT La méthode correcte a besoin block, pas selector. Voir ma réponse.


- (void)handleAudioSessionInterruption:(NSNotification*)notification 
{ 
    if (_playQueue) 
    { 
     NSNumber *interruptionType  = [[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey]; 
     NSNumber *interruptionOption = [[notification userInfo] objectForKey:AVAudioSessionInterruptionOptionKey]; 

     NSLog(@"in-app Audio playback will be stopped by %@ %lu", notification.name, (unsigned long)interruptionType.unsignedIntegerValue); 

     switch (interruptionType.unsignedIntegerValue) 
     { 
      case AVAudioSessionInterruptionTypeBegan: 
      { 
       if (interruptionOption.unsignedIntegerValue == AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation) 
       { 
        NSLog(@"notify other apps that audio is now available"); 
       } 
      } 
       break; 

      default: 
       break; 
     } 
    } 
} 

Répondre

0

Réponse Ma méthode pour gérer AudioSessionInterruption ne souscrivait pas le observer correctement avec . Ceci a été corrigé en ajoutant observer à l'aide block, pas selector.

La solution remplace dépréciée AVAudioSession delegate méthodes AudioBufferPlayer, extrêmement adapté à l'usage lecteur audio initialement développé pour la synthèse audio directe par Matthias Hollejmans. Plusieurs fonctions obsolètes, y compris InterruptionListenerCallback ont été mises à jour par Mario Diana. La solution (ci-dessous) utilise NSNotification permettant aux utilisateurs de quitter AVAudioSession gracieusement en appuyant sur un bouton.

Voici le code approprié.

PlayViewController.m

l'action UIButton effectue un arrêt ordonné de synth, le timer et invalident affiche la notification qui quittera AVAudioSession

- (void)fromEscButton:(UIButton*)button 
{ 
    [self stopConcertClock]; 

    ...       // code for Exit PlayViewController not shown 
} 

- (void)stopConcertClock 
{ 
    [_synthLock lock]; 
    [_synth stopAllNotes]; 
    [_synthLock unlock]; 
    [timer invalidate]; 
    timer = nil; 
    [self postAVAudioSessionInterruptionNotification]; 
    NSLog(@"Esc button pressed or sequence ended. Exit PlayViewController "); 
} 


- (void) postAVAudioSessionInterruptionNotification 
{ 
    [[NSNotificationCenter defaultCenter] 
    postNotificationName:@"AVAudioSessionInterruptionNotification" 
    object:self]; 
} 

le AVAudioSession comprend Initialiser la souscription d'une seule notification d'interruption avant de commencer startAudioPlayer en AudioBufferPlayer

- (id)init 
{ 
    if (self = [super init]) 
    { 
     NSLog(@"PlayViewController starts MotionListener and AudioSession"); 

     [self startAudioSession]; 
    } 
    return self; 
} 


- (void)startAudioSession 
    { 
    // Synth and the AudioBufferPlayer must use the same sample rate. 

    _synthLock = [[NSLock alloc] init]; 

    float sampleRate = 44100.0f; 

    // Initialise synth to fill the audio buffer with audio samples. 

    _synth = [[Synth alloc] initWithSampleRate:sampleRate]; 

    // Initialise the audio buffer. 

    _player = [[AudioBufferPlayer alloc] initWithSampleRate:sampleRate 
                channels:1 
              bitsPerChannel:16 
              packetsPerBuffer:1024]; 
    _player.gain = 0.9f; 

    __block __weak PlayViewController *weakSelf = self; 


    _player.block = ^(AudioQueueBufferRef buffer, AudioStreamBasicDescription audioFormat) 
{ 
    PlayViewController *blockSelf = weakSelf; 

    if (blockSelf != nil) 
    { 
     // Lock access to the synth. This callback runs on an internal Audio Queue thread and we don't 
     // want another thread to change the Synth's state while we're still filling up the audio buffer. 

    [blockSelf -> _synthLock lock]; 

     // Calculate how many packets fit into this buffer. Remember that a packet equals one frame 
     // because we are dealing with uncompressed audio; a frame is a set of left+right samples 
     // for stereo sound, or a single sample for mono sound. Each sample consists of one or more 
     // bytes. So for 16-bit mono sound, each packet is 2 bytes. For stereo it would be 4 bytes. 

    int packetsPerBuffer = buffer -> mAudioDataBytesCapacity/audioFormat.mBytesPerPacket; 

     // Let the Synth write into the buffer. The Synth just knows how to fill up buffers 
     // in a particular format and does not care where they come from. 

    int packetsWritten    = [blockSelf -> _synth fillBuffer:buffer->mAudioData frames:packetsPerBuffer]; 

     // We have to tell the buffer how many bytes we wrote into it. 

    buffer -> mAudioDataByteSize = packetsWritten * audioFormat.mBytesPerPacket; 

    [blockSelf -> _synthLock unlock]; 
    } 
}; 

// Set up notifications 

    [self subscribeForBlockNotification]; 

    [_player startAudioPlayer]; 
} 


- (void)subscribeForBlockNotification 
{ 
    NSNotificationCenter * __weak center = [NSNotificationCenter defaultCenter]; 

    id __block token = [center addObserverForName:@"AVAudioSessionInterruptionNotification" 

             object:nil 
             queue:[NSOperationQueue mainQueue] 
            usingBlock:^(NSNotification *note) { 
             NSLog(@"Received the notification!"); 
             [_player stopAudioPlayer]; 
             [center removeObserver:token]; 
            }]; 
} 

PlayViewController.h

Ce sont des paramètres d'interface pertinents

@interface PlayViewController : UIViewController <EscButtonDelegate> 

{  
    ... 

    // Initialisation of audio player and synth 

    AudioBufferPlayer* player; 
    Synth*    synth; 
    NSLock*   synthLock; 
} 

    ... 

    - (AudioBufferPlayer*)player; 
    - (Synth*)synth; 

@end 

AudioBufferPlayer.m

- (void)stopAudioPlayer 
{ 
    [self stopPlayQueue]; 
    [self tearDownPlayQueue]; 
    [self tearDownAudioSession]; 
} 


- (void)stopPlayQueue 
{ 
    if (_audioPlaybackQueue != NULL) 
    { 
     AudioQueuePause(_audioPlaybackQueue); 
     AudioQueueReset(_audioPlaybackQueue); 
     _playing = NO; 
    } 
} 


- (void)tearDownPlayQueue 
{ 
    AudioQueueDispose(_audioPlaybackQueue, NO); 
    _audioPlaybackQueue = NULL; 
} 


- (BOOL)tearDownAudioSession 
{ 
    NSError *deactivationError = nil; 
    BOOL success = [[AVAudioSession sharedInstance] setActive:NO 
                withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation 
                 error:nil]; 
    if (!success) 
    { 
     NSLog(@"%s AVAudioSession Error: %@", __FUNCTION__, deactivationError); 
    } 
    return success; 
}