mjpeg-player: fomatting and removed bootstrap.

This commit is contained in:
Trek H 2020-01-02 11:35:43 +10:30
parent fb11dde938
commit 372865132b
7 changed files with 135 additions and 139 deletions

View File

@ -19,7 +19,7 @@ LICENSE
for more details.
You should have received a copy of the GNU General Public License in gpl.txt.
If not, see [GNU licenses](http://www.gnu.org/licenses).
If not, see http://www.gnu.org/licenses.
For hls.js Copyright notice and license, see LICENSE file.
*/
@ -27,11 +27,11 @@ LICENSE
// MTSDemuxer demultiplexes an MPEG-TS stream into its individual streams.
// While it is possible that the MPEG-TS stream may contain many streams,
// this demuxer will result in at most one stream of each type ie. video, audio, id3 metadata.
class MTSDemuxer{
constructor(){
class MTSDemuxer {
constructor() {
this.init();
}
// init initialises MTSDemuxer's state. It can be used to reset an MTSDemuxer instance.
init() {
this.pmtParsed = false;
@ -47,7 +47,7 @@ class MTSDemuxer{
* @param {string} type 'audio' | 'video' | 'id3' | 'text'
* @return {object} MTSDemuxer's internal track model.
*/
static createTrack (type) {
static createTrack(type) {
return {
type,
pid: -1,
@ -56,10 +56,10 @@ class MTSDemuxer{
}
// _getTracks returns this MTSDemuxer's tracks.
_getTracks(){
_getTracks() {
return {
video: this._videoTrack,
audio: this._audioTrack,
video: this._videoTrack,
audio: this._audioTrack,
id3: this._id3Track
};
}
@ -67,7 +67,7 @@ class MTSDemuxer{
// _syncOffset scans the first 'maxScanWindow' bytes and returns an offset to the beginning of the first three MTS packets,
// or -1 if three are not found.
// A TS fragment should contain at least 3 TS packets, a PAT, a PMT, and one PID, each starting with 0x47.
static _syncOffset (data) {
static _syncOffset(data) {
const maxScanWindow = 1000; // 1000 is a reasonable number of bytes to search for the first MTS packets.
const scanWindow = Math.min(maxScanWindow, data.length - 3 * 188);
let i = 0;
@ -100,10 +100,10 @@ class MTSDemuxer{
parsePES = this._parsePES;
const syncOffset = MTSDemuxer._syncOffset(data);
// Don't parse last TS packet if incomplete.
len -= (len + syncOffset) % 188;
// Loop through TS packets.
for (start = syncOffset; start < len; start += 188) {
if (data[start] === 0x47) {
@ -122,84 +122,84 @@ class MTSDemuxer{
offset = start + 4;
}
switch (pid) {
case videoId:
if (pusi) {
if (videoData && (pes = parsePES(videoData)) && pes.pts !== undefined) {
videoTrack.data.push(pes.data);
// TODO: here pes contains data, pts, dts and len. Are all these needed?
case videoId:
if (pusi) {
if (videoData && (pes = parsePES(videoData)) && pes.pts !== undefined) {
videoTrack.data.push(pes.data);
// TODO: here pes contains data, pts, dts and len. Are all these needed?
}
videoData = { data: [], size: 0 };
}
videoData = { data: [], size: 0 };
}
if (videoData) {
videoData.data.push(data.subarray(offset, start + 188));
videoData.size += start + 188 - offset;
}
break;
case audioId:
if (pusi) {
if (audioData && (pes = parsePES(audioData)) && pes.pts !== undefined) {
audioTrack.data.push(pes.data);
if (videoData) {
videoData.data.push(data.subarray(offset, start + 188));
videoData.size += start + 188 - offset;
}
audioData = { data: [], size: 0 };
}
if (audioData) {
audioData.data.push(data.subarray(offset, start + 188));
audioData.size += start + 188 - offset;
}
break;
case id3Id:
if (pusi) {
if (id3Data && (pes = parsePES(id3Data)) && pes.pts !== undefined) {
id3Track.data.push(pes.data);
break;
case audioId:
if (pusi) {
if (audioData && (pes = parsePES(audioData)) && pes.pts !== undefined) {
audioTrack.data.push(pes.data);
}
audioData = { data: [], size: 0 };
}
if (audioData) {
audioData.data.push(data.subarray(offset, start + 188));
audioData.size += start + 188 - offset;
}
break;
case id3Id:
if (pusi) {
if (id3Data && (pes = parsePES(id3Data)) && pes.pts !== undefined) {
id3Track.data.push(pes.data);
}
id3Data = { data: [], size: 0 };
}
if (id3Data) {
id3Data.data.push(data.subarray(offset, start + 188));
id3Data.size += start + 188 - offset;
}
break;
case 0:
if (pusi) {
offset += data[offset] + 1;
}
id3Data = { data: [], size: 0 };
}
if (id3Data) {
id3Data.data.push(data.subarray(offset, start + 188));
id3Data.size += start + 188 - offset;
}
break;
case 0:
if (pusi) {
offset += data[offset] + 1;
}
pmtId = this._pmtId = parsePAT(data, offset);
break;
case pmtId:
if (pusi) {
offset += data[offset] + 1;
}
pmtId = this._pmtId = parsePAT(data, offset);
break;
case pmtId:
if (pusi) {
offset += data[offset] + 1;
}
let parsedPIDs = parsePMT(data, offset);
let parsedPIDs = parsePMT(data, offset);
// Only update track id if track PID found while parsing PMT.
// This is to avoid resetting the PID to -1 in case track PID transiently disappears from the stream,
// this could happen in case of transient missing audio samples for example.
videoId = parsedPIDs.video;
if (videoId > 0) {
videoTrack.pid = videoId;
}
audioId = parsedPIDs.audio;
if (audioId > 0) {
audioTrack.pid = audioId;
}
id3Id = parsedPIDs.id3;
if (id3Id > 0) {
id3Track.pid = id3Id;
}
// Only update track id if track PID found while parsing PMT.
// This is to avoid resetting the PID to -1 in case track PID transiently disappears from the stream,
// this could happen in case of transient missing audio samples for example.
videoId = parsedPIDs.video;
if (videoId > 0) {
videoTrack.pid = videoId;
}
audioId = parsedPIDs.audio;
if (audioId > 0) {
audioTrack.pid = audioId;
}
id3Id = parsedPIDs.id3;
if (id3Id > 0) {
id3Track.pid = id3Id;
}
if (unknownPIDs && !pmtParsed) {
// Reparse from beginning.
unknownPIDs = false;
// We set it to -188, the += 188 in the for loop will reset start to 0.
start = syncOffset - 188;
}
pmtParsed = this.pmtParsed = true;
break;
default:
unknownPIDs = true;
break;
if (unknownPIDs && !pmtParsed) {
// Reparse from beginning.
unknownPIDs = false;
// We set it to -188, the += 188 in the for loop will reset start to 0.
start = syncOffset - 188;
}
pmtParsed = this.pmtParsed = true;
break;
default:
unknownPIDs = true;
break;
}
} else {
console.error('TS packet did not start with 0x47');
@ -214,7 +214,7 @@ class MTSDemuxer{
// Either pesPkts null or PES truncated, keep it for next frag parsing.
videoTrack.pesData = videoData;
}
if (audioData && (pes = parsePES(audioData)) && pes.pts !== undefined) {
audioTrack.data.push(pes.data);
audioTrack.pesData = null;
@ -222,7 +222,7 @@ class MTSDemuxer{
// Either pesPkts null or PES truncated, keep it for next frag parsing.
audioTrack.pesData = audioData;
}
if (id3Data && (pes = parsePES(id3Data)) && pes.pts !== undefined) {
id3Track.data.push(pes.data);
id3Track.pesData = null;
@ -232,16 +232,16 @@ class MTSDemuxer{
}
}
_parsePAT (data, offset) {
_parsePAT(data, offset) {
// Skip the PSI header and parse the first PMT entry.
return (data[offset + 10] & 0x1F) << 8 | data[offset + 11];
// console.log('PMT PID:' + this._pmtId);
}
_parsePMT (data, offset) {
let programInfoLength, pid, result = { audio: -1, video: -1, id3: -1},
sectionLength = (data[offset + 1] & 0x0f) << 8 | data[offset + 2],
tableEnd = offset + 3 + sectionLength - 4;
_parsePMT(data, offset) {
let programInfoLength, pid, result = { audio: -1, video: -1, id3: -1 },
sectionLength = (data[offset + 1] & 0x0f) << 8 | data[offset + 2],
tableEnd = offset + 3 + sectionLength - 4;
// To determine where the table is, we have to figure out how
// long the program info descriptors are.
programInfoLength = (data[offset + 10] & 0x0f) << 8 | data[offset + 11];
@ -253,16 +253,16 @@ class MTSDemuxer{
case 0x1c: // MJPEG
case 0xdb: // SAMPLE-AES AVC.
case 0x1b: // ITU-T Rec. H.264 and ISO/IEC 14496-10 (lower bit-rate video).
if (result.video === -1) {
result.video = pid;
}
if (result.video === -1) {
result.video = pid;
}
break;
case 0xcf: // SAMPLE-AES AAC.
case 0x0f: // ISO/IEC 13818-7 ADTS AAC (MPEG-2 lower bit-rate audio).
case 0xd2: // ADPCM audio.
case 0x03: // ISO/IEC 11172-3 (MPEG-1 audio).
case 0x24:
// console.warn('HEVC stream type found, not supported for now');
// console.warn('HEVC stream type found, not supported for now');
case 0x04: // or ISO/IEC 13818-3 (MPEG-2 halved sample rate audio).
if (result.audio === -1) {
result.audio = pid;
@ -284,7 +284,7 @@ class MTSDemuxer{
return result;
}
_parsePES (stream) {
_parsePES(stream) {
let i = 0, frag, pesFlags, pesPrefix, pesLen, pesHdrLen, pesData, pesPts, pesDts, payloadStartOffset, data = stream.data;
// Safety check.
if (!stream || stream.size === 0) {

View File

@ -6,7 +6,6 @@
<title>Mjpeg Player</title>
<script type="text/javascript" src="main.js"></script>
<script type="module" src="player.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
<body style="height: 100%">

View File

@ -19,36 +19,36 @@ LICENSE
for more details.
You should have received a copy of the GNU General Public License in gpl.txt.
If not, see [GNU licenses](http://www.gnu.org/licenses).
If not, see http://www.gnu.org/licenses.
*/
// MJPEGLexer lexes a byte array containing MJPEG into individual JPEGs.
class MJPEGLexer{
constructor(src){
class MJPEGLexer {
constructor(src) {
this.src = src;
this.off = 0;
}
// read returns the next single frame.
read(){
read() {
// Check if the src can contain at least the start and end flags (4B).
if(this.off+4 > this.src.length){
if (this.off + 4 > this.src.length) {
return null;
}
// Iterate through bytes until the start flag is found.
while(this.src[this.off] != 0xff || this.src[this.off+1] != 0xd8){
while (this.src[this.off] != 0xff || this.src[this.off + 1] != 0xd8) {
this.off++;
if(this.off+4 > this.src.length){
if (this.off + 4 > this.src.length) {
return null;
}
}
// Start after the start flag and loop until the end flag is found.
let end = this.off+2;
while(true){
if(end+2 > this.src.length){
let end = this.off + 2;
while (true) {
if (end + 2 > this.src.length) {
return null;
}
if(this.src[end] == 0xff && this.src[end+1] == 0xd9){
if (this.src[end] == 0xff && this.src[end + 1] == 0xd9) {
break;
}
end++;
@ -56,8 +56,8 @@ class MJPEGLexer{
// Copy the frame's bytes to a new array to return.
// Note: optimally this would return a reference but since we are in a worker thread,
// the main thread doesn't have access to the ArrayBuffer that we are working with.
let frame = this.src.slice(this.off, end+2);
this.off = end+2
let frame = this.src.slice(this.off, end + 2);
this.off = end + 2
return frame;
}
}

View File

@ -19,7 +19,7 @@ LICENSE
for more details.
You should have received a copy of the GNU General Public License in gpl.txt.
If not, see [GNU licenses](http://www.gnu.org/licenses).
If not, see http://www.gnu.org/licenses.
*/
// play will process and play the chosen target file.
@ -32,12 +32,12 @@ function play() {
const player = new Worker("player.js");
let rate = document.getElementById('rate');
if(rate.value && rate.value > 0){
player.postMessage({msg:"setFrameRate", data: rate.value});
if (rate.value && rate.value > 0) {
player.postMessage({ msg: "setFrameRate", data: rate.value });
}
player.onmessage = e => {
switch(e.data.msg){
switch (e.data.msg) {
case "frame":
const blob = new Blob([new Uint8Array(e.data.data)], {
type: 'video/x-motion-jpeg'
@ -56,17 +56,17 @@ function play() {
}
};
switch(input.name.split('.')[1]){
case "mjpeg":
case "mjpg":
player.postMessage({msg:"loadMjpeg", data:event.target.result}, [event.target.result]);
break;
case "ts":
player.postMessage({msg:"loadMtsMjpeg", data:event.target.result}, [event.target.result]);
break;
default:
console.error("unknown file format");
break;
switch (input.name.split('.')[1]) {
case "mjpeg":
case "mjpg":
player.postMessage({ msg: "loadMjpeg", data: event.target.result }, [event.target.result]);
break;
case "ts":
player.postMessage({ msg: "loadMtsMjpeg", data: event.target.result }, [event.target.result]);
break;
default:
console.error("unknown file format");
break;
}
};
reader.onerror = error => reject(error);

View File

@ -19,13 +19,13 @@ LICENSE
for more details.
You should have received a copy of the GNU General Public License in gpl.txt.
If not, see [GNU licenses](http://www.gnu.org/licenses).
If not, see http://www.gnu.org/licenses.
*/
let frameRate = 30;
onmessage = e => {
switch(e.data.msg){
switch (e.data.msg) {
case "setFrameRate":
frameRate = e.data.data;
break;
@ -54,36 +54,36 @@ onmessage = e => {
}
};
class Player{
constructor(buffer){
class Player {
constructor(buffer) {
this.buffer = buffer;
this.frameRate = frameRate;
}
setFrameRate(rate){
setFrameRate(rate) {
this.frameRate = rate;
}
start(){
start() {
let frame = this.buffer.read();
if(frame != null){
postMessage({msg:"frame", data:frame.buffer}, [frame.buffer]);
setTimeout(() => { this.start();}, 1000/this.frameRate);
if (frame != null) {
postMessage({ msg: "frame", data: frame.buffer }, [frame.buffer]);
setTimeout(() => { this.start(); }, 1000 / this.frameRate);
} else {
postMessage({msg:"stop"});
postMessage({ msg: "stop" });
}
}
}
// FrameBuffer allows an array of subarrays (MJPEG frames) to be read one at a time.
class FrameBuffer{
constructor(src){
class FrameBuffer {
constructor(src) {
this.src = src;
this.off = 0;
}
// read returns the next single frame.
read(){
read() {
return this.src[this.off++];
}
}

1
go.mod
View File

@ -12,5 +12,4 @@ require (
github.com/pkg/errors v0.8.1
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e
gocv.io/x/gocv v0.21.0
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

2
go.sum
View File

@ -1,5 +1,3 @@
bitbucket.org/ausocean/iot v1.2.8 h1:m2UhfAbG/6RKPBzY4OJ5S7zLxTmuzyMKPNbu0qaxFIw=
bitbucket.org/ausocean/iot v1.2.8/go.mod h1:wCLOYeEDCxDquneSZ/zTEcKGXZ6uan+6sgZyTMlNVDo=
bitbucket.org/ausocean/iot v1.2.9 h1:3tzgiekH+Z0yXhkwnqBzxxe8qQJ2O7YTkz4s0T6stgw=
bitbucket.org/ausocean/iot v1.2.9/go.mod h1:Q5FwaOKnCty3dVeVtki6DLwYa5vhNpOaeu1lwLyPCg8=
bitbucket.org/ausocean/utils v1.2.11 h1:zA0FOaPjN960ryp8PKCkV5y50uWBYrIxCVnXjwbvPqg=