function CmsParser() { this.CMS_FLAG_FORMAT_FOUND = 0x01; this.CMS_FLAG_TRACK_FOUND = 0x02; this.CMS_FLAG_STOP_WHEN_ERROR = 0x04; this.CMS_PART_UNKNOWN = 'na'; this.CMS_PART_H264_i_FRAME = 'i'; this.CMS_PART_H264_p_FRAME = 'p'; this.CMS_PART_AUDIO = 'a'; this.CMS_PART_JPG = 'j'; this.CMS_E_HEADER_FIELD = 0; this.CMS_E_HEADER_END = 1; this.CMS_E_PART_HEADER = 2; this.CMS_E_PART_HEADER_FIELD = 3; this.CMS_E_PART_HEADER_END = 4; this.CMS_E_PART_END = 5; this.CMS_E_CHUNK = 6; this.CMS_E_PARSE_FAIL = 7; this.CMS_HEADER = 0; this.CMS_DETECT_BOUNDARY = 1; this.CMS_PART_HEADER = 2; this.CMS_FINISHED = 3; this.CMS_FAIL = 4; this.flag = 0; this.shift = 0; this.line_buf = new Uint8Array(2000); this.line_buf_ptr = 0; this.state = this.CMS_HEADER; this.cache = new Uint8Array(100); // for boundary this.cached_len = 0; this.is_first_part = true; this.callback_context = null; this.callback = null; this.boundary = null; this.boundary_size = 0; this.boundary_ptr = 0; // boundary = strdup("\r\n----asdfasdf\r\n"); // boundary.length = strlen(boundary); this._header = { hasAudio: false, hasVideo: false }; this.parts = []; this.part = { header: {}, shift: 0, chunks: [] }; }; CmsParser.prototype.fail = function (error) { this.emit(this.CMS_E_PARSE_FAIL, error); if (this.flag & this.CMS_FLAG_STOP_WHEN_ERROR) { this.state = this.CMS_FAIL; return true; } else { this.state = this.CMS_DETECT_BOUNDARY; return false; } } CmsParser.prototype.parse = function (data) { var chunk = 0; for (var i = 0; i < data.length; i++) { var ch = data[i]; ch = String.fromCharCode(ch); this.shift++; switch (this.state) { case this.CMS_FAIL: { if (this.fail("CmsParser parse stoped.")) { return; } break; }; case this.CMS_HEADER: { if (ch === '\r') { } else if (ch === '\n') { if (this.line_buf_ptr === 0) { this.emit(this.CMS_E_HEADER_END); if (!this.boundary) { if (this.fail("Boundary not found.")) { return; } } else if (!(this.flag & this.CMS_FLAG_FORMAT_FOUND)) { if (this.fail("Format not found.")) { return; } } else if (!(this.flag & this.CMS_FLAG_TRACK_FOUND)) { if (this.fail("Track not found.")) { return; } } else { this.state = this.CMS_DETECT_BOUNDARY; this.boundary_ptr = 2; chunk = i + 1; } } else { var s = String.fromCharCode.apply(null, this.line_buf.slice(0, this.line_buf_ptr)); s = s.split(":"); var key = s.shift().trim(); var value = s.join(":").trim(); // console.log("cms header:", key, '=', value); if (key && value) { if (key === "boundary") { this.boundary = "\r\n--" + value + "\r\n"; } else if (key === "format" && value === "cms") { this.flag = this.flag | this.CMS_FLAG_FORMAT_FOUND; } else if (key === "track") { this.flag = this.flag | this.CMS_FLAG_TRACK_FOUND; } var kv = { key: key, value: value }; this.emit(this.CMS_E_HEADER_FIELD, kv); } else { if (this.fail("Invalid key value.")) { return; } } } this.line_buf_ptr = 0; this.line_buf.fill(0); } else { this.line_buf[this.line_buf_ptr] = ch.charCodeAt(0); this.line_buf_ptr++; } break; }; case this.CMS_DETECT_BOUNDARY: { if (!this.boundary) { break; } if (ch == this.boundary[this.boundary_ptr]) { this.boundary_ptr++; } else if (this.boundary_ptr == 1 && ch == this.boundary[this.boundary_ptr - 1]) { } else { if (this.cached_len > 0) { this.emit(this.CMS_E_CHUNK, this.cache.slice(0, this.cached_len)); this.cached_len = 0; } this.boundary_ptr = 0; } if (this.boundary_ptr === this.boundary.length) { if (i - chunk >= (this.boundary.length - this.cached_len)) { var chunk_len = i - chunk - (this.boundary.length - this.cached_len) + 1; this.emit(this.CMS_E_CHUNK, data.slice(chunk, chunk + chunk_len)); } this.cached_len = 0; if (this.is_first_part) { this.is_first_part = false; } else { this.emit(this.CMS_E_PART_END); } this.emit(this.CMS_E_PART_HEADER); this.state = this.CMS_PART_HEADER; this.part = { header: { type: this.CMS_PART_UNKNOWN, track: -1, length: 0, ts: -1 }, shift: this.shift, chunks: [] } chunk = i + 1; this.boundary_ptr = 0; this.line_buf_ptr = 0; this.line_buf[0] = 0; } break; }; case this.CMS_PART_HEADER: { if (ch == '\r') { } else if (ch == '\n') { if (this.line_buf_ptr == 0) { this.emit(this.CMS_E_PART_HEADER_END); this.state = this.CMS_DETECT_BOUNDARY; chunk = i + 1; } else { var s = String.fromCharCode.apply(null, this.line_buf.slice(0, this.line_buf_ptr)).split(":"); var key = s.shift().trim(); var value = s.join(":").trim(); // console.log("part header:", key, '=', value); if (key && value) { if (key === "f") { if (value === "i") { this.part.header.type = this.CMS_PART_H264_i_FRAME; } else if (value === "p") { this.part.header.type = this.CMS_PART_H264_p_FRAME; } else if (value === "a") { this.part.header.type = this.CMS_PART_AUDIO; } else if (value === "j") { this.part.header.type = this.CMS_PART_JPG; } else { console.error("Unknown frame type:", value); } } else if (key === "ts") { this.part.header.ts = parseInt(value, 10); } else if (key === "l") { this.part.header.length = parseInt(value, 10); } else if (key === "t") { this.part.header.track = parseInt(value, 10); } var kv = { key: key, value: value }; this.emit(this.CMS_E_PART_HEADER_FIELD, kv); } else { if (this.fail("Part header Invalid key value.")) { return; } } } this.line_buf_ptr = 0; this.line_buf.fill(0); } else { if (this.line_buf_ptr < this.line_buf.length - 1) { this.line_buf[this.line_buf_ptr] = ch.charCodeAt(0); this.line_buf_ptr++; } else { if (this.fail("Part header line too long.")) { return; } } } break; }; } } if (this.state == this.CMS_DETECT_BOUNDARY) { if (chunk < data.length) { if (this.boundary_ptr > 0) { this.cached_len = this.boundary_ptr; var pos = data.length - this.boundary_ptr; if (pos >= 0) { this.cache = data.slice(pos, this.cached_len); } } this.emit(this.CMS_E_CHUNK, data.slice(chunk, chunk + data.length - chunk - this.boundary_ptr)); } } }; CmsParser.prototype.set_callback = function (cb, context) { this.callback = cb; this.callback_context = context; }; CmsParser.prototype.emit = function (e, data, size) { if (this.callback) { return this.callback(this, e, data, size, this.callback_context); } else { this.cache_data(e, data); } }; CmsParser.prototype.cache_data = function (e, data) { switch (e) { case this.CMS_E_HEADER_FIELD: { var key = data.key; var value = data.value; this._header[key] = value; if (key === 'track') { var tracks = value.split(';'); this._header.tracks = []; for (var t = 0; t < tracks.length; t++) { var fields = tracks[t].split(','); var track = {}; for (var j = 0; j < fields.length; j++) { var f = fields[j].split('='); var key = f.shift(); var value = f.join('='); track[key] = value; if (key === 'codec') { if (value === 'alaw') { this._header.hasAudio = true; this._header.audioTrack = track; } else if (value === 'h264') { this._header.hasVideo = true; this._header.videoTrack = track; } } } this._header.tracks.push(track); } } else if (key === "duration") { this._header.duration = parseInt(value, 10); } break; }; case this.CMS_E_HEADER_END: { this.header = this._header; break; }; case this.CMS_E_PART_HEADER: { break; }; case this.CMS_E_PART_HEADER_FIELD: { break; }; case this.CMS_E_PART_HEADER_END: { break; }; case this.CMS_E_PART_END: { var chunks = this.part.chunks; var size = 0; for (var i = 0; i < chunks.length; i++) { size = size + chunks[i].length; } this.part.data = new ArrayBuffer(size); var data = new Uint8Array(this.part.data); var shift = 0; for (var i = 0; i < chunks.length; i++) { data.set(chunks[i], shift); shift = shift + chunks[i].length; } this.parts.push(this.part); break; }; case this.CMS_E_CHUNK: { this.part.chunks.push(data); break; }; case this.CMS_E_PARSE_FAIL: { break; }; } }; module.exports = CmsParser; // var cms = new CmsParser(); // var fs = require('fs'); // var f = fs.createReadStream("/home/gxy/work/cms/cms/tmp/ttt.cms"); // f.on('end', function (err) { // console.log("end"); // }); // f.on('error', function (err) { // console.error(err); // }); // f.on('data', function (chunk) { // cms.parse(chunk); // if (cms.header) { // console.log(cms.header); // cms.header = null; // } // var parts = cms.parts; // while (parts.length > 0) { // var part = parts.shift(); // console.log(part.header); // var chunks = part.chunks; // var len = 0; // for (var i = 0; i < chunks.length; i++) { // len = len + chunks[i].length; // } // if (len !== part.header.length) { // console.log("mismatch: len=%d part.len=%d", len, part.header.length); // } // } // });