Skip to content

Commit

Permalink
add webcodecs sample
Browse files Browse the repository at this point in the history
  • Loading branch information
dong-heun committed Jul 2, 2024
1 parent 7697ba3 commit 9924707
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 0 deletions.
116 changes: 116 additions & 0 deletions webcodecs/webgl-enc-dec/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<title>WebCodecs demo: Encoding and Decoding</title>
<style>
canvas {
padding: 10px;
background: gold;
margin: 5px;
}
</style>
</head>

<body>
<canvas id="src" width="640" height="360"></canvas>
<div id="dst"></div>

<script>
const foods = ['🍇','🍈','🍉','🍊','🍋','🍌','🍍','🥭','🍎','🍏','🍐','🍑','🍒','🍓','🥝','🍅','🥥','🥑','🍆','🥔','🥕','🌽','🌶️','🥒','🥬','🥦','🧄','🧅','🍄','🥜','🌰',
'🍞','🍞','🥐','🥖','🥨','🥯','🥞','🧇','🧀','🍖','🍗','🥩','🥓','🍔','🍟','🍕','🌭','🥪','🌮','🌯','🥙','🧆','🥚','🍳','🥘','🍲','🥣','🥗','🍿','🧈','🧂',
'🥫','🍱','🍘','🍙','🍚','🍛','🍜','🍝','🍠','🍢','🍣','🍤','🍥','🥮','🍡','🥟','🥠','🥡','🦀','🦞','🦐','🦑','🦪','🍦','🍧','🍨','🍩','🍪','🎂','🍰','🧁',
'🥧','🍫','🍬','🍭','🍮','🍯','🍼','🥛','☕','🍵','🍶','🍾','🍷','🍸','🍹','🍺','🍻','🥂','🥃','🥤','🧃','🧉','🧊'];
function getRandomFood() {
let index = Math.floor(Math.random() * foods.length);
return foods[index];
}

// Draw pretty animation on the source canvas
async function startDrawing() {
let cnv = document.getElementById("src");
var ctx = cnv.getContext('2d');

ctx.fillStyle = "#fff5e6";
let width = cnv.width;
let height = cnv.height;
let cx = width / 2;
let cy = height / 2;
let r = Math.min(width, height) / 5;

ctx.font = '30px Helvetica';
const text = getRandomFood() + "📹📷Hello WebCodecs 🎥🎞️" + getRandomFood();
const size = ctx.measureText(text).width;

let drawOneFrame = function (time) {
let angle = Math.PI * 2 * (time / 5000);
let scale = 1 + 0.3 * Math.sin(Math.PI * 2 * (time / 7000));
ctx.save();
ctx.fillRect(0, 0, width, height);

ctx.translate(cx, cy);
ctx.rotate(angle);
ctx.scale(scale, scale);

ctx.fillStyle = "hsl(" + (angle * 40 ) + ",80%,50%)";
ctx.fillRect(-size / 2, 10, size, 25);

ctx.fillStyle = 'black';
ctx.fillText(text, -size / 2, 0);

ctx.restore();
window.requestAnimationFrame(drawOneFrame);
}
window.requestAnimationFrame(drawOneFrame);
}

function startWorker() {
let worker = new Worker('video-worker.js', { name: "Video worker"});
worker.onmessage = function(e) {
// Recreate worker in case of an error
console.log('Worker error: ' + e.data);
worker.terminate();
//startWorker();
};

// Capture animation track for the source canvas
let src_cnv = document.getElementById("src");
const fps = 20;
let stream = src_cnv.captureStream(fps);
const track = stream.getVideoTracks()[0]
media_processor = new MediaStreamTrackProcessor(track);
const reader = media_processor.readable;

// Create a new destination canvas
const dst_cnv = document.createElement('canvas');
dst_cnv.width = src_cnv.width;
dst_cnv.height = src_cnv.height;
const dst = document.getElementById("dst");
if (dst.firstChild)
dst.removeChild(dst.firstChild);
dst.appendChild(dst_cnv);
let offscreen = dst_cnv.transferControlToOffscreen();
worker.postMessage({
canvas : offscreen,
frame_source : reader,
fps : fps
}, [offscreen, reader]);
}

function main() {
if (!("VideoFrame" in window)) {
document.body.innerHTML = "<h1>WebCodecs API is not supported.</h1>";
return;
}

startDrawing();
startWorker();
}

document.body.onload = main;
</script>

</body>

</html>
115 changes: 115 additions & 0 deletions webcodecs/webgl-enc-dec/video-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
let codec_string = "avc1.42001F";

function reportError(e) {
// Report error to the main thread
console.log(e.message)
postMessage(e.message);
}

function captureAndEncode(frame_source, cnv, fps, processChunk) {
let frame_counter = 0;

const init = {
output: processChunk,
error: reportError
};

const config = {
codec: codec_string,
width: cnv.width,
height: cnv.height,
//bitrate: 1000000,
//avc : { format: "annexb" },
framerate: fps,
hardwareAcceleration : "prefer-hardware",
};

let encoder = new VideoEncoder(init);
encoder.configure(config);

let reader = frame_source.getReader();
async function readFrame () {
const result = await reader.read();
let frame = result.value;

if (encoder.encodeQueueSize < 2) {
frame_counter++;
const insert_keyframe = false; // (frame_counter % 130) == 0;
encoder.encode(frame, { keyFrame: insert_keyframe });
frame.close();
} else {
// Too many frames in flight, encoder is overwhelmed
// let's drop this frame.
console.log("dropping a frame");
frame.close();
}

setTimeout(readFrame, 1);
};

readFrame();
}

function startDecodingAndRendering(cnv) {
let ctx = cnv.getContext("2d");
let ready_frames = [];
let underflow = true;

async function renderFrame() {
if (ready_frames.length == 0) {
underflow = true;
return;
}
let frame = ready_frames.shift();
underflow = false;

ctx.drawImage(frame, 0, 0);
frame.close();

// Immediately schedule rendering of the next frame
setTimeout(renderFrame, 0);
}

function handleFrame(frame) {
ready_frames.push(frame);
if (underflow) {
underflow = false;
setTimeout(renderFrame, 0);
}
}

const init = {
output: handleFrame,
error: reportError
};

let decoder = new VideoDecoder(init);
return decoder;
}

var pre_config = null;
function main(frame_source, canvas, fps) {
let decoder = startDecodingAndRendering(canvas);
function processChunk(chunk, md) {
let config = md.decoderConfig;
if (config) {
if (pre_config == null) {
config.hardwareAcceleration = 'prefer-hardware';
console.log("decoder reconfig", JSON.stringify(config, undefined, 2));
decoder.configure(config);
pre_config = config;
}
}

decoder.decode(chunk);
}
captureAndEncode(frame_source, canvas, fps, processChunk);
}

self.onmessage = async function(e) {
let frame_source = e.data.frame_source;
let canvas = e.data.canvas;
let fps = e.data.fps;

main(frame_source, canvas, fps);
}

0 comments on commit 9924707

Please sign in to comment.