9#include <QtMultimedia/qvideosink.h>
10#include <QtMultimedia/private/qmemoryvideobuffer_p.h>
11#include <QtMultimedia/private/qvideoframe_p.h>
12#include <QtGui/qopenglcontext.h>
13#include <QtGui/rhi/qrhi_platform.h>
14#include <QtCore/qdebug.h>
15#include <QtCore/qpointer.h>
16#include <QtCore/private/qstdweb_p.h>
18#include <emscripten/emscripten.h>
19#include <emscripten/em_js.h>
20#include <emscripten/html5.h>
21#include <emscripten/val.h>
27using namespace emscripten;
28using namespace Qt::Literals;
39 if (!
video) {
return; }
49 : m_videoOutput(videoOutput)
56 if (m_animationFrameRequestId)
57 emscripten_cancel_animation_frame(m_animationFrameRequestId);
62 m_glContextHandle = 0;
63 m_hasWebGLContext =
false;
65 QRhi *rhi = m_videoOutput->m_wasmSink ? m_videoOutput->m_wasmSink->rhi() :
nullptr;
66 if (!rhi || rhi->backend() != QRhi::OpenGLES2)
69 const auto *nativeHandles =
static_cast<
const QRhiGles2NativeHandles *>(rhi->nativeHandles());
70 if (!nativeHandles || !nativeHandles->context)
72 QOpenGLContext *openGLContext = nativeHandles->context;
74 auto tryGetHandleFromSurface = [&]() ->
bool {
75 QSurface *surface = openGLContext->surface();
81 if (QOpenGLContext::currentContext() != openGLContext
82 && !openGLContext->makeCurrent(surface))
84 m_glContextHandle = emscripten_webgl_get_current_context();
85 m_hasWebGLContext = (m_glContextHandle != 0);
86 return m_hasWebGLContext;
89 if (!tryGetHandleFromSurface())
90 qWarning() << Q_FUNC_INFO <<
"could not determine the WebGL context for the current RHI";
96 emscripten::val videoElement = m_videoOutput->currentVideoElement();
102 emscripten::val oneVideoFrame = emscripten::val::take_ownership(
103 static_cast<EM_VAL>(EM_ASM_PTR({
105 return Emval.toHandle(
new VideoFrame(Emval.toValue($0)));
107 return Emval.toHandle(null);
109 }, videoElement.as_handle())));
111 if (oneVideoFrame.isNull() || oneVideoFrame.isUndefined()) {
112 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"VideoFrame not ready yet, skipping";
116 emscripten::val options = emscripten::val::object();
117 emscripten::val rectOptions = emscripten::val::object();
119 int displayWidth = oneVideoFrame[
"displayWidth"].as<
int>();
120 int displayHeight = oneVideoFrame[
"displayHeight"].as<
int>();
122 rectOptions.set(
"width", displayWidth);
123 rectOptions.set(
"height", displayHeight);
124 options.set(
"rect", rectOptions);
126 emscripten::val frameBytesAllocationSize = oneVideoFrame.call<emscripten::val>(
"allocationSize", options);
127 emscripten::val frameBuffer =
128 emscripten::val::global(
"Uint8Array").new_(frameBytesAllocationSize);
130 QPointer<QWasmVideoOutput> videoOutput = m_videoOutput;
132 qstdweb::PromiseCallbacks copyToCallback;
133 copyToCallback.thenFunc = [videoOutput, oneVideoFrame, frameBuffer,
134 displayWidth, displayHeight]
135 (emscripten::val frameLayout)
138 oneVideoFrame.call<emscripten::val>(
"close");
141 if (frameLayout.isNull() || frameLayout.isUndefined()) {
142 qCDebug(qWasmMediaVideoOutput) <<
"theres no frameLayout";
147 const QSize frameSize(displayWidth, displayHeight);
149 QByteArray frameBytes = QByteArray::fromEcmaUint8Array(frameBuffer);
151 QVideoFrameFormat::PixelFormat pixelFormat =
152 fromJsPixelFormat(oneVideoFrame[
"format"].as<
std::string>());
153 if (pixelFormat == QVideoFrameFormat::Format_Invalid)
154 pixelFormat = QVideoFrameFormat::Format_RGBA8888;
155 QVideoFrameFormat frameFormat = QVideoFrameFormat(frameSize, pixelFormat);
157 if (videoOutput->m_useCameraRotation)
158 frameFormat.setRotation(videoOutput->m_rotateBy);
159 if (videoOutput->m_streamFrameRate > 0)
160 frameFormat.setStreamFrameRate(videoOutput->m_streamFrameRate);
161 auto buffer = std::make_unique<QMemoryVideoBuffer>(
162 std::move(frameBytes),
163 frameLayout[0][
"stride"].as<
int>());
166 QVideoFramePrivate::createFrame(std::move(buffer), std::move(frameFormat));
168 if (!videoOutput->m_wasmSink) {
169 qWarning() <<
"ERROR ALERT!! video sink not set";
172 videoOutput->m_wasmSink->setVideoFrame(vFrame);
173 oneVideoFrame.call<emscripten::val>(
"close");
175 copyToCallback.catchFunc = [oneVideoFrame](emscripten::val error)
177 qCDebug(qWasmMediaVideoOutput) <<
"copyTo error"
178 << QString::fromStdString(error[
"name"].as<std::string>())
179 << QString::fromStdString(error[
"message"].as<std::string>());
180 oneVideoFrame.call<emscripten::val>(
"close");
183 qstdweb::Promise::make(oneVideoFrame, u"copyTo"_s, std::move(copyToCallback), frameBuffer, options);
189 emscripten_webgl_make_context_current(m_glContextHandle);
191 GLuint rawTextureId = 0;
192 glGenTextures(1, &rawTextureId);
193 QGlTextureHandle textureHandle{ rawTextureId };
195 glBindTexture(GL_TEXTURE_2D, textureHandle.get());
197 int width = 0, height = 0;
200 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
203 glBindTexture(GL_TEXTURE_2D, 0);
205 if (!textureHandle || width == 0 || height == 0) {
206 qCWarning(qWasmMediaVideoOutput) <<
"VideoFrame upload failed";
210 std::unique_ptr<QHwVideoBuffer> hwBuffer =
211 std::make_unique<QWasmGLTextureVideoBuffer>(
212 std::move(textureHandle), QSize(width, height),
214 m_videoOutput->m_wasmSink ? m_videoOutput->m_wasmSink->rhi() :
nullptr);
216 QVideoFrameFormat frameFormat(QSize(width, height), QVideoFrameFormat::Format_RGBA8888);
217 if (m_videoOutput->m_streamFrameRate > 0)
218 frameFormat.setStreamFrameRate(m_videoOutput->m_streamFrameRate);
220 QVideoFramePrivate::createFrame(std::move(hwBuffer), std::move(frameFormat));
222 m_videoOutput->m_wasmSink->setVideoFrame(vFrame);
227 if (!m_webGLContextChecked) {
228 m_webGLContextChecked =
true;
229 detectWebGLContext();
232 m_videoOutput->detectIosCameraRotation();
235 if (m_animationFrameRequestId)
241 static EM_BOOL (*frame)(
double,
void *) = [](
double frameTime,
void *context) -> EM_BOOL {
245 QWasmVideoFrameGrabber *frameGrabber =
reinterpret_cast<QWasmVideoFrameGrabber *>(context);
246 frameGrabber->m_animationFrameRequestId = 0;
247 QWasmVideoOutput *videoOutput = frameGrabber->m_videoOutput;
248 if (!videoOutput || videoOutput->m_isStopped) {
249 qCWarning(qWasmMediaVideoOutput) <<
"frame loop exit: isStopped=" << (videoOutput ? videoOutput->m_isStopped :
true)
250 <<
"mode=" << (videoOutput ? videoOutput->m_currentVideoMode : -1);
254 if (videoOutput->m_currentVideoMode == QWasmVideoOutput::VideoDisplay
255 && videoOutput->m_currentMediaStatus != QWasmVideoOutput::MediaStatus::LoadedMedia) {
256 frameGrabber->m_animationFrameRequestId = emscripten_request_animation_frame(frame, context);
260 emscripten::val videoElement = videoOutput->currentVideoElement();
261 if (videoElement.isNull() || videoElement.isUndefined()) {
262 qCWarning(qWasmMediaVideoOutput) <<
"frame loop exit: video element null, mode=" << videoOutput->m_currentVideoMode;
266 if (videoElement[
"paused"].as<
bool>() || videoElement[
"ended"].as<
bool>()
267 || videoElement[
"readyState"].as<
int>() < 2) {
268 qCDebug(qWasmMediaVideoOutput) <<
"frame loop waiting: mode=" << videoOutput->m_currentVideoMode
269 <<
"paused=" << videoElement[
"paused"].as<
bool>()
270 <<
"ended=" << videoElement[
"ended"].as<
bool>()
271 <<
"readyState=" << videoElement[
"readyState"].as<
int>();
272 frameGrabber->m_animationFrameRequestId = emscripten_request_animation_frame(frame, context);
276 if (frameGrabber->m_glContextHandle)
277 frameGrabber->processWebGLVideoFrame();
279 frameGrabber->processVideoFrame();
281 frameGrabber->m_animationFrameRequestId = emscripten_request_animation_frame(frame, context);
284 if ((!m_videoOutput->m_isStopped
285 && m_videoOutput->m_video[
"className"].as<
std::string>() ==
"Camera"
286 && m_videoOutput->m_cameraIsReady)
287 || (!m_videoOutput->m_isStopped
290 m_animationFrameRequestId = emscripten_request_animation_frame(frame,
this);
295 if (videoFormat ==
"I420")
296 return QVideoFrameFormat::Format_YUV420P;
299 else if (videoFormat ==
"I422")
300 return QVideoFrameFormat::Format_YUV422P;
303 else if (videoFormat ==
"NV12")
304 return QVideoFrameFormat::Format_NV12;
305 else if (videoFormat ==
"RGBA")
306 return QVideoFrameFormat::Format_RGBA8888;
307 else if (videoFormat ==
"RGBX")
308 return QVideoFrameFormat::Format_RGBX8888;
309 else if (videoFormat ==
"BGRA")
310 return QVideoFrameFormat::Format_BGRA8888;
311 else if (videoFormat ==
"BGRX")
312 return QVideoFrameFormat::Format_BGRX8888;
314 return QVideoFrameFormat::Format_Invalid;
QWasmVideoFrameGrabber(QWasmVideoOutput *videoOutput)
~QWasmVideoFrameGrabber()
std::string m_videoSurfaceId
Combined button and popup list for selecting options.
EM_JS(void, em_texImage2DFromVideo,(const char *videoId, int *widthOut, int *heightOut), { videoId=Number(videoId);widthOut=Number(widthOut);heightOut=Number(heightOut);var gl=GL.currentContext.GLctx;var video=document.getElementById(UTF8ToString(videoId));if(!video) { return;} var frame;try { frame=new VideoFrame(video);} catch(e) { return;} HEAP32[widthOut/4]=frame.displayWidth;HEAP32[heightOut/4]=frame.displayHeight;gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame);frame.close();})