惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

MyScale Blog
MyScale Blog
F
Fortinet All Blogs
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
D
Docker
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
爱范儿
爱范儿
V
Visual Studio Blog
Last Week in AI
Last Week in AI
WordPress大学
WordPress大学
aimingoo的专栏
aimingoo的专栏
小众软件
小众软件
L
LangChain Blog
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
IT之家
IT之家
P
Proofpoint News Feed
博客园_首页
D
DataBreaches.Net
T
The Blog of Author Tim Ferriss
The GitHub Blog
The GitHub Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Check Point Blog
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Как я написал лучшее приложение для изучения иностранных ...
ferreto · 2026-04-28 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение5 мин

Охват и читатели297

Вообще-то, я бэкендер последние лет 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 = tempURL
exportSession.outputFileType = .m4a
exportSession.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. Иначе, зачем они существуют?