赞
踩
目录
视频文件是一个容器,里面有很多不同的轨道信息。如:图像、声音、字幕等。而视频图像信息又是由一系列图片序列帧的集合。如10秒时长的视频,假设每秒30帧。那大概有300条图像数据。
怎么得到图像数据,通常有几种方法:
像下面通过mp4box可以一次性得到整个videoTrack的samples数据,再转换为EncoderVideoChunk,通过VideoDecoder解码,能够非常快抽帧。比使用video元素快很多倍
非完整代码:
- let currentTime=startTime;
- const videoDecoder = new VideoDecoder({
- output: (videoFrame) => {
- const timestamp = videoFrame.timestamp / 1e6
-
- if (timestamp >= currentTime&×tamp<=endTime) {
-
- ctx.drawImage(videoFrame, 0, 0, canvas.width, canvas.height)
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
- frames.push({ imageData: imageData })
- currentTime += perFrameTime
- }
- videoFrame.close()
-
- },
- error: (e) => {
- console.log('解编码错误:', e)
- }
- })
- videoDecoder.configure({
- codec: videoTrack.codec,
- codedWidth: videoTrack.video.width,
- codedHeight: videoTrack.video.height,
- description: videoDesc,
- // optimizeForLatency: false
- })
- mp4boxfile.onSamples = async (id, user, samples) => {
-
- for (let i = 0, len = samples.length; i < len; i++) {
- const s = samples[i]
- const timestamp = s.cts / s.timescale
- const duration = s.duration / s.timescale
- const type = (s.is_sync ? 'key' : 'delta')
- const videoChunkData = s.data
-
- const videoChunk = new EncodedVideoChunk({
- type: type,
- timestamp: timestamp * 1e6,
- duration: duration * 1e6,
- data: videoChunkData,
-
- })
- videoDecoder.decode(videoChunk)
- }
- // 逻辑判断,当前所有处理完
- if(complete){
- await videoDecoder.flush() // 启动解码
- }
- }
- mp4boxfile.appendBuffer(videoBuffer)
- mp4boxfile.flush();

前端版ffmpeg-wasm抽帧比较特殊,需要执行命令转换你想要的格式,因为它是一类似终端执行命令去解析文件写入到它的内存虚拟文件系统中。(你可以用FileSystemAPI 获取物理磁盘权限,真正写入磁盘中,这样可以做到减少内存空间)。所以你要先写入对应的文件。再读取对应文件的arraybuffer数据。如下面,我可以得到图像的yuv420的格式的图像数据
- async fileToImageData(path) {
- const buffer = await this.ffmpeg.readFile(path)
- return buffer
-
- }
- async fileToImageDataList(basename, imageList) {
- return Promise.all(imageList.filter(d => !d.isDir).map(d => this.fileToImageData(basename + '/' + d.name)))
- }
- // 抽帧
- async extractVideoFrames(inputPath, startTime, endTime) {
- this.clear()
- let fileName = getFileName(inputPath)
- let fileFrameDir = `${fileName}/images`
- await this.createDir(fileFrameDir)
- let images = await this.readDir(fileFrameDir)
- if (!images.length) {
- this.input(inputPath)
- this.everyFrame('1')// 每秒一帧
- this.size('100*100').aspect('1:1').pixelFormat('yuv420p')
- if (startTime) {
- this.toSS(startTime)
- }
- if (endTime) {
- this.to(endTime)
- }
- this.outFile(fileFrameDir + '/frame%03d.bmp')
- await this.run()
- images = await this.readDir(fileFrameDir)
- }
- return await this.fileToImageDataList(fileFrameDir, images)
- }

- video.ontimeupdate=async ()=>{
- if(video.currentTime>endTime||currentTime>endTime||video.ended){
- console.log('完成')
- resolve(frames)
- return
- }
- if(video.currentTime>=currentTime){
- currentTime+=perFrameTime;
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height, imageDataSettings)
- frames.push({
- imageData: imageData
- })
- video.currentTime=currentTime
-
- }
- }
- video.play()

几个核心对象
VideoFrame可以直接绘制到Canvas上面,进行与其它图层合成,也可以再通过canvas获取ImageDdata转换为ArrayBuffer。VideoFrame.copyTo(buffer) 也可以将ArrayBuffer数据拷贝给目标.但一般videoFrame的图像数据是yuv格式。
看上面的流程:假设视频源是一个直播摄像头,它采集了数据、我们需要编码,封装压缩(这样会减少带宽传输和传输速度).数据来到了后台管理,然后解封装,解码,中间可以为视频添加场景或特效。再通类似WEBRTC实时传输协议给客户端去播放.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。