Уже сейчас 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<QObject*>();//куда отправляем событие, говорящее,
//что все готово к принятию видеопотока?
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 перепутали.
Скачать исправленный пример можно здесь.
ncix
QImage::Format_RGB32 4 The image is stored using a 32-bit RGB format (0xffRRGGBB)
QVideoFrame::Format_BGR32 10 The frame is stored using a 32-bit BGR format (0xBBGGRRff)
Имеете в виду, дока не соответствует коду? Может вы не в том порядке «вынимали» байты?
MarkWatney Автор
Да, похоже моя ошибка, не учел обратный порядок байт. Вот только напрямую с байтами я не работаю, данные приходят уже в виде текстуры, либо в виде массива и сразу отправляются в текстуру. А OpenGL, судя по всему, никак не учитывает этот порядок байт. Довольно не очевидная проблема по моему.