|
|
@@ -30,6 +30,8 @@ var jsmpeg = window.jsmpeg = function( url, opts ) {
|
|
30
|
30
|
this.autoplay = !!opts.autoplay;
|
|
31
|
31
|
this.loop = !!opts.loop;
|
|
32
|
32
|
this.externalLoadCallback = opts.onload || null;
|
|
|
33
|
+ this.bwFilter = opts.bwFilter || false;
|
|
|
34
|
+ this.externalDecodeCallback = opts.ondecodeframe || null;
|
|
33
|
35
|
|
|
34
|
36
|
this.customIntraQuantMatrix = new Uint8Array(64);
|
|
35
|
37
|
this.customNonIntraQuantMatrix = new Uint8Array(64);
|
|
|
@@ -53,6 +55,8 @@ var jsmpeg = window.jsmpeg = function( url, opts ) {
|
|
53
|
55
|
|
|
54
|
56
|
jsmpeg.prototype.waitForIntraFrame = true;
|
|
55
|
57
|
jsmpeg.prototype.socketBufferSize = 512 * 1024; // 512kb each
|
|
|
58
|
+jsmpeg.prototype.onlostconnection = null;
|
|
|
59
|
+
|
|
56
|
60
|
jsmpeg.prototype.initSocketClient = function( client ) {
|
|
57
|
61
|
this.buffer = new BitReader(new ArrayBuffer(this.socketBufferSize));
|
|
58
|
62
|
|
|
|
@@ -119,9 +123,8 @@ jsmpeg.prototype.receiveSocketMessage = function( event ) {
|
|
119
|
123
|
|
|
120
|
124
|
// If we are still here, we found the next picture start code!
|
|
121
|
125
|
|
|
122
|
|
-
|
|
123
|
|
-
|
|
124
|
|
- // Skip picture decoding until we find the first intra frame
|
|
|
126
|
+
|
|
|
127
|
+ // Skip picture decoding until we find the first intra frame?
|
|
125
|
128
|
if( this.waitForIntraFrame ) {
|
|
126
|
129
|
next.advance(10); // skip temporalReference
|
|
127
|
130
|
if( next.getBits(3) == PICTURE_TYPE_I ) {
|
|
|
@@ -138,7 +141,7 @@ jsmpeg.prototype.receiveSocketMessage = function( event ) {
|
|
138
|
141
|
}
|
|
139
|
142
|
|
|
140
|
143
|
|
|
141
|
|
- // Copy the picture chunk over to 'buffer' and schedule decoding.
|
|
|
144
|
+ // Copy the picture chunk over to 'this.buffer' and schedule decoding.
|
|
142
|
145
|
var chunkEnd = ((next.index) >> 3);
|
|
143
|
146
|
|
|
144
|
147
|
if( chunkEnd > next.chunkBegin ) {
|
|
|
@@ -181,9 +184,12 @@ jsmpeg.prototype.didStartRecordingCallback = null;
|
|
181
|
184
|
|
|
182
|
185
|
jsmpeg.prototype.recordBuffers = [];
|
|
183
|
186
|
|
|
|
187
|
+jsmpeg.prototype.canRecord = function(){
|
|
|
188
|
+ return (this.client && this.client.readyState == this.client.OPEN);
|
|
|
189
|
+};
|
|
|
190
|
+
|
|
184
|
191
|
jsmpeg.prototype.startRecording = function(callback) {
|
|
185
|
|
- if( !this.client ) {
|
|
186
|
|
- throw("Can't record when loading from file.");
|
|
|
192
|
+ if( !this.canRecord() ) {
|
|
187
|
193
|
return;
|
|
188
|
194
|
}
|
|
189
|
195
|
|
|
|
@@ -192,6 +198,9 @@ jsmpeg.prototype.startRecording = function(callback) {
|
|
192
|
198
|
this.isRecording = true;
|
|
193
|
199
|
this.recorderWaitForIntraFrame = true;
|
|
194
|
200
|
this.didStartRecordingCallback = callback || null;
|
|
|
201
|
+
|
|
|
202
|
+ this.recordedFrames = 0;
|
|
|
203
|
+ this.recordedSize = 0;
|
|
195
|
204
|
|
|
196
|
205
|
// Fudge a simple Sequence Header for the MPEG file
|
|
197
|
206
|
|
|
|
@@ -300,6 +309,7 @@ jsmpeg.prototype.loadCallback = function(file) {
|
|
300
|
309
|
|
|
301
|
310
|
jsmpeg.prototype.play = function(file) {
|
|
302
|
311
|
if( this.playing ) { return; }
|
|
|
312
|
+ this.targetTime = Date.now();
|
|
303
|
313
|
this.playing = true;
|
|
304
|
314
|
this.scheduleNextFrame();
|
|
305
|
315
|
};
|
|
|
@@ -360,6 +370,7 @@ jsmpeg.prototype.firstSequenceHeader = 0;
|
|
360
|
370
|
jsmpeg.prototype.targetTime = 0;
|
|
361
|
371
|
|
|
362
|
372
|
jsmpeg.prototype.nextFrame = function() {
|
|
|
373
|
+ if( !this.buffer ) { return; }
|
|
363
|
374
|
while(true) {
|
|
364
|
375
|
var code = this.buffer.findNextMPEGStartCode();
|
|
365
|
376
|
|
|
|
@@ -389,9 +400,9 @@ jsmpeg.prototype.nextFrame = function() {
|
|
389
|
400
|
};
|
|
390
|
401
|
|
|
391
|
402
|
jsmpeg.prototype.scheduleNextFrame = function() {
|
|
392
|
|
- var wait = (1000/this.pictureRate) - this.lateTime;
|
|
|
403
|
+ this.lateTime = Date.now() - this.targetTime;
|
|
|
404
|
+ var wait = Math.max(0, (1000/this.pictureRate) - this.lateTime);
|
|
393
|
405
|
this.targetTime = Date.now() + wait;
|
|
394
|
|
-
|
|
395
|
406
|
if( wait < 18 ) {
|
|
396
|
407
|
this.scheduleAnimation();
|
|
397
|
408
|
}
|
|
|
@@ -472,6 +483,7 @@ jsmpeg.prototype.initBuffers = function() {
|
|
472
|
483
|
this.canvas.height = this.height;
|
|
473
|
484
|
|
|
474
|
485
|
this.currentRGBA = this.canvasContext.getImageData(0, 0, this.width, this.height);
|
|
|
486
|
+ this.currentRGBA32 = new Uint32Array( this.currentRGBA.data.buffer );
|
|
475
|
487
|
this.fillArray(this.currentRGBA.data, 255);
|
|
476
|
488
|
};
|
|
477
|
489
|
|
|
|
@@ -541,8 +553,17 @@ jsmpeg.prototype.decodePicture = function(skipOutput) {
|
|
541
|
553
|
|
|
542
|
554
|
|
|
543
|
555
|
if( skipOutput != DECODE_SKIP_OUTPUT ) {
|
|
544
|
|
- this.YCbCrToRGBA();
|
|
|
556
|
+ if( this.bwFilter ) {
|
|
|
557
|
+ this.YToRGBA();
|
|
|
558
|
+ }
|
|
|
559
|
+ else {
|
|
|
560
|
+ this.YCbCrToRGBA();
|
|
|
561
|
+ }
|
|
545
|
562
|
this.canvasContext.putImageData(this.currentRGBA, 0, 0);
|
|
|
563
|
+
|
|
|
564
|
+ if(this.externalDecodeCallback) {
|
|
|
565
|
+ this.externalDecodeCallback(this, this.canvas);
|
|
|
566
|
+ }
|
|
546
|
567
|
}
|
|
547
|
568
|
|
|
548
|
569
|
// If this is a reference picutre then rotate the prediction pointers
|
|
|
@@ -577,10 +598,11 @@ jsmpeg.prototype.YCbCrToRGBA = function() {
|
|
577
|
598
|
var pCr = this.currentCr;
|
|
578
|
599
|
var pRGBA = this.currentRGBA.data;
|
|
579
|
600
|
|
|
580
|
|
-
|
|
581
|
|
-
|
|
582
|
601
|
// Chroma values are the same for each block of 4 pixels, so we proccess
|
|
583
|
602
|
// 2 lines at a time, 2 neighboring pixels each.
|
|
|
603
|
+ // I wish we could use 32bit writes to the RGBA buffer instead of writing
|
|
|
604
|
+ // each byte separately, but we need the automatic clamping of the RGBA
|
|
|
605
|
+ // buffer.
|
|
584
|
606
|
|
|
585
|
607
|
var yIndex1 = 0;
|
|
586
|
608
|
var yIndex2 = this.codedWidth;
|
|
|
@@ -643,6 +665,31 @@ jsmpeg.prototype.YCbCrToRGBA = function() {
|
|
643
|
665
|
}
|
|
644
|
666
|
};
|
|
645
|
667
|
|
|
|
668
|
+jsmpeg.prototype.YToRGBA = function() {
|
|
|
669
|
+ // Luma only
|
|
|
670
|
+
|
|
|
671
|
+ var pY = this.currentY;
|
|
|
672
|
+ var pRGBA = this.currentRGBA32;
|
|
|
673
|
+
|
|
|
674
|
+ var yIndex = 0;
|
|
|
675
|
+ var yNext2Lines = (this.codedWidth - this.width);
|
|
|
676
|
+
|
|
|
677
|
+ var rgbaIndex = 0;
|
|
|
678
|
+ var cols = this.width;
|
|
|
679
|
+ var rows = this.height;
|
|
|
680
|
+
|
|
|
681
|
+ var y;
|
|
|
682
|
+
|
|
|
683
|
+ for( var row = 0; row < rows; row++ ) {
|
|
|
684
|
+ for( var col = 0; col < cols; col++ ) {
|
|
|
685
|
+ y = pY[yIndex++];
|
|
|
686
|
+ pRGBA[rgbaIndex++] = 0xff000000 | y << 16 | y << 8 | y;
|
|
|
687
|
+ }
|
|
|
688
|
+
|
|
|
689
|
+ yIndex += yNext2Lines;
|
|
|
690
|
+ }
|
|
|
691
|
+};
|
|
|
692
|
+
|
|
646
|
693
|
|
|
647
|
694
|
|
|
648
|
695
|
|
|
|
@@ -857,6 +904,7 @@ jsmpeg.prototype.copyMacroblock = function(motionH, motionV, sY, sCr, sCb ) {
|
|
857
|
904
|
H, V, oddH, oddV,
|
|
858
|
905
|
src, dest, last;
|
|
859
|
906
|
|
|
|
907
|
+ // We use 32bit writes here
|
|
860
|
908
|
var dY = this.currentY32;
|
|
861
|
909
|
var dCb = this.currentCb32;
|
|
862
|
910
|
var dCr = this.currentCr32;
|
|
|
@@ -948,7 +996,12 @@ jsmpeg.prototype.copyMacroblock = function(motionH, motionV, sY, sCr, sCb ) {
|
|
948
|
996
|
}
|
|
949
|
997
|
}
|
|
950
|
998
|
|
|
|
999
|
+ if( this.bwFilter ) {
|
|
|
1000
|
+ // No need to copy chrominance when black&white filter is active
|
|
|
1001
|
+ return;
|
|
|
1002
|
+ }
|
|
951
|
1003
|
|
|
|
1004
|
+
|
|
952
|
1005
|
// Chrominance
|
|
953
|
1006
|
|
|
954
|
1007
|
width = this.halfWidth;
|