网页已经可以编解码h264,h265 等等常规编码,我们可以使用网页来进行gb28181的录像和解码以及形成文件,当然以前也可以,不过现在更为简便。需求来自于客户端不再进行更新,而使用网页版本来制作gb28181的本地录像(非服务器录像),同时创建链接可以下载,格式位webm。
以下将使用两种方式来存储录像,1 是重新编码,可以叠加数据,2 是不重新编码,我们的格式是flv ,将flv demux后直接存储
VideoTrackReader 根据文档已经deprecated,现在比较新的chrome版本应该使用MediaStreamTrackProcessor,并且有些函数已经过时,已经变成全局函数,比如createImageBitmap,不再属于videoFrame的内部函数,取而代之请使用全局createImageBitmap。不过还是给出一段VideoTrackReader的代码
// VideoTrackReader is deprecated; use MediaStreamTrackProcessor instead.
function startReader(stream) {
if (videoTrackReader) {
console.warn('VideoTrackReader ALREADY exist');
return;
}
const track = stream.getVideoTracks()[0];
videoTrackReader = new VideoTrackReader(track);
videoTrackReader.start(async (videoFrame) => {
if (keepAnimation) {
// 注意videoFrame的createImageBitmap也已经过时
const imageBitmap = await videoFrame.createImageBitmap();
drawCanvasBitmap(imageBitmap);
imageBitmap.close();
}
videoFrame.destroy();
});
}
function startProcessor(stream) {
if (processor) {
console.warn('MediaStreamTrackProcessor ALREADY exist');
return;
}
const track = stream.getVideoTracks()[0];
processor = new MediaStreamTrackProcessor(track);
writable = new WritableStream({
start() {
console.log('Writable start');
},
async write(videoFrame) {
const imageBitmap =await createImageBitmap(videoFrame);
drawCanvasBitmap(imageBitmap);
imageBitmap.close();
if (videoFrame.close) {
videoFrame.close();
}
else {
videoFrame.destroy();
}
},
// stop() {
// console.log('Writable stop');
// },
close() {
console.log('Writable close');
},
abort(reason) {
console.log('Writable abort:', reason);
},
})
processor.readable
.pipeTo(writable);
}
可以使用getUserMedia 来做测试,假定视频已经在网页上播放,制作一个界面
async function startVideo() {
const constraints = { video: true, audio: false };
const stream = await navigator.mediaDevices.getUserMedia(constraints)
.catch(err => {
console.error('Media ERROR:', err);
return;
});
video.srcObject = stream;
await video.play().catch(err => console.error('media ERROR:', err));
}
async function stopVideo() {
if (video.srcObject) {
video.pause();
stopMediaStream(video.srcObject);
video.srcObject = null;
}
}
点击开始录像以后,允许摄像头进行采集,如果没有摄像头,可以使用canvas,随手画上一段动画就行,下面给出一个示例
async function startDrawing() {
const cnv = document.getElementById("src");
const ctx = cnv.getContext("2d", { alpha: false });
ctx.fillStyle = "white";
const { width } = cnv;
const { height } = cnv;
const cx = width / 2;
const cy = height / 2;
const r = Math.min(width, height) / 5;
var drawtime = 0;
const drawOneFrame = function drawOneFrame(time) {
//const angle = Math.PI * 2 * (time / 5000);
//const scale = 1 + 0.3 * Math.sin(Math.PI * 2 * (time / 7000));
ctx.save();
ctx.fillRect(0, 0, width, height);
ctx.translate(cx, cy);
//ctx.rotate(angle);
//ctx.scale(scale, scale);
ctx.font = "30px Verdana";
ctx.fillStyle = "black";
const text = "this is " +drawtime++;
const size = ctx.measureText(text).width;
ctx.fillText(text, -size / 2, 0);
ctx.restore();
window.requestAnimationFrame(drawOneFrame);
};
以上使用不断变化的数字来代替gb28181 的播放,如上图所示。下面为播放中的界面。
使用接收的videoFrame去存储,比如我们是flv方式,那就是demux flv以后,装载成videoFrame,然后解码展现,可以将数据直接存储,这样可以省却编码,那么方式1的优势是什么呢?在里面可以直接叠加数据,比如AI 目标框和文字。原理和方式1 无大的差别,读者自己可以实践。demax后,可以使用EncodedVideoChunk来包装接收到的flv数据,变成了videoFrame, 然后进行pipe,如下面代码所示:
function inputChunk(data, pts, iskey) {
console.log(data, pts, iskey)
const chunk = new EncodedVideoChunk({
timestamp: pts,
type: iskey ? 'key' : 'delta',
data: data
});
decoder.decode(chunk);
}