Заставляем камеру в Qt работать на Android

Заставляем камеру в Qt работать на Android

504
ПОДЕЛИТЬСЯ

Так, к примеру , раз испытать запустить обычный пример с камерой, он будет работать в системе Windows, но не на Android. Уже на данный момент Qt – хорошая среда для разработки мобильных приложений, но некие моменты там остаются недоработанными. А раз мы желаем свободы иметь доступ к видеопотоку? А означает работа с камерой на Android — реализована, но полного доступа к ней нет. При этом примеры, использующие камеру в через Qml, полностью рабочие.

Тем не наименее, обеспечить полный доступ к видеопотоку камеры может быть. При исследовании исходников модуля QtMultimedia стало ясно, что причина ограничений работы с камерой – это необходимость скрыть костыли. А эти костыли пришлось поставить, чтоб обеспечить аппаратный вывод через OpenGL.

А как, написано тут. Перед тем, как начать разъяснение, стоит предупредить, что необязательно делать все описанное ниже, чтоб получить отдельные снимки. Вы сможете просто применять камеру через Qml и написать собственный компонент на нем, чтоб захватывать отдельные кадры.

И для вывода нам придется применять OpenGL. Мы напишем заместо него собственный. Для вывода изображения в нем употребляется объект класса QCameraViewfinder. Чтоб не писать все с нуля, возьмём тот самый пример Qt с заглавием “Camera Example” (который на скриншоте) и заставим его работать.

А так делать нельзя. Для написания собственных классов вывода кадров, получаемых от медиа-объектов, в Qt заготовлен абстрактный класс QAbstractVideoSurface с виртуальными функциями, через которые происходит взаимодействие. Можно было бы и объединить эти два класса, но при наследовании от QAbstractVideoSurface и QOpenGLWidget возникает двойное наследование от класса QObject. Сделаем собственный класс на базе него, который будет отвечать за получение кадра, и назовем его CameraSurface. А за вывод кадра будет отвечать класс CameraSurfaceWidget наследуемый от QOpenGLWidget.

Весь код реализации этого вы сможете поглядеть ниже, а тут я просто обрисую главные моменты. И на всякий вариант, выяснить подробнее, как работать с классом QAbstractVideoSurface, сможете тут.

Новейший кадр мы получаем в функции bool CameraSurface::present(const QVideoFrame& frame). Данные, которые могут придти с камеры, могут быть в виде массива (так происходит в Windows либо Symbian) либо в виде текстуры (в Android). Параметр frame и есть тот самый новейший кадр нашего видеопотока. Просто копируйте кадр (frame) и читайте данные при рисовании. И раз для вас пришла текстура не вздумайте сходу ее считывать. И пусть ключевое слово const в объявлении функции вас не обманывает, данные снутри коварно помечены как mutable. Но вызывается эта функция не в вашем потоке, а означает, этот контекст OpenGL тут не будет работать. При вызове frame.handle() вы сможете поразмыслить, что вы всего-то получаете индекс текстуры, но на самом деле в этот же момент происходит хитрецкая инициализация ресурсов на базе контекста OpenGL вашего потока.

При связывании с камерой, у нашего CameraSurface возникает скрытое свойство «GLContext», и ожидается, что вы запишите туда собственный контекст OpenGL. И это событие обязано иметь код QEvent::User. Вообщем, в Windows все работает и без действий, но раз это не сделать на Android, то камера просто не начнет присылать кадры. Но это не все, что нужно знать. И сделать это лучше в потоке объекта CameraSurface, то есть, используя вызов слота через функционал сигналов и слотов Qt. По идее это пользовательский тип действия, но ведь вы вообщем не должны были знать о этих костылях, так что плевать. А позже отправьте событие, говорящее о записи в «GLContext», через объект характеристики «_q_GLThreadCallback».

Короче, код рисования будет приблизительно таковой:

if (glThreadCallback) {
QEvent event(QEvent::User);//Событие с пользовательским флагом
glThreadCallback->event(&event);//сейчас его отправляем
}
//И эта часть выше не нужна для винды. } else {
QVideoFrame& frame = _surface->frame();
//рисование кадра
}
} void CameraSurfaceWidget::paintGL()
{
if (!_surface->isActive()) {//раз мы не начали принимать кадры с камеры, то
_surface->scheduleOpenGLContextUpdate();//необходимо выслать данные о контексте OpenGL
QObject* glThreadCallback = (_surface->property("_q_GLThreadCallback")).value<QObject*>();//куда отправляем событие, говорящее,
//что все готово к принятию видеопотока? Но, основное, она там ничего не сломает.
В итоге получаем возможность обрабатывать поток и виндоподобный интерфейс на Android. Можно еще получать кадры через QVideoProbe, но тогда все видимо обрабатывается на процессоре, поэтому что дико лагает. Данные из текстуры кадра, кстати, можно вынуть, используя Frame Buffer Object и glReadPixels (glGetTexImage в OpenGL ES нет). И это не единственный метод это сделать. Так что вообщем-то лучше просто про это забудьте.

Еще странности QtИ еще одна странноватая вещь напоследок. Что-то в Qt перепутали. Раз формат кадра — Format_RGB32, то каналы цвета размещаются в порядке B G R. Раз формат — Format_BGR32, то R G B.

Скачать исправленный пример можно тут. habrahabr.ru