Speech.framework в iOS 10

в 10:05, , рубрики: e-legion, iOS 10, ios development, speech framework, speech recognition, Блог компании e-Legion Ltd., разработка под iOS

Speech.framework в iOS 10 - 1

Обзор

Очередная конференция — очередные новшества. Судя по настроениям нас ждет отмена клавиатур и устройств ввода. Apple в iOS 10 представила разработчикам возможность работать с речью. Мой коллега Геор Касапиди уже описал возможности Siri в своей статье, а я расскажу о Speech.framework. Рассмотренный в статье материал реализован в демо-приложении what_i_say. На момент написания статьи официальной документации нет, так что будем основываться на том, что рассказал Henry Mason.

Работа с речью полностью завязана на доступе к Интернету, и это неудивительно, так как используется тот же самый движок Siri. Разработчику даётся возможность либо распознавать живую речь, либо уже записанную в файл. Различие в объекте запроса:

SFSpeechURLRecognitionRequest — используется для распознавания из файла;
SFSpeechAudioBufferRecognitionRequest — используется для распознания диктовки.

Рассмотрим работу с real-time диктовкой.

Вся работа основывается на принципиальной схеме:

Speech.framework в iOS 10 - 2

Но так как Speech.framework не содержит специального API для работы с микрофоном, разработчику потребуется задействовать AVAudioEngine.

Авторизация приложения

Первым делом разработчику необходимо добавить два ключа в Info.plist файл

NSMicrophoneUsageDescription — для чего нужен доступ к микрофону
NSSpeechRecognitionUsageDescription — для чего нужен доступ к распознанию речи

Speech.framework в iOS 10 - 3

это важно, потому как без них будет неминуемый крэш.

Далее нам необходимо обработать статус авторизации:

- (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. Я предполагаю, что этот статус планировался под ограниченные права доступа, но пока для “Распознавание речи” нет дополнительных опций. Что ж, дождёмся официальной документации.

Speech.framework в iOS 10 - 4

При первом запуске приложения после установки распознаватель вернёт статус 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, открываются большие возможности по управлению гаджетами, домом, автомобилем. А пока ждём, тренируемся и исследуем дальше.

Ссылки

Автор: e-Legion Ltd.

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js