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/private/qstdweb_p.h>
17#include <emscripten/emscripten.h>
18#include <emscripten/em_js.h>
19#include <emscripten/html5.h>
20#include <emscripten/val.h>
26using namespace emscripten;
27using namespace Qt::Literals;
38 if (!
video) {
return; }
48 : m_videoOutput(videoOutput)
54 m_glContextHandle = 0;
55 m_hasWebGLContext =
false;
57 QRhi *rhi = m_videoOutput->m_wasmSink ? m_videoOutput->m_wasmSink->rhi() :
nullptr;
58 if (!rhi || rhi->backend() != QRhi::OpenGLES2)
61 const auto *nativeHandles =
static_cast<
const QRhiGles2NativeHandles *>(rhi->nativeHandles());
62 if (!nativeHandles || !nativeHandles->context)
64 QOpenGLContext *openGLContext = nativeHandles->context;
66 auto tryGetHandleFromSurface = [&]() ->
bool {
67 QSurface *surface = openGLContext->surface();
73 if (QOpenGLContext::currentContext() != openGLContext
74 && !openGLContext->makeCurrent(surface))
76 m_glContextHandle = emscripten_webgl_get_current_context();
77 m_hasWebGLContext = (m_glContextHandle != 0);
78 return m_hasWebGLContext;
81 if (!tryGetHandleFromSurface())
82 qWarning() << Q_FUNC_INFO <<
"could not determine the WebGL context for the current RHI";
88 emscripten::val videoElement = m_videoOutput->currentVideoElement();
94 emscripten::val oneVideoFrame = emscripten::val::take_ownership(
95 static_cast<EM_VAL>(EM_ASM_PTR({
97 return Emval.toHandle(
new VideoFrame(Emval.toValue($0)));
99 return Emval.toHandle(null);
101 }, videoElement.as_handle())));
103 if (oneVideoFrame.isNull() || oneVideoFrame.isUndefined()) {
104 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"VideoFrame not ready yet, skipping";
108 emscripten::val options = emscripten::val::object();
109 emscripten::val rectOptions = emscripten::val::object();
111 int displayWidth = oneVideoFrame[
"displayWidth"].as<
int>();
112 int displayHeight = oneVideoFrame[
"displayHeight"].as<
int>();
114 rectOptions.set(
"width", displayWidth);
115 rectOptions.set(
"height", displayHeight);
116 options.set(
"rect", rectOptions);
118 emscripten::val frameBytesAllocationSize = oneVideoFrame.call<emscripten::val>(
"allocationSize", options);
119 emscripten::val frameBuffer =
120 emscripten::val::global(
"Uint8Array").new_(frameBytesAllocationSize);
123 qstdweb::PromiseCallbacks copyToCallback;
124 copyToCallback.thenFunc = [videoOutput, oneVideoFrame, frameBuffer,
125 displayWidth, displayHeight]
126 (emscripten::val frameLayout)
128 if (frameLayout.isNull() || frameLayout.isUndefined()) {
129 qCDebug(qWasmMediaVideoOutput) <<
"theres no frameLayout";
134 const QSize frameSize(displayWidth, displayHeight);
136 QByteArray frameBytes = QByteArray::fromEcmaUint8Array(frameBuffer);
138 QVideoFrameFormat::PixelFormat pixelFormat =
139 fromJsPixelFormat(oneVideoFrame[
"format"].as<
std::string>());
140 if (pixelFormat == QVideoFrameFormat::Format_Invalid)
141 pixelFormat = QVideoFrameFormat::Format_RGBA8888;
142 QVideoFrameFormat frameFormat = QVideoFrameFormat(frameSize, pixelFormat);
144 if (videoOutput->m_useCameraRotation)
145 frameFormat.setRotation(videoOutput->m_rotateBy);
146 if (videoOutput->m_streamFrameRate > 0)
147 frameFormat.setStreamFrameRate(videoOutput->m_streamFrameRate);
148 auto buffer = std::make_unique<QMemoryVideoBuffer>(
149 std::move(frameBytes),
150 frameLayout[0][
"stride"].as<
int>());
153 QVideoFramePrivate::createFrame(std::move(buffer), std::move(frameFormat));
155 if (!videoOutput->m_wasmSink) {
156 qWarning() <<
"ERROR ALERT!! video sink not set";
159 videoOutput->m_wasmSink->setVideoFrame(vFrame);
160 oneVideoFrame.call<emscripten::val>(
"close");
162 copyToCallback.catchFunc = [oneVideoFrame](emscripten::val error)
164 qCDebug(qWasmMediaVideoOutput) <<
"copyTo error"
165 << QString::fromStdString(error[
"name"].as<std::string>())
166 << QString::fromStdString(error[
"message"].as<std::string>());
167 oneVideoFrame.call<emscripten::val>(
"close");
170 qstdweb::Promise::make(oneVideoFrame, u"copyTo"_s, std::move(copyToCallback), frameBuffer, options);
176 emscripten_webgl_make_context_current(m_glContextHandle);
178 GLuint rawTextureId = 0;
179 glGenTextures(1, &rawTextureId);
180 QGlTextureHandle textureHandle{ rawTextureId };
182 glBindTexture(GL_TEXTURE_2D, textureHandle.get());
184 int width = 0, height = 0;
187 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
190 glBindTexture(GL_TEXTURE_2D, 0);
192 if (!textureHandle || width == 0 || height == 0) {
193 qCWarning(qWasmMediaVideoOutput) <<
"VideoFrame upload failed";
197 std::unique_ptr<QHwVideoBuffer> hwBuffer =
198 std::make_unique<QWasmGLTextureVideoBuffer>(
199 std::move(textureHandle), QSize(width, height),
201 m_videoOutput->m_wasmSink ? m_videoOutput->m_wasmSink->rhi() :
nullptr);
203 QVideoFrameFormat frameFormat(QSize(width, height), QVideoFrameFormat::Format_RGBA8888);
204 if (m_videoOutput->m_streamFrameRate > 0)
205 frameFormat.setStreamFrameRate(m_videoOutput->m_streamFrameRate);
207 QVideoFramePrivate::createFrame(std::move(hwBuffer), std::move(frameFormat));
209 m_videoOutput->m_wasmSink->setVideoFrame(vFrame);
214 if (!m_webGLContextChecked) {
215 m_webGLContextChecked =
true;
216 detectWebGLContext();
219 m_videoOutput->detectIosCameraRotation();
224 static EM_BOOL (*frame)(
double,
void *) = [](
double frameTime,
void *context) -> EM_BOOL {
228 QWasmVideoFrameGrabber *frameGrabber =
reinterpret_cast<QWasmVideoFrameGrabber *>(context);
229 QWasmVideoOutput *videoOutput = frameGrabber ? frameGrabber->m_videoOutput :
nullptr;
230 if (!videoOutput || videoOutput->m_isStopped) {
231 qCWarning(qWasmMediaVideoOutput) <<
"frame loop exit: isStopped=" << (videoOutput ? videoOutput->m_isStopped :
true)
232 <<
"mode=" << (videoOutput ? videoOutput->m_currentVideoMode : -1);
236 if (videoOutput->m_currentVideoMode == QWasmVideoOutput::VideoDisplay
237 && videoOutput->m_currentMediaStatus != QWasmVideoOutput::MediaStatus::LoadedMedia) {
238 emscripten_request_animation_frame(frame, context);
242 emscripten::val videoElement = videoOutput->currentVideoElement();
243 if (videoElement.isNull() || videoElement.isUndefined()) {
244 qCWarning(qWasmMediaVideoOutput) <<
"frame loop exit: video element null, mode=" << videoOutput->m_currentVideoMode;
248 if (videoElement[
"paused"].as<
bool>() || videoElement[
"ended"].as<
bool>()
249 || videoElement[
"readyState"].as<
int>() < 2) {
250 qCDebug(qWasmMediaVideoOutput) <<
"frame loop waiting: mode=" << videoOutput->m_currentVideoMode
251 <<
"paused=" << videoElement[
"paused"].as<
bool>()
252 <<
"ended=" << videoElement[
"ended"].as<
bool>()
253 <<
"readyState=" << videoElement[
"readyState"].as<
int>();
254 emscripten_request_animation_frame(frame, context);
258 if (frameGrabber->m_glContextHandle)
259 frameGrabber->processWebGLVideoFrame();
261 frameGrabber->processVideoFrame();
263 emscripten_request_animation_frame(frame, context);
266 if ((!m_videoOutput->m_isStopped
267 && m_videoOutput->m_video[
"className"].as<
std::string>() ==
"Camera"
268 && m_videoOutput->m_cameraIsReady)
269 || (!m_videoOutput->m_isStopped
272 emscripten_request_animation_frame(frame,
this);
277 if (videoFormat ==
"I420")
278 return QVideoFrameFormat::Format_YUV420P;
281 else if (videoFormat ==
"I422")
282 return QVideoFrameFormat::Format_YUV422P;
285 else if (videoFormat ==
"NV12")
286 return QVideoFrameFormat::Format_NV12;
287 else if (videoFormat ==
"RGBA")
288 return QVideoFrameFormat::Format_RGBA8888;
289 else if (videoFormat ==
"RGBX")
290 return QVideoFrameFormat::Format_RGBX8888;
291 else if (videoFormat ==
"BGRA")
292 return QVideoFrameFormat::Format_BGRA8888;
293 else if (videoFormat ==
"BGRX")
294 return QVideoFrameFormat::Format_BGRX8888;
296 return QVideoFrameFormat::Format_Invalid;
QWasmVideoFrameGrabber(QWasmVideoOutput *videoOutput)
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();})