|
|
@@ -33,15 +33,20 @@ var jsmpeg = window.jsmpeg = function( url, opts ) {
|
|
33
|
33
|
this.externalLoadCallback = opts.onload || null;
|
|
34
|
34
|
this.externalDecodeCallback = opts.ondecodeframe || null;
|
|
35
|
35
|
this.externalFinishedCallback = opts.onfinished || null;
|
|
36
|
|
- this.bwFilter = opts.bwFilter || false;
|
|
37
|
36
|
|
|
38
|
37
|
this.customIntraQuantMatrix = new Uint8Array(64);
|
|
39
|
38
|
this.customNonIntraQuantMatrix = new Uint8Array(64);
|
|
40
|
39
|
this.blockData = new Int32Array(64);
|
|
41
|
40
|
this.zeroBlockData = new Int32Array(64);
|
|
42
|
41
|
this.fillArray(this.zeroBlockData, 0);
|
|
43
|
|
-
|
|
44
|
|
- this.canvasContext = this.canvas.getContext('2d');
|
|
|
42
|
+
|
|
|
43
|
+ // use WebGL for YCbCrToRGBA conversion if possible (much faster)
|
|
|
44
|
+ if( !opts.forceCanvas2D && this.initWebGL() ) {
|
|
|
45
|
+ this.renderFrame = this.renderFrameGL;
|
|
|
46
|
+ } else {
|
|
|
47
|
+ this.canvasContext = this.canvas.getContext('2d');
|
|
|
48
|
+ this.renderFrame = this.renderFrame2D;
|
|
|
49
|
+ }
|
|
45
|
50
|
|
|
46
|
51
|
if( url instanceof WebSocket ) {
|
|
47
|
52
|
this.client = url;
|
|
|
@@ -271,7 +276,10 @@ jsmpeg.prototype.load = function( url ) {
|
|
271
|
276
|
that.loadCallback(request.response);
|
|
272
|
277
|
}
|
|
273
|
278
|
};
|
|
274
|
|
- request.onprogress = this.updateLoader.bind(this);
|
|
|
279
|
+
|
|
|
280
|
+ if( !this.gl ) {
|
|
|
281
|
+ request.onprogress = this.updateLoader.bind(this);
|
|
|
282
|
+ }
|
|
275
|
283
|
|
|
276
|
284
|
request.open('GET', url);
|
|
277
|
285
|
request.responseType = "arraybuffer";
|
|
|
@@ -542,13 +550,10 @@ jsmpeg.prototype.initBuffers = function() {
|
|
542
|
550
|
this.canvas.width = this.width;
|
|
543
|
551
|
this.canvas.height = this.height;
|
|
544
|
552
|
|
|
545
|
|
- this.currentRGBA = this.canvasContext.getImageData(0, 0, this.width, this.height);
|
|
546
|
|
-
|
|
547
|
|
- if( this.bwFilter ) {
|
|
548
|
|
- // This fails in IE10; don't use the bwFilter if you need to support it.
|
|
549
|
|
- this.currentRGBA32 = new Uint32Array( this.currentRGBA.data.buffer );
|
|
|
553
|
+ if( !this.gl ) {
|
|
|
554
|
+ this.currentRGBA = this.canvasContext.getImageData(0, 0, this.width, this.height);
|
|
|
555
|
+ this.fillArray(this.currentRGBA.data, 255);
|
|
550
|
556
|
}
|
|
551
|
|
- this.fillArray(this.currentRGBA.data, 255);
|
|
552
|
557
|
};
|
|
553
|
558
|
|
|
554
|
559
|
|
|
|
@@ -617,13 +622,7 @@ jsmpeg.prototype.decodePicture = function(skipOutput) {
|
|
617
|
622
|
|
|
618
|
623
|
|
|
619
|
624
|
if( skipOutput != DECODE_SKIP_OUTPUT ) {
|
|
620
|
|
- if( this.bwFilter ) {
|
|
621
|
|
- this.YToRGBA();
|
|
622
|
|
- }
|
|
623
|
|
- else {
|
|
624
|
|
- this.YCbCrToRGBA();
|
|
625
|
|
- }
|
|
626
|
|
- this.canvasContext.putImageData(this.currentRGBA, 0, 0);
|
|
|
625
|
+ this.renderFrame();
|
|
627
|
626
|
|
|
628
|
627
|
if(this.externalDecodeCallback) {
|
|
629
|
628
|
this.externalDecodeCallback(this, this.canvas);
|
|
|
@@ -725,31 +724,113 @@ jsmpeg.prototype.YCbCrToRGBA = function() {
|
|
725
|
724
|
}
|
|
726
|
725
|
};
|
|
727
|
726
|
|
|
728
|
|
-jsmpeg.prototype.YToRGBA = function() {
|
|
729
|
|
- // Luma only
|
|
730
|
|
- var pY = this.currentY;
|
|
731
|
|
- var pRGBA = this.currentRGBA32;
|
|
732
|
|
-
|
|
733
|
|
- var yIndex = 0;
|
|
734
|
|
- var yNext2Lines = (this.codedWidth - this.width);
|
|
|
727
|
+jsmpeg.prototype.renderFrame2D = function() {
|
|
|
728
|
+ this.YCbCrToRGBA();
|
|
|
729
|
+ this.canvasContext.putImageData(this.currentRGBA, 0, 0);
|
|
|
730
|
+};
|
|
735
|
731
|
|
|
736
|
|
- var rgbaIndex = 0;
|
|
737
|
|
- var cols = this.width;
|
|
738
|
|
- var rows = this.height;
|
|
739
|
732
|
|
|
740
|
|
- var y;
|
|
|
733
|
+// ----------------------------------------------------------------------------
|
|
|
734
|
+// Accelerated WebGL YCbCrToRGBA conversion
|
|
|
735
|
+
|
|
|
736
|
+jsmpeg.prototype.gl = null;
|
|
|
737
|
+jsmpeg.prototype.program = null;
|
|
|
738
|
+jsmpeg.prototype.YTexture = null;
|
|
|
739
|
+jsmpeg.prototype.UTexture = null;
|
|
|
740
|
+jsmpeg.prototype.VTexture = null;
|
|
|
741
|
+
|
|
|
742
|
+jsmpeg.prototype.createTexture = function(index, name) {
|
|
|
743
|
+ var gl = this.gl;
|
|
|
744
|
+ var texture = gl.createTexture();
|
|
|
745
|
+
|
|
|
746
|
+ gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
|
747
|
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
|
748
|
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
|
749
|
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
|
750
|
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
|
751
|
+ gl.uniform1i(gl.getUniformLocation(this.program, name), index);
|
|
|
752
|
+
|
|
|
753
|
+ return texture;
|
|
|
754
|
+};
|
|
741
|
755
|
|
|
742
|
|
- for( var row = 0; row < rows; row++ ) {
|
|
743
|
|
- for( var col = 0; col < cols; col++ ) {
|
|
744
|
|
- y = pY[yIndex++];
|
|
745
|
|
- pRGBA[rgbaIndex++] = 0xff000000 | y << 16 | y << 8 | y;
|
|
746
|
|
- }
|
|
|
756
|
+jsmpeg.prototype.compileShader = function(type, source) {
|
|
|
757
|
+ var gl = this.gl;
|
|
|
758
|
+ var shader = gl.createShader(type);
|
|
|
759
|
+ gl.shaderSource(shader, source);
|
|
|
760
|
+ gl.compileShader(shader);
|
|
|
761
|
+
|
|
|
762
|
+ if( !gl.getShaderParameter(shader, gl.COMPILE_STATUS) ) {
|
|
|
763
|
+ throw new Error(gl.getShaderInfoLog(shader));
|
|
|
764
|
+ }
|
|
747
|
765
|
|
|
748
|
|
- yIndex += yNext2Lines;
|
|
|
766
|
+ return shader;
|
|
|
767
|
+};
|
|
|
768
|
+
|
|
|
769
|
+jsmpeg.prototype.initWebGL = function() {
|
|
|
770
|
+ // attempt to get a webgl context
|
|
|
771
|
+ try {
|
|
|
772
|
+ var gl = this.gl = this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl');
|
|
|
773
|
+ } catch (e) {
|
|
|
774
|
+ return false;
|
|
749
|
775
|
}
|
|
|
776
|
+
|
|
|
777
|
+ if (!gl) {
|
|
|
778
|
+ return false;
|
|
|
779
|
+ }
|
|
|
780
|
+
|
|
|
781
|
+ // setup shaders
|
|
|
782
|
+ this.program = gl.createProgram();
|
|
|
783
|
+ gl.attachShader(this.program, this.compileShader(gl.VERTEX_SHADER, YCBCRTORGBA_VERTEX_SHADER));
|
|
|
784
|
+ gl.attachShader(this.program, this.compileShader(gl.FRAGMENT_SHADER, YCBCRTORGBA_FRAGMENT_SHADER));
|
|
|
785
|
+ gl.linkProgram(this.program);
|
|
|
786
|
+
|
|
|
787
|
+ if( !gl.getProgramParameter(this.program, gl.LINK_STATUS) ) {
|
|
|
788
|
+ throw new Error(gl.getProgramInfoLog(this.program));
|
|
|
789
|
+ }
|
|
|
790
|
+
|
|
|
791
|
+ gl.useProgram(this.program);
|
|
|
792
|
+
|
|
|
793
|
+ // setup textures
|
|
|
794
|
+ this.YTexture = this.createTexture(0, 'YTexture');
|
|
|
795
|
+ this.UTexture = this.createTexture(1, 'UTexture');
|
|
|
796
|
+ this.VTexture = this.createTexture(2, 'VTexture');
|
|
|
797
|
+
|
|
|
798
|
+ // init buffers
|
|
|
799
|
+ this.buffer = gl.createBuffer();
|
|
|
800
|
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
|
|
|
801
|
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]), gl.STATIC_DRAW);
|
|
|
802
|
+
|
|
|
803
|
+ var vertexAttr = gl.getAttribLocation(this.program, 'vertex');
|
|
|
804
|
+ gl.enableVertexAttribArray(vertexAttr);
|
|
|
805
|
+ gl.vertexAttribPointer(vertexAttr, 2, gl.FLOAT, false, 0, 0);
|
|
|
806
|
+
|
|
|
807
|
+ return true;
|
|
750
|
808
|
};
|
|
751
|
809
|
|
|
|
810
|
+jsmpeg.prototype.renderFrameGL = function() {
|
|
|
811
|
+ var gl = this.gl;
|
|
752
|
812
|
|
|
|
813
|
+ // WebGL doesn't like Uint8ClampedArrays, so we have to create a Uint8Array view for
|
|
|
814
|
+ // each plane
|
|
|
815
|
+ var uint8Y = new Uint8Array(this.currentY.buffer),
|
|
|
816
|
+ uint8Cr = new Uint8Array(this.currentCr.buffer),
|
|
|
817
|
+ uint8Cb = new Uint8Array(this.currentCb.buffer);
|
|
|
818
|
+
|
|
|
819
|
+ gl.activeTexture(gl.TEXTURE0);
|
|
|
820
|
+ gl.bindTexture(gl.TEXTURE_2D, this.YTexture);
|
|
|
821
|
+
|
|
|
822
|
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, this.width, this.height, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, uint8Y);
|
|
|
823
|
+
|
|
|
824
|
+ gl.activeTexture(gl.TEXTURE1);
|
|
|
825
|
+ gl.bindTexture(gl.TEXTURE_2D, this.UTexture);
|
|
|
826
|
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, this.halfWidth, this.halfHeight, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, uint8Cr);
|
|
|
827
|
+
|
|
|
828
|
+ gl.activeTexture(gl.TEXTURE2);
|
|
|
829
|
+ gl.bindTexture(gl.TEXTURE_2D, this.VTexture);
|
|
|
830
|
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, this.halfWidth, this.halfHeight, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, uint8Cb);
|
|
|
831
|
+
|
|
|
832
|
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
|
833
|
+};
|
|
753
|
834
|
|
|
754
|
835
|
|
|
755
|
836
|
// ----------------------------------------------------------------------------
|
|
|
@@ -1054,12 +1135,6 @@ jsmpeg.prototype.copyMacroblock = function(motionH, motionV, sY, sCr, sCb ) {
|
|
1054
|
1135
|
}
|
|
1055
|
1136
|
}
|
|
1056
|
1137
|
}
|
|
1057
|
|
-
|
|
1058
|
|
- if( this.bwFilter ) {
|
|
1059
|
|
- // No need to copy chrominance when black&white filter is active
|
|
1060
|
|
- return;
|
|
1061
|
|
- }
|
|
1062
|
|
-
|
|
1063
|
1138
|
|
|
1064
|
1139
|
// Chrominance
|
|
1065
|
1140
|
|
|
|
@@ -2210,7 +2285,39 @@ var
|
|
2210
|
2285
|
START_SLICE_LAST = 0xAF,
|
|
2211
|
2286
|
START_PICTURE = 0x00,
|
|
2212
|
2287
|
START_EXTENSION = 0xB5,
|
|
2213
|
|
- START_USER_DATA = 0xB2;
|
|
|
2288
|
+ START_USER_DATA = 0xB2,
|
|
|
2289
|
+
|
|
|
2290
|
+ // Shaders for accelerated WebGL YCbCrToRGBA conversion
|
|
|
2291
|
+ YCBCRTORGBA_FRAGMENT_SHADER = [
|
|
|
2292
|
+ 'precision highp float;',
|
|
|
2293
|
+ 'uniform sampler2D YTexture;',
|
|
|
2294
|
+ 'uniform sampler2D UTexture;',
|
|
|
2295
|
+ 'uniform sampler2D VTexture;',
|
|
|
2296
|
+ 'varying vec2 texCoord;',
|
|
|
2297
|
+
|
|
|
2298
|
+ 'void main() {',
|
|
|
2299
|
+ 'float y = texture2D(YTexture, texCoord).r;',
|
|
|
2300
|
+ 'float cr = texture2D(UTexture, texCoord).r - 0.5;',
|
|
|
2301
|
+ 'float cb = texture2D(VTexture, texCoord).r - 0.5;',
|
|
|
2302
|
+
|
|
|
2303
|
+ 'gl_FragColor = vec4(',
|
|
|
2304
|
+ 'y + 1.4 * cr,',
|
|
|
2305
|
+ 'y + -0.343 * cb - 0.711 * cr,',
|
|
|
2306
|
+ 'y + 1.765 * cb,',
|
|
|
2307
|
+ '1.0',
|
|
|
2308
|
+ ');',
|
|
|
2309
|
+ '}'
|
|
|
2310
|
+ ].join('\n'),
|
|
|
2311
|
+
|
|
|
2312
|
+ YCBCRTORGBA_VERTEX_SHADER = [
|
|
|
2313
|
+ 'attribute vec2 vertex;',
|
|
|
2314
|
+ 'varying vec2 texCoord;',
|
|
|
2315
|
+
|
|
|
2316
|
+ 'void main() {',
|
|
|
2317
|
+ 'texCoord = vertex;',
|
|
|
2318
|
+ 'gl_Position = vec4((vertex * 2.0 - 1.0) * vec2(1, -1), 0.0, 1.0);',
|
|
|
2319
|
+ '}'
|
|
|
2320
|
+ ].join('\n');
|
|
2214
|
2321
|
|
|
2215
|
2322
|
var MACROBLOCK_TYPE_TABLES = [
|
|
2216
|
2323
|
null,
|