网站首页 > 技术文章 正文
初探Web Codecs API 三
前言
在之前的文章中,咱们简单的介绍了解码相关的东西,这一节咱们来简单聊聊编码相关的东西。
编码的目的就是为了压缩,去除空间、时间维度的冗余。
这里又不得不提起前面所说的I 帧、P 帧、B 帧和 IDR 帧。
众所周知,视频是连续的图像序列,由连续的帧构成,一帧就是一幅图像。
直接存储图片需要占用大量的存储空间,而且传输也不方便。
为了解决这个问题,于是视频编码出现了,它的作用就是将一张一张的图片压缩成一个视频格式,以便通过特定的解码器对其进行解码、播放。
介绍
其大概过程如下:
编码器
首先来说,我们要明确我们使用什么编码格式,然后再对编码器进行设置。
编码器是 WebCodecs API 中的 VideoEncoder 类, 在我们对其实例化后,就需要通过 configure 方法进行配置;
我们可以通过 isConfigSupported 方法去验证配置是否支持;
Config 的参数如下:
● codec:包含有效编解码器字符串的字符串;
● width: 比特率调整前输出 Encoded VideoChunk 的宽度,单位为像素(可选);
● height:比特率调整前输出 Encoded VideoChunk 的高度,单位为像素(可选);
● displayWidth:显示时每个输出 Encoded VideoChunk 的预期显示宽度,单位为像素(可选);
● displayHeight:显示时每个输出 Encoded VideoChunk 的预期显示高度,单位为像素(可选);
● hardwareAcceleration:编解码器硬件加速方法的提示;
● bitrate:含编码视频的平均比特率,单位为比特每秒;
● framerate:预期的帧速率,单位为每秒帧数;
● alpha:是否丢弃 VideoFrame 输入的 alpha 分量;
● scalabilityMode:WebRTC中定义的编码可伸缩性模式标识符;
● bitrateMode:比特率模式(可选);
● latencyMode: 配置此编解码器延迟行为的值(可选);
关于配置参数的具体内容请查阅 MDN。
我们使用的配置为:
{
codec: "avc1.420034",
width: WIDTH, // 960
height: HEIGHT,// 540
hardwareAcceleration: "prefer-software",
avc: { format: "annexb" }, // 这个好像有点问题
}
这样我们就配置好了编码器
帧
编码器是为了对帧进行编码,那么我们的帧从哪里来呢?
就要用到 Web Codecs API 中的 VideoFrame 类;
创建帧对其实例化就行;
其参数如下:
image:新视频帧的图像数据的图像,可以是 SVGImageElement、HTMLVideoElement、HTMLCanvasElement、ImageBitmap、OffscreenCanvas 或其他 VideoFrame。
options:{
duration: 帧的持续时间,单位为微秒(可选);
timestamp: 帧的时间戳,单位为微秒;
alpha:如何处理alpha通道(可选);
visibleRect:VideoFrame可见矩形的对象(可选);
displayWidth:应用宽高比调整后显示的VideoFrame的宽度,单位为像素(可选);
displayHeight:应用宽高比调整后显示的VideoFrame的高度,单位为像素(可选);
}
我们为了方便 参数只选择了必要的 timestamp;
const frame = new VideoFrame(canvas, { timestamp });
到这里我们的编码所需的图像就准备好了。
调用videoEncoder.encode();
其参数如下:
frame:VideoFrame 对象,
options:{
keyFrame: 是否为编码为一个关键帧;
// 还存在对编码器的配置,与比特率模式(bitrateMode)相关
}
这样,我们就得到了编码后的 Encoded VideoChunk 对象;编码到此结束。
生成视频文件
但是这玩意怎么变成视频文件呢?
编码数据是需要容器保存的;这里又是我们的老朋友 MP4Box.js,帮助我们封装,该部分与编码无关,所以不在我们讨论的范围内,我们只做简单介绍;
this.mp4boxfile = MP4Box.createFile();
this.videoEncodingTrackOptions = {
timescale: 1000000,
brands: ['isom', 'iso2', 'avc1', 'mp41'],
avcDecoderConfigRecord: null,
};
this.encodingVideoTrack = this.mp4boxfile.addTrack(this.videoEncodingTrackOptions);
// 在Encoded的output 中:
const buffer = new ArrayBuffer(chunk.byteLength)
chunk.copyTo(buffer);
that.idx += 1
const videoEncodingSampleOptions = {
duration: 1e6 / 30,
dts: that.idx * 1e6 / 30,
cts: that.idx * 1e6 / 30,
is_sync: chunk.type === 'key',
};
that.mp4boxfile.addSample(that.encodingVideoTrack, buffer, videoEncodingSampleOptions);
// 结束时就可以通过save生成MP4
this.videoEncoder.flush().then(() => {
this.mp4boxfile.flush();
this.mp4boxfile.save('test.mp4')
});
但是这样生成的视频只能在浏览器中播放,原因的话是因为编码器的描述可能不匹配,导致解码的时候找不到正确的解码器,暂时也没有时间去排查和搞清楚,但是在浏览器中直接播放是可以的。
附解码及封装demo 代码;
class VideoDemo {
constructor() {
this.frameIndex = 0
this.init();
}
/**
* @description: 初始化 MP4
* @return {*}
*/
initMp4() {
// MP4Box.js
this.mp4boxfile = MP4Box.createFile();
this.videoEncodingTrackOptions = {
timescale: 1000000,
brands: ['isom', 'iso2', 'avc1', 'mp41'],
avcDecoderConfigRecord: null,
};
this.encodingVideoTrack = this.mp4boxfile.addTrack(this.videoEncodingTrackOptions);
}
/**
* @description: 初始化
* @return {*}
*/
init() {
this.initMp4();
const that = this
this.idx = 0;
this.output = {
output(chunk, metadata) {
const buffer = new ArrayBuffer(chunk.byteLength)
chunk.copyTo(buffer);
that.idx += 1
const videoEncodingSampleOptions = {
duration: 1e6 / 30,
dts: that.idx * 1e6 / 30,
cts: that.idx * 1e6 / 30,
is_sync: chunk.type === 'key',
};
that.mp4boxfile.addSample(that.encodingVideoTrack, buffer, videoEncodingSampleOptions);
},
error(error) {
console.log(error);
},
}
this.config = {
codec: "avc1.420034",
width: 960,
height: 540,
hardwareAcceleration: "prefer-software",
avc: { format: "annexb" },
}
this.videoEncoder = new VideoEncoder(this.output);
this.videoEncoder.configure(this.config);
}
/**
* @description: 需要解码时
* @param {*} canvas
* @param {*} timestamp
* @return {*}
*/
onFrame(canvas, timestamp) {
if (this.frameIndex > 1000) return;
// 假设只要 1000帧
if (this.frameIndex === 1000) {
this.videoEncoder.flush().then(() => {
this.mp4boxfile.flush();
this.mp4boxfile.save('test.mp4')
});
console.log('end')
return true;
}
const frame = new VideoFrame(canvas, { timestamp });
// 30 帧一个关键帧
const insert_keyframe = this.frameIndex % 30 === 0;
this.videoEncoder.encode(frame, { keyFrame: insert_keyframe });
// 销毁帧,避免占用大量内存
frame.close();
this.frameIndex += 1;
}
}
– 欢迎点赞、关注、转发、收藏【我码玄黄】,各大平台同名。
猜你喜欢
- 2024-11-11 一行JS代码实现一个简单的模板字符串替换「实践」
- 2024-11-11 14个 JavaScript 代码优化技巧 页面代码优化方法及技巧
- 2024-11-11 Web 端实时防挡脸弹幕(基于机器学习)
- 2024-11-11 聊聊苹果营销页中几个有趣的交互动画
- 2024-11-11 由浅入深,66条JavaScript面试知识点(一)
- 2024-11-11 前端JS实现字符串/图片/excel文件下载
- 2024-11-11 吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【上】
- 2024-11-11 「前端进阶」高性能渲染十万条数据(时间分片)
- 2024-11-11 我是如何零基础入门前端开发的(2021 版)
- 2024-11-11 简单实现一个虚拟形象系统 虚拟形象制作软件下载
- 标签列表
-
- content-disposition (47)
- nth-child (56)
- math.pow (44)
- 原型和原型链 (63)
- canvas mdn (36)
- css @media (49)
- promise mdn (39)
- readasdataurl (52)
- if-modified-since (49)
- css ::after (50)
- border-image-slice (40)
- flex mdn (37)
- .join (41)
- function.apply (60)
- input type number (64)
- weakmap (62)
- js arguments (45)
- js delete方法 (61)
- blob type (44)
- math.max.apply (51)
- js (44)
- firefox 3 (47)
- cssbox-sizing (52)
- js删除 (49)
- js for continue (56)
- 最新留言
-