[Из песочницы] Заставляем камеру в Qt работать на Android

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

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

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

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

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

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

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

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

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

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

© Habrahabr.ru