Pull to refresh

Qt и мобильная камера. Часть 2, Meego

Development of mobile applications *
Здравствуйте, Хабравчане!

В прошлой статье я рассказал о том, как создать удобный Qt-видоискатель для камеры на Symbian устройствах. Теперь пришло время сделать наш код более универсальным и добавить совместимость с Meego 1.2, что, как оказалось, является задачей не самой тривиальной.

Вкратце напомню, что мы получили в прошлой части:
  • Класс позволяющий получить и обработать каждый фрейм видеопотока в отдельности
  • Виджет видоискателя, перерисовываемый на каждом новом фрейме, что позволяет делать с входящим изображением все, что угодно
Теперь о том, почему мы не можем просто взять и добавить этот виджет в приложение для Nokia N9 (N900, N950). Все дело в формате изображения, возвращаемого камерой — UYVY. При запуске нашего видоискателя все что мы получим — лишь сообщение в дебаг-лог о неподходящем формате. Что такое UYVY я не знаю, но, к счастью, не без помощи зарубежных пользователей Maemo/Meego, мне удалось найти решение этой проблемы. Далее я приведу лишь отрывки кода из первой статьи с новыми модификациями.

Решение это состоит в конвертации UYVY в более привычный и, что самое главное, понятный для Qt формат RGB. Как ни странно, код, который мы будем в дальнейшем использовать, входит в QtMobility и найти его можно в исходниках (ссылка в подвале статьи). План действий по внедрению UYVY->RGB16 конвертора таков:
  1. Добавляем мнимую подддержку UYVY к нашему видоискателю, чтобы получить доступ к изображению, а не впустую читать логи
  2. Реализуем в классе обработчика фреймов функцию конвертации
  3. Модифицируем метод QAbstractVideoSurface::present() с добавлением нашей новой функции
Для начала нам необходимо указать системе, что UYVY является допустимым форматом. Для этого добавляем значение QVideoFrame::Format_UYVY в список форматов, возвращаемый методом QAbstractVideoSurface::supportedPixelFormats():

>>myvideosurface.cpp
	QList<QVideoFrame::PixelFormat> myVideoSurface::supportedPixelFormats(
			QAbstractVideoBuffer::HandleType handleType) const
{
	if (handleType == QAbstractVideoBuffer::NoHandle) {
		return QList<QVideoFrame::PixelFormat>()
				<< QVideoFrame::Format_RGB32
				<< QVideoFrame::Format_ARGB32
				<< QVideoFrame::Format_ARGB32_Premultiplied
				<< QVideoFrame::Format_RGB565
				<< QVideoFrame::Format_RGB555
				<< QVideoFrame::Format_UYVY; //новая строка
	} else {
		return QList<QVideoFrame::PixelFormat>();
	}
}

Далее самая главная часть, из-за которой вся эта статья и появилась — UYVY->RGB16 конвертация. Для этого используется функция построчного преобразования, оптимизированная под ARM\Neon графический процессор. Как я писал выше, найти ее можно и в исходниках QtMobility (\src\multimedia\qgraphicsvideoitem_maemo5.cpp). Однако, у людей случаются проблемы с переносом этой функции в свой уютный код (признаться, у меня самого получилось только со второго раза), так что приведу код полностью. Вникать в смысл этого ASM-снипетта совершенно необязательно (в рамках решаемой задачи). Я и сам не имею представления о том, что в нем происходит (не считая строк с комментариями), так что достаточно просто скопипастить этот кусок к себе. Добавить его можно в myvideosurface.cpp перед реализацией методов класса myVideoSurface.
	#include <stdint.h>
	
#ifdef  __ARM_NEON__

/*
* ARM NEON optimized implementation of UYVY -> RGB16 convertor
*/
static void uyvy422_to_rgb16_line_neon (uint8_t * dst, const uint8_t * src, int n)
{
	 /* and this is the NEON code itself */
	 static __attribute__ ((aligned (16))) uint16_t acc_r[8] = {
	   22840, 22840, 22840, 22840, 22840, 22840, 22840, 22840,
	 };
	 static __attribute__ ((aligned (16))) uint16_t acc_g[8] = {
	   17312, 17312, 17312, 17312, 17312, 17312, 17312, 17312,
	 };
	 static __attribute__ ((aligned (16))) uint16_t acc_b[8] = {
	   28832, 28832, 28832, 28832, 28832, 28832, 28832, 28832,
	 };
	 /*
	  * Registers:
	  * q0, q1 : d0, d1, d2, d3  - are used for initial loading of YUV data
	  * q2     : d4, d5          - are used for storing converted RGB data
	  * q3     : d6, d7          - are used for temporary storage
	  *
	  * q6     : d12, d13        - are used for converting to RGB16
	  * q7     : d14, d15        - are used for storing RGB16 data
	  * q4-q5 - reserved
	  *
	  * q8, q9 : d16, d17, d18, d19  - are used for expanded Y data
	  * q10    : d20, d21
	  * q11    : d22, d23
	  * q12    : d24, d25
	  * q13    : d26, d27
	  * q13, q14, q15            - various constants (#16, #149, #204, #50, #104, #154)
	  */
	 asm volatile (".macro convert_macroblock size\n"
		 /* load up to 16 source pixels in UYVY format */
		 ".if \\size == 16\n"
		 "pld [%[src], #128]\n"
		 "vld1.32 {d0, d1, d2, d3}, [%[src]]!\n"
		 ".elseif \\size == 8\n"
		 "vld1.32 {d0, d1}, [%[src]]!\n"
		 ".elseif \\size == 4\n"
		 "vld1.32 {d0}, [%[src]]!\n"
		 ".elseif \\size == 2\n"
		 "vld1.32 {d0[0]}, [%[src]]!\n"
		 ".else\n" ".error \"unsupported macroblock size\"\n" ".endif\n"
		 /* convert from 'packed' to 'planar' representation */
		 "vuzp.8      d0, d1\n"    /* d1 - separated Y data (first 8 bytes) */
		 "vuzp.8      d2, d3\n"    /* d3 - separated Y data (next 8 bytes) */
		 "vuzp.8      d0, d2\n"    /* d0 - separated U data, d2 - separated V data */
		 /* split even and odd Y color components */
		 "vuzp.8      d1, d3\n"    /* d1 - evenY, d3 - oddY */
		 /* clip upper and lower boundaries */
		 "vqadd.u8    q0, q0, q4\n"
		 "vqadd.u8    q1, q1, q4\n"
		 "vqsub.u8    q0, q0, q5\n"
		 "vqsub.u8    q1, q1, q5\n"
		 "vshr.u8     d4, d2, #1\n"    /* d4 = V >> 1 */
		 "vmull.u8    q8, d1, d27\n"       /* q8 = evenY * 149 */
		 "vmull.u8    q9, d3, d27\n"       /* q9 = oddY * 149 */
		 "vld1.16     {d20, d21}, [%[acc_r], :128]\n"      /* q10 - initialize accumulator for red */
		 "vsubw.u8    q10, q10, d4\n"      /* red acc -= (V >> 1) */
		 "vmlsl.u8    q10, d2, d28\n"      /* red acc -= V * 204 */
		 "vld1.16     {d22, d23}, [%[acc_g], :128]\n"      /* q11 - initialize accumulator for green */
		 "vmlsl.u8    q11, d2, d30\n"      /* green acc -= V * 104 */
		 "vmlsl.u8    q11, d0, d29\n"      /* green acc -= U * 50 */
		 "vld1.16     {d24, d25}, [%[acc_b], :128]\n"      /* q12 - initialize accumulator for blue */
		 "vmlsl.u8    q12, d0, d30\n"      /* blue acc -= U * 104 */
		 "vmlsl.u8    q12, d0, d31\n"      /* blue acc -= U * 154 */
		 "vhsub.s16   q3, q8, q10\n"       /* calculate even red components */
		 "vhsub.s16   q10, q9, q10\n"      /* calculate odd red components */
		 "vqshrun.s16 d0, q3, #6\n"        /* right shift, narrow and saturate even red components */
		 "vqshrun.s16 d3, q10, #6\n"       /* right shift, narrow and saturate odd red components */
		 "vhadd.s16   q3, q8, q11\n"       /* calculate even green components */
		 "vhadd.s16   q11, q9, q11\n"      /* calculate odd green components */
		 "vqshrun.s16 d1, q3, #6\n"        /* right shift, narrow and saturate even green components */
		 "vqshrun.s16 d4, q11, #6\n"       /* right shift, narrow and saturate odd green components */
		 "vhsub.s16   q3, q8, q12\n"       /* calculate even blue components */
		 "vhsub.s16   q12, q9, q12\n"      /* calculate odd blue components */
		 "vqshrun.s16 d2, q3, #6\n"        /* right shift, narrow and saturate even blue components */
		 "vqshrun.s16 d5, q12, #6\n"       /* right shift, narrow and saturate odd blue components */
		 "vzip.8      d0, d3\n"    /* join even and odd red components */
		 "vzip.8      d1, d4\n"    /* join even and odd green components */
		 "vzip.8      d2, d5\n"    /* join even and odd blue components */
		 "vshll.u8     q7, d0, #8\n" //red
		 "vshll.u8     q6, d1, #8\n" //greed
		 "vsri.u16   q7, q6, #5\n"
		 "vshll.u8     q6, d2, #8\n" //blue
		 "vsri.u16   q7, q6, #11\n" //now there is rgb16 in q7
		 ".if \\size == 16\n"
		 "vst1.16 {d14, d15}, [%[dst]]!\n"
		 //"vst3.8  {d0, d1, d2}, [%[dst]]!\n"
		 "vshll.u8     q7, d3, #8\n" //red
		 "vshll.u8     q6, d4, #8\n" //greed
		 "vsri.u16   q7, q6, #5\n"
		 "vshll.u8     q6, d5, #8\n" //blue
		 "vsri.u16   q7, q6, #11\n" //now there is rgb16 in q7
		 //"vst3.8  {d3, d4, d5}, [%[dst]]!\n"
		 "vst1.16 {d14, d15}, [%[dst]]!\n"
		 ".elseif \\size == 8\n"
		 "vst1.16 {d14, d15}, [%[dst]]!\n"
		 //"vst3.8  {d0, d1, d2}, [%[dst]]!\n"
		 ".elseif \\size == 4\n"
		 "vst1.8 {d14}, [%[dst]]!\n"
		 ".elseif \\size == 2\n"
		 "vst1.8 {d14[0]}, [%[dst]]!\n"
		 "vst1.8 {d14[1]}, [%[dst]]!\n"
		 ".else\n"
		 ".error \"unsupported macroblock size\"\n"
		 ".endif\n"
		 ".endm\n"
		 "vmov.u8     d8, #15\n"  /* add this to U/V to saturate upper boundary */
		 "vmov.u8     d9, #20\n"   /* add this to Y to saturate upper boundary */
		 "vmov.u8     d10, #31\n"  /* sub this from U/V to saturate lower boundary */
		 "vmov.u8     d11, #36\n"  /* sub this from Y to saturate lower boundary */
		 "vmov.u8     d26, #16\n"
		 "vmov.u8     d27, #149\n"
		 "vmov.u8     d28, #204\n"
		 "vmov.u8     d29, #50\n"
		 "vmov.u8     d30, #104\n"
		 "vmov.u8     d31, #154\n"
		 "subs        %[n], %[n], #16\n"
		 "blt         2f\n"
		 "1:\n"
		 "convert_macroblock 16\n"
		 "subs        %[n], %[n], #16\n"
		 "bge         1b\n"
		 "2:\n"
		 "tst         %[n], #8\n"
		 "beq         3f\n"
		 "convert_macroblock 8\n"
		 "3:\n"
		 "tst         %[n], #4\n"
		 "beq         4f\n"
		 "convert_macroblock 4\n"
		 "4:\n"
		 "tst         %[n], #2\n"
		 "beq         5f\n"
		 "convert_macroblock 2\n"
		 "5:\n"
		 ".purgem convert_macroblock\n":[src] "+&r" (src),[dst] "+&r" (dst),
		 [n] "+&r" (n)
		 :[acc_r] "r" (&acc_r[0]),[acc_g] "r" (&acc_g[0]),[acc_b] "r" (&acc_b[0])
		 :"cc", "memory", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "d10", "d11", "d12", "d13", "d14", "d15",
		 "d16", "d17", "d18", "d19", "d20", "d21", "d22", "d23",
		 "d24", "d25", "d26", "d27", "d28", "d29", "d30", "d31");
}

#endif

Вот таким нехитрым способом можно сделать одну строку UYVY шестнадцатиразрядной RGB. Теперь осталось внедрить это преобразование в наш обработчик фреймов. Всю конвертацию я вынес в отдельный метод myVideoSurface::convertFrame(). Выглядит это следующим образом:

>>myvideosurface.cpp
	QPixmap myVideoSurface::convertFrame(QVideoFrame lastVideoFrame)
{
	QPixmap lastFrame = QPixmap();

	if (!lastVideoFrame.isValid()){
		return QPixmap();
	}

	if (lastVideoFrame.map(QAbstractVideoBuffer::ReadOnly)) {

#ifdef  __ARM_NEON__
		if (lastVideoFrame.pixelFormat() == QVideoFrame::Format_UYVY) {
			QImage lastImage(lastVideoFrame.size(), QImage::Format_RGB16);

			const uchar *src = lastVideoFrame.bits();
			uchar *dst = lastImage.bits();
			const int srcLineStep = lastVideoFrame.bytesPerLine();
			const int dstLineStep = lastImage.bytesPerLine();
			const int h = lastVideoFrame.height();
			const int w = lastVideoFrame.width();

			/**
			*	Построчная конвертация itself
			*/
			for (int y=0; y<h; y++) {
				uyvy422_to_rgb16_line_neon(dst, src, w);
				src += srcLineStep;
				dst += dstLineStep;
			}
			lastFrame = QPixmap::fromImage(
				lastImage.scaled(lastVideoFrame.size(), Qt::IgnoreAspectRatio, Qt::FastTransformation));
		} else
#endif
		{
			QImage::Format imgFormat = QVideoFrame::imageFormatFromPixelFormat(lastVideoFrame.pixelFormat());

			if (imgFormat != QImage::Format_Invalid) {
				QImage lastImage(lastVideoFrame.bits(),
								 lastVideoFrame.width(),
								 lastVideoFrame.height(),
								 lastVideoFrame.bytesPerLine(),
								 imgFormat);

				lastFrame = QPixmap::fromImage(
						lastImage.scaled(lastVideoFrame.size(), Qt::IgnoreAspectRatio, Qt::FastTransformation));
			}
		}

		lastVideoFrame.unmap();

		return lastFrame;
	}

}

Финальных штрих — модифицируем метод myVideoSurface::present() с учетом нововведений:

>>myvideosurface.cpp
bool myVideoSurface::present(const QVideoFrame &frame){
	m_frame = frame;
	if(surfaceFormat().pixelFormat() != m_frame.pixelFormat() ||
		surfaceFormat().frameSize() != m_frame.size()) {
		stop();
		return false;
	} else {
		observer->newImage(convertFrame(frame));
		return true;
	}
}

Вот и все, теперь в наш колбэк вернется изображение в RGB16, работать с которым можно точно также как и с RGB24 (этот формат используется в Symbian). А так как пробразование у нас оптимизированное — происходит это без потери скорости работы приложения. В конечном итоге виджет видоискателя можно использовать точно также, как и в версии для Symbian.

В целом, такой принцип обработки изображений испольуется в том же QtMobility, для которого мы и реализуем его самостоятельно, вопрос только в платформе (как видно из исходников, внутри mobility этот метод используется в Maemo 5). Так что остается надеятся, что в будущем такая конвертация будет внедрена и в код mobility для Meego.

P.S. К слову, на момент написания этого кода, в Qt (4.7.2) до сих пор не было поддержки сохранения RGB16 в JPEG (хотя на багтрекере и было указано, что баг исправлен). Поэтому в колбэке перед таким сохранением пришлось применять еще один шаг конвертации — RGB16->RGB888. Делается это в одну строку:
image = image.convertToFormat(QImage::Format_RGB888);
Здесь image — это объект QImage, пришедший в метод newImage(). На скорость этот процесс тоже не влияет, так что вполне применим при выводе изображения на экран в реальном времени.

Удачной конвертации!

Ссылки:
  • Иисходники QtMobility — git
Tags:
Hubs:
Total votes 16: ↑16 and ↓0 +16
Views 2.4K
Comments 15
Comments Comments 15

Posts