mirror of https://bitbucket.org/ausocean/av.git
mjpeg-player: fomatting and removed bootstrap.
This commit is contained in:
parent
fb11dde938
commit
372865132b
|
@ -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) {
|
||||
|
|
|
@ -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%">
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
1
go.mod
|
@ -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
2
go.sum
|
@ -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=
|
||||
|
|
Loading…
Reference in New Issue