Реалізація накладання фільтрів на відео в мобільному застосунку
Розробка відеофільтрів для мобільних застосунків — це завжди баланс між якістю зображення та продуктивністю. Одного разу клієнт прийшов із завданням: накласти LUT-фільтри на відео 4K 60fps у реальному часі. На iPhone 14 Pro перші тести давали 12 fps — пропуск кожного другого кадру. Проблема була у створенні CIContext на кожен кадр. Після оптимізації utilisation GPU знизилася до 30%, fps стабілізувалися на 60. Ми використовуємо GPU-шейдери для обробки кадрів. На iOS — Swift з Metal, на Android — GLSL або Vulkan. Для крос-платформних проектів — нативні модулі через платформенні канали. Завдяки GPU досягається в 4 рази вища пропускна здатність порівняно з CPU.
Два режими: попередній перегляд та експорт
Попередній перегляд у реальному часі (під час відтворення) вимагає обробки на 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 мс |
GPU забезпечує в 4 рази вищу швидкість обробки, що критично для реального часу.
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 дні. Вартість розраховується індивідуально — орієнтовний бюджет від $5,000 до $15,000 під ключ залежно від складності. Економія до 30% за рахунок готових шейдерів. Зв'яжіться для оцінки вашого проекту.
Ми гарантуємо стабільну роботу фільтрів на пристроях від iPhone 8 та Android 10. Наш досвід — 5+ років у мобільній розробці, понад 30 проектів з відеообробкою. Пишіть — допоможемо реалізувати відеофільтри для вашого застосунку під ключ.







