Как я написал лучшее приложение для изучения иностранных языков с помощью SFSpeechRecognizer (нет)

от автора

Вообще-то, я бэкендер последние лет 20, но недавно остался без работы (и AI тут не причём), решил «замутить» свой «стартап», пока ищу новую работу Java-программиста. А заодно подтянуть новые технологии, поглубже изучить немецкий и английский и немного развеяться…

Писать приложения под iOS было моим хобби последние лет 10, и пару моих приложений до сих пор постоянно висят в топе в Российском AppStore, но это были всё «игрушки», а захотелось сделать что-то взаправду стоящее, и так возникла идея написать лучшее (ни больше ни меньше) приложение для изучения языков с помощью аудирования. Точнее, товарищ подсказал идею. А ещё точнее — идея давно была реализована под Андроид, но аналогов под iOS нет, а очень хотелось. И мне, и товарищу :-). Да и смартфона с андроидом у меня нет и никогда не было, не судите строго, но не люблю я вирусы и глюки.

Идея следующая: берёте любое аудио на любом нужном вам языке, загружаете в приложение, и оно автоматически (можно так же вручную) разбивает аудиофайл на нужные вам сегменты для «шэдоуинга», аудирования, многократного прослушивания и тому подобного. Аналогов в сторе я не нашёл, точнее, что-то отдалённо похожее там есть, но без своих настроек, без выбора своего контента для изучения, без красивой визуализации аудио, короче, без всего того, что нам бы хотелось иметь.

Итак, идея есть, какие технологии использовать? В старых моих приложениях был UIKit, Realm/CoreData, и, сториборды. Не судите строго, я как бэкэндер тогда не знал, что использование сторибордов среди «трушных» айосников считается плохим тоном и плохой приметой. Но теперь-то я решил использовать современные технологии! И выбрал такой стэк: SwiftUI, SwiftData, Speech Framework. Что касается последнего, то он вроде бы доступен ещё с iOS 10, но я решил, что технологии развиваются, и распознавание текста из аудио должно было бы сделать со времени iOS 10 огромный рывок вперёд. Но теперь я не так сильно в этом уверен, и об этом эта моя статья…

Почему не нейросети? Все их используют, это модно, круто, и вообще, скоро они заменят всех нас :-). Возможно, в ближайшем будущем, я прикручу какую-нибудь LLM-ку (об этом ниже), но сразу мне показалось это лишним, раз уж можно использовать нативные iOS-ные библиотеки. В итоге приложение получилось всего 3,5 мегабайт, несмотря на то, что количество исходных файлов уже перевалило за 60 и возможности приложения довольно внушительные.

Итак, идея: берём аудио, разбиваем на куски, с помощью плеера проигрываем, перематываем, повторяем… И причём здесь SFSpeechRecognizer? А притом, что мне захотелось видеть субтитры. К тому же, это — «киллер‑фича», — подумал я и стал «гуглить» у Дипсика, как транскрибировать речь из аудио. А теперь я расскажу, как я реализовал эту функцию, с какими проблемами столкнулся и как их решал (но до сих пор не решил).

Для распознавания мы используем SFSpeechRecognizer из Speech framework. На старте мы формируем список доступных локалей через SFSpeechRecognizer.supportedLocales() и даём пользователю выбрать язык. Перед первым запуском обязательно запрашиваем разрешение: SFSpeechRecognizer.requestAuthorization. Если пользователь отказывает — увы, генерация субтитров недоступна.

Для распознавания нам доступны два режима:
Онлайн — более точный, качественный, но использует серверные модели Apple и требует интернета. Офлайн — работает полностью на устройстве, что важно для конфиденциальности и для мест с плохой связью. Включается установкой флага requiresOnDeviceRecognition = true (доступно на iOS 16+). Не все языки поддерживают офлайн, и для них мы оставляем только онлайн.

Как только режим выбран, можно начинать распознавание. Но не всё так просто. Главная боль: длинные аудио. SFSpeechRecognizer рассчитан на короткие фразы. Отправив ему часовую запись, вы рискуете получить ошибку или результат только для первой минуты. Официальные ограничения не задокументированы, но практика показала:

  • Для онлайн режима комфортный размер фрагмента — около 40 секунд.

  • Для офлайн режима — 10 секунд.

В итоге я решил разрезать аудио на чанки с перекрытием в 2 секунды, чтобы не рубить слова на границах. Делаем это с помощью AVAssetExportSession, каждый фрагмент сохраняем во временный m4a файл, распознаём отдельно, а затем сдвигаем временные метки на начало чанка. После обработки временные файлы удаляются. Да, не очень рационально, но иначе как?

Код экспорта чанка выглядит так:

let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetAppleM4A)exportSession.outputURL = tempURLexportSession.outputFileType = .m4aexportSession.timeRange = CMTimeRange(start: startTime, end: endTime)await exportSession.export()

Для стиля «фразы» мы включаем shouldReportPartialResults = true, чтобы показывать пользователю текст ещё до окончания распознавания. Это реализовано через колбэк recognitionTask(with:), который вызывается многократно. Мы оборачиваем его в withCheckedThrowingContinuation для удобной работы с async/await и добавляем таймаут 120 секунд. Если распознавание затягивается (например, пропал интернет), задача принудительно отменяется.

Далее запускаем постобработку: при стиле «по словам» мы просто удаляем дубликаты субтитров с одинаковыми временными метками. Для стиля «по фразам» логика сложнее: группируем слова, если пауза между ними меньше 0,5 секунды, и объединяем их текст в одну фразу. Попутно убираем идущие подряд одинаковые слова — такое иногда случается при шумном аудио.

Если флаг ddsPunctuation = true (доступен с iOS 16+), система сама расставляет знаки препинания, но офлайн‑режим делает это менее аккуратно.

Результаты распознавания вместе с временными метками сохраняем в SRT файл и цепляем его к «проекту» с аудио и сегментами.

Что в итоге? Я как перфекционист не очень доволен, как распознается аудио. Результат в принципе хорош, но не идеален. Некоторые слова пропускаются, некоторые распознаются не точно. Что же касается пунктуации — тут вообще беда. Скажем так — терпимо. Но хочется ИДЕАЛЬНОГО результата (помните, я же собирался сделать лучшее приложение). Буду рад подсказкам от более опытных iOS‑разработчиков именно по работе с SFSpeechRecognizer.

Да, я знаю, что в iOS 26 появился SpeechAnalyzer и вроде бы он даёт лучший результат и работает с длинными аудио, но очень не хочется ограничивать пользователей iOS 26. На данный момент приложение поддерживает iOS 18+, а если перейти на iOS 26, то уже даже на iPhone 11 и SE приложение работать не будет :-(.

Пока что есть идея: прикрутить бэкенд на Java (или чём‑то более сейчас модным) и дёргать через него, скажем DeepSeek API для улучшения качества распознанного текста. Пробные попытки показали, что и с пунктуацией дипсик справится «на ура», и пропущенные слова вставит и неправильно распознанные заменит. Если у вас есть другие идеи — буду благодарен. Сразу скажу, использование Wisper не рассматриваю по идеологическим мотивам — не хочется гонять аудио по всему интернету, давайте остановимся хотя бы на тексте :-). Да и хочется использовать именно нативные библиотеки iOS. Иначе, зачем они существуют?

ссылка на оригинал статьи https://habr.com/ru/articles/1029188/