[Из песочницы] Заставляем камеру в Qt работать на Android
Уже сейчас 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
Скачать исправленный пример можно здесь.