这篇文章发布于 2023年05月21日,星期日,20:36,归类于 JS实例。 阅读 22256 次, 今日 8 次 11 条评论
by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=10815 鑫空间-鑫生活
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。
一、生成WebM视频
Web端基于图像素材生成webM格式的视频算是比较成熟的技术了,实现也比较简单。
我之前是使用whammy.js实现的:https://github.com/antimatter15/whammy
不过此项目已经多年不更新了,看看时间,七八年了。
对于webP格式的图片序列,whammy会有黑屏的问题,因为21年的时候Chrome修改了 webp 图片的编码方法,从 WEBPVP8 改为了 WEBPVP8X,其并没有进行及时的更新。
不过JPG或者PNG图片序列功能不受影响,大家如果想使用也是可以的。
webm-writer
w3c官方项目demo使用的是更新的 webm-writer.js,这个项目是基于 Whammy 实现的,比较新,最近更新是2年前。
项目地址:https://github.com/thenickdude/webm-writer-js
这里,我就使用webm-writer示意下如何使用纯JS在前端生成webM视频。
您可以狠狠地点击这里:webm-writer.js实现webm视频合成并下载demo
点击页面的“生成webM视频”按钮,就可以在下方看到生成好的webM视频了,点击播放,可以预览效果,点击下载,可以下载该webM视频。
支持Chrome,Firefox以及Safari浏览器(桌面Safari 16+)。
例如,下面的MP4录屏就是我Chrome 112版本下的实现效果(不动点击播放):
代码示意
// 点击按钮的webM生成 button.onclick = function () { // 构造webm生成器 var videoWriter = new WebMWriter({ // 每秒30帧 frameRate: 30 }); // 创建屏幕外 canvas var canvas = document.createElement('canvas'); // 总共50帧canvas绘制 var currentFrame = 1; var maxFrame = 50; for (var index = 1; index <= maxFrame; index++) { currentFrame = index; // 绘制函数略 draw(); videoWriter.addFrame(canvas); // 如果是最后一帧,那就完成 if (currentFrame === maxFrame && complete) { videoWriter.complete().then(function(webMBlob) { var blobUrl = URL.createObjectURL(webMBlob); // blobUrl就是webM视频地址了,可播放,可下载 }); } } };
webM视频合成的实现其实很简单,主要就是下面三步:
- 构造:
var videoWriter = new WebMWriter(options);
- 添加帧:
videoWriter.addFrame(canvas);
- 结束添加,完成生成:
videoWriter.complete().then(function(webMBlob) { video.src = URL.createObjectURL(webMBlob); })
其他
- webm-writer似乎不支持直接使用图像元素作为帧内容添加,具体我没测,我是看源码得出的结论,这个就没有 Whammy 好用。
- webm-writer好像都是用的webP图像进行视频合成的,所以,图像一定会有损失,不可能100%还原。
其实,有了canvas流,借助MediaRecorder API,我们无需使用第三方开源的JS组件,也能实现webM视频的合并,下面是案例,适合摄像头的视频流保存为视频的场景。
function record(canvas, time) { var recordedChunks = []; return new Promise(function (res, rej) { var stream = canvas.captureStream(25 /*fps*/); mediaRecorder = new MediaRecorder(stream, { mimeType: "video/webm; codecs=vp9" }); //数据记录的起始时间 `time || 4000 ms` mediaRecorder.start(time || 4000); mediaRecorder.ondataavailable = function (event) { recordedChunks.push(event.data); //`dataavilable`事件结束后执行,只会执行一次 if (mediaRecorder.state === 'recording') { mediaRecorder.stop(); } } mediaRecorder.onstop = function (event) { var blob = new Blob(recordedChunks, {type: "video/webm" }); var url = URL.createObjectURL(blob); res(url); } }) }
然而,无论是whammy还是webm-writer,虽然用来合成webM视频很方便,但是有个问题,那就是无法添加音频进行,也就是视频合成的时候,没法带声音。
这个问题就是我之前遇到的问题,所以后来寻求了使用ffmpeg.wasm来实现,也就是之前这篇文章“借助ffmpeg.wasm纯前端实现多音频和视频的合成”的由来。
长久以来,我一直以为此事无解,但是,随着浏览器的发展,事情出现了转机,Chrome 94开始支持了WebCodecs API(兼容性见下图),这是个图像、音频和视频编解码API集合,很强。
有了WebCodecs API,纯前端,不依赖任何插件,迅速实现带音乐的视频合成成为了可能,这是本文要介绍的重点。
二、带audio声音的webM合成
实现原理也比较好理解,AudioEncoder编码音频,VideoEncoder编码视频,然后进行合并封装。
其中,编码视频和编码音频相关的API并不完全对等(根据我看一些老的资料,一开始,也就是规范阶段,浏览器还没支持的时候,是近似的,例如音频中还有AudioFrame的概念,现在没有了,只有VideoFrame)。
原理好懂,但是实操细节却不简单。
因为涉及到非常多的音视频概念,和各种API对象,例如,WebCodecs API中的Audio编码有个名叫AudioData的概念,这是新的概念,和Web Audio API中decodeAudioData(audioData)中的audioData参数不是一个东西。
想要完全了解,非一朝一夕,哪怕一周一月,都不太可能,也没有必要。
除非你是专门从事音视频开发的前端。
所以,我们可以借助社区的力量帮助我们上手WebCodecs API的使用。
不过,由于WebCodecs API刚出来没多久,目前学习资源有限,MDN上虽然有对API的介绍,但基本上都看不到example演示,所以,相关的学习与尝试,还是花了我不少时间的。
webm-muxer项目
找到了一个基于WebCodecs API实现的Webm视频合并项目:https://github.com/Vanilagy/webm-muxer
muxer 其实是计算机中的一个术语,指合并,例如将视频文件、音频文件和字幕文件合并为某一个视频格式就是典型的muxer,还有个demuxer,表示拆分。
此项目提供了两个demo,可以将麦克风录音和canvas绘制合并成webm视频,如下截图示意。
不知道是不是我设备的问题,我保存的视频是绿屏。
不过这个不重要,重要的是此demo为我们的需求解决提供了重要的参考。
demo之canvas+mp3=>webm视频
你可以狠狠地点击这里:webm-muxer实现带音乐的webm视频demo
在Chrome浏览器下,点击“生成webm”按钮,稍等数秒,就可以看到合成的webm视频了,如下图所示:
还可以点击右侧的“下载”按钮下载此webM视频。
实现过程概述
1. 构造
构造合并器muxer,音视频解码器,下面这些代码结构都是固定的,一些参数值,例如视频尺寸和帧率根据实际情况自行设置。
// 构造包装器 var muxer = new WebMMuxer.Muxer({ target: new WebMMuxer.ArrayBufferTarget(), video: { codec: 'V_VP9', width: 600, height: 400, frameRate: 30 }, audio: { codec: 'A_OPUS', sampleRate: 48000, numberOfChannels: 1 }, firstTimestampBehavior: 'offset' }); // 音视频编码器,这里使用的是WebCodese API var videoEncoder = new VideoEncoder({ output: (chunk, meta) => muxer.addVideoChunk(chunk, meta), error: e => console.error(e) }); videoEncoder.configure({ codec: 'vp09.00.10.08', width: 600, height: 400, bitrate: 1e6 }); // 音频的 var audioEncoder = new AudioEncoder({ output: (chunk, meta) => muxer.addAudioChunk(chunk, meta), error: e => console.error(e) }); audioEncoder.configure({ codec: 'opus', numberOfChannels: 1, sampleRate: 22050, bitrate: 64000 });
提示:如果遇到不兼容的提示(Input audio buffer is incompatible with codec parameters),一般都是configure()中的sampleRate设置的不对,和音频文件的采样率不匹配。
2. 音频编码
这里是实现的难点,我一开始是尝试直接fetch MP3音频文件流,然后借助AudioEncoder进行编码,结果发现路走不通,总是提示,不是需要的音频数据。
查了很多资料,也未见相关实现案例,隐隐觉得,技术上就不通,不然不会都是借助MediaDevices的getUserMedia()方法实现的例子,也就是麦克风或摄像头捕捉的数据。
所以,我就退而求其次,音频播放的时候,基于播放的音频流进行编码,这也是为何demo页面合成时候,会播放音频的原因。
// 音频资源获取 const myAudio = new Audio(); fetch(audio.src).then(res => { var reader = res.body.getReader(); return reader.read().then(result => { return result; }); }).then(data => { var blob = new Blob([data.value], { type: 'audio/mp3' }); var blobUrl = URL.createObjectURL(blob); // 创建音频对象 myAudio.src = blobUrl; // 隐藏不可见 myAudio.hidden = true; // 静音,避免干扰(静音后,合成的视频也会没声音) // myAudio.muted = true; // 在页面内,方便播放 document.body.append(myAudio); }); // 捕捉播放的音轨 const audioTrack = myAudio.captureStream().getAudioTracks()[0]; // MediaStreamTrackProcessor可以用来生成媒体帧流 let trackProcessor = new MediaStreamTrackProcessor({ track: audioTrack }); // 音频播放,并实时抓取视频流 // 交给webcodecs API进行编码 myAudio.play(); // 编码音频数据 let consumer = new WritableStream({ write(audioData) { if (!audioEncoder) { return; } audioEncoder.encode(audioData); audioData.close(); } }); trackProcessor.readable.pipeTo(consumer);
这里可能是我的积累还不够,如果有谁知道直接fetch音频并encode()的方法,欢迎不吝赐教。
更新于2023年9月20日
找到了直接fetch音频的方法了,可以参见下面的MP4合成demo示意。
3. 视频编码
视频编码案例比较多,相对简单的多。
视频中,有专门的VideoFrame,只需要进行制定每一帧的资源、时间间隔,以及关键帧即可。
可以直接使用canvas作为资源,IMG也是可以的。
demo中的实现示意:
// 创建屏幕外 canvas var canvas = document.createElement('canvas'); canvas.width = 600; canvas.height = 400; // 编码视频数据 var startTime = document.timeline.currentTime; var frameCounter = 0; // handleDraw源码可右键页面查看 // 每次绘制都会走一遍后面的function函数 handleDraw(canvas, function () { // 定义视频帧 let frame = new VideoFrame(canvas, { timestamp: (frameCounter * 1000 / 30) * 1000 }); frameCounter++; // 制定关键帧,这是规范要求,最多多少秒之内一定要有个关键帧的 videoEncoder.encode(frame, { keyFrame: frameCounter % 30 === 0 }); frame.close(); });
4. 结束编码
当视频完全绘制结束,同时确保音频播放到视频时长,可以结束。
async () => { await videoEncoder?.flush(); await audioEncoder?.flush(); muxer.finalize(); };
5. 视频生成
let { buffer } = muxer.target; // buffer就是视频数据 // 我们可以将其作为blobURL地址进行播放或下载 var blobUrl = URL.createObjectURL(new Blob([buffer]));
以上就是实现全部过程。
三、MP4视频的合成
webm-muxer项目的作者还弄了个类似的项目,mp4-muxer,可以辅助我们在Web浏览器中合成MP4视频。
项目地址:https://github.com/Vanilagy/mp4-muxer
语法和webm-muxer项目类似,我就不赘述了,我们直接看demo实现的效果。
您可以狠狠地点击这里:mp4-muxer实现带声音的mp4视频demo
下面的视频就是生成的(点击播放,有音频,注意场合):
虽说代码类似,但也完全不一样。
其中一个区别就是,MP4视频合成对音频文件的质量要求更高。
采样率至少是44100,品质较低的音频是不行的。
可能是mp4a编码格式要求的,为此,我专门下载了个高质量的MP3背景音乐,这是相关的配置:
audioEncoder.configure({ codec: 'mp4a.40.2', numberOfChannels: 2, sampleRate: 44100, bitrate: 128000 });
代码实现逻辑和webm-muxer类似,我就不重复展示了,代码都在demo页面上,都是原生的,非常适合大家学习上手WebCodecs API。
更新于2023-09-20
无需播放音频,直接根据audioData数据合成MP4的demo弄出来了。
您可以狠狠地点击这里:canvas图片序列加声音合成mp4视频demo
1.67s的视频,80ms就完成了,强无敌。
兴奋,激动,ffmpeg再见了。
不带音频的MP4合成
如果不带音频,使用WebCodecs API合成mp4视频的速度极快,秒生成。
有兴趣想要体验的可以狠狠地点击这里:mp4-muxer实现纯画面mp4视频demo
点击按钮后,只需要0.02秒就可以得到视频,快如闪电。
MP4视频所有浏览器,所有操作系统都能播放,因此,上面这个demo可能反而会更加实用。
五、终于结语了
继上一篇使用“WebCodecs API之ImageDecoder解码GIF”已经过去两周了,一直在盘这篇文章,不容易,终于要发布了。
算是目前关于浏览器音视频合成,尤其WebCodecs API这块比较稀缺的内容了。
好东西就是要让大家都知道的,欢迎,点赞。
在学习的过程中,还是发现自己对各种stream、buffer(如sharedArrayBuffer、ringbuffer)、chunk、track等理解还不够深入,还需要多多积累。
以写作为契机,逼迫自己学深入自己未曾深入的领域,是非常高效的一种学习方法,推荐给大家。
好了,就说这么多。
如果你觉得本文的内容对你的学习很有帮助,嗯……买本《CSS新世界》支持下吧。
本文为原创文章,欢迎分享,勿全文转载,如果实在喜欢,可收藏,永不过期,且会及时更新知识点及修正错误,阅读体验也更好。
本文地址:https://www.zhangxinxu.com/wordpress/?p=10815
(本篇完)
- JS改变AudioBuffer音量并下载为新audio音频 (0.303)
- 借助ffmpeg.wasm纯前端实现多音频和视频的合成 (0.261)
- node环境中使用fluent-ffmpeg每隔一秒视频截图 (0.210)
- 使用JS快速获取video视频任意位置的缩略图 (0.162)
- mp4box.js加WebCodecs 解码MP4视频帧并渲染 (0.149)
- 使用JS提取视频中的音频资源 (0.114)
- JS视频解码JSMpeg和Broadway开箱测评 (0.107)
- 剪映APP的视频特效如何在Web中JS实现 (0.089)
- 让所有浏览器支持HTML5 video视频标签 (0.075)
- 腾讯开源的酷炫动画播放解决方案Vap初体验 (0.071)
- 使用纯CSS实现噪点效果 (RANDOM - 0.005)
透明视频不行哦
webm-writer.js 可以导出无背景的视频吗?
可直接使用AudioDecoder来解析音频,进而进行encoder转码操作
请问,有demo示例吗?
感谢提醒,搞定了 https://www.zhangxinxu.com/study/202305/canvas-audio-to-mp4-video-demo.php
大牛
张老师研究的面越来越广了,最近也在看这块,学习学习~
我:你这是浏览器??你这不是~!’!&#*#&……
Election :?
Chromebook:?!
有了这些是不是可以在后台偷偷记录用户的操作
如果是记录本网页:基于DOM的方法早有了,参考各大错误上报框架
如果是录屏:要权限的。
哦哦,没接触过这块