Обзор
Очередная конференция — очередные новшества. Судя по настроениям нас ждет отмена клавиатур и устройств ввода. Apple в iOS 10 представила разработчикам возможность работать с речью. Мой коллега Геор Касапиди уже описал возможности Siri в своей статье, а я расскажу о Speech.framework. Рассмотренный в статье материал реализован в демо-приложении what_i_say. На момент написания статьи официальной документации нет, так что будем основываться на том, что рассказал Henry Mason.
Работа с речью полностью завязана на доступе к Интернету, и это неудивительно, так как используется тот же самый движок Siri. Разработчику даётся возможность либо распознавать живую речь, либо уже записанную в файл. Различие в объекте запроса:
SFSpeechURLRecognitionRequest
— используется для распознавания из файла;
SFSpeechAudioBufferRecognitionRequest
— используется для распознания диктовки.
Рассмотрим работу с real-time диктовкой.
Вся работа основывается на принципиальной схеме:
Но так как Speech.framework не содержит специального API для работы с микрофоном, разработчику потребуется задействовать AVAudioEngine.
Авторизация приложения
Первым делом разработчику необходимо добавить два ключа в Info.plist
файл
NSMicrophoneUsageDescription
— для чего нужен доступ к микрофону
NSSpeechRecognitionUsageDescription
— для чего нужен доступ к распознанию речи
это важно, потому как без них будет неминуемый крэш.
Далее нам необходимо обработать статус авторизации:
- (void)handleAuthorizationStatus:(SFSpeechRecognizerAuthorizationStatus)s {
switch (s) {
case SFSpeechRecognizerAuthorizationStatusNotDetermined:
// система еще не запрашивала доступ у пользователя
[self requestAuthorization];
break;
case SFSpeechRecognizerAuthorizationStatusDenied:
// пользователь запретил доступ
[self informDelegateErrorType:(EVASpeechManagerErrorSpeechRecognitionDenied)];
break;
case SFSpeechRecognizerAuthorizationStatusRestricted:
// доступ ограничен
break;
case SFSpeechRecognizerAuthorizationStatusAuthorized: {
// можно работать
}
break;
}
}
На этапе знакомства с Speech.framework я так и не нашел use-case использования статуса SFSpeechRecognizerAuthorizationStatusRestricted
. Я предполагаю, что этот статус планировался под ограниченные права доступа, но пока для “Распознавание речи” нет дополнительных опций. Что ж, дождёмся официальной документации.
При первом запуске приложения после установки распознаватель вернёт статус SFSpeechRecognizerAuthorizationStatusNotDetermined
, поэтому необходимо запросить авторизацию у пользователя:
- (void)requestAuthorization {
[SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) {
[self handleAuthorizationStatus:status];
}];
}
В API нет ни методов, ни нотификаций для отслеживания изменения статуса. Система форсированно перезагрузит приложение, если пользователь изменит состояние авторизации.
Настройка
Для успешной работы по распознаванию речи нам потребуется четыре объекта:
@property (nonatomic, strong) SFSpeechRecognizer *recognizer;
@property (nonatomic, strong) AVAudioEngine *audioEngine;
@property (nonatomic, strong) SFSpeechAudioBufferRecognitionRequest *request;
@property (nonatomic, strong) SFSpeechRecognitionTask *currentTask;
и реализовать протокол:
@interface EVASpeechManager () <SFSpeechRecognitionTaskDelegate>
recognizer
— объект, который будет обрабатывать запросы.
Из важного:
- инициализируется локалью initWithLocale:. Если локаль не поддерживается, то вернется nil. На момент написания статьи поддерживались следующие локали:
ro-RO en-IN he-IL tr-TR en-NZ sv-SE fr-BE it-CH de-CH pl-PL pt-PT uk-UA fi-FI vi-VN ar-SA zh-TW es-ES en-GB yue-CN th-TH en-ID ja-JP en-SA en-AE da-DK fr-FR sk-SK de-AT ms-MY hu-HU ca-ES ko-KR fr-CH nb-NO en-AU el-GR ru-RU zh-CN en-US en-IE nl-BE es-CO pt-BR es-US hr-HR fr-CA zh-HK es-MX id-ID it-IT nl-NL cs-CZ en-ZA es-CL en-PH en-CA en-SG de-DE.
распознаватель не переключает локаль автоматически. Он работает в рамках локали, определенной при инициализации. Т.е. если инициализация была локалью en-US
, а пользователь говорит на другом языке, то распознаватель отработает и вернёт пустой ответ;
- имеет свойство
queue
(по умолчаниюmainQueue
), что даёт возможность обрабатывать callback’и асинхронно. Это позволит обработать достаточно большой текст без задержки.
audioEngine
— объект, который будет связывать входной сигнал с микрофона (диктуемую речь) и выходной сигнал в буфер.
self.audioEngine = [[AVAudioEngine alloc] init];
AVAudioInputNode *node = self.audioEngine.inputNode;
AVAudioFormat *recordingFormat = [node outputFormatForBus:bus];
[node installTapOnBus:0
bufferSize:1024
format:recordingFormat
block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
[self.request appendAudioPCMBuffer:buffer];
}];
request
— запрос на распознавание речи.
Из интересного:
- у запроса есть свойство SFSpeechRecognitionTaskHint, которое может принять одно из четырёх значений:
- SFSpeechRecognitionTaskHintUnspecified
- SFSpeechRecognitionTaskHintDictation
- SFSpeechRecognitionTaskHintSearch
- SFSpeechRecognitionTaskHintConfirmation
по описанию можно предположить, что планируется разделить запросы еще и на типы, т. е. обычная диктовка, диктовка для поиска информации, диктовка для подтверждения чего-либо. Увы, сейчас они нигде не используются, разве что как маркеры. Предполагаю, будет развитие этого звена.
currentTask
— объект задачи на обработку одного запроса.
Из важного:
- свойство
error
. Здесь можно получить ошибку, если задача была провалена; - два свойства
finishing
/cancelled
по которым можно определить была закончена задача или отменена. При отмене задачи система моментально отменяет дальнейшую обработку и распознавание, но разработчик в любом случае может получить ту часть распознанного, что уже обработана. При завершении задачи система вернёт полностью обработанный результат.
Использование
Запуск
После успешной настройки менеджера речи можно приступить к самой работе.
Необходимо создать запрос:
self.request = [[SFSpeechAudioBufferRecognitionRequest alloc] init];
и передать его в распознаватель:
Важно! перед тем, как запрос передать в распознаватель, необходимо активировать audioEngine
для получения аудиопотока с микрофона.
- (void)performRecognize {
[self.audioEngine prepare];
NSError *error = nil;
if ([self.audioEngine startAndReturnError:&error]) {
self.currentTask = [self.recognizer recognitionTaskWithRequest:self.request
delegate:self];
} else {
[self informDelegateError:error];
}
}
Приняв запрос, система вернёт нам объект задачи. Он потребуется позже для остановки процесса.
Остановка
Объект запроса нельзя переиспользовать пока он активен, иначе будет крэш “reason: 'SFSpeechAudioBufferRecognitionRequest cannot be re-used’”
. Поэтому важно по завершении процесса остановить задачу и audioEngine:
- (void)stopRecognize {
if ([self isTaskInProgress]) {
[self.currentTask finish];
[self.audioEngine stop];
}
}
Обработка результата
По окончании задачи результат будет доставлен в методы делегата SFSpeechRecognitionTaskDelegate
:
обработать распознанный текст можно в методе:
- (void)speechRecognitionTask:(SFSpeechRecognitionTask *)task didFinishRecognition:(SFSpeechRecognitionResult *)recognitionResult {
self.buffer = recognitionResult.bestTranscription.formattedString;
}
а ошибки в методе:
- (void)speechRecognitionTask:(SFSpeechRecognitionTask *)task didFinishSuccessfully:(BOOL)successfully {
if (!successfully) {
[self informDelegateError:task.error];
} else {
[self.delegate manager:self didRecognizeText:[self.buffer copy]];
}
}
Ошибки
За время работы с приложением я столкнулся с ошибками:
Error: SessionId=com.siri.cortex.ace.speech.session.event.SpeechSessionId@439e90ed, Message=Timeout waiting for command after 30000 ms
Error: SessionId=com.siri.cortex.ace.speech.session.event.SpeechSessionId@714a717a, Message=Empty recognition
первая ошибка прилетела, когда после небольшой диктовки речь была закончена (пользователь перестал говорить), а распознаватель продолжал работу;
вторая ошибка была, когда распознаватель не получил никакого аудиопотока и процесс был отменен пользователем.
Обе ошибки были пойманы в методе делегата:
- (void)speechRecognitionTask:(SFSpeechRecognitionTask *)task didFinishSuccessfully:(BOOL)successfully {
if (!successfully) {
// здесь требуется обработка ошибок task.error
}
}
Заключение
После первого знакомства с Speech.framework стало понятно, что он ещё “сырой”. Многое необходимо доработать и протестировать. Начало неплохое, есть стремление в сторону искусственного интеллекта и работы без средств ввода информации. В комбинации с IoT, открываются большие возможности по управлению гаджетами, домом, автомобилем. А пока ждём, тренируемся и исследуем дальше.
beststream
Спасибо за материал! Могли бы вы выложить видео работы вашей демо программы?