Из прошлых статей вы уже знаете, что такое Intel INDEи его компонент Intel INDE Media Pack, предоставляющий разнообразные возможности работы с видео. В этот раз я хочу поподробнее остановиться на такой возможности Intel INDE Media Pack, как захват видео в приложениях, использующих OpenGL.
Начну я не с примеров и рассказа о том, как это все работает, а с ответов на вопросы, которые чаще всего задают разработчики, когда речь заходит о захвате видео в Media Pack: «Зачем мне вообще делать возможность захвата видео в своем приложении?» и «Зачем использовать Media Pack, если в Android 4.4 появилась возможность захвата видео через ADB?»
Зачем это разработчику и пользователям
1. Это модель использования, которая может лечь в основу приложения, такого, как например Toy Story: Story Theater. Пользователь производит действия с объектами на экране, приложение захватывает видео, пишет его в файл, далее дает возможность его просмотреть, сохранить, поделиться в социальных сетях.
2. Новая возможность для пользователя – записать и сохранить удачный игровой момент, способ прохождения уровня, которым он также сможет поделиться в социальных сетях. Для разработчика это будет один из способов популяризации приложения.
Захват видео через ADB
Основное отличие от данного способа - это отсутствие необходимости подключать мобильное устройство к хосту, захват происходит прямо на устройстве, при этом не требуется «рутовать» устройство, используются стандартные механизмы. На выходе получается MP4 видео, готовое к просмотру или загрузке в сеть. Второе отличие – есть возможность захватывать не только видео, но и аудио со встроенного микрофона, что делает возможным захват звука из приложения, плюс возможность комментирования того, что происходит на экране.
Захват видео в OpenGL приложениях с помощью Media Pack
Первым делом необходимо скачать и установить INDE Media Pack, о том как это делается я подробно рассказал в этой статье. Внутри находятся две библиотеки, которые необходимо включить в свое приложение - android-<номер версии>.jarи domain-<номер версии>.jar
Всю работу по захвату видео делает класс GLCapture. Принцип его работы прост: он имеет собственную поверхность (Surface), содержимое которой он кодирует в кадры и пишет в видео. Приложение сначала отрисовывает текущий кадр на экран, затем переключает контекст на поверхность GLCaptureи рисует сцену еще раз, при восстановлении контекста текущее содержимое поверхности кодируется и пишется в результирующее видео. Перед началом использования GLCaptureего необходимо подготовить, а именно:
- Задать параметры видео
- Аудио (если есть необходимость писать звук)
- Указать, куда будет сохраняться видео (путь к файлу)
- Сконфигурировать поверхность
// Объявление GLCapture capturer; … // Создание экземпляра capturer = new GLCapture(new AndroidMediaObjectFactory());
Настройка параметров видео
// Создаем и инициализируем видео формат // Формат видео String videoMimeType = “video/avc”; // Ширина кадра int videoFrameWidth = 640; // Высота кадра int videoFrameHeight = 480; // Битрейт в килобайтах int videoBitRate = 5000; // Частота кадров в секунду int videoFrameRate = 30; // Частота ключевых кадров int videoIFrameInterval = 1; VideoFormatAndroid videoFormat = new VideoFormatAndroid(videoMimeType, videoFrameWidth, videoFrameHeight); videoFormat.setVideoBitRateInKBytes(videoBitRate); videoFormat.setVideoFrameRate(videoFrameRate); videoFormat.setVideoIFrameInterval(videoIFrameInterval); // Задаем видео формат сapturer.setTargetVideoFormat(videoFormat);
Настройка параметров аудио
Если нет необходимости писать звук, этот шаг можно пропустить
// Создаем и инициализируем аудио формат // Формат Audio String audioMimeType = “audio/mp4a-latm”; // Частота аудио сэмплов int audioSampleRate = 44100; // Количество уадио каналов int audioChannelCount = 1; AudioFormatAndroid audioFormat = new AudioFormatAndroid(audioMimeType , audioSampleRate, audioChannelCount); // Задаем аудио формат сapturer.setTargetAudioFormat(audioFormat);
Путь к результирующему видео
String dstPath = “…”; capture.setTargetFile(dstPath);
Инициализация поверхности
Перед первым использованием необходимо инициализировать поверхность, делается это путем вызова
capture.setSurfaceSize(videoFrameWidth, videoFrameHeight)
Одно условие – вызов должен быть осуществлен из функции с активным OpenGLконтекстом, т.е. где-то в
onSurfaceChanged(GL10 gl, int width, int height) или onDrawFrame(GL10 gl) После того, как параметры заданы, поверхность сконфигурирована, можем начинать сохранять кадры в видео. В простейшем случае можно дважды отрисовать сцену – сначала на экран, затем на поверхность.
Способ первый: двойная отрисовка
// Рисуем сцену на экран render(); // Переключаем контекст capturer.beginCaptureFrame(); // Рисуем сцену на поверхность GLCapture render(); // Восстанавливаем контекст capturer.endCaptureFrame();
В некоторых случаях такой подход может сказаться на производительности, например в случае сцен с большим количеством объектов, текстур, пост обработкой кадра. Чтобы избежать двойной отрисовки, можно использовать кадровый буфер (frame buffer).
Способ второй: кадровый буфер
В этом случае алгоритм выглядит следующим образом:
- Создаем кадровый буфер и привязанную к нему текстуру
- Рисуем сцену на текстуру
- Рисуем полноэкранную текстуру на экран
- Переключаем контекст и рисуем текстуру на поверхность GLCapture
Дабы сэкономить время разработчикам на реализацию этого метода, мы включили в библиотеку все необходимые компоненты для работы с кадровым буфером.
// Класс-обертка FrameBuffer frameBuffer; // Вспомогательный класс для рисования полноэкранной текстуры FullFrameTexture texture; // Где-то в функции с активным OpenGL контекстом, например public void onSurfaceChanged(GL10 gl, int width, int height) { frameBuffer = new FrameBuffer(EglUtil.getInstance()); frameBuffer.setResolution(new Resolution(width, height)); texture = new FullFrameTexture(); }
Как видите - минимум кода для создания и инициализации. При желание нашу реализацию можно заменить на собственную, никаких ограничений.
public void onDrawFrame(GL10 gl) {
// Будем рисовать на текстуру frameBuffer.bind(); // Рисуем сцену renderScene(); // Восстанавливаем контекст frameBuffer.unbind(); // Рисуем текстуру на экран texture.draw(frameBuffer.getTextureId()); // Переключаем контекст capture.beginCaptureFrame(); // Рисуем текстуру на поверхность texture.draw(frameBuffer.getTextureId()); // Восстанавливаем контекст capture.endCaptureFrame(); }
С целью упростить пример я исключил часть кода, который вычисляет и устанавливает область просмотра (view port), что является необходимым условием для корректного отображения захвата видео в случаях, когда размер экрана и разрешение результирующего видео не совпадают. Полную версию примера вы сможете найти в приложении samples, поставляемом в составе Intel INDE Media Pack, класс GameRenderer.
Данный пример демонстрирует возможность захвата как с помощью двойной отрисовки, так и с использованием кадрового буфера, а также позволяет оценить производительность каждого способа, отображая количество кадров в секунду.