17 октября 2017

Let me Speech SDK

Статья нашего iOS-разработчика Дмитрия о том, как совместить приятное с полезным и применять свои знания технологий без отрыва от изучения иностранного языка.  

Хочу поделиться историей создания одного простого приложения от идеи до выпуска в AppStore, а также рассказать о технологии Speech SDK, которую Apple представила в iOS 10.

Данную статью я решил выпустить под релиз iOS 11, потому что только сейчас можно использовать все возможности Speech SDK, практически не задумываясь о том, установлена ли необходимая версия iOS на целевые устройства. График:

На момент написания статьи лишь на 9% устройств не была установлена iOS 10 и выше.

Итак, позвольте мне рассказать вам историю создания приложения SpeacherApp.

Одним дождливым утром, в прекрасном городе П, один из рядовых iOS-разработчиков пришел в офис. В этот день проводились курсы английского языка, и наш разработчик, чувствуя скорое пришествие своей любимой преподавательницы, поспешил в аудиторию, где эти курсы проводились. Помимо него туда направились и другие разработчики, каждый со своими сильными и слабыми сторонами английского произношения.

В течение всего занятия, собственно, как и на прошлой неделе, наш разработчик ловил себя на мысли, что постоянно путается в произношении. Конечно, преподавательница всегда говорила правильно. Ее яркий и ласкающий слух британский акцент казался чем-то недостижимым. Но каждый из учащихся то и дело произносил ответные фразы по-своему. Кому-то постоянно не давалось сочетание “the”, кто-то подменял буквы, произнося слова. И тут нашему разработчику пришла в голову мысль: а почему бы не создать приложение, которое проверяет его речь и помогает ему правильно поставить произношение? Тут ему и вспомнился Speech SDK.

Идея состояла в следующем: пользователь произносит заданную фразу, а Speech SDK должна преобразовать звук в текст. Если более чем N процентов распознанного текста соответствует изначальной фразе, этап считается пройденным и пользователь переходит на следующий. N по умолчанию равнялась 95%. В дальнейшем, после общения с коллегами-разработчиками, добавился экран, на котором “эталонная” Siri сама воспроизводит заданную фразу.

После нескольких выходных, проведенных за разработкой приложения, была готова первая версия. И сразу отправлена всем коллегам из офиса :)

И результат не заставил себя долго ждать: вот уже у нашего разработчика стопка электронных писем на почте с пожеланиями, а где-то даже требованиями улучшить исходный продукт.

От теории — к практике: вам потребуется XCode 8+, пустое приложение и немного усидчивости.

Для начала нужно подключить необходимые библиотеки, как представлено на скриншоте:

Далее, как и во всех случаях использования системных функций, нам нужно, чтобы пользователь разрешил нашему приложению использовать микрофон и распознавание речи:

// Вызываем эту функцию после согласия пользователя предоставить доступ
private func requestSpeechAccess() {
    SFSpeechRecognizer.requestAuthorization { (status) in
        switch status {
        case .authorized:
             // Переходим на следующий экран
        default: ()
            // Остаемся на экране (невозможно получить доступ) 
       }
    }
}

При этом в вашем .plist файле должны быть 2 (!) дополнительные записи, а именно:

Первая содержит текст, который будет продемонстрирован пользователю при запросе разрешения на использование микрофона. Вторая — для распознавания речи.

Было решено, что если пользователь не предоставил доступ, дальше он не попадет, т.к. работа приложения без этого функционала невозможна. Будьте внимательны, т.к. в приложении нужно при входе постоянно проверять этот статус — пользователь может в настройках отключить его.

Перейдем к коду, который необходим, чтобы преобразовать нашу речь в текст. На мой взгляд, API от Apple могла быть гораздо проще, но она такая, какая есть. Давайте разберемся с ней подробнее:

Объявим необходимые переменные:

 // MARK: - Private Properties
 
// Мастер-Объект который обеспечивает выполнение задач по распознаванию речи.
// О значении входного параметра - см. ниже 

private let speechRecognizer = SFSpeechRecognizer(locale: Locale.init(identifier: "en-US"))
 
// Объект, в который может быть встроен аудиобуфер
// Представляет из себя запрос на распознавание текста
 
private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
 
// Объект, который представляет из себя задачу на распознавание текста,
// согласно входному запрос (recognitionRequest)
 
private var recognitionTask: SFSpeechRecognitionTask?
 
// Используется для того, чтобы получить аудиобуфер, который будет встроен в recognitionRequest
    
private let audioEngine = AVAudioEngine()

Обратите внимание: SFSpeechRecognizer имеет четко заданную локализацию — en-US. Это значит, что при распознавании речи Siri будет основываться на американском произношении. Так как наш программист был фанатом американских сериалов Netflix и изначально создавал приложение для себя, он оставил за собой право захардкодить этот параметр. Если вы фанат Шерлока и Игры престолов — настоятельно рекомендуем задать en-GB.

Большинство разработчиков наверняка имели опыт разработки клиент-серверных приложений.  Можно провести такую аналогию: recognitionRequest — это запрос на сервер, recognitionTask — задача, которая обеспечивает выполнение этого запроса.

После объявления переменных приступим к их инициализации и настройке. Но прежде документация Apple требует выполнить следующие операции:

// Подготовка к работе с AVAudioSession

let audioSession = AVAudioSession.sharedInstance()
do {
    try audioSession.setCategory(AVAudioSessionCategoryRecord)
    try audioSession.setMode(AVAudioSessionModeMeasurement)
    try audioSession.setActive(true, with: .notifyOthersOnDeactivation)
} catch {
    print("audioSession properties weren't set because of an error.")
}

Здесь мы подготавливаем  AVAudioSession к работе в необходимом нам режиме. Если просто запустить приложение без этого кода, может показаться, что никаких проблем не возникло, и он является избыточным, но это не так. Если пользователь разговаривает с кем-то, используя  это устройство, записывает видео с экрана или производит любые действия, которые делают невозможной дальнейшую работу приложения, мы обязаны это должным образом обработать.

// Инициализируем новый запрос
recognitionRequest = SFSpeechAudioBufferRecognitionRequest()

// Обрабатываем ошибку в случае некорректной инициализации
guard let recognitionRequest = recognitionRequest else {
    fatalError("Unable to create an SFSpeechAudioBufferRecognitionRequest object")
}

Инициализируем запрос. Хочу обратить внимание, что инициализатор у SFSpeechAudioBufferRecognitionRequest может вернуть nil, поэтому это также необходимо проверить.

// Получаем доступ к микрофону
 
guard let inputNode = audioEngine.inputNode else {
    fatalError("Audio engine has no input node")
}

Получаем ссылку на объект, из которого далее получим аудиобуфер. inputNode может быть nil, если в системе отсутствует микрофон, будьте осторожны: кто знает, что пользователи делали со своими устройствами =)

 // Получаем фразы в течении всего запроса
recognitionRequest.shouldReportPartialResults = true

Далее настроим запрос — нам нужно знать о частичном результате, чтобы обновлять прогресс на UI.

let recordingFormat = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in
    self.recognitionRequest?.append(buffer)
}

Вот код, который добавит наш аудиобуфер в запрос. Он взят из Apple-документации, и  на первый взгляд показался мне черной магией. Давайте не будем загружать себе голову и оставим это на совести разработчиков Apple.

Итак, объявляем recognitionTask:

recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest,
                                                    resultHandler: { [weak self] (result, error) in
    // см. Пункт 1
    let recognizerString = result?.bestTranscription.formattedString
    // см. Пункт 2
    let conformResult = spc.phrasesService.conform(text: recognizerString, with: self.phrase.text)
    
    //recognizerString - то что произнес пользователь
    //conformResult.progress - соответствие произнесенной фразы заданной (в %)
             
    if conformResult.finished || result?.isFinal == true {
        self?.audioEngine.stop()
        inputNode.removeTap(onBus: 0)

        self?.recognitionRequest = nil
        self?.recognitionTask = nil
        
        if conformResult.progress >= kMinConformTextProgress {
            //Пользователь смог успешно повторить заданную фразу
        } else {
            //Пользователь ошибся в произношении
        }
        
    }
})
  1. Мы получаем лучшую транскрипцию, по мнению Speech SDK. Хочу заметить, что также предоставляется возможность получить доступ к массиву потенциально возможных фраз, которые произнес пользователь. Для нашего случая подходит исключительно первый вариант.
  2. Производим сверку распознанного текста с заданным при помощи специальной функции. Логика данной функции — тема для отдельной статьи. Пока нам достаточно  знать, что она возвращает 2 параметра: произнес ли пользователь фразу необходимой длины и на сколько % фраза соответствует заданной.

Далее, если пользователь закончил произнесение фразы или произнес достаточно для ее распознавания, мы очищаем использованные объекты и принимаем решение, что делать дальше.

И теперь все, что остается, — это запустить audioEngine:

audioEngine.prepare()
do {
    try audioEngine.start()
} catch {
    print("audioEngine couldn't start because of an error.")
}

Этого кода достаточно, чтобы имплементировать ту логику, которая была задумана. Останется лишь, если вам необходимо, перед повторной попыткой воспроизведения вызывать функцию:

func endRecording() {
    if audioEngine.isRunning {
        audioEngine.stop()
        recognitionRequest?.endAudio()
    }
}

Она завершает все запущенные процессы для того, чтобы наш код мог сработать вновь.

Ну вот и все. Более не смею вас загружать практическими аспектами, а лишь подведу итоги:

— в iOS 10 компания Apple добавила мощнейший инструмент для разработчиков, его область применения — безгранична. Представьте себе, что вы сможете одними лишь голосовыми командами полностью управлять приложением. Без касаний.

— Уже сейчас существуют прототипы чат-ботов, которые принимают аудиосообщение и выполняют заданный запрос. Возможно, Apple будет совершенствовать эту технологию, и в дальнейшем можно будет при ее помощи идентифицировать пользователя. Это позволит, например, отправлять деньги другу, выключать свет в комнате и делать многое другое всего лишь одной голосовой командой.

Область применения данной технологии ограничивается лишь вашей фантазией =)

P.S. А приложение, о котором говорилось в статье, было выпущено и доступно по ссылке: https://itunes.apple.com/ru/app/speecherapp/id1259943470?mt=8

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Читайте в нашем блоге

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: