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

news2024/11/24 9:03:05

目录

mp4Box抽帧

ffmpeg抽帧

video元素抽帧

WebCodecs 核心API 


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

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

        

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

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

非完整代码: 

mp4Box抽帧

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的格式的图像数据

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元素抽帧

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(opens new window)

        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实时传输协议给客户端去播放.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2036302.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

二叉树——9.找树左下角的值

力扣题目链接 给定一个二叉树&#xff0c;在树的最后一行找到最左边的值。 示例&#xff1a; 输出&#xff1a;7 题干很简单&#xff0c;找到树的最后一行&#xff0c;在该行找到最左边的值&#xff0c;结合完整代码进行分析。 完整代码如下&#xff1a; class Solution:d…

Django后台数据获取展示

​ 续接Django REST Framework&#xff0c;使用Vite构建Vue3的前端项目 1.跨域获取后台接口并展示 安装Axios npm install axios --save 前端查看后端所有定义的接口 // 访问后端定义的可视化Api接口文档 http://ip:8000/docs/ // 定义的学生类信息 http://ip:8000/api/v1…

Springboot发邮件如何配置SMTP服务器信息?

Springboot发邮件安全性考虑&#xff1f;如何用Springboot发信&#xff1f; 在 SpringBoot中配置邮件发送功能相对简单&#xff0c;但需要正确设置 SMTP 服务器信息。AokSend将详细介绍如何在 SpringBoot应用程序中配置 SMTP 服务器信息&#xff0c;以实现邮件发送功能。 Spr…

如何构建一个高效的编程学习笔记系统(万字总结)

你是否曾经在编程学习的海洋中迷失方向&#xff1f;是否感觉自己学了很多&#xff0c;却总是记不住关键知识点&#xff1f;别担心&#xff0c;今天我们将一起探索一种革命性的笔记方法&#xff0c;它将彻底改变你的学习体验&#xff01; 目录 引言&#xff1a;为什么我们需要…

鸿蒙(API 12 Beta3版)【本地媒体会话概述】 音视频播控服务

交互过程 本地媒体会话的数据源均在设备本地&#xff0c;交互过程如图所示。 此过程中涉及两大角色&#xff0c;媒体会话提供方和媒体会话控制方。 说明 媒体会话控制方为系统应用&#xff0c;三方应用可以成为媒体会话提供方。 本地媒体会话中&#xff0c;媒体会话提供方通…

[大模型实战] DAMODEL云算力平台部署LLama3.1大语言模型

[大模型实战] DAMODEL云算力平台部署LLama3.1大语言模型 目录 一、LLama3.1二、DAMODEL云算力平台2.1 提供的服务2.1.1 AI训练2.1.2 AI推理2.1.3 高性能计算2.1.4 图像&#xff0f;视频渲染2.1.5 定制化部署 2.2 支持的GPU 三、在DAMODEL部署LLama3.13.1 在DAMODEL创建实例&…

【案例43】打开节点-后台任务日志优化案例

问题现象 通过SPR日志发现 打开节点-后台任务日志节点sql调用严重。 通过nmc查看&#xff0c;后台线程耗时20s &#xff0c;基本都卡在sql层面 一直在执行如下sql selectl.pk_workingtasklog frompub_workingtasklog l inner joinpub_alertregistry ron l.pkregistry r.pk_a…

Black Forest Labs 的 Flux——文本转图像模型的下一个飞跃,它比 Midjourney 更好吗?

一、前言 Black Forest Labs是开创性稳定扩散模型的团队&#xff0c;现已发布Flux——一套最先进的模型&#xff0c;有望重新定义 AI 生成图像的功能。但 Flux 是否真正代表了该领域的飞跃&#xff1f;它与 Midjourney 等行业领导者相比如何&#xff1f;让我们深入探索 Flux 的…

【Kubernetes】Service 概念与实战

Service 概念与实战 1.通过 Service 向外部暴露 Pod2.Service 的多端口设置3.集群内部的 DNS 服务4.无头 Service 在 Kubernetes 中部署的应用可能对应一个或者多个 Pod&#xff0c;而每个 Pod 又具有独立的 IP 地址。Service&#xff08;服务&#xff09;能够为一组功能相同的…

DI (providedIn: XXXModule)

angular版本为^17.3.0&#xff1b; 依赖注入Module 代码结构如下&#xff1a; 点击后为 demo2 works!demo2 providedIn Demo1Module depdemo2 works!demo2 providedIn Demo1Module 打包后大小为 改写为 demo2去掉 imports: [Demo1Module], 打包后大小为 结果比较&#…

DAMA学习笔记(十五)-数据管理组织与角色期望

1.引言 随着数据领域的快速发展&#xff0c;组织需要改进管理和治理数据的方式。当前&#xff0c;大多数组织正面临着越来越多的数据。这些数据格式多样、数量 庞大&#xff0c;并来源于不同的渠道。由于数据数量和种类的增加&#xff0c;加剧了数据 管理的复杂性。与此同时&am…

shiro注解不起作用:shiro进行权限校验时,@RequireRoles(“admin“)注解不起作用的解决方法

今天在写前后端分离项目时&#xff0c;用jwt加shiro进行登录权限校验时&#xff0c;RequireRoles("admin")注解不起作用&#xff0c;记录一下。 前提&#xff1a;数据库里面的user_type代表用户类型 &#xff1a;0普通用户 &#xff1b;1&#xff1a;专家&#xff1…

Introduction to Snapdragon Profiler (Snapdragon 分析器)

Introduction to Snapdragon Profiler {Snapdragon 分析器} 1. Snapdragon Profiler2. Release Notes3. Tools and resourcesReferences Snapdragon Profiler (骁龙分析器) https://www.qualcomm.com/developer/software/snapdragon-profiler Snapdragon Profiler Documentati…

HAProxy基本配置及参数实操

目录 ​编辑什么是负载均衡 为什么用负载均衡 四层和七层的区别 实验环境 实验步骤 webserver上安装nginx 启动nginx 安装haproxy 编辑配置文件 多进程 多线程 SORRY SERVER 访问重定向 maxconne最大可承受连接 socat 工具 常用示例 ha p r ox y 的 算 法 静 …

思科静态路由配置1

#路由协议实现# #任务一静态路由配置1# #1配置Switch-A的名称及其接口IP地址 Switch>enable Switch(config)#hostname Switch-A Switch-A(config)#ip routing Switch-A(config)#int f0/1 Switch-A(config-if)#no switchport Switch-A(config-if)#ip add 192.168.10…

leetcode日记(72)最大矩形

依旧是看了答案才知道大概方法…太难想到了 和上一道题思路相似&#xff01;可以直接调用上题的函数&#xff0c;只不过调用前的准备非常难想到&#xff0c;就是建造形状相同的矩阵&#xff0c;第i行j列的元素是i行中j列前相邻的“1”的个数。 class Solution { public:int m…

RS®ZN-Z8x 开关矩阵

R&SZN-Z8x 开关矩阵 专为多端口矢量网络分析仪测量而设计 R&SZN-Z8x 开关矩阵经过优化设计&#xff0c;专门用于罗德与施瓦茨的矢量网络分析仪。这款经济高效的多方位解决方案可用于多端口设备或多个设备的简单和复杂的测量任务。开关矩阵支持宽频率范围&#xff0…

北斗科技助力运动健身:开启智能健身新篇章

近年来&#xff0c;随着科技的迅猛发展&#xff0c;智能化设备已逐渐渗透到健身领域&#xff0c;为运动健身爱好者带来了无尽的利好。作为中国自主研发的全球卫星导航系统&#xff0c;北斗定位技术凭借其高精度和可靠性&#xff0c;正在成为运动健身领域的新宠。本文将深入探讨…

Git相关教程

版本控制 学习目标 理解版本控制的必要性了解常用的版本控制方式熟悉 Git 的使用方法 什么是版本控制 可以把一个版本控制系统理解为一个“数据库”&#xff0c;在需要的时候&#xff0c;它可以帮你完整地保存一个项目的快照。当你需要查看一个之前的快照&#xff08;称之为…

Vue3 el-table里input设置为必填

Vue3 el-table里input设置为必填 Vue3 el-table里input设置为必填页面效果实现代码 Vue3 el-table里input设置为必填 页面效果 实现代码 <template><el-form :model"tableData" ref"formRef"><el-table :data"tableData" style…