Ошибка 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, который обеспечивает быстрый доступ к данным ввода / вывода).