Реализация наложения фильтров на видео в мобильном приложении
Разработка видеофильтров для мобильных приложений — это всегда баланс между качеством изображения и производительностью. Однажды клиент пришёл с задачей: наложить LUT-фильтры на видео 4K 60fps в реальном времени. На iPhone 14 Pro первые тесты давали 12 fps — пропуск каждого второго кадра. Проблема была в создании CIContext на каждый кадр. После оптимизации utilisation GPU снизилась до 30%, fps стабилизировались на 60. Мы используем GPU-шейдеры для обработки кадров. На iOS — Swift с Metal, на Android — GLSL или Vulkan. Для кросс-платформенных проектов — нативные модули через платформенные каналы.
Два режима: предпросмотр и экспорт
Предпросмотр в реальном времени (во время воспроизведения) требует обработки на GPU без записи результата в файл. Экспорт — финальная запись с применённым фильтром, может занять секунды, но результат должен быть покадрово точным.
Почему важен один CIContext на всё приложение?
Создание нового CIContext на каждый кадр — смерть производительности. На iPhone 13 это даёт 3–4 fps вместо 30. Контекст создаётся один раз и переиспользуется. CIContext с Metal бэкендом обязателен — CIContext(mtlDevice: MTLCreateSystemDefaultDevice()!). Без явного указания на некоторых устройствах используется CPU-рендеринг, и видео зависает.
Как избежать падения fps при экспорте?
CVPixelBuffer из renderContext.newPixelBuffer() должен быть возвращён сразу после finish(withComposedVideoFrame:). Удержание ссылки ведёт к утечке пула буферов и крэшу с kCVReturnInvalidArgument. На Android: GPUImageFilter не thread-safe — нельзя применять к одному экземпляру из нескольких потоков одновременно.
Сравнение платформ
| Платформа | Предпросмотр | Экспорт | Инструменты |
|---|---|---|---|
| iOS | AVPlayer + Metal | AVVideoComposition + CIContext | Swift, Metal, Core Image |
| Android | GPUImageView + GLShader | media3 Transformer + GlEffect | Kotlin, GPUImage, media3 |
| Flutter | PlatformView (нативный код) | MethodChannel + native filter | Dart + Swift/Kotlin |
Производительность: CPU против GPU
| Подход | Пропускная способность (60fps 1080p) | Задержка |
|---|---|---|
| CPU (vImage) | 12–15 fps | ~70 мс |
| GPU (Metal/GL) | 60 fps | ~16 мс |
iOS: AVVideoComposition + Core Image
AVVideoCompositionCoreAnimationTool для статичных оверлеев — подходит для текста и логотипов. Для пиксельных фильтров — AVVideoComposition с кастомным AVVideoCompositing:
class FilterCompositor: NSObject, AVVideoCompositing { func startRequest(_ asyncVideoCompositionRequest: AVAsynchronousVideoCompositionRequest) { guard let frame = asyncVideoCompositionRequest.sourceFrame(byTrackID: trackID) else { return } let ciImage = CIImage(cvPixelBuffer: frame) let filtered = applyFilter(ciImage) let output = asyncVideoCompositionRequest.renderContext.newPixelBuffer()! ciContext.render(filtered, to: output) asyncVideoCompositionRequest.finish(withComposedVideoFrame: output) } } Для LUT-фильтров — CIColorCubeWithColorSpace с 64×64×64 таблицей. Подробнее о Core Image.
Android: GPUImage и media3 Effects
Предпросмотр в реальном времени — GPUImageView с кастомным GPUImageFilter. Шейдер пишется на GLSL:
precision mediump float; uniform sampler2D inputImageTexture; varying vec2 textureCoordinate; void main() { vec4 color = texture2D(inputImageTexture, textureCoordinate); float luma = dot(color.rgb, vec3(0.299, 0.587, 0.114)); gl_FragColor = vec4(mix(vec3(luma), color.rgb, 1.3), color.a); } Для экспорта с фильтром — media3 Transformer с GlEffect. MatrixTextureProcessor принимает шейдер и применяет его к каждому кадру при перекодировании.
Flutter. Полноценные видеофильтры в реальном времени требуют платформенного кода. Нативные плагины с MethodChannel — единственный рабочий подход для production-приложений.
Применение фильтра к записи с камеры в реальном времени
Ещё более требовательный сценарий — фильтр при съёмке. На iOS: AVCaptureSession + AVCaptureVideoDataOutput → SampleBufferDelegate → Metal-шейдер → рендер в MTKView. Задержка цепочки должна быть < 16 мс для 60fps. kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_420YpCbCr8BiPlanarFullRange — формат YUV из камеры быстрее конвертируется в текстуру Metal, чем BGRA.
На Android: CameraX Analysis + ImageAnalysis.Analyzer → GLES-шейдер → GLSurfaceView. ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST — отбрасываем старые кадры, если GPU не успел обработать предыдущий.
Процесс работы
- Аналитика: оценка требований к фильтрам, целевые устройства, fps.
- Проектирование: выбор архитектуры (нативный/кросс-платформа), прототипирование шейдеров.
- Реализация: написание GPU-шейдеров, интеграция с AVFoundation/Media3, оптимизация производительности.
- Тестирование: на реальных устройствах (iPhone 8–14, Samsung Galaxy S10–S23), измерение fps, проверка артефактов.
- Деплой: публикация в App Store/Google Play, настройка TestFlight/App Distribution.
Типичная ошибка при экспорте
Часто забывают переиспользовать `CIContext` — в результате fps падает до 2–3. Также важно возвращать `CVPixelBuffer` сразу после использования, иначе пул буферов истощается.Что входит в работу
- Исходный код шейдеров и интеграции (Swift, Kotlin, Dart).
- Документация по интеграции и настройке.
- Инструкция по добавлению новых фильтров.
- Поддержка в течение 1 месяца после сдачи.
- Тестирование на 3+ реальных устройствах.
Сроки и гарантии
5–7 рабочих дней: разработка шейдеров для 5–8 фильтров, интеграция предпросмотра и экспорта на iOS и Android. Flutter-версия с нативным кодом — плюс 2 дня. Стоимость рассчитывается индивидуально — ориентировочный бюджет от нескольких тысяч долларов. Свяжитесь для оценки вашего проекта.
Мы гарантируем стабильную работу фильтров на устройствах от iPhone 8 и Android 10. Наш опыт — 5+ лет в мобильной разработке, более 30 проектов с видеообработкой. Пишите — поможем реализовать видеофильтры для вашего приложения.







