Skip to content

Commit

Permalink
Add WebAssembly mpeg1 video and mp2 audio decoder
Browse files Browse the repository at this point in the history
  • Loading branch information
phoboslab committed Oct 9, 2018
1 parent 64d8e56 commit 47daa9d
Show file tree
Hide file tree
Showing 15 changed files with 3,244 additions and 16 deletions.
78 changes: 76 additions & 2 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,63 @@
#!/bin/sh
uglifyjs \


# Build the .wasm Module first

# Since we're compiling a side module here, so that we can load it without the
# runtime cruft, we have to explicitly compile in support for malloc and
# friends.
# Note memcpy, memmove and memset are explicitly exported, otherwise they will
# be eliminated by the SIDE_MODULE=2 setting - not sure why that happens.
emcc \
src/wasm/mpeg1.c \
src/wasm/mp2.c \
src/wasm/buffer.c \
$EMSCRIPTEN/system/lib/emmalloc.cpp \
$EMSCRIPTEN/system/lib/libc/musl/src/string/memcpy.c \
$EMSCRIPTEN/system/lib/libc/musl/src/string/memmove.c \
$EMSCRIPTEN/system/lib/libc/musl/src/string/memset.c \
-s WASM=1 \
-s SIDE_MODULE=2 \
-s TOTAL_STACK=5242880\
-s USE_PTHREADS=0 \
-s LEGALIZE_JS_FFI=0\
-s NO_FILESYSTEM=1 \
-s DEFAULT_LIBRARY_FUNCS_TO_INCLUDE="[]" \
-s "EXPORTED_FUNCTIONS=[
'_memcpy',
'_memmove',
'_memset',
'_mpeg1_decoder_create',
'_mpeg1_decoder_destroy',
'_mpeg1_decoder_get_write_ptr',
'_mpeg1_decoder_get_index',
'_mpeg1_decoder_set_index',
'_mpeg1_decoder_did_write',
'_mpeg1_decoder_has_sequence_header',
'_mpeg1_decoder_get_frame_rate',
'_mpeg1_decoder_get_coded_size',
'_mpeg1_decoder_get_width',
'_mpeg1_decoder_get_height',
'_mpeg1_decoder_get_y_ptr',
'_mpeg1_decoder_get_cr_ptr',
'_mpeg1_decoder_get_cb_ptr',
'_mpeg1_decoder_decode',
'_mp2_decoder_create',
'_mp2_decoder_destroy',
'_mp2_decoder_get_write_ptr',
'_mp2_decoder_get_index',
'_mp2_decoder_set_index',
'_mp2_decoder_did_write',
'_mp2_decoder_get_left_channel_ptr',
'_mp2_decoder_get_right_channel_ptr',
'_mp2_decoder_get_sample_rate',
'_mp2_decoder_decode']" \
-O3 \
-o jsmpeg.wasm


# Concat all .js sources
cat \
src/jsmpeg.js \
src/video-element.js \
src/player.js \
Expand All @@ -10,8 +68,24 @@ uglifyjs \
src/ts.js \
src/decoder.js \
src/mpeg1.js \
src/mpeg1-wasm.js \
src/mp2.js \
src/mp2-wasm.js \
src/webgl.js \
src/canvas2d.js \
src/webaudio.js \
-o jsmpeg.min.js
src/wasm-module.js \
> jsmpeg.js

# Append the .wasm module to the .js source as base64 string
echo "JSMpeg.WASM_BINARY_INLINED='$(base64 -w 0 jsmpeg.wasm)';" \
>> jsmpeg.js


# Minify
uglifyjs jsmpeg.js -o jsmpeg.min.js

# Cleanup
rm jsmpeg.js
rm jsmpeg.wasm

7 changes: 4 additions & 3 deletions jsmpeg.min.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ BitBuffer.prototype.write = function(buffers) {
else {
this.appendSingleBuffer(buffers);
}

return totalLength;
};

BitBuffer.prototype.appendSingleBuffer = function(buffer) {
Expand Down
26 changes: 21 additions & 5 deletions src/decoder.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var BaseDecoder = function(options) {
this.canPlay = false;

this.collectTimestamps = !options.streaming;
this.bytesWritten = 0;
this.timestamps = [];
this.timestampIndex = 0;

Expand All @@ -14,20 +15,34 @@ var BaseDecoder = function(options) {
Object.defineProperty(this, 'currentTime', {get: this.getCurrentTime});
};

BaseDecoder.prototype.destroy = function() {};

BaseDecoder.prototype.connect = function(destination) {
this.destination = destination;
};

BaseDecoder.prototype.bufferGetIndex = function() {
return this.bits.index;
};

BaseDecoder.prototype.bufferSetIndex = function(index) {
this.bits.index = index;
};

BaseDecoder.prototype.bufferWrite = function(buffers) {
return this.bits.write(buffers);
};

BaseDecoder.prototype.write = function(pts, buffers) {
if (this.collectTimestamps) {
if (this.timestamps.length === 0) {
this.startTime = pts;
this.decodedTime = pts;
}
this.timestamps.push({index: this.bits.byteLength << 3, time: pts});
this.timestamps.push({index: this.bytesWritten << 3, time: pts});
}

this.bits.write(buffers);
this.bytesWritten += this.bufferWrite(buffers);
this.canPlay = true;
};

Expand All @@ -46,11 +61,11 @@ BaseDecoder.prototype.seek = function(time) {

var ts = this.timestamps[this.timestampIndex];
if (ts) {
this.bits.index = ts.index;
this.bufferSetIndex(ts.index);
this.decodedTime = ts.time;
}
else {
this.bits.index = 0;
this.bufferSetIndex(0);
this.decodedTime = this.startTime;
}
};
Expand All @@ -62,8 +77,9 @@ BaseDecoder.prototype.decode = function() {
BaseDecoder.prototype.advanceDecodedTime = function(seconds) {
if (this.collectTimestamps) {
var newTimestampIndex = -1;
var currentIndex = this.bufferGetIndex();
for (var i = this.timestampIndex; i < this.timestamps.length; i++) {
if (this.timestamps[i].index > this.bits.index) {
if (this.timestamps[i].index > currentIndex) {
break;
}
newTimestampIndex = i;
Expand Down
19 changes: 18 additions & 1 deletion src/jsmpeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,23 @@ var JSMpeg = {
array[i] = value;
}
}
}
},

Base64ToArrayBuffer: function(base64) {
var binary = window.atob(base64);
var length = binary.length;
var bytes = new Uint8Array(length);
for (var i = 0; i < length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes.buffer;
},

// The build process may append `JSMpeg.WASM_BINARY_INLINED = base64data;`
// to the minified source.
// If this property is present, jsmpeg will use the inlined binary data
// instead of trying to load a jsmpeg.wasm file via Ajax.
WASM_BINARY_INLINED: null
};

// Automatically create players for all found <div class="jsmpeg"/> elements.
Expand All @@ -103,3 +119,4 @@ else {
document.addEventListener('DOMContentLoaded', JSMpeg.CreateVideoElements);
}


106 changes: 106 additions & 0 deletions src/mp2-wasm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
JSMpeg.Decoder.MP2AudioWASM = (function(){ "use strict";

// Based on kjmp2 by Martin J. Fiedler
// http://keyj.emphy.de/kjmp2/

var MP2WASM = function(options) {
JSMpeg.Decoder.Base.call(this, options);

this.module = options.wasmModule;

this.bufferSize = options.audioBufferSize || 128*1024;
this.bufferMode = options.streaming
? JSMpeg.BitBuffer.MODE.EVICT
: JSMpeg.BitBuffer.MODE.EXPAND;

this.sampleRate = 0;
};

MP2WASM.prototype = Object.create(JSMpeg.Decoder.Base.prototype);
MP2WASM.prototype.constructor = MP2WASM;

MP2WASM.prototype.initializeWasmDecoder = function() {
if (!this.module.instance) {
console.warn('JSMpeg: WASM module not compiled yet');
return;
}
this.instance = this.module.instance;
this.functions = this.module.instance.exports;
this.decoder = this.functions._mp2_decoder_create(this.bufferSize, this.bufferMode);
};

MP2WASM.prototype.destroy = function() {
this.functions._mp2_decoder_destroy(this.decoder);
};

MP2WASM.prototype.bufferGetIndex = function() {
return this.functions._mp2_decoder_get_index(this.decoder);
};

MP2WASM.prototype.bufferSetIndex = function(index) {
this.functions._mp2_decoder_set_index(this.decoder, index);
};

MP2WASM.prototype.bufferWrite = function(buffers) {
if (!this.decoder) {
this.initializeWasmDecoder();
}

var totalLength = 0;
for (var i = 0; i < buffers.length; i++) {
totalLength += buffers[i].length;
}

var ptr = this.functions._mp2_decoder_get_write_ptr(this.decoder, totalLength);
for (var i = 0; i < buffers.length; i++) {
this.instance.heapU8.set(buffers[i], ptr);
ptr += buffers[i].length;
}

this.functions._mp2_decoder_did_write(this.decoder, totalLength);
return totalLength;
};

MP2WASM.prototype.decode = function() {
if (!this.decoder) { return; }

var decodedBytes = this.functions._mp2_decoder_decode(this.decoder);

if (decodedBytes === 0) {
return false;
}

if (!this.sampleRate) {
this.sampleRate = this.functions._mp2_decoder_get_sample_rate(this.decoder);
}

if (this.destination) {
// Create a Float32 View into the modules output channel data
var leftPtr = this.functions._mp2_decoder_get_left_channel_ptr(this.decoder),
rightPtr = this.functions._mp2_decoder_get_right_channel_ptr(this.decoder);

var leftOffset = leftPtr / Float32Array.BYTES_PER_ELEMENT,
rightOffset = rightPtr / Float32Array.BYTES_PER_ELEMENT;

var left = this.instance.heapF32.subarray(leftOffset, leftOffset + MP2WASM.SAMPLES_PER_FRAME),
right = this.instance.heapF32.subarray(rightOffset, rightOffset + MP2WASM.SAMPLES_PER_FRAME);

this.destination.play(this.sampleRate, left, right);
}

this.advanceDecodedTime(MP2WASM.SAMPLES_PER_FRAME / this.sampleRate);
return true;
};


MP2WASM.prototype.getCurrentTime = function() {
var enqueuedTime = this.destination ? this.destination.enqueuedTime : 0;
return this.decodedTime - enqueuedTime;
};

MP2WASM.SAMPLES_PER_FRAME = 1152;

return MP2WASM;

})();

Loading

0 comments on commit 47daa9d

Please sign in to comment.