Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qandroidcameraframe.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include <jni.h>
6#include <QDebug>
7#include <QtCore/qjnitypes.h>
8#include <QtCore/QLoggingCategory>
9
11Q_STATIC_LOGGING_CATEGORY(qLCAndroidCameraFrame, "qt.multimedia.ffmpeg.android.camera.frame");
12
13namespace {
14bool isWorkaroundForEmulatorNeeded() {
15 const static bool workaroundForEmulator
16 = QtJniTypes::QtVideoDeviceManager::callStaticMethod<jboolean>("isEmulator");
17 return workaroundForEmulator;
18}
19}
20
21bool QAndroidCameraFrame::parse(const QJniObject &frame)
22{
23 QJniEnvironment jniEnv;
24
25 if (!frame.isValid())
26 return false;
27
28 auto planes = frame.callMethod<QtJniTypes::AndroidImagePlaneArray>("getPlanes");
29 if (!planes.isValid())
30 return false;
31
32 int numberPlanes = jniEnv->GetArrayLength(planes.object<jarray>());
33 // create and populate temporary array structure
34 int pixelStrides[numberPlanes];
35 int rowStrides[numberPlanes];
36 int bufferSize[numberPlanes];
37 uint8_t *buffer[numberPlanes];
38
39 auto resetPlane = [&](int index) {
40 if (index < 0 || index > numberPlanes)
41 return;
42
43 rowStrides[index] = 0;
44 pixelStrides[index] = 0;
45 bufferSize[index] = 0;
46 buffer[index] = nullptr;
47 };
48
49 for (int index = 0; index < numberPlanes; index++) {
50 QJniObject plane = jniEnv->GetObjectArrayElement(planes.object<jobjectArray>(), index);
51 if (jniEnv.checkAndClearExceptions() || !plane.isValid()) {
52 resetPlane(index);
53 continue;
54 }
55
56 rowStrides[index] = plane.callMethod<jint>("getRowStride");
57 pixelStrides[index] = plane.callMethod<jint>("getPixelStride");
58
59 auto byteBuffer = plane.callMethod<QtJniTypes::JavaByteBuffer>("getBuffer");
60 if (!byteBuffer.isValid()) {
61 resetPlane(index);
62 continue;
63 }
64
65 // Uses direct access which is garanteed by android to work with
66 // ImageReader bytebuffer
67 buffer[index] = static_cast<uint8_t *>(jniEnv->GetDirectBufferAddress(byteBuffer.object()));
68 bufferSize[index] = byteBuffer.callMethod<jint>("remaining");
69 }
70
72
73 // finding the image format
74 // the ImageFormats that can happen here are stated here:
75 // https://developer.android.com/reference/android/media/Image#getFormat()
76 int format = frame.callMethod<jint>("getFormat");
77 AndroidImageFormat imageFormat = AndroidImageFormat(format);
78
79 switch (imageFormat) {
80 case AndroidImageFormat::JPEG:
81 calculedPixelFormat = QVideoFrameFormat::Format_Jpeg;
82 break;
83 case AndroidImageFormat::YUV_420_888:
84 if (numberPlanes < 3) {
85 // something went wrong on parsing. YUV_420_888 format must always have 3 planes
86 calculedPixelFormat = QVideoFrameFormat::Format_Invalid;
87 break;
88 }
89 if (pixelStrides[1] == 1)
90 calculedPixelFormat = QVideoFrameFormat::Format_YUV420P;
91 else if (pixelStrides[1] == 2) {
92 if (buffer[1] - buffer[2] == -1) // Interleaved UVUV -> NV12
93 calculedPixelFormat = QVideoFrameFormat::Format_NV12;
94 else if (buffer[1] - buffer[2] == 1) // Interleaved VUVU -> NV21
95 calculedPixelFormat = QVideoFrameFormat::Format_NV21;
96 }
97 break;
98 case AndroidImageFormat::HEIC:
99 // QImage cannot parse HEIC
100 calculedPixelFormat = QVideoFrameFormat::Format_Invalid;
101 break;
102 case AndroidImageFormat::RAW_PRIVATE:
103 case AndroidImageFormat::RAW_SENSOR:
104 // we cannot know raw formats
105 calculedPixelFormat = QVideoFrameFormat::Format_Invalid;
106 break;
107 case AndroidImageFormat::FLEX_RGBA_8888:
108 case AndroidImageFormat::FLEX_RGB_888:
109 // these formats are only returned by Mediacodec.getOutputImage, they are not used as a
110 // Camera2 Image frame return
111 calculedPixelFormat = QVideoFrameFormat::Format_Invalid;
112 break;
113 case AndroidImageFormat::YUV_422_888:
114 case AndroidImageFormat::YUV_444_888:
115 case AndroidImageFormat::YCBCR_P010:
116 // not dealing with these formats, they require higher API levels than the current Qt min
117 calculedPixelFormat = QVideoFrameFormat::Format_Invalid;
118 break;
119 default:
120 calculedPixelFormat = QVideoFrameFormat::Format_Invalid;
121 break;
122 }
123
124 if (calculedPixelFormat == QVideoFrameFormat::Format_Invalid) {
125 qCWarning(qLCAndroidCameraFrame) << "Cannot determine image format!";
126 return false;
127 }
128
129 auto copyPlane = [&](int mapIndex, int arrayIndex) {
130 if (arrayIndex >= numberPlanes)
131 return;
132
133 m_planes[mapIndex].rowStride = rowStrides[arrayIndex];
134 m_planes[mapIndex].size = bufferSize[arrayIndex];
135 m_planes[mapIndex].data = buffer[arrayIndex];
136 };
137
138 int width = frame.callMethod<jint>("getWidth");
139 int height = frame.callMethod<jint>("getHeight");
140 m_size = QSize(width, height);
141
142 switch (calculedPixelFormat) {
144 m_numberPlanes = 3;
145 copyPlane(0, 0);
146 copyPlane(1, 1);
147 copyPlane(2, 2);
148
149 if (isWorkaroundForEmulatorNeeded()) {
150 for (int i = 0; i < 3; ++i) {
151 const int dataSize = (i == 0) ? width * height : width * height / 4;
152 m_planes[i].data = new uint8_t[dataSize];
153 memcpy(m_planes[i].data, buffer[i], dataSize);
154 }
155 }
156
157 m_pixelFormat = QVideoFrameFormat::Format_YUV420P;
158 break;
161 // Y-plane and combined interleaved UV-plane
162 m_numberPlanes = 2;
163 copyPlane(0, 0);
164
165 // Android reports U and V planes as planes[1] and planes[2] respectively, regardless of the
166 // order of interleaved samples. We point to whichever is first in memory.
167 copyPlane(1, calculedPixelFormat == QVideoFrameFormat::Format_NV21 ? 2 : 1);
168
169 // With interleaved UV plane, Android reports the size of each plane as the smallest size
170 // that includes all samples of that plane. For example, if the UV plane is [u, v, u, v],
171 // the size of the U-plane is 3, not 4. With FFmpeg we need to count the total number of
172 // bytes in the UV-plane, which is 1 more than what Android reports.
173 m_planes[1].size++;
174
175 m_pixelFormat = calculedPixelFormat;
176 break;
178 qCWarning(qLCAndroidCameraFrame)
179 << "FFmpeg HW Mediacodec does not encode other than YCbCr formats";
180 // we still parse it to preview the frame
181 m_image = QImage::fromData(buffer[0], bufferSize[0]);
182 m_planes[0].rowStride = m_image.bytesPerLine();
183 m_planes[0].size = m_image.sizeInBytes();
184 m_planes[0].data = m_image.bits();
185 m_pixelFormat = QVideoFrameFormat::pixelFormatFromImageFormat(m_image.format());
186 break;
187 default:
188 break;
189 }
190
191 long timestamp = frame.callMethod<jlong>("getTimestamp");
192 m_timestamp = timestamp / 1000;
193
194 return true;
195}
196
198 : m_pixelFormat(QVideoFrameFormat::Format_Invalid), m_parsed(parse(frame))
199{
200 if (isParsed()) {
201 // holding the frame java object
202 QJniEnvironment jniEnv;
203 m_frame = jniEnv->NewGlobalRef(frame.object());
204 jniEnv.checkAndClearExceptions();
205 } else if (frame.isValid()) {
206 frame.callMethod<void>("close");
207 }
208}
209
211{
212 if (!isParsed()) // nothing to clean
213 return;
214
215 QJniObject qFrame(m_frame);
216 if (qFrame.isValid())
217 qFrame.callMethod<void>("close");
218
219 QJniEnvironment jniEnv;
220 if (m_frame)
221 jniEnv->DeleteGlobalRef(m_frame);
222
223 if (isWorkaroundForEmulatorNeeded()) {
224 if (m_pixelFormat == QVideoFrameFormat::Format_YUV420P) {
225 for (int i = 0; i < 3; ++i)
226 delete[] m_planes[i].data;
227 }
228 }
229}
230
Plane plane(int index) const
QAndroidCameraFrame(QJniObject frame)
qsizetype bytesPerLine() const
Returns the number of bytes per image scanline.
Definition qimage.cpp:1560
qsizetype sizeInBytes() const
Definition qimage.cpp:1548
uchar * bits()
Returns a pointer to the first pixel data.
Definition qimage.cpp:1698
Format format() const
Returns the format of the image.
Definition qimage.cpp:2162
static QImage fromData(QByteArrayView data, const char *format=nullptr)
Definition qimage.cpp:3841
\inmodule QtCore
\inmodule QtCore
\inmodule QtCore
Definition qsize.h:25
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
PixelFormat
Enumerates video data types.
static PixelFormat pixelFormatFromImageFormat(QImage::Format format)
Returns a video pixel format equivalent to an image format.
Combined button and popup list for selecting options.
QJniArray< AndroidImagePlane > AndroidImagePlaneArray
#define qCWarning(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)
GLint GLsizei GLsizei height
GLuint index
[2]
GLenum GLsizei dataSize
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLint GLsizei width
GLint GLsizei GLsizei GLenum format
QFrame frame
[0]