example program for using a c++ program to stream a webcam in browser

This commit is contained in:
frank 2022-09-06 20:52:34 -04:00
parent 52ef535eac
commit f63cb2bfb2
10 changed files with 693 additions and 1 deletions

View File

@ -71,6 +71,9 @@ Test collision detection between a 2D sprite and other 2D sprites and boxes. Per
Map an image from a rectangle to a circle or from a circle to a rectangle using a shader program. Based on the elliptical grid mapping equations at http://squircular.blogspot.com/2015/09/mapping-circle-to-square.html
### browser webcam
An example for using a C++ program to display a webcam stream in the browser using Emscripten to translate the code from C++ to WebAssembly. Get the frame pixel data from a canvas element, read it into a SPACEBOX object, write the pixel data to an OpenGL texture, and use Emscripten to display the video.
Other libraries
---------------
@ -126,7 +129,7 @@ To build a WASM library that can be used to build an Emscripten version of a SPA
$ find . -iname *.a
./zbar/.libs/libzbar.a
There is a detailed tutorial on using Zbar with Web Assembly at https://barkeywolf.consulting/posts/barcode-scanner-webassembly/
There is a detailed tutorial on using Zbar with WebAssembly at https://barkeywolf.consulting/posts/barcode-scanner-webassembly/
Font
----

View File

@ -0,0 +1,103 @@
# Browser Webcam Test
#######################
# Location parameters #
#######################
# Location of project specific source files
SRC_DIR := ./
# Locations of [SPACEBOX] source and dependencies required to be compiled from source. These locations are configured to match the
# structure of the [SPACEBOX] repository but can be modified as necessary.
SB_DIR := ../../
SB_SRC_DIR := $(SB_DIR)src/
SB_LIB_DIR := $(SB_DIR)lib/
SDLGFX2_DIR := $(SB_LIB_DIR)sdl2-gfx/
GLEW_DIR := $(SB_LIB_DIR)glew/
# C and C++ compiler commands
CC := clang
CXX := clang++
# Location of SDL config program
SDLCONFIG := $(HOME)/local/sdl/bin/sdl2-config
# Edit to point to the location of BPmono.ttf
CREATE_FONT_SYMLINK := ln -nsf $(SB_DIR)"BPmono.ttf" .
#############################
# Based on above parameters #
#############################
SDL_CFLAGS = $(shell $(SDLCONFIG) --cflags)
SDL_LFLAGS := $(shell $(SDLCONFIG) --libs)
SB_H_FILES := $(wildcard $(addprefix $(SB_SRC_DIR),*.hpp))
SB_O_FILES := $(filter-out $(addprefix $(SB_SRC_DIR),filesystem.o),$(SB_H_FILES:.hpp=.o))
SRC_H_FILES := $(wildcard $(addprefix $(SRC_DIR),*.hpp))
SRC_O_FILES := browser_webcam_test.o $(SRC_H_FILES:.hpp=.o)
#####################################################################
# Targets for building [SPACE BOX], dependencies and project source #
#####################################################################
$(SDLGFX2_DIR)%.o: $(SDLGFX2_DIR)%.c $(SDLGFX2_DIR)%.h
$(GLEW_DIR)%.o: $(GLEW_DIR)%.c $(GLEW_DIR)%.h
$(CC) $< $(CFLAGS) -c -o $@
$(SB_SRC_DIR)extension.o : $(addprefix $(SB_SRC_DIR),Box.hpp Segment.hpp Color.hpp filesystem.hpp Pixels.hpp Log.hpp)
$(SB_SRC_DIR)Node.o : $(addprefix $(SB_SRC_DIR),Game.hpp Configuration.hpp Delegate.hpp Display.hpp Input.hpp Box.hpp Audio.hpp Log.hpp)
$(SB_SRC_DIR)Sprite.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Box.hpp Animation.hpp Color.hpp extension.hpp Pixels.hpp Log.hpp)
$(SB_SRC_DIR)Game.o : $(addprefix $(SB_SRC_DIR),extension.hpp Node.hpp Sprite.hpp Recorder.hpp Input.hpp Configuration.hpp \
Delegate.hpp Audio.hpp Log.hpp)
$(SB_SRC_DIR)Animation.o : $(addprefix $(SB_SRC_DIR),Node.hpp Timer.hpp)
$(SB_SRC_DIR)Recorder.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Configuration.hpp Delegate.hpp Animation.hpp extension.hpp)
$(SB_SRC_DIR)Input.o : $(addprefix $(SB_SRC_DIR),Node.hpp Animation.hpp Configuration.hpp Delegate.hpp)
$(SB_SRC_DIR)Configuration.o : $(addprefix $(SB_SRC_DIR),Node.hpp Animation.hpp Log.hpp)
$(SB_SRC_DIR)Delegate.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Input.hpp)
$(SB_SRC_DIR)Display.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Box.hpp Configuration.hpp Delegate.hpp Log.hpp)
$(SB_SRC_DIR)Box.o : $(addprefix $(SB_SRC_DIR),extension.hpp Segment.hpp)
$(SB_SRC_DIR)Segment.o : $(addprefix $(SB_SRC_DIR),extension.hpp Box.hpp)
$(SB_SRC_DIR)Pixels.o : $(addprefix $(SB_SRC_DIR),Box.hpp extension.hpp Log.hpp utility.hpp)
$(SB_SRC_DIR)Audio.o : $(addprefix $(SB_SRC_DIR),Node.hpp Display.hpp Configuration.hpp Box.hpp filesystem.hpp extension.hpp)
$(SB_SRC_DIR)GLObject.o : $(addprefix $(SB_SRC_DIR),Log.hpp)
$(SB_SRC_DIR)Texture.o : $(addprefix $(SB_SRC_DIR),GLObject.hpp filesystem.hpp Log.hpp)
$(SB_SRC_DIR)VBO.o : $(addprefix $(SB_SRC_DIR),Log.hpp GLObject.hpp Attributes.hpp extension.hpp)
$(SB_SRC_DIR)Attributes.o : $(addprefix $(SB_SRC_DIR),Log.hpp extension.hpp)
$(SRC_DIR)Model.o : $(addprefix $(SB_SRC_DIR),extension.hpp Attributes.hpp Texture.hpp utility.hpp)
$(SRC_DIR)*.o : $(SRC_H_FILES) $(SB_H_FILES)
%.o : %.cpp %.hpp
$(CXX) $(CXXFLAGS) $< -c -o $@
#############
# Web build #
#############
# Use Emscripten to output JavaScript and an HTML index page for running in the browser
EMSCRIPTENHOME = $(HOME)/ext/software/emsdk/upstream/emscripten
EMSCRIPTEN_CFLAGS = -O1 -Wall -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS="['png', 'jpg']" -s USE_SDL_TTF=2 -s USE_SDL_MIXER=2 \
-I $(SB_LIB_DIR) -I $(SB_SRC_DIR)
EMSCRIPTEN_LFLAGS = -s MIN_WEBGL_VERSION=2 -s EXPORTED_FUNCTIONS="['_main']" -s ALLOW_MEMORY_GROWTH=1 -s FULL_ES3=1 \
-s LLD_REPORT_UNDEFINED $(wildcard $(addprefix $(HOME)/ext/software/opencv-4.6.0/build_wasm/lib/,*.a)) $(HOME)/ext/software/ZBar/zbar/.libs/libzbar.a \
--bind
EMSCRIPTEN_PRELOADS = --preload-file "BPmono.ttf"@/ --preload-file "shaders/"@/"shaders/" --preload-file "config.json"@/
emscripten : CC = $(EMSCRIPTENHOME)/emcc
emscripten : CXX = $(EMSCRIPTENHOME)/em++
emscripten : CFLAGS = $(EMSCRIPTEN_CFLAGS)
emscripten : CXXFLAGS = $(CFLAGS) --std=c++17
emscripten : $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPrimitives.o) $(SB_O_FILES) $(SRC_O_FILES)
$(CREATE_FONT_SYMLINK)
$(CXX) $^ $(CXXFLAGS) $(EMSCRIPTEN_LFLAGS) $(EMSCRIPTEN_PRELOADS) -o browser_webcam_test.js
#########################
# Clean up object files #
#########################
clean :
-find $(SRC_DIR) -iname "*.o" -delete
rm -f BPmono.ttf browser_webcam_test.data browser_webcam_test.js browser_webcam_test.wasm
clean-all : clean
-find $(SB_SRC_DIR) -iname "*.o" -delete
-find $(SB_LIB_DIR) -iname "*.o" -delete

View File

@ -0,0 +1,166 @@
/* Browser Webcam Test - "Model.cpp"
*
* This is a class for creating an OpenGL model (basically a collection of vertices, optional texture, and optional transformation).
* It contains convenience functions for loading models into OpenGL.
*
* It is copied from the in-development game Gunkiss. It will eventually be merged into [SPACE BOX].
*/
#include "Model.hpp"
/* Default constructor for Model */
Model::Model() {};
/* Construct a Model, adding Attributes each already wrapped in a shared pointer. The attributes should
* be passed as a map with each key being a name and each value being a shared pointer to attributes. */
Model::Model(const std::map<std::string, std::shared_ptr<sb::Attributes>>& attributes_pack)
{
for (auto attributes : attributes_pack)
{
this->attributes(attributes.second, attributes.first);
}
}
/* Construct a Model, adding Attributes, which will each be wrapped in a shared pointer and stored in the
* created object. The attributes should be passed as a map with each key being a name and each value being
* an attributes object. */
Model::Model(const std::map<std::string, sb::Attributes>& attributes_pack)
{
for (auto attributes : attributes_pack)
{
this->attributes(attributes.second, attributes.first);
}
}
/* Construct a new model object by passing a list of names which will be used to initialize
* empty attributes objects with the given names */
Model::Model(const std::initializer_list<std::string>& names)
{
for (const std::string& name : names)
{
this->attributes(sb::Attributes(), name);
}
}
/* Get the entire map of attributes, each wrapped in its shared pointer held by this object.
* Can be used to iterate through the attributes. */
std::map<std::string, std::shared_ptr<sb::Attributes>>& Model::attributes()
{
return model_attributes;
}
/* Get the attributes under name, wrapped in the shared pointer held by this object. This
* function uses the at method of std::map, so name must refer to attributes already
* stored in this model. Use this function to share ownership of the attributes or to gain
* access to the public interface of the attributes. */
std::shared_ptr<sb::Attributes>& Model::attributes(const std::string& name)
{
return attributes().at(name);
}
/* Get the attributes under name, wrapped in the shared pointer held by this object. This
* function uses operator[] or std::map, so this can be used to add new attributes to the
* object if they are wrapped in a shared pointer. */
std::shared_ptr<sb::Attributes>& Model::operator[](const std::string& name)
{
auto element = attributes().find(name);
/* add an empty Attributes at name if it doesn't exist yet */
if (element == attributes().end())
{
attributes(sb::Attributes{}, name);
}
return attributes()[name];
}
/* Assign name to attributes, copy and wrap in a shared pointer. The model can share
* ownership of the created attribute memory with callers that request it. */
void Model::attributes(const sb::Attributes& attributes, const std::string& name)
{
this->attributes(std::make_shared<sb::Attributes>(attributes), name);
}
/* Assign name to attributes and share ownership. */
void Model::attributes(const std::shared_ptr<sb::Attributes>& attributes, const std::string& name)
{
this->attributes()[name] = attributes;
}
/* Enable all attributes. */
void Model::enable()
{
for (const auto& attributes : this->attributes())
{
attributes.second->enable();
}
}
/* Disable all attributes. */
void Model::disable()
{
for (const auto& attributes : this->attributes())
{
attributes.second->disable();
}
}
/* Return a reference to the texture container. */
std::map<std::string, sb::Texture>& Model::textures()
{
return model_textures;
}
/* Get the texture at name. This can be used to read the texture memory, share ownership of it, or
* anything else a Texture object can be used for with direct calls to GL functions. */
sb::Texture& Model::texture(const std::string& name)
{
return textures().at(name);
}
/* Get the default texture. The default texture must have previously been set with the default key as
* the name, which can be done using Model::texture(sb::Texture). */
sb::Texture& Model::texture()
{
return texture(DEFAULT_TEXTURE_NAME);
}
/* Assign name to texture and share ownership. */
void Model::texture(const sb::Texture& texture, const std::string& name)
{
textures()[name] = texture;
}
/* If no name is specified, use the default texture. This can be used to conveniently setup a model
* with only one texture. */
void Model::texture(const sb::Texture& texture)
{
this->texture(texture, DEFAULT_TEXTURE_NAME);
}
/* Get the model's transformation matrix. */
const glm::mat4& Model::transformation() const
{
return model_transformation;
}
/* Set the model's transformation matrix. */
void Model::transformation(const glm::mat4& transformation)
{
model_transformation = transformation;
}
/* Return the size in bytes of the sum of the attributes. */
std::size_t Model::size()
{
std::size_t sum = 0;
for (const auto& attributes : this->attributes())
{
sum += attributes.second->size();
}
return sum;
}
/* Return the transformation matrix. */
Model::operator glm::mat4() const
{
return model_transformation;
}

View File

@ -0,0 +1,81 @@
/* Browser Webcam Test - "Model.hpp"
*
* This is a class for creating an OpenGL model (basically a collection of vertices, optional texture, and optional transformation).
* It contains convenience functions for loading models into OpenGL.
*
* It is copied from the in-development game Gunkiss. It will eventually be merged into [SPACE BOX].
*/
#ifndef MODEL_H_
#define MODEL_H_
/* GL functions */
#if defined(__EMSCRIPTEN__)
#include <GL/glew.h>
#else
#include "glew/glew.h"
#endif
#include <iostream>
#include <string>
#include <map>
#include <memory>
#include <iterator>
#include "glm/glm.hpp"
#include "Attributes.hpp"
#include "Texture.hpp"
class Model
{
private:
inline static const std::string DEFAULT_TEXTURE_NAME = "default";
std::map<std::string, sb::Texture> model_textures;
std::map<std::string, std::shared_ptr<sb::Attributes>> model_attributes;
glm::mat4 model_transformation {1.0f};
public:
Model();
Model(const std::map<std::string, std::shared_ptr<sb::Attributes>>&);
Model(const std::map<std::string, sb::Attributes>&);
Model(const std::initializer_list<std::string>&);
std::map<std::string, std::shared_ptr<sb::Attributes>>& attributes();
std::shared_ptr<sb::Attributes>& attributes(const std::string&);
void attributes(const sb::Attributes&, const std::string&);
void attributes(const std::shared_ptr<sb::Attributes>&, const std::string&);
std::shared_ptr<sb::Attributes>& operator[](const std::string&);
void enable();
void disable();
std::map<std::string, sb::Texture>& textures();
sb::Texture& texture(const std::string&);
sb::Texture& texture();
void texture(const sb::Texture&, const std::string&);
void texture(const sb::Texture&);
const glm::mat4& transformation() const;
void transformation(const glm::mat4&);
std::size_t size();
operator glm::mat4() const;
};
class Plane : public Model
{
public:
inline const static std::shared_ptr<sb::Attributes> position = std::make_shared<sb::Attributes>(sb::Attributes{
{-1.0f, 1.0f}, {1.0f, 1.0f}, {-1.0f, -1.0f},
{1.0f, 1.0f}, {1.0f, -1.0f}, {-1.0f, -1.0f}
});
inline const static std::shared_ptr<sb::Attributes> uv = std::make_shared<sb::Attributes>(sb::Attributes{
{0.0f, 1.0f}, {1.0f, 1.0f}, {0.0f, 0.0f},
{1.0f, 1.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}
});
Plane() : Model(std::map<std::string, std::shared_ptr<sb::Attributes>>({{"position", position}, {"uv", uv}})) {}
};
#endif

View File

@ -0,0 +1,35 @@
Emscripten webcam pixel data test
=================================
This is a demo program for passing image data from an HTML5 canvas object in JavaScript to an OpenGL context in a C++ program. It can be useful, for example, for developing cross-platform applications that use the same codebase to export both desktop and web versions, or for using C++ libraries and code to edit and display images on a web page.
It uses the [SPACEBOX][] engine to set up an SDL + GL environment and create a model conveniently, but it can be ported to just Emscripten.
Setup
-----
### Emscripten
Install the latest Emscripten version to a directory and specify the directory in the Makefile
### SPACE BOX
The [SPACEBOX][] game and interactive application framework is required for setting up SDL + OpenGL. It is being used for convenience in setting up the rendering, but this technique can be used without it. Get it from https://git.nugget.fun/nugget/spacebox and specify the path to it in the Makefile.
Compiling
---------
Run at the root of the directory after setting up Emscripten and SPACEBOX
make emscripten
Running
-------
Run at the root of the directory to create an HTTP server
python -m http.server
Browse to http://localhost:8000 to view the demo
[SPACEBOX]: https://git.nugget.fun/nugget/spacebox

View File

@ -0,0 +1,154 @@
/*
* Browser Webcam Test by frank at shampoo.ooo
*
* Program for testing passing image data from an HTML5 canvas object in JavaScript to an OpenGL context in this C++ program.
* This can be useful, for example, for developing cross-platform applications that use the same codebase to export both desktop
* and web versions, or for using C++ libraries and code to edit and display images on a web page.
*
* It uses the [SPACE BOX] engine (https://git.shampoo.ooo/nugget/spacebox) to set up an SDL + GL environment and create a model
* conveniently, but it can be ported to just Emscripten.
*/
#include <emscripten/bind.h>
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include "Game.hpp"
#include "GLObject.hpp"
#include "VBO.hpp"
#include "Model.hpp"
using namespace emscripten;
/* These variables will be bound to JS. They are placed in the global scope, so they can be read and written by both
* C++ and JS. The following functions are bound to JS so they can be used to write values to the variables.
*/
unsigned int emscripten_heap_offset = 0;
bool new_frame_flag = false;
void flag_frame()
{
new_frame_flag = true;
}
void set_heap_offset(int offset)
{
emscripten_heap_offset = offset;
}
class Browser_Webcam_Test : public Game
{
public:
sb::VAO vao;
sb::VBO vbo;
GLuint flat_program, texture_uniform;
Plane camera_frame_model;
Browser_Webcam_Test()
{
/* The parent constructor initializes SDL and SDL extensions, sets GL attributes and creates a window. */
};
/* Load pixel data from the Emscripten heap into an OpenGL texture. This will be called whenever new frame data is added to the Emscripten heap */
void refresh()
{
/* Address of frame RGBA pixel data on the Emscripten heap (received as an unsigned int and cast to an unsigned 8-bit pointer) */
unsigned char* pos = reinterpret_cast<std::uint8_t*>(emscripten_heap_offset);
/* Print the first four 8-bit values, which should be an RGBA color */
std::cout << "heap address " << emscripten_heap_offset << " first 4 bytes: ";
for (std::size_t ii = 0; ii < 4; ii++)
{
std::cout << static_cast<int>(pos[ii]) << " ";
}
std::cout << std::endl;
// If OpenCV were being used, a cv::Mat could be created:
// frame = cv::Mat(FW, FH, CV_8UC4, pos);;
/* Add a texture to the camera Plane for storing frame image data */
camera_frame_model.texture().load(pos, configuration()["display"]["dimensions"]);
/* Indicate pixel data has finished loading */
new_frame_flag = false;
}
/* Set up GL buffers for attributes. Set up shaders and uniforms. Create the texture that will hold the camera pixel data. */
void load_gl_context()
{
/* [SPACE BOX] creates an SDL GL context and initializes GLEW. */
Game::load_gl_context();
/* Generate a vertex array object ID, bind it as current (requirement of OpenGL) */
vao.generate();
vao.bind();
/* Generate ID for the vertex buffer object that will hold all vertex data. Using one buffer for all attributes, data
* will be copied in one after the other. */
vbo.generate();
vbo.bind();
/* Load, configure, and set GL to use the shader program */
GLuint vertex_shader = load_shader("shaders/flat.vert", GL_VERTEX_SHADER);
GLuint fragment_shader = load_shader("shaders/flat.frag", GL_FRAGMENT_SHADER);
flat_program = glCreateProgram();
glAttachShader(flat_program, vertex_shader);
glAttachShader(flat_program, fragment_shader);
Plane::position->bind(0, flat_program, "in_position");
Plane::uv->bind(1, flat_program, "vertex_uv");
link_shader(flat_program);
glUseProgram(flat_program);
/* Fill VBO with attribute data */
vbo.allocate(camera_frame_model.size(), GL_STATIC_DRAW);
vbo.add(*Plane::position);
vbo.add(*Plane::uv);
/* Set the active texture unit to #0, Get the texture uniform from the shader and set it use texture #0. See
* https://www.khronos.org/opengl/wiki/Sampler_(GLSL)#Binding_textures_to_samplers */
glActiveTexture(GL_TEXTURE0);
texture_uniform = glGetUniformLocation(flat_program, "base_texture");
glUniform1i(texture_uniform, 0);
/* Create a texture the size of the video resolution (defined as 320, 240 in config.json and index.html) */
camera_frame_model.texture(sb::Texture());
camera_frame_model.texture().generate(configuration()["display"]["dimensions"]);
camera_frame_model.texture().bind();
camera_frame_model.enable();
}
/* This gets called every frame by the parent class. Refresh the texture pixel data if a new frame is available.
* Clear the screen, then draw the camera model, which will render the texture. */
void update()
{
/* This flag is set in JS whenever a camera frame is read and stored */
if (new_frame_flag)
{
refresh();
}
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, camera_frame_model.attributes("position")->count());
SDL_GL_SwapWindow(window());
}
};
/* Emscripten will call this function. Create a game object, load its GL context, and run the game. */
int main()
{
Browser_Webcam_Test browser_webcam_test = Browser_Webcam_Test();
browser_webcam_test.load_gl_context();
browser_webcam_test.run();
browser_webcam_test.quit();
return 0;
}
/* This will bind the global functions at the beginning of the file to Emscripten so those values can be set and read by this program */
EMSCRIPTEN_BINDINGS(my_module)
{
function("flag_frame", &flag_frame);
function("set_heap_offset", &set_heap_offset);
}

View File

@ -0,0 +1,6 @@
{
"display": {
"render driver": "opengles3",
"dimensions": [320, 240]
}
}

View File

@ -0,0 +1,98 @@
<!doctype html>
<html>
<body>
<!-- WebGL output will be drawn here through Emscripten -->
<canvas id="canvas"></canvas>
<!-- navigator.mediaDevices.getUserMedia will stream the webcam video directly here for testing -->
<video id="webcam"></video>
<script>
const FPS = 15;
const BPP = 4;
// Direct output of webcam
var video = document.getElementById("webcam");
video.width = 320;
video.height = 240;
// Undisplayed canvas which is used to draw the video frame and then read the pixel data directly
var intermediate = document.createElement("canvas");
intermediate.width = video.width;
intermediate.height = video.height;
var context = intermediate.getContext("2d");
// Indicates whether webcam is opened or not
var streaming = false;
// Address of the webcam frame pixel data on the Emscripten heap
var image_heap_address;
var Module = {
onRuntimeInitialized: function()
{
// Open the webcam and start displaying frames if successfully opened. Allocate space for 32-bit RGBA frame pixel data
// on the Emscripten heap.
navigator.mediaDevices.getUserMedia({video: {width: video.width, height: video.height}, audio: false})
.then(function(stream) {
video.srcObject = stream;
video.play();
streaming = true;
// Get the memory address of the pixel data
image_heap_address = Module._malloc(video.width * video.height * BPP);
// Pass the address to the C++ program
Module.set_heap_offset(image_heap_address);
})
.catch(function(err) {
console.log('Camera Error: ' + err.name + ' ' + err.message);
});
// This function will run continuously, drawing the webcam frame to the intermediate canvas, reading the pixel data,
// storing the data on the heap, and setting the new frame available flag.
function processVideo()
{
try
{
if (streaming)
{
// Draw the webcam frame on a hidden canvas
context.drawImage(video, 0, 0, video.width, video.height);
// Read the pixel data
image_data = context.getImageData(0, 0, video.width, video.height).data;
// Get a memory view object that provides access to the heap at the previously allocated address
image_heap_data = new Uint8Array(Module.HEAPU8.buffer, image_heap_address, video.width * video.height * BPP);
// Write the pixel data to the heap
image_heap_data.set(image_data);
// Flag the C++ that new data is available
Module.flag_frame();
}
// Loop processVideo at roughly the FPS
let begin = Date.now();
let delay = 1000/FPS - (Date.now() - begin);
setTimeout(processVideo, delay);
}
catch (err)
{
console.log(err);
}
}
processVideo();
},
// Tell Emscripten to use this canvas for display
canvas: document.getElementById("canvas")
};
</script>
<!-- This file is built by Emscripten when compiling the program -->
<script src="browser_webcam_test.js"></script>
</body>
</html>

View File

@ -0,0 +1,21 @@
#version 300 es
/* Browser Webcam Test */
/* The precision declaration is required by OpenGL ES */
precision mediump float;
/* Forwarded from the vertex shader */
in vec2 uv;
/* The texture is provided by the program when it sets the uniform value. */
uniform sampler2D base_texture;
/* Setting this to a color value will color the fragment in the output display */
out vec4 myOutputColor;
void main(void)
{
/* Get the color from the texture at the UV coordinates */
myOutputColor = texture(base_texture, uv);
}

View File

@ -0,0 +1,25 @@
#version 300 es
/* Browser Webcam Test */
/* The precision declaration is required by OpenGL ES */
precision mediump float;
/* Values which are bound the VBO. */
in vec2 in_position;
in vec2 vertex_uv;
/* Will be forwarded to the fragment shader */
out vec2 uv;
/* Value is provided by the program when it sets the uniform value. */
uniform mat4 transformation;
void main(void)
{
/* Reflect 2D coordinates over the X-axis to flip the canvas coordinates into GL coordinates and return as a vec4 */
gl_Position = vec4(vec2(1, -1) * in_position, 0, 1);
/* Forward the UV coordinates to the fragment shader. */
uv = vertex_uv;
}