Cтандартные библиотеки распознавания речи и озвучки текста в iOS дают массу возможностей. Из доклада VolkovRoman вы узнаете, как за счёт минимального количества кода научить ваше приложение проговаривать текст и кастомизировать озвучку. Рома рассмотрел API распознавания речи, его ограничения и особенности, lifecycle запроса на распознавание и методы работы в офлайн-режиме. Вас ждут примеры UX, обход существующих багов и особенности работы с аудиосессией.
— Всем привет, меня зовут Роман Волков. Сегодня мы поговорим о том, как научить ваше мобильное приложение общаться с вашими пользователями.
Прежде чем мы начнем, коротко обо мне. До iOS-разработки я занимался разработкой интеграционных систем в банковской сфере и разработкой аналитических систем в нефтяной сфере. Я не понаслышке знаю, что такое стандарт PCI DSS и, например, как инженеры понимают, что происходит в скважине во время бурения, только лишь на основе температурных данных.
С 2016 года я занимаюсь iOS-разработкой. У меня есть опыт как фриланса, так и удаленки, опыт участия в запуске нескольких стартапов. В том числе я делал брендовое приложение для компании Rolls Royce.
В 2018 году я присоединился к команде Prisma, развивал приложение Prisma и участвовал в разработке и запуске фоторедактора Lensa. С 2019 года я перешел в Яндекс в качестве iOS-разработчика. С 2020 года я руковожу группой мобильной разработки Яндекс.Переводчика. Приложение Переводчик перестало быть просто приложением для работы с текстом. У нас есть много классных фич, таких как перевод по фото, диалоговый режим, ввод голосом, озвучка и многое другое.
Только начав погружаться в тему работы со звуком в iOS, я не нашел какого-то компактного материала, который включал бы в себя и работу с аудиосессией, и с синтезом, и с распознаванием речи. Именно поэтому я решил сделать этот доклад.
Он будет состоять из четырех частей. Сначала мы поговорим о том, что такое аудиосессия, как с ней правильно работать, как она влияет на работу вашего приложения. Далее перейдем к синтезу речи. Рассмотрим, как можно в несколько строк кода озвучить людей текст прямо на телефоне. Далее мы переключимся на распознавание речи. И в заключение посмотрим, как все эти возможности можно предоставить пользователю в офлайн-режиме и какие у этого особенности.
Вариантов использования озвучки и распознавания речи довольно-таки много. Мое самое любимое — конвертация чужих аудиосообщений в текст. И меня радует, что, например, команда Яндекс.Мессенджера как раз таки сделала такую фичу. Надеюсь, другие мессенджеры подтянутся и сделают это у себя тоже.
Мы плавно переходим к первой части доклада, это AVAudioSession.
Аудиосессия — прослойка между нашим приложением и операционной системой. Точнее — между вашим приложением и железом для работы со звуком: динамиком и микрофоном. В iOS, watchOS и tvOS каждое приложение имеет преднастроенную аудиосессию по умолчанию. Эта преднастройка варьируется от одной ОС к другой.
Если говорить именно про iOS, аудиосессия по умолчанию поддерживает воспроизведение звука, но запрещает любую запись. Если переключатель беззвучного режима выставлен в режим «без звука», то глушатся абсолютно все звуки внутри вашего приложения. И третье: блокировка устройства останавливает проигрывание всех звуков внутри вашего приложения.
Настройка аудиосессии состоит из трех пунктов: это выбор категории, режима и дополнительных опций. Мы рассмотрим каждый из пунктов по отдельности.
Начнем с категории. Категория — это некий набор настроек базового поведения аудиосессии. Категория представляет из себя набор параметров, которые позволяют операционной системе максимально соответствовать, скажем так, названию этой категории. Поэтому Apple рекомендует выбирать для своего приложения категорию, максимально приближенную из доступных. На данный момент в iOS 13 доступно шесть категорий. Есть еще седьмая категория, но она помечена как deprecated, ее не стоит использовать.
В докладе мы рассмотрим три категории: playback, record и playAndRecord. Режим позволяет дополнять возможности установленной категории, так как некоторые режимы доступны только для определенных категорий.
Например, на слайде вы видите режим moviePlayback, и его можно установить только для категории Playback.
Установка режима moviePlayback позволяет аудиосессии автоматически улучшать качество проигрываемого звука для встроенных динамиков и для наушников. В докладе мы будем использовать только режим «по умолчанию». Но я хочу отметить, что если вы будете использовать несовместимую пару категории и режима, то будет использован дефолтный Mode.
Третье — options, точечные настройки работы аудиосессии. Например, можно настроить то, как звук с вашего приложения будет миксоваться со звуком из других приложений, настроить правильную деактивацию аудиосессии так, чтобы другие приложения могли узнать, что ваше приложение закончило работу со звуком.
Сначала мы рассмотрим установку категории для проигрывания, а именно playback. Это одна из категорий, предназначенных только для проигрывания звука. Если она установлена, то активация аудиосессии прерывает другие проигрываемые аудио, например, из других приложений.
Важно еще и то, что аудио будет проигрываться, даже если переключатель беззвучного режима переведен в беззвучный режим.
Также для этой категории доступна опция проигрывания в бэкграунд-состоянии, но для этого у вашего приложения должен быть включен Audio, AirPlay, and Picture in Picture.
Рассмотрим две опции, которые видны на слайде. Первая — mixWithOthers. Если вы активируете аудиосессию с данной опцией, то проигрывание звука внутри вашего приложения будет смиксовано с текущим проигрываемым звуком, например с музыкой, с одним уровнем громкости. Но если вы хотите, чтобы ваш звук превалировал в плане громкости над текущим проигрыванием, вы можете использовать опцию duckOthers. Она понижает громкость звука, который проигрывается в бэкграунде, и возвращает обратно, когда проигрывание звука внутри вашего приложения закончилось.
Например, такое можно наблюдать в навигационных приложениях: для анонса маршрута то, что вы слушаете сейчас, приглушается, проигрывается анонс, и затем все возвращается в первоначальное состояние.
Рассмотрим вариант настройки аудиосессии для распознавания с микрофона. Категория Record заглушает все проигрываемые аудио, пока аудиосессия с этой категорией активна в приложении. Record не может заглушить системные звуки, такие как звонки, будильники — в общем, стандартные звуки, которые имеют более высокий приоритет.
Также вы можете добавить опцию allowBluetoothA2DP, это позволяет использовать гарнитуры типа AirPods для записи звука с микрофона и для проигрывания звука в них. Для этого существует более старая опция, которая звучит просто как allowBluetooth, но с ее использованием сильно падает качество звука.
У нас раньше использовалась старая опция, и были жалобы от пользователей, что их не устраивает качество проигрываемого и записываемого звука внутри приложения. Мы поменяли опцию, все стало лучше.
Если вы хотите использовать одновременно и распознавание, и синтез речи, используйте категорию playAndRecord. Тогда в рамках активированной аудиосессии можно использовать и запись, и проигрывание звука.
Отдельно стоит рассмотреть опцию notifyOthersOnDeactivation. Используется она в методе активации аудиосессии. Почему она так важна?
Если аудиосессия была деактивирована с этой опцией, то другие приложения получат идентификацию AVAudioSessionInterruptionNotification с параметром AVAudioSessionInterruptionTypeEnded с параметром, что прерывание их аудиосессии закончилось и они могут продолжить работу со звуком, начатую до того, как они были прерваны.
Такой сценарий возможен, если вы используете категорию playback в приложении без опции mixWithOthers, потому что в противном случае вы не будете прерывать звук другого приложения, ваше аудио просто будет замиксовано с другим приложением.
Использование этой опции и правильная обработка модификации позволяет предоставить пользователям комфортный user experience при работе с вашим приложением.
На слайде можно увидеть пример, как правильно обрабатывать нотификацию о том, что ваше приложение было прервано другим в рамках аудиосессии, и ситуацию, когда прерывание закончилось. То есть мы подписываемся на определенную нотификацию и, может быть, два типа: когда прерывание только началось и когда оно закончилось.
В первом случае вы можете сохранить состояние, а во втором можете продолжить проигрывание звука, который был прерван другим приложением.
Вот пример, как это может работать:
Видео будет проигрываться с того момента, где демонстрируется пример
В этом примере музыка проигрывалась в другом приложении, а именно в VLC, затем я запустил озвучку внутри нашего приложения. Музыка прервалась, произошло проигрывание синтезированной речи, затем музыка автоматически продолжила проигрываться.
Хочу отметить, что не все приложения правильно обрабатывают ситуацию, когда их звук прерывается. Например, некоторые популярные мессенджеры не возобновляют проигрывание звука.
Подытожим. Мы разобрали принцип работы аудиосессии. Рассмотрели возможности конфигурации аудиосессии под требования ваших приложений и научились комфортно для пользователя активировать и деактивировать аудиосессию.
Идем дальше. Синтез речи.
На слайде условно представлена диаграмма задействованных классов в процессе синтеза речи. Основными классами являются AVSpeechSynthesiser, AVSpeechUtterance и AVSpeechSynthesisVoice с его настройками.
Отдельно отмечу, что есть AVSpeechSynthesizerDelegate, который позволяет вам получать уведомления о жизненном цикле всего запроса. Поскольку озвучивание текста — это проигрывание звука, неявной зависимостью здесь будет рассмотренная ранее AVAudioSession.
Можно сделать запрос на распознавание и без настройки аудиосессии, но для любых продакшен-приложений важно понимать, как ее настраивать. Мы говорили об этом ранее.
Самый коротенький пример того, как можно быстро сделать запрос на синтез речи. Вам нужно создать объект класса AVSpeechUtterance, где вы указываете текст, который хотите озвучить, желаемый голос и язык. Если вы не укажете локаль языка при создании голоса, то будет использоваться локаль вашего телефона по умолчанию. Но о выборе голосов и о том, как с ними работать, мы поговорим в следующих слайдах.
Далее вы создаете объект класса AVSpeechSynthesizer и вызываете метод speak. Всё. После этого текст будет синтезирован и проигран, вы услышите результат.
Но на самом деле, это только начало. У синтеза речи намного больше возможностей, о которых мы сейчас и поговорим.
Первое — можно задать скорость при приеме звука. Скорость задается в виде вещественного числа в диапазоне от нуля до единицы. Фактическая скорость изменяется в значении от нуля до единицы, если вы устанавливаете свойство rate в диапазоне от 0 до 0,5.
Если же вы устанавливаете значение rate в диапазоне от 0,5 до 1, то скорость меняется пропорционально в значениях от 1X до 4X.
Пример того, как можно работать со скоростью.
В AVFoundation существует константа AVSpeechUtteranceDefault, которая на самом деле равняется 0,5, что эквивалентно обычной скорости проигрывания звука.
Также вы можете указать скорость вдвое меньше обычной, вам нужно указать значение 0,25. Если вы укажете 0,75, скорость будет увеличена в 2,5 раза от нормальной. Также для удобства есть константы на минимальную скорость и на максимальную.
Сейчас я проиграю несколько примеров:
Видео будет проигрываться с того момента, где демонстрируется пример
Это был пример того, как Макинтош в первый раз заговорил собственным голосом на одной из презентаций Apple. И это был пример нормальной скорости синтезированной речи.
Это — замедленная в 2 раза.
Это — ускоренная в 2,5 раза.
Отдельно последними строчками я вывел свойства preUtteranceDelay и postUtteranceDelay. Это задержка перед тем, как звук начнет проигрываться, и задержка после того, как он закончил проигрываться. Удобно использовать, когда вы миксуете свое приложение со звуком из других приложений и хотите, чтобы громкость понизилась, прошло какое-то время и вы проиграли свой результат. Потом подождали еще какое-то время, и только после этого громкость в другом приложении вернулась на исходную позицию.
Посмотрим следующий параметр — выбор голоса. Голоса для синтеза речи голоса делятся в основном по локали, языку и качеству. AVFoundation предлагает несколько способов создать или получить объект AVSpeechSynthesisVoice. Первый — по идентификатору голоса. У каждого голоса есть свой уникальный ID, и список всех доступных голосов можно узнать, обратившись к статическому свойству SpeechVoice. Получение этого свойства имеет некоторые особенности, мы поговорим о них далее.
Стоит отметить, что если вы передадите не валидный идентификатор в конструктор, то конструктор вернет «нет».
Второй вариант — получить по коду языка или локали. Также хочу отметить, что Apple говорит, что голоса Siri недоступны, но это не совсем правда: нам удалось получить идентификаторы некоторых голосов, которые используются в Siri на некоторых устройствах. Возможно, это баг.
У голосов бывает два качества — дефолтное и улучшенное. Для некоторых голосов можно скачать улучшенную версию, мы поговорим об этом в последнем разделе, обсудим, как можно докачать необходимые голоса.
Пример того, как можно выбрать конкретный голос. Первый способ — по конкретному идентификатору, второй — по строке, обозначающей код языка, третий — по конкретной локали.
Хочу вам сейчас проиграть два примера озвучивания одного и того же текста разными локалями.
Видео будет проигрываться с того момента, где демонстрируется пример
Второй вариант, мне кажется, ближе к российскому произношению.
Также в iOS 13 появился Gender. И это свойство доступно только на iOS 13 и больше, оно работает только для голосов, которые были добавлены в iOS 13. Поэтому Gender задается в виде enum и имеет три свойства: Female, Male и Unspecified.
В нашем приложении можно выбрать пол голоса, которым будет озвучиваться текст. Для старых голосов мы сами составили список и храним его у нас в приложении. Разделение того, какой голос мы считаем мужским, какой женским, для тех голосов, для которых система возвращает Unspecified.
В iOS 13.1 список голосов может вернуть пустой список при первом обращении. Решение: вы можете перезапрашивать раз в какое-то количество секунд весь этот список. Как только он возвращается не пустой, считаем, что мы наконец-то получили актуальный список голосов.
В последующих версиях iOS этот баг был исправлен, но не удивляйтесь, если встретите такое в своих приложениях.
Интересный момент, на который я натолкнулся, когда исследовал документацию: существует статическое свойство AVSpeechSynthesisVoiceAlexIdentifier. Это очень интересный идентификатор, потому что, во-первых, далеко не на всех устройствах можно с этим идентификатором создать голос. Во-вторых, мне непонятно, почему он находится отдельно. В-третьих, если же все-таки получить голос с этим идентификатором, то этот голос имеет уникальный и отличный от других класс.
При этом исследование хедеров фреймворков ничего полезного и интересного мне недало. Если вы знаете какую-то информацию про этот идентификатор — зачем он нужен, почему появился, — расскажите мне, пожалуйста. Я так и не смог найти ответ на этот вопрос.
Здесь можно наблюдать пример того, как мы сделали в интерфейсе выбор голоса на основе локали, пола и как мы даем возможность указывать скорость озвучки для конкретного языка.
Коротко расскажу про систему знаков для записи транскрипции на основе латинского языка. Когда вы отдаете текст на озвучку, то можете указать произношение конкретных слов внутри него. В iOS это делается через NSAttributedString, с указанием специального ключа. Генерация этого произношения доступна прямо на iOS-устройстве в разделе Accessibility. Но для больших объемов, мне кажется, это очень неудобно, и можно автоматизировать генерацию фонетической транскрипции другими способами.
Например, вот репозиторий, в котором для английского языка есть большой словарь соотношения слова и его произношения.
На слайде — пример того, как можно для одной локали заменить произношение конкретного слова. В данном случае это |t??m??t??|, tomato.
Видео будет проигрываться с того момента, где демонстрируется пример
Сейчас был проигран вариант с установкой атрибута на произношение и без.
Итого мы рассмотрели способы создания запроса на синтез речи. Научились работать с голосами. Рассмотрели обход одного из багов, который вам может встретиться, и посмотрели на фонетическую транскрипцию, как ее можно использовать.
Плавно переходим к распознаванию речи. Оно в iOS представлено в виде фреймворка под названием Speech, и оно позволяет осуществлять распознавание речи прямо на ваших устройствах.
Поддерживается приблизительно 50 языков и диалектов, доступно начиная с iOS 10. Обычно распознавание речи требует подключения к интернету. Но для некоторых устройств и для некоторых языков распознавание может работать в офлайн-режиме. Мы поговорим об этом в четвертой части моего доклада.
Распознавание речи доступно как с микрофона, так и из аудиофайла. Если вы хотите предоставить пользователю возможность распознавать речь с микрофона, то пользователь должен дать разрешение на два permissions. Первое — на доступ к микрофону, второе — на то, что его речь будет передана на сервера Apple для проведения распознавания.
К сожалению, нельзя там, где вы можете использовать только офлайн-распознавание, не запрашивать этот permission. Его нужно запросить в любом случае.
Список взят с сайта Apple. Это те языки и локали, которые доступны для распознавания речи. Но на самом деле это список языков и локалей, доступных для диктовки на стандартной клавиатуре. И API фреймворка Speech под капотом ссылается на реализацию диктовки из стандартной клавиатуры.
Распознавание речи бесплатно для нас как для разработчиков, но оно имеет лимит на использование. Первое — лимит по устройствам и запросам в день. Второе — общий лимит для приложения. И третье — распознать можно максимум одну минуту. Исключение составляет офлайн-режим. В нем вы можете делать распознавание длинных записанных аудиосообщений.
Apple, конечно, не говорит конкретные цифры по лимитам, и как было написано или было сказано на докладе WWDC, нужно быть готовым к обработке ошибок писать им, если вы часто, скажем так, утыкаетесь в эти лимиты. Но у нас такой проблемы нет. Мы для русского языка используем SpeechKit в качестве движка для распознавания речи. А большая часть наших пользователей русскоязычная, поэтому мы так и не столкнулись с лимитами.
Еще обязательно думайте о приватности. Не позволяйте проводить озвучку данных — паролей, данных кредитных карт. Любая sensitive или приватная информация не должна быть доступна для распознавания.
На слайде можно увидеть условную диаграмму задействованных классов в процессе распознавания речи. Аналогично синтезу, работа с распознанием — это работа с аудиожелезом, поэтому здесь тоже явной зависимостью выступает AVAudioSession.
Поддержка. Чтобы получить набор всех поддерживаемых локалей, вам нужно обратиться к страничному свойству supportedLocales. Поддержка определенной локали в целом не гарантирует того, что в данный момент распознавание речи для нее доступно. Например, может требоваться постоянное соединение с серверами Apple.
Поддержка локалей для распознавания соответствует списку локалей для диктовки в клавиатуре на iOS. Вот полный список. Чтобы убедиться, что данная локаль может быть обработана прямо сейчас, вы можете использовать свойство isAvailable.
В распознавании речи на iOS нет приоритета локалей для каждого языка, в отличие от синтеза. Поэтому если из списка всех локалей брать первую локаль конкретного языка, то там может быть какая-нибудь не самая популярная локаль. Поэтому для некоторых языков в Переводчике мы сделали приоритет конкретной локали для конкретного языка.
Например, для английского мы используем en-US. Когда пользователь первый раз пытается что-то распознавать на английском языке, мы используем американскую локаль.
Запрос на распознавание из файла. Здесь все просто. Вам нужно получить и составить ссылку на файл, создать объект SFSpeechRecognizer с указанием локали, которую вы хотите использовать. Проверить, что распознавание доступно в данный момент. Создать SFSpeechURLRecognitionRequest, используя конструкты, в которые вы передаете путь к файлу. И запустить задачу на распознавание.
В итоге вы получите либо ошибку при распознавании, либо результат. У результата есть свойство isFinal, которое обозначает, что этот результат является конечным и его можно использовать далее.
Здесь чуть более сложный пример — запрос на распознавание с микрофона. Чтобы его делать, нам еще нужен объект AVAudioEngine, который отвечает за работу с микрофоном. Не будем вдаваться в детали, как это работает. Вы устанавливаете нужную категорию — либо .record, либо .playRecord. Включаете аудиосессию. Настраиваете AudioEngine и подписываетесь на получение звуковых буферов с микрофона. Вы добавляете их к запросу на распознавание и, когда вы закончили распознавание, можете завершить работу с микрофоном.
Стоит отметить, что свойство shouldReportPartialResults, которое отвечает за выдачу временных результатов распознавания, установлено в true. Давайте посмотрим варианты: как может выглядеть приложение с установленным флагом shouldReportPartialResults и без него.
Видео будет проигрываться с того момента, где демонстрируется пример
В примере слева я оставил реагирование микрофона на звук, на изменение громкости. Там видно, что я что-то говорю. Но пока я не закончу говорить, вы ничего не видите. Проходит достаточно долгое время, пока пользователь получает результат того, что же он надиктовал.
Если же выставить shouldReportPartialResults в true и правильно это обрабатывать, то пользователь будет видеть, что он говорит, по мере своей речи. Это очень удобно и это правильный путь к тому, как нужно делать интерфейс в плане диктовки.
Вот пример того, как мы у себя обрабатываем работу с аудиосессией. Внутри Переводчика мы используем не только работу со звуком, который написали мы, но и другие фреймворки, которые что-то могут делать с аудиосессией.
Мы написали контроллер, который, во-первых, проверяет, что настройки, категории те, которые нам нужны, и во-вторых, не делает того, что постоянно включает и выключает аудиосессию.
Еще до разработки диалогового режима ввод голосом и озвучка у нас сами включали и выключали аудиосессию. Когда мы начали делать диалоговый режим, получилось, что эти включения-выключения добавляют лишнюю задержку между тем, как ты что-то произнес, и получением озвучки.
У запроса на распознавание речи можно указать подсказку — тип распознаваемой речи. Это может быть unspecified, может быть диктовка, поиск или короткое подтверждение. Чаще всего, если ваш пользователь будет говорить что-то длинное, лучше использовать dictation.
Начиная с iOS 13 нам доступна аналитика аудио. На слайде представлены те параметры, которые можно получить в результате распознанной речи. То есть вам в качестве результата придет не только то, что пользователь сказал, но и то, каким голосом он это говорил.
Не будем на этом долго останавливаться. Вот пример того, как можно получить аналитику в результате распознанного текста.
Итого мы изучили возможности фреймворка Speech для распознавания речи, научились давать подсказки для распознавания речи и быстро посмотрели на возможности аналитики.
И последнее по списку, но не по важности: работа в офлайн-режиме. Первое, о чем хочется рассказать, — это список офлайн-языков для синтеза речи. Нигде в документации я не нашел упоминания того, как можно явно скачать голоса для работы в офлайне. Что на докладах, что в документации говорится о том, что эти голоса могут быть докачены, но где — не написано.
Я поискал по системе и нашел, что если зайти в Настройки, в раздел Accessibility, далее «Устный контент» и «Голоса», то вы увидите, во-первых, список языков, для которых это доступно. Во-вторых, перейдя в конкретный язык, можно докачать новые голоса.
И этот список четко совпадает с тем, что возвращает AVSpeechSynthesisVoice.speechVoices внутри приложения. Это означает, что вы можете научить ваших пользователей, что они могут докачать нужные языки, чтобы пользоваться синтезом речи в офлайне.
Список офлайн-языков для распознавания. Нигде в документации он явно не указан, но судя по разным форумам и по тому, с чем мы сталкивались, так выглядит список языков и локалей для них, которые могут работать в офлайне без доступа в интернет.
Стоит отметить, что распознавание в офлайне доступно на устройствах с чипом A9 и старше.
Теперь самое интересное. Список офлайн-языков для распознавания речи. В отличие от синтеза, здесь вообще нет возможности явно скачать себе языки. Если добавить язык в стандартную клавиатуру, для него может быть скачать офлайн-пакет. Но к сожалению, это не детерминировано. Перейдем в Настройки > Основные > Клавиатура > Диктовка. Я, например, добавлял испанский. После этого под «Диктовкой» появляется маленькая подсказка, что диктовка может быть доступна вот для этих языков. Там появился испанский.
Затем я перешел в наше приложение, отключил интернет, и к моей радости в офлайн-режиме распознавание на испанском работало.
К сожалению, на это можно повлиять только косвенно, единственный способ — добавить язык в стандартную клавиатуру. Но это не гарантирует того, что офлайн-пакет для распознавания будет скачан.
В iOS, даже если у вас есть доступ к интернету на телефоне, вы можете зафорсить устройство и делать распознавание речи на нем, если распознавание, конечно же, доступно.
Есть свойство supportsOnDeviceRecognition, оно доступно начиная с iOS 13. Но это свойство работает некорректно, я справа внизу показал скриншот ошибки. Ошибка была исправлена только в 13.2. Свойство всегда возвращает false при первом запросе. Согласно Apple, оно вернет корректное значение спустя несколько секунд.
Причем это свойство может отдавать false, но в то же время установка флага requiresOnDeviceRecognition в true работает успешно. Значит, распознавание полностью работает на устройстве, даже если этот проверочный флаг возвращает false.
Здесь может быть несколько решений. Во-первых, вы можете делать офлайн-распознавание только на iOS 13.2. Во-вторых, можете подобрать какое-то количество секунд для перезапроса этого свойства и обновлять интерфейс для пользователя. И в-третьих, вы можете забить на это свойство: пробовать распознавать голос в офлайне, а в случае ошибки просто показывать ее пользователю.
Мы посмотрели, как можно явным образом скачать пакеты для синтеза речи в офлайне, и нашли способ попробовать заставить iOS скачать офлайн-пакеты для распознавания.
Теперь вы знаете, как быстро добавить в ваши приложения синтез и распознавание речи. У меня всё, спасибо за внимание.
— Всем привет, меня зовут Роман Волков. Сегодня мы поговорим о том, как научить ваше мобильное приложение общаться с вашими пользователями.
Прежде чем мы начнем, коротко обо мне. До iOS-разработки я занимался разработкой интеграционных систем в банковской сфере и разработкой аналитических систем в нефтяной сфере. Я не понаслышке знаю, что такое стандарт PCI DSS и, например, как инженеры понимают, что происходит в скважине во время бурения, только лишь на основе температурных данных.
С 2016 года я занимаюсь iOS-разработкой. У меня есть опыт как фриланса, так и удаленки, опыт участия в запуске нескольких стартапов. В том числе я делал брендовое приложение для компании Rolls Royce.
В 2018 году я присоединился к команде Prisma, развивал приложение Prisma и участвовал в разработке и запуске фоторедактора Lensa. С 2019 года я перешел в Яндекс в качестве iOS-разработчика. С 2020 года я руковожу группой мобильной разработки Яндекс.Переводчика. Приложение Переводчик перестало быть просто приложением для работы с текстом. У нас есть много классных фич, таких как перевод по фото, диалоговый режим, ввод голосом, озвучка и многое другое.
Только начав погружаться в тему работы со звуком в iOS, я не нашел какого-то компактного материала, который включал бы в себя и работу с аудиосессией, и с синтезом, и с распознаванием речи. Именно поэтому я решил сделать этот доклад.
Он будет состоять из четырех частей. Сначала мы поговорим о том, что такое аудиосессия, как с ней правильно работать, как она влияет на работу вашего приложения. Далее перейдем к синтезу речи. Рассмотрим, как можно в несколько строк кода озвучить людей текст прямо на телефоне. Далее мы переключимся на распознавание речи. И в заключение посмотрим, как все эти возможности можно предоставить пользователю в офлайн-режиме и какие у этого особенности.
Вариантов использования озвучки и распознавания речи довольно-таки много. Мое самое любимое — конвертация чужих аудиосообщений в текст. И меня радует, что, например, команда Яндекс.Мессенджера как раз таки сделала такую фичу. Надеюсь, другие мессенджеры подтянутся и сделают это у себя тоже.
Мы плавно переходим к первой части доклада, это AVAudioSession.
Аудиосессия — прослойка между нашим приложением и операционной системой. Точнее — между вашим приложением и железом для работы со звуком: динамиком и микрофоном. В iOS, watchOS и tvOS каждое приложение имеет преднастроенную аудиосессию по умолчанию. Эта преднастройка варьируется от одной ОС к другой.
Если говорить именно про iOS, аудиосессия по умолчанию поддерживает воспроизведение звука, но запрещает любую запись. Если переключатель беззвучного режима выставлен в режим «без звука», то глушатся абсолютно все звуки внутри вашего приложения. И третье: блокировка устройства останавливает проигрывание всех звуков внутри вашего приложения.
Настройка аудиосессии состоит из трех пунктов: это выбор категории, режима и дополнительных опций. Мы рассмотрим каждый из пунктов по отдельности.
Начнем с категории. Категория — это некий набор настроек базового поведения аудиосессии. Категория представляет из себя набор параметров, которые позволяют операционной системе максимально соответствовать, скажем так, названию этой категории. Поэтому Apple рекомендует выбирать для своего приложения категорию, максимально приближенную из доступных. На данный момент в iOS 13 доступно шесть категорий. Есть еще седьмая категория, но она помечена как deprecated, ее не стоит использовать.
В докладе мы рассмотрим три категории: playback, record и playAndRecord. Режим позволяет дополнять возможности установленной категории, так как некоторые режимы доступны только для определенных категорий.
Например, на слайде вы видите режим moviePlayback, и его можно установить только для категории Playback.
Установка режима moviePlayback позволяет аудиосессии автоматически улучшать качество проигрываемого звука для встроенных динамиков и для наушников. В докладе мы будем использовать только режим «по умолчанию». Но я хочу отметить, что если вы будете использовать несовместимую пару категории и режима, то будет использован дефолтный Mode.
Третье — options, точечные настройки работы аудиосессии. Например, можно настроить то, как звук с вашего приложения будет миксоваться со звуком из других приложений, настроить правильную деактивацию аудиосессии так, чтобы другие приложения могли узнать, что ваше приложение закончило работу со звуком.
Сначала мы рассмотрим установку категории для проигрывания, а именно playback. Это одна из категорий, предназначенных только для проигрывания звука. Если она установлена, то активация аудиосессии прерывает другие проигрываемые аудио, например, из других приложений.
Важно еще и то, что аудио будет проигрываться, даже если переключатель беззвучного режима переведен в беззвучный режим.
Также для этой категории доступна опция проигрывания в бэкграунд-состоянии, но для этого у вашего приложения должен быть включен Audio, AirPlay, and Picture in Picture.
Рассмотрим две опции, которые видны на слайде. Первая — mixWithOthers. Если вы активируете аудиосессию с данной опцией, то проигрывание звука внутри вашего приложения будет смиксовано с текущим проигрываемым звуком, например с музыкой, с одним уровнем громкости. Но если вы хотите, чтобы ваш звук превалировал в плане громкости над текущим проигрыванием, вы можете использовать опцию duckOthers. Она понижает громкость звука, который проигрывается в бэкграунде, и возвращает обратно, когда проигрывание звука внутри вашего приложения закончилось.
Например, такое можно наблюдать в навигационных приложениях: для анонса маршрута то, что вы слушаете сейчас, приглушается, проигрывается анонс, и затем все возвращается в первоначальное состояние.
Рассмотрим вариант настройки аудиосессии для распознавания с микрофона. Категория Record заглушает все проигрываемые аудио, пока аудиосессия с этой категорией активна в приложении. Record не может заглушить системные звуки, такие как звонки, будильники — в общем, стандартные звуки, которые имеют более высокий приоритет.
Также вы можете добавить опцию allowBluetoothA2DP, это позволяет использовать гарнитуры типа AirPods для записи звука с микрофона и для проигрывания звука в них. Для этого существует более старая опция, которая звучит просто как allowBluetooth, но с ее использованием сильно падает качество звука.
У нас раньше использовалась старая опция, и были жалобы от пользователей, что их не устраивает качество проигрываемого и записываемого звука внутри приложения. Мы поменяли опцию, все стало лучше.
Если вы хотите использовать одновременно и распознавание, и синтез речи, используйте категорию playAndRecord. Тогда в рамках активированной аудиосессии можно использовать и запись, и проигрывание звука.
Отдельно стоит рассмотреть опцию notifyOthersOnDeactivation. Используется она в методе активации аудиосессии. Почему она так важна?
Если аудиосессия была деактивирована с этой опцией, то другие приложения получат идентификацию AVAudioSessionInterruptionNotification с параметром AVAudioSessionInterruptionTypeEnded с параметром, что прерывание их аудиосессии закончилось и они могут продолжить работу со звуком, начатую до того, как они были прерваны.
Такой сценарий возможен, если вы используете категорию playback в приложении без опции mixWithOthers, потому что в противном случае вы не будете прерывать звук другого приложения, ваше аудио просто будет замиксовано с другим приложением.
Использование этой опции и правильная обработка модификации позволяет предоставить пользователям комфортный user experience при работе с вашим приложением.
На слайде можно увидеть пример, как правильно обрабатывать нотификацию о том, что ваше приложение было прервано другим в рамках аудиосессии, и ситуацию, когда прерывание закончилось. То есть мы подписываемся на определенную нотификацию и, может быть, два типа: когда прерывание только началось и когда оно закончилось.
В первом случае вы можете сохранить состояние, а во втором можете продолжить проигрывание звука, который был прерван другим приложением.
Вот пример, как это может работать:
Видео будет проигрываться с того момента, где демонстрируется пример
В этом примере музыка проигрывалась в другом приложении, а именно в VLC, затем я запустил озвучку внутри нашего приложения. Музыка прервалась, произошло проигрывание синтезированной речи, затем музыка автоматически продолжила проигрываться.
Хочу отметить, что не все приложения правильно обрабатывают ситуацию, когда их звук прерывается. Например, некоторые популярные мессенджеры не возобновляют проигрывание звука.
Подытожим. Мы разобрали принцип работы аудиосессии. Рассмотрели возможности конфигурации аудиосессии под требования ваших приложений и научились комфортно для пользователя активировать и деактивировать аудиосессию.
Идем дальше. Синтез речи.
На слайде условно представлена диаграмма задействованных классов в процессе синтеза речи. Основными классами являются AVSpeechSynthesiser, AVSpeechUtterance и AVSpeechSynthesisVoice с его настройками.
Отдельно отмечу, что есть AVSpeechSynthesizerDelegate, который позволяет вам получать уведомления о жизненном цикле всего запроса. Поскольку озвучивание текста — это проигрывание звука, неявной зависимостью здесь будет рассмотренная ранее AVAudioSession.
Можно сделать запрос на распознавание и без настройки аудиосессии, но для любых продакшен-приложений важно понимать, как ее настраивать. Мы говорили об этом ранее.
Самый коротенький пример того, как можно быстро сделать запрос на синтез речи. Вам нужно создать объект класса AVSpeechUtterance, где вы указываете текст, который хотите озвучить, желаемый голос и язык. Если вы не укажете локаль языка при создании голоса, то будет использоваться локаль вашего телефона по умолчанию. Но о выборе голосов и о том, как с ними работать, мы поговорим в следующих слайдах.
Далее вы создаете объект класса AVSpeechSynthesizer и вызываете метод speak. Всё. После этого текст будет синтезирован и проигран, вы услышите результат.
Но на самом деле, это только начало. У синтеза речи намного больше возможностей, о которых мы сейчас и поговорим.
Первое — можно задать скорость при приеме звука. Скорость задается в виде вещественного числа в диапазоне от нуля до единицы. Фактическая скорость изменяется в значении от нуля до единицы, если вы устанавливаете свойство rate в диапазоне от 0 до 0,5.
Если же вы устанавливаете значение rate в диапазоне от 0,5 до 1, то скорость меняется пропорционально в значениях от 1X до 4X.
Пример того, как можно работать со скоростью.
В AVFoundation существует константа AVSpeechUtteranceDefault, которая на самом деле равняется 0,5, что эквивалентно обычной скорости проигрывания звука.
Также вы можете указать скорость вдвое меньше обычной, вам нужно указать значение 0,25. Если вы укажете 0,75, скорость будет увеличена в 2,5 раза от нормальной. Также для удобства есть константы на минимальную скорость и на максимальную.
Сейчас я проиграю несколько примеров:
Видео будет проигрываться с того момента, где демонстрируется пример
Это был пример того, как Макинтош в первый раз заговорил собственным голосом на одной из презентаций Apple. И это был пример нормальной скорости синтезированной речи.
Это — замедленная в 2 раза.
Это — ускоренная в 2,5 раза.
Отдельно последними строчками я вывел свойства preUtteranceDelay и postUtteranceDelay. Это задержка перед тем, как звук начнет проигрываться, и задержка после того, как он закончил проигрываться. Удобно использовать, когда вы миксуете свое приложение со звуком из других приложений и хотите, чтобы громкость понизилась, прошло какое-то время и вы проиграли свой результат. Потом подождали еще какое-то время, и только после этого громкость в другом приложении вернулась на исходную позицию.
Посмотрим следующий параметр — выбор голоса. Голоса для синтеза речи голоса делятся в основном по локали, языку и качеству. AVFoundation предлагает несколько способов создать или получить объект AVSpeechSynthesisVoice. Первый — по идентификатору голоса. У каждого голоса есть свой уникальный ID, и список всех доступных голосов можно узнать, обратившись к статическому свойству SpeechVoice. Получение этого свойства имеет некоторые особенности, мы поговорим о них далее.
Стоит отметить, что если вы передадите не валидный идентификатор в конструктор, то конструктор вернет «нет».
Второй вариант — получить по коду языка или локали. Также хочу отметить, что Apple говорит, что голоса Siri недоступны, но это не совсем правда: нам удалось получить идентификаторы некоторых голосов, которые используются в Siri на некоторых устройствах. Возможно, это баг.
У голосов бывает два качества — дефолтное и улучшенное. Для некоторых голосов можно скачать улучшенную версию, мы поговорим об этом в последнем разделе, обсудим, как можно докачать необходимые голоса.
Пример того, как можно выбрать конкретный голос. Первый способ — по конкретному идентификатору, второй — по строке, обозначающей код языка, третий — по конкретной локали.
Хочу вам сейчас проиграть два примера озвучивания одного и того же текста разными локалями.
Видео будет проигрываться с того момента, где демонстрируется пример
Второй вариант, мне кажется, ближе к российскому произношению.
Также в iOS 13 появился Gender. И это свойство доступно только на iOS 13 и больше, оно работает только для голосов, которые были добавлены в iOS 13. Поэтому Gender задается в виде enum и имеет три свойства: Female, Male и Unspecified.
В нашем приложении можно выбрать пол голоса, которым будет озвучиваться текст. Для старых голосов мы сами составили список и храним его у нас в приложении. Разделение того, какой голос мы считаем мужским, какой женским, для тех голосов, для которых система возвращает Unspecified.
В iOS 13.1 список голосов может вернуть пустой список при первом обращении. Решение: вы можете перезапрашивать раз в какое-то количество секунд весь этот список. Как только он возвращается не пустой, считаем, что мы наконец-то получили актуальный список голосов.
В последующих версиях iOS этот баг был исправлен, но не удивляйтесь, если встретите такое в своих приложениях.
Интересный момент, на который я натолкнулся, когда исследовал документацию: существует статическое свойство AVSpeechSynthesisVoiceAlexIdentifier. Это очень интересный идентификатор, потому что, во-первых, далеко не на всех устройствах можно с этим идентификатором создать голос. Во-вторых, мне непонятно, почему он находится отдельно. В-третьих, если же все-таки получить голос с этим идентификатором, то этот голос имеет уникальный и отличный от других класс.
При этом исследование хедеров фреймворков ничего полезного и интересного мне недало. Если вы знаете какую-то информацию про этот идентификатор — зачем он нужен, почему появился, — расскажите мне, пожалуйста. Я так и не смог найти ответ на этот вопрос.
Здесь можно наблюдать пример того, как мы сделали в интерфейсе выбор голоса на основе локали, пола и как мы даем возможность указывать скорость озвучки для конкретного языка.
Коротко расскажу про систему знаков для записи транскрипции на основе латинского языка. Когда вы отдаете текст на озвучку, то можете указать произношение конкретных слов внутри него. В iOS это делается через NSAttributedString, с указанием специального ключа. Генерация этого произношения доступна прямо на iOS-устройстве в разделе Accessibility. Но для больших объемов, мне кажется, это очень неудобно, и можно автоматизировать генерацию фонетической транскрипции другими способами.
Например, вот репозиторий, в котором для английского языка есть большой словарь соотношения слова и его произношения.
На слайде — пример того, как можно для одной локали заменить произношение конкретного слова. В данном случае это |t??m??t??|, tomato.
Видео будет проигрываться с того момента, где демонстрируется пример
Сейчас был проигран вариант с установкой атрибута на произношение и без.
Итого мы рассмотрели способы создания запроса на синтез речи. Научились работать с голосами. Рассмотрели обход одного из багов, который вам может встретиться, и посмотрели на фонетическую транскрипцию, как ее можно использовать.
Плавно переходим к распознаванию речи. Оно в iOS представлено в виде фреймворка под названием Speech, и оно позволяет осуществлять распознавание речи прямо на ваших устройствах.
Поддерживается приблизительно 50 языков и диалектов, доступно начиная с iOS 10. Обычно распознавание речи требует подключения к интернету. Но для некоторых устройств и для некоторых языков распознавание может работать в офлайн-режиме. Мы поговорим об этом в четвертой части моего доклада.
Распознавание речи доступно как с микрофона, так и из аудиофайла. Если вы хотите предоставить пользователю возможность распознавать речь с микрофона, то пользователь должен дать разрешение на два permissions. Первое — на доступ к микрофону, второе — на то, что его речь будет передана на сервера Apple для проведения распознавания.
К сожалению, нельзя там, где вы можете использовать только офлайн-распознавание, не запрашивать этот permission. Его нужно запросить в любом случае.
Список взят с сайта Apple. Это те языки и локали, которые доступны для распознавания речи. Но на самом деле это список языков и локалей, доступных для диктовки на стандартной клавиатуре. И API фреймворка Speech под капотом ссылается на реализацию диктовки из стандартной клавиатуры.
Распознавание речи бесплатно для нас как для разработчиков, но оно имеет лимит на использование. Первое — лимит по устройствам и запросам в день. Второе — общий лимит для приложения. И третье — распознать можно максимум одну минуту. Исключение составляет офлайн-режим. В нем вы можете делать распознавание длинных записанных аудиосообщений.
Apple, конечно, не говорит конкретные цифры по лимитам, и как было написано или было сказано на докладе WWDC, нужно быть готовым к обработке ошибок писать им, если вы часто, скажем так, утыкаетесь в эти лимиты. Но у нас такой проблемы нет. Мы для русского языка используем SpeechKit в качестве движка для распознавания речи. А большая часть наших пользователей русскоязычная, поэтому мы так и не столкнулись с лимитами.
Еще обязательно думайте о приватности. Не позволяйте проводить озвучку данных — паролей, данных кредитных карт. Любая sensitive или приватная информация не должна быть доступна для распознавания.
На слайде можно увидеть условную диаграмму задействованных классов в процессе распознавания речи. Аналогично синтезу, работа с распознанием — это работа с аудиожелезом, поэтому здесь тоже явной зависимостью выступает AVAudioSession.
Поддержка. Чтобы получить набор всех поддерживаемых локалей, вам нужно обратиться к страничному свойству supportedLocales. Поддержка определенной локали в целом не гарантирует того, что в данный момент распознавание речи для нее доступно. Например, может требоваться постоянное соединение с серверами Apple.
Поддержка локалей для распознавания соответствует списку локалей для диктовки в клавиатуре на iOS. Вот полный список. Чтобы убедиться, что данная локаль может быть обработана прямо сейчас, вы можете использовать свойство isAvailable.
В распознавании речи на iOS нет приоритета локалей для каждого языка, в отличие от синтеза. Поэтому если из списка всех локалей брать первую локаль конкретного языка, то там может быть какая-нибудь не самая популярная локаль. Поэтому для некоторых языков в Переводчике мы сделали приоритет конкретной локали для конкретного языка.
Например, для английского мы используем en-US. Когда пользователь первый раз пытается что-то распознавать на английском языке, мы используем американскую локаль.
Запрос на распознавание из файла. Здесь все просто. Вам нужно получить и составить ссылку на файл, создать объект SFSpeechRecognizer с указанием локали, которую вы хотите использовать. Проверить, что распознавание доступно в данный момент. Создать SFSpeechURLRecognitionRequest, используя конструкты, в которые вы передаете путь к файлу. И запустить задачу на распознавание.
В итоге вы получите либо ошибку при распознавании, либо результат. У результата есть свойство isFinal, которое обозначает, что этот результат является конечным и его можно использовать далее.
Здесь чуть более сложный пример — запрос на распознавание с микрофона. Чтобы его делать, нам еще нужен объект AVAudioEngine, который отвечает за работу с микрофоном. Не будем вдаваться в детали, как это работает. Вы устанавливаете нужную категорию — либо .record, либо .playRecord. Включаете аудиосессию. Настраиваете AudioEngine и подписываетесь на получение звуковых буферов с микрофона. Вы добавляете их к запросу на распознавание и, когда вы закончили распознавание, можете завершить работу с микрофоном.
Стоит отметить, что свойство shouldReportPartialResults, которое отвечает за выдачу временных результатов распознавания, установлено в true. Давайте посмотрим варианты: как может выглядеть приложение с установленным флагом shouldReportPartialResults и без него.
Видео будет проигрываться с того момента, где демонстрируется пример
В примере слева я оставил реагирование микрофона на звук, на изменение громкости. Там видно, что я что-то говорю. Но пока я не закончу говорить, вы ничего не видите. Проходит достаточно долгое время, пока пользователь получает результат того, что же он надиктовал.
Если же выставить shouldReportPartialResults в true и правильно это обрабатывать, то пользователь будет видеть, что он говорит, по мере своей речи. Это очень удобно и это правильный путь к тому, как нужно делать интерфейс в плане диктовки.
Вот пример того, как мы у себя обрабатываем работу с аудиосессией. Внутри Переводчика мы используем не только работу со звуком, который написали мы, но и другие фреймворки, которые что-то могут делать с аудиосессией.
Мы написали контроллер, который, во-первых, проверяет, что настройки, категории те, которые нам нужны, и во-вторых, не делает того, что постоянно включает и выключает аудиосессию.
Еще до разработки диалогового режима ввод голосом и озвучка у нас сами включали и выключали аудиосессию. Когда мы начали делать диалоговый режим, получилось, что эти включения-выключения добавляют лишнюю задержку между тем, как ты что-то произнес, и получением озвучки.
У запроса на распознавание речи можно указать подсказку — тип распознаваемой речи. Это может быть unspecified, может быть диктовка, поиск или короткое подтверждение. Чаще всего, если ваш пользователь будет говорить что-то длинное, лучше использовать dictation.
Начиная с iOS 13 нам доступна аналитика аудио. На слайде представлены те параметры, которые можно получить в результате распознанной речи. То есть вам в качестве результата придет не только то, что пользователь сказал, но и то, каким голосом он это говорил.
Не будем на этом долго останавливаться. Вот пример того, как можно получить аналитику в результате распознанного текста.
Итого мы изучили возможности фреймворка Speech для распознавания речи, научились давать подсказки для распознавания речи и быстро посмотрели на возможности аналитики.
И последнее по списку, но не по важности: работа в офлайн-режиме. Первое, о чем хочется рассказать, — это список офлайн-языков для синтеза речи. Нигде в документации я не нашел упоминания того, как можно явно скачать голоса для работы в офлайне. Что на докладах, что в документации говорится о том, что эти голоса могут быть докачены, но где — не написано.
Я поискал по системе и нашел, что если зайти в Настройки, в раздел Accessibility, далее «Устный контент» и «Голоса», то вы увидите, во-первых, список языков, для которых это доступно. Во-вторых, перейдя в конкретный язык, можно докачать новые голоса.
И этот список четко совпадает с тем, что возвращает AVSpeechSynthesisVoice.speechVoices внутри приложения. Это означает, что вы можете научить ваших пользователей, что они могут докачать нужные языки, чтобы пользоваться синтезом речи в офлайне.
Список офлайн-языков для распознавания. Нигде в документации он явно не указан, но судя по разным форумам и по тому, с чем мы сталкивались, так выглядит список языков и локалей для них, которые могут работать в офлайне без доступа в интернет.
Стоит отметить, что распознавание в офлайне доступно на устройствах с чипом A9 и старше.
Теперь самое интересное. Список офлайн-языков для распознавания речи. В отличие от синтеза, здесь вообще нет возможности явно скачать себе языки. Если добавить язык в стандартную клавиатуру, для него может быть скачать офлайн-пакет. Но к сожалению, это не детерминировано. Перейдем в Настройки > Основные > Клавиатура > Диктовка. Я, например, добавлял испанский. После этого под «Диктовкой» появляется маленькая подсказка, что диктовка может быть доступна вот для этих языков. Там появился испанский.
Затем я перешел в наше приложение, отключил интернет, и к моей радости в офлайн-режиме распознавание на испанском работало.
К сожалению, на это можно повлиять только косвенно, единственный способ — добавить язык в стандартную клавиатуру. Но это не гарантирует того, что офлайн-пакет для распознавания будет скачан.
В iOS, даже если у вас есть доступ к интернету на телефоне, вы можете зафорсить устройство и делать распознавание речи на нем, если распознавание, конечно же, доступно.
Есть свойство supportsOnDeviceRecognition, оно доступно начиная с iOS 13. Но это свойство работает некорректно, я справа внизу показал скриншот ошибки. Ошибка была исправлена только в 13.2. Свойство всегда возвращает false при первом запросе. Согласно Apple, оно вернет корректное значение спустя несколько секунд.
Причем это свойство может отдавать false, но в то же время установка флага requiresOnDeviceRecognition в true работает успешно. Значит, распознавание полностью работает на устройстве, даже если этот проверочный флаг возвращает false.
Здесь может быть несколько решений. Во-первых, вы можете делать офлайн-распознавание только на iOS 13.2. Во-вторых, можете подобрать какое-то количество секунд для перезапроса этого свойства и обновлять интерфейс для пользователя. И в-третьих, вы можете забить на это свойство: пробовать распознавать голос в офлайне, а в случае ошибки просто показывать ее пользователю.
Мы посмотрели, как можно явным образом скачать пакеты для синтеза речи в офлайне, и нашли способ попробовать заставить iOS скачать офлайн-пакеты для распознавания.
Теперь вы знаете, как быстро добавить в ваши приложения синтез и распознавание речи. У меня всё, спасибо за внимание.