Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qandroidvideoframebuffer.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
4#include <QtFFmpegMediaPluginImpl/private/qandroidvideoframebuffer_p.h>
5
6#include <QtCore/qdebug.h>
7#include <QtCore/qjnitypes.h>
8#include <QtCore/qloggingcategory.h>
9
10#include <jni.h>
11
13Q_STATIC_LOGGING_CATEGORY(qLCAndroidCameraFrame, "qt.multimedia.ffmpeg.android.camera.frame");
14
15namespace {
16bool isWorkaroundForEmulatorNeeded() {
17 const static bool workaroundForEmulator
18 = QtJniTypes::QtVideoDeviceManager::callStaticMethod<jboolean>("isEmulator");
19 return workaroundForEmulator;
20}
21}
22
23bool QAndroidVideoFrameBuffer::useCopiedData() const
24{
25 return m_policy == MemoryPolicy::Copy;
26}
27
28bool QAndroidVideoFrameBuffer::parse(const QJniObject &frame)
29{
30 QJniEnvironment jniEnv;
31
32 if (!frame.isValid())
33 return false;
34
35 const auto planes = frame.callMethod<QtJniTypes::ImagePlane[]>("getPlanes");
36 if (!planes.isValid())
37 return false;
38
39 const int numberPlanes = planes.size();
40 Q_ASSERT(numberPlanes <= MAX_PLANES);
41
42 // create and populate temporary array structure
43 int pixelStrides[MAX_PLANES];
44 int rowStrides[MAX_PLANES];
45 int bufferSize[MAX_PLANES];
46 char *buffer[MAX_PLANES];
47
48 auto resetPlane = [&](int index) {
49 if (index < 0 || index > numberPlanes)
50 return;
51
52 rowStrides[index] = 0;
53 pixelStrides[index] = 0;
54 bufferSize[index] = 0;
55 buffer[index] = nullptr;
56 };
57
58 for (qsizetype index = 0; index < numberPlanes; ++index) {
59 auto plane = planes.at(index);
60 if (!plane.isValid()) {
61 resetPlane(index);
62 continue;
63 }
64
65 rowStrides[index] = plane.callMethod<jint>("getRowStride");
66 pixelStrides[index] = plane.callMethod<jint>("getPixelStride");
67
68 auto byteBuffer = plane.callMethod<QtJniTypes::ByteBuffer>("getBuffer");
69 if (!byteBuffer.isValid()) {
70 resetPlane(index);
71 continue;
72 }
73
74 // Uses direct access which is garanteed by android to work with
75 // ImageReader bytebuffer
76 buffer[index] = static_cast<char *>(jniEnv->GetDirectBufferAddress(byteBuffer.object()));
77 bufferSize[index] = byteBuffer.callMethod<jint>("remaining");
78 }
79
80 QVideoFrameFormat::PixelFormat calculedPixelFormat = QVideoFrameFormat::Format_Invalid;
81
82 // finding the image format
83 // the ImageFormats that can happen here are stated here:
84 // https://developer.android.com/reference/android/media/Image#getFormat()
85 int format = frame.callMethod<jint>("getFormat");
86 AndroidImageFormat imageFormat = AndroidImageFormat(format);
87
88 switch (imageFormat) {
89 case AndroidImageFormat::RGBA_8888: {
90 // RGBA_8888 should have a single plane
91 calculedPixelFormat = numberPlanes != 1 ? QVideoFrameFormat::Format_Invalid :
92 QVideoFrameFormat::Format_RGBA8888;
93 break;
94 }
95 case AndroidImageFormat::JPEG:
96 calculedPixelFormat = QVideoFrameFormat::Format_Jpeg;
97 break;
98 case AndroidImageFormat::YUV_420_888:
99 if (numberPlanes < 3) {
100 // something went wrong on parsing. YUV_420_888 format must always have 3 planes
101 calculedPixelFormat = QVideoFrameFormat::Format_Invalid;
102 break;
103 }
104 if (pixelStrides[1] == 1)
105 calculedPixelFormat = QVideoFrameFormat::Format_YUV420P;
106 else if (pixelStrides[1] == 2) {
107 if (buffer[1] - buffer[2] == -1) // Interleaved UVUV -> NV12
108 calculedPixelFormat = QVideoFrameFormat::Format_NV12;
109 else if (buffer[1] - buffer[2] == 1) // Interleaved VUVU -> NV21
110 calculedPixelFormat = QVideoFrameFormat::Format_NV21;
111 }
112 break;
113 case AndroidImageFormat::HEIC:
114 // QImage cannot parse HEIC
115 calculedPixelFormat = QVideoFrameFormat::Format_Invalid;
116 break;
117 case AndroidImageFormat::RAW_PRIVATE:
118 case AndroidImageFormat::RAW_SENSOR:
119 // we cannot know raw formats
120 calculedPixelFormat = QVideoFrameFormat::Format_Invalid;
121 break;
122 case AndroidImageFormat::FLEX_RGBA_8888:
123 case AndroidImageFormat::FLEX_RGB_888:
124 // these formats are only returned by Mediacodec.getOutputImage, they are not used as a
125 // Camera2 Image frame return
126 calculedPixelFormat = QVideoFrameFormat::Format_Invalid;
127 break;
128 case AndroidImageFormat::YUV_422_888:
129 case AndroidImageFormat::YUV_444_888:
130 case AndroidImageFormat::YCBCR_P010:
131 // not dealing with these formats, they require higher API levels than the current Qt min
132 calculedPixelFormat = QVideoFrameFormat::Format_Invalid;
133 break;
134 default:
135 calculedPixelFormat = QVideoFrameFormat::Format_Invalid;
136 break;
137 }
138
139 if (calculedPixelFormat == QVideoFrameFormat::Format_Invalid) {
140 qCWarning(qLCAndroidCameraFrame) << "Cannot determine image format!";
141 return false;
142 }
143
144 auto copyPlane = [&](int mapIndex, int arrayIndex) {
145 if (arrayIndex >= numberPlanes)
146 return;
147
148 m_mapData.bytesPerLine[mapIndex] = rowStrides[arrayIndex];
149 dataCleaner[mapIndex] = useCopiedData() ?
150 QByteArray(buffer[arrayIndex], bufferSize[arrayIndex])
151 : QByteArray::fromRawData(buffer[arrayIndex], bufferSize[arrayIndex]);
152 m_mapData.data[mapIndex] = (uchar *)dataCleaner[mapIndex].constData();
153 m_mapData.dataSize[mapIndex] = bufferSize[arrayIndex];
154 };
155
156 int width = frame.callMethod<jint>("getWidth");
157 int height = frame.callMethod<jint>("getHeight");
158 QVideoFrameFormat::PixelFormat pixelFormat = QVideoFrameFormat::Format_Invalid;
159
160 switch (calculedPixelFormat) {
161 case QVideoFrameFormat::Format_RGBA8888:
162 m_mapData.planeCount = 1;
163 if (isWorkaroundForEmulatorNeeded())
164 m_policy = MemoryPolicy::Copy;
165
166 copyPlane(0, 0);
167
168 pixelFormat = QVideoFrameFormat::Format_RGBA8888;
169 break;
170 case QVideoFrameFormat::Format_YUV420P:
171 m_mapData.planeCount = 3;
172 if (isWorkaroundForEmulatorNeeded())
173 m_policy = MemoryPolicy::Copy;
174 copyPlane(0, 0);
175 copyPlane(1, 1);
176 copyPlane(2, 2);
177
178 pixelFormat = QVideoFrameFormat::Format_YUV420P;
179 break;
180 case QVideoFrameFormat::Format_NV12:
181 case QVideoFrameFormat::Format_NV21:
182 // Y-plane and combined interleaved UV-plane
183 m_mapData.planeCount = 2;
184 copyPlane(0, 0);
185
186 // Android reports U and V planes as planes[1] and planes[2] respectively, regardless of the
187 // order of interleaved samples. We point to whichever is first in memory.
188 // With interleaved UV plane, Android reports the size of each plane as the smallest size
189 // that includes all samples of that plane. For example, if the UV plane is [u, v, u, v],
190 // the size of the U-plane is 3, not 4. With FFmpeg we need to count the total number of
191 // bytes in the UV-plane, which is 1 more than what Android reports.
192 {
193 const int indexOfFirstPlane = calculedPixelFormat == QVideoFrameFormat::Format_NV21 ?
194 2 : 1;
195 m_mapData.bytesPerLine[1] = rowStrides[indexOfFirstPlane];
196
197 dataCleaner[1] = useCopiedData() ?
198 QByteArray(buffer[indexOfFirstPlane],
199 bufferSize[indexOfFirstPlane] + 1)
200 : QByteArray::fromRawData(buffer[indexOfFirstPlane],
201 bufferSize[indexOfFirstPlane] + 1);
202 m_mapData.data[1] = (uchar *)dataCleaner[1].constData();
203 m_mapData.dataSize[1] = bufferSize[indexOfFirstPlane] + 1;
204 }
205 pixelFormat = calculedPixelFormat;
206 break;
207 case QVideoFrameFormat::Format_Jpeg:
208 qCWarning(qLCAndroidCameraFrame)
209 << "FFmpeg HW Mediacodec does not encode other than YCbCr formats";
210 // we still parse it to preview the frame
211 m_policy = MemoryPolicy::Copy;
212 m_image = QImage::fromData((uchar *)buffer[0], bufferSize[0]);
213 m_mapData.bytesPerLine[0] = m_image.bytesPerLine();
214 dataCleaner[0] = QByteArray::fromRawData((char*)m_image.bits(), m_image.sizeInBytes());
215 m_mapData.data[0] = (uchar *)dataCleaner[0].constData();
216 m_mapData.dataSize[0] = m_image.sizeInBytes();
217 pixelFormat = QVideoFrameFormat::pixelFormatFromImageFormat(m_image.format());
218 break;
219 default:
220 break;
221 }
222
223 long timestamp = frame.callMethod<jlong>("getTimestamp");
224 m_timestamp = timestamp / 1000;
225
226 m_videoFrameFormat = QVideoFrameFormat(QSize(width, height), pixelFormat);
227 return true;
228}
229
230QAndroidVideoFrameBuffer::QAndroidVideoFrameBuffer(QJniObject frame,
231 std::shared_ptr<FrameReleaseDelegate> frameReleaseDelegate,
232 MemoryPolicy policy,
233 QtVideo::Rotation rotation)
234 : m_frameReleaseDelegate(frameReleaseDelegate)
235 , m_policy(policy)
236 , m_parsed(parse(frame))
237{
238 if (isParsed() && !useCopiedData()) {
239 // holding the frame java object
240 QJniEnvironment jniEnv;
241 m_nativeFrame = jniEnv->NewGlobalRef(frame.object());
242 jniEnv.checkAndClearExceptions();
243 } else if (frame.isValid()) {
244 frame.callMethod<void>("close");
245 m_frameReleaseDelegate->onFrameReleased();
246 }
247 m_videoFrameFormat.setRotation(rotation);
248}
249
250QAndroidVideoFrameBuffer::~QAndroidVideoFrameBuffer()
251{
252 if (!isParsed() || useCopiedData()) // nothing to clean
253 return;
254
255 QJniObject qFrame(m_nativeFrame);
256 if (qFrame.isValid()) {
257 qFrame.callMethod<void>("close");
258 m_frameReleaseDelegate->onFrameReleased();
259 }
260
261 QJniEnvironment jniEnv;
262 if (m_nativeFrame)
263 jniEnv->DeleteGlobalRef(m_nativeFrame);
264}
265
266QT_END_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")