Змушуємо камеру в 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<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 переплутали.

Скачати виправлений приклад можна тут.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.