Ошибка AVAudioSession при использовании SFSpeechRecognizer после AVSpeechUtterance

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

[avas] ОШИБКА: AVAudioSession.mm:1049: -[AVAudioSession setActive:withOptions:error:]: отключение аудиосеанса, в котором выполняется ввод / вывод. Все операции ввода-вывода должны быть остановлены или приостановлены до отключения аудиосеанса.

Это работает несколько раз. Не ясно, почему это не работает последовательно.

Я попробовал решения, упомянутые в других сообщениях SO, где упоминается, чтобы проверить, работают ли аудиоплееры. Я добавил эту проверку в речь к текстовой части кода. Он возвращает false (т. Е. Никакой другой аудиоплеер не работает), но все же речь к тексту не начинает слушать речь пользователя. Можете ли вы направить меня на то, что идет не так.

Тестирую на iPhone 6 под управлением iOS 10.3

Ниже приведены фрагменты кода:

TextToSpeech:

- (void) speak:(NSString *) textToSpeak {
    [[AVAudioSession sharedInstance] setActive:NO withOptions:0 error:nil];
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback
      withOptions:AVAudioSessionCategoryOptionDuckOthers error:nil];

    [synthesizer stopSpeakingAtBoundary:AVSpeechBoundaryImmediate];

    AVSpeechUtterance* utterance = [[AVSpeechUtterance new] initWithString:textToSpeak];
    utterance.voice = [AVSpeechSynthesisVoice voiceWithLanguage:locale];
    utterance.rate = (AVSpeechUtteranceMinimumSpeechRate * 1.5 + AVSpeechUtteranceDefaultSpeechRate) / 2.5 * rate * rate;
    utterance.pitchMultiplier = 1.2;
    [synthesizer speakUtterance:utterance];
}

- (void)speechSynthesizer:(AVSpeechSynthesizer*)synthesizer didFinishSpeechUtterance:(AVSpeechUtterance*)utterance {
    //Return success message back to caller

    [[AVAudioSession sharedInstance] setActive:NO withOptions:0 error:nil];
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient
      withOptions: 0 error: nil];
    [[AVAudioSession sharedInstance] setActive:YES withOptions: 0 error:nil];
}

Речь к тексту:

- (void) recordUserSpeech:(NSString *) lang {
    NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:lang];
    self.sfSpeechRecognizer = [[SFSpeechRecognizer alloc] initWithLocale:locale];
    [self.sfSpeechRecognizer setDelegate:self];

    NSLog(@"Step1: ");
    // Cancel the previous task if it's running.
    if ( self.recognitionTask ) {
        NSLog(@"Step2: ");
        [self.recognitionTask cancel];
        self.recognitionTask = nil;
    }

    NSLog(@"Step3: ");
    [self initAudioSession];

    self.recognitionRequest = [[SFSpeechAudioBufferRecognitionRequest alloc] init];
    NSLog(@"Step4: ");

    if (!self.audioEngine.inputNode) {
        NSLog(@"Audio engine has no input node");
    }

    if (!self.recognitionRequest) {
        NSLog(@"Unable to created a SFSpeechAudioBufferRecognitionRequest object");
    }

    self.recognitionTask = [self.sfSpeechRecognizer recognitionTaskWithRequest:self.recognitionRequest resultHandler:^(SFSpeechRecognitionResult *result, NSError *error) {

        bool isFinal= false;

        if (error) {
            [self stopAndRelease];
            NSLog(@"In recognitionTaskWithRequest.. Error code ::: %ld, %@", (long)error.code, error.description);
            [self sendErrorWithMessage:error.localizedFailureReason andCode:error.code];
        }

        if (result) {

            [self sendResults:result.bestTranscription.formattedString];
            isFinal = result.isFinal;
        }

        if (isFinal) {
            NSLog(@"result.isFinal: ");
            [self stopAndRelease];
            //return control to caller
        }
    }];

    NSLog(@"Step5: ");

    AVAudioFormat *recordingFormat = [self.audioEngine.inputNode outputFormatForBus:0];

    [self.audioEngine.inputNode installTapOnBus:0 bufferSize:1024 format:recordingFormat block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
        //NSLog(@"Installing Audio engine: ");
        [self.recognitionRequest appendAudioPCMBuffer:buffer];
    }];

    NSLog(@"Step6: ");

    [self.audioEngine prepare];
    NSLog(@"Step7: ");
    NSError *err;
    [self.audioEngine startAndReturnError:&err];
}
- (void) initAudioSession
{
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    [audioSession setCategory:AVAudioSessionCategoryRecord error:nil];
    [audioSession setMode:AVAudioSessionModeMeasurement error:nil];
    [audioSession setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil];
}

-(void) stopAndRelease
{
    NSLog(@"Invoking SFSpeechRecognizer stopAndRelease: ");
    [self.audioEngine stop];
    [self.recognitionRequest endAudio];
    [self.audioEngine.inputNode removeTapOnBus:0];
    self.recognitionRequest = nil;
    [self.recognitionTask cancel];
    self.recognitionTask = nil;
}

Что касается добавленных журналов, я могу видеть все журналы, пока не будет напечатан "Step7".

При отладке кода в устройстве он последовательно вызывает разрыв в следующих строках (у меня установлены точки прерывания исключения), однако, продолжает продолжать выполнение. Однако это происходит так же и во время нескольких успешных казней.

AVAudioFormat * recordingFormat = [self.audioEngine.inputNode outputFormatForBus: 0];

[self.audioEngine prepare];

1 ответ

Причина в том, что звук не полностью закончился, когда -speechSynthesizer:didFinishSpeechUtterance: был вызван, поэтому вы получаете такую ​​ошибку, пытаясь вызвать setActive:NO, Вы не можете деактивировать AudioSession или измените любые настройки во время ввода-вывода. Обходной путь: подождите несколько мс (как долго читайте ниже), а затем выполните AudioSession дезактивация и прочее.

Несколько слов о завершении воспроизведения аудио.

На первый взгляд это может показаться странным, но я потратил много времени на изучение этой проблемы. Когда вы помещаете последний звуковой блок на выход устройства, у вас есть только приблизительное время, когда оно действительно будет завершено. Посмотрите на AudioSession свойство ioBufferDuration:

Длительность буфера аудио ввода / вывода - это количество секунд для одного цикла ввода / вывода аудио. Например, при длительности буфера ввода / вывода 0,005 с в каждом цикле ввода / вывода аудио:

  • Вы получаете 0,005 с аудио при получении ввода.
  • Вы должны предоставить 0,005 с аудио, если выдает вывод.

Типичная максимальная длительность буфера ввода / вывода составляет 0,93 с (что соответствует 4096 выборочным кадрам при частоте выборки 44,1 кГц). Минимальная длительность буфера ввода / вывода составляет не менее 0,005 с (256 кадров), но может быть ниже в зависимости от используемого оборудования.

Таким образом, мы можем интерпретировать это значение как время воспроизведения одного блока. Но у вас все еще есть небольшая не рассчитанная продолжительность между этой временной шкалой и фактическим завершением воспроизведения звука (аппаратная задержка). Я бы сказал, что вам нужно подождать ioBufferDuration * 1000 + delay мс, чтобы убедиться, что воспроизведение звука завершено (ioBufferDuration * 1000 - потому что это длительность в секундах), где delay это довольно небольшое значение.

Более того, кажется, что даже разработчики Apple также не совсем уверены во времени завершения аудио. Быстрый взгляд на новый класс аудио AVAudioPlayerNode и func scheduleBuffer(_ buffer: AVAudioPCMBuffer, completionHandler: AVFoundation.AVAudioNodeCompletionHandler? = nil):

@param completeHandler вызывается после того, как игрок использовал буфер, или игрок остановлен. может быть ноль.

@discussion Планирует воспроизведение буфера после любых ранее запланированных команд. Возможно, что completeHandler вызывается до начала рендеринга или до полного воспроизведения буфера.

Вы можете прочитать больше об обработке аудио в разделе "Понимание функции обратного вызова аудиоустройства" (AudioUnit является низкоуровневым API, который обеспечивает быстрый доступ к данным ввода / вывода).

Другие вопросы по тегам