Video视频抽帧和WebCodecs API视频抽帧介绍

目录

mp4Box抽帧

ffmpeg抽帧

video元素抽帧

[WebCodecs 核心API](#WebCodecs 核心API)


视频文件是一个容器,里面有很多不同的轨道信息。如:图像、声音、字幕等。而视频图像信息又是由一系列图片序列帧的集合。如10秒时长的视频,假设每秒30帧。那大概有300条图像数据。

怎么得到图像数据,通常有几种方法:

  1. 用video元素指向视频文件,通过将它绘制到canvas上面获取图像数据,如果想要更高级的操作,可以调用video.captureStream()获取媒体流对象。它可以通过getTracks()获取上面所说的视频的轨道信息.有视频和音频数据。利用MediaStreamTrackProcessor对象。可以读取Track解码后的Frame(VideoFrame和AudioFrame)数据.但是这有一个问题就是它不能一次性得到整个视频帧的数据,需要通过播放或seek到某个时间去播放获取对应时间的帧

  2. 直接解封装视频文件的ArrayBuffer数据得到完整帧,这样可以自己再通过timestamp属性去获取自己想要某个时间段的帧,速度是非常快的,不过解封装通常非常的复杂。你需要在这领域对视频各种格式有非常深的了解。一般在我们可以使用现有的库去处理。如用ffmpeg或mp4box.前端使用ffmpeg-wasm版本的话,性能并不好。但它功能很全面,可以应对几乎所有格式,并且它还提供格式转换视频滤波裁剪很多功能。mp4box只能处理mp4格式,用它只解封装,它性能非常快,而且利用它来进行抽帧。我们可以不依赖video元素,这样我们可以将buffer放在worker pool线程池去进行并行一次处理多个视频素材

像下面通过mp4box可以一次性得到整个videoTrack的samples数据,再转换为EncoderVideoChunk,通过VideoDecoder解码,能够非常快抽帧。比使用video元素快很多倍

非完整代码:

mp4Box抽帧

javascript 复制代码
let currentTime=startTime; 
const videoDecoder = new VideoDecoder({
                            output: (videoFrame) => {
                                const timestamp = videoFrame.timestamp / 1e6
                                
                                if (timestamp >= currentTime&&timestamp<=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抽帧

前端版ffmpeg-wasm抽帧比较特殊,需要执行命令转换你想要的格式,因为它是一类似终端执行命令去解析文件写入到它的内存虚拟文件系统中。(你可以用FileSystemAPI 获取物理磁盘权限,真正写入磁盘中,这样可以做到减少内存空间)。所以你要先写入对应的文件。再读取对应文件的arraybuffer数据。如下面,我可以得到图像的yuv420的格式的图像数据

javascript 复制代码
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元素抽帧

javascript 复制代码
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()

WebCodecs 核心API

几个核心对象

VideoFrame可以直接绘制到Canvas上面,进行与其它图层合成,也可以再通过canvas获取ImageDdata转换为ArrayBuffer。VideoFrame.copyTo(buffer) 也可以将ArrayBuffer数据拷贝给目标.但一般videoFrame的图像数据是yuv格式。

  • 图像编码器: VideoEncoder(opens new window)
    可以自定义new VideoFrame(canvas),videoEncoder.encode(videoFrame).将图像数据编码为EncodedVideoChunk 数据,最终封装导出一个处理后的视频文件.(就像你在剪映,把源视频编辑后,导出新的视频)
  • 压缩图像数据: EncodedVideoChunk(opens new window)
    视频的图像压缩数据,通常要使用VideoDecoder对象解压缩成VideoFrame
  • 图像解码器: VideoDecoder
    和VideoEncoder的作用,正好相后。它是解编码的

看上面的流程:假设视频源是一个直播摄像头,它采集了数据、我们需要编码,封装压缩(这样会减少带宽传输和传输速度).数据来到了后台管理,然后解封装,解码,中间可以为视频添加场景或特效。再通类似WEBRTC实时传输协议给客户端去播放.

相关推荐
崔庆才丨静觅10 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606111 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了11 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅11 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅11 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅12 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment12 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅12 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊12 小时前
jwt介绍
前端
爱敲代码的小鱼12 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax