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

目录

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 / 1e6if (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.timescaleconst duration = s.duration / s.timescaleconst type = (s.is_sync ? 'key' : 'delta')const videoChunkData = s.dataconst 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.rhkb.cn/news/402788.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大公报发表欧科云链署名文章:发行港元稳定币,建Web3.0新生态

欧科云链研究院资深研究员蒋照生近日与香港科技大学副校长兼香港Web3.0协会首席科学顾问汪扬、零壹智库创始人兼CEO柏亮&#xff0c;在大公报发布联合署名文章 ——《Web3.0洞察 / 发行港元稳定币&#xff0c;建Web3.0新生态》&#xff0c;引发市场广泛讨论。 文章就香港稳定币…

2024年【汽车驾驶员(技师)】考试报名及汽车驾驶员(技师)试题及解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 汽车驾驶员&#xff08;技师&#xff09;考试报名参考答案及汽车驾驶员&#xff08;技师&#xff09;考试试题解析是安全生产模拟考试一点通题库老师及汽车驾驶员&#xff08;技师&#xff09;操作证已考过的学员汇总…

【二分查找】--- 初阶题目赏析

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 算法Joureny 上篇我们讲解了关于二分的朴素模板和边界模板&#xff0c;本篇博客我们试着运用这些模板。 &#x1f3e0; 搜索插入位置 &#x1f4cc; 题目…

【RISC-V设计-12】- RISC-V处理器设计K0A之验证环境

【RISC-V设计-12】- RISC-V处理器设计K0A之验证环境 文章目录 【RISC-V设计-12】- RISC-V处理器设计K0A之验证环境1.简介2.验证顶层3.顶层代码4.模型结构4.1 地址映射4.2 特殊功能寄存器 5.模型代码6.运行脚本7.总结 1.简介 在前几篇文章中&#xff0c;分别介绍了各个模块的设…

VM下kali设置桥接网络

一、查看主机ip 1.winr输入cmd 2.进入终端输入ipconfig 3.查看ip 二、虚拟机网络设置 1.进入vm的虚拟网络编辑器 2.桥接网卡自己选&#xff0c;1是有线网卡2是无线网卡&#xff0c;选择记得点应用 3.虚拟机的网络适配器也要选择桥接模式 三、kali网络配置 1.打开kali终端编辑文…

【经典算法】BFS_最短路问题

1. 最短路问题介绍 最短路径问题是图论中的一类十分重要的问题。本篇文章只介绍边权为1(或边权相同)的最简单的最短路径问题。所谓边权&#xff0c;就是两点之间的距离。 这类问题通俗的说就是告诉你起点和终点&#xff0c;要你找出最短的路径或是最短路径是多少。 解决方法&…

精通C++ STL(五):list的介绍及使用

目录 ​编辑 list的介绍 list的使用 list的定义方式 list的插入和删除 push_front和pop_front push_back和pop_back insert erase list的迭代器使用 begin和end rbegin和rend list的元素获取 front和back list的大小控制 size resize empty clear list的操作函数 sort splic…

水利机械5G智能制造工厂物联数字孪生平台,推进制造业数字化转型

在当今这个科技日新月异的时代&#xff0c;水利机械行业正经历着一场深刻的变革&#xff0c;其中5G智能制造工厂物联数字孪生平台的引入&#xff0c;无疑是推动制造业数字化转型的重要驱动力。工业物联数字孪生平台是智能制造工厂的核心组成部分&#xff0c;它基于物理世界的真…

【项目】基于Vue2+Router+Vant 前端面经项目

环境配置 Vue脚手架的创建 在终端中打开输入 vue create 项目包名 -m npm注意⚠️&#xff1a;项目名称不再允许包含大写字母。 选择第三项 3.选择要安装的模块 从上到下的功能模块&#xff1a; Babel - ES&#xff1a;降级处理Router-Vue&#xff1a;路由插件CSS预处理器E…

仿Muduo库实现高并发服务器——Buffer模块

Buffer模块建议描述图。 关于Buffer模块主要就是信息的读取写入。 这个模块在Connect模块中使用&#xff0c;作为输入输出缓冲区进行使用。 以下是这个模块在本项目中的作用。 写入&#xff1a; 涉及到是否有足够大小的存储空间。 //确保可写空间足够&#xff08;整体空闲空间…

FASTSPEECH 2论文阅读

FASTSPEECH 2: FAST AND HIGH-QUALITY END-TOEND TEXT TO SPEECH 现状 非自回归模型可以在质量相当的情况下显著快于先前的自回归模型合成模型。但FastSpeech模型训练依赖与自回归教师模型进行时长预测&#xff08;提供更多的信息作为输入&#xff09;和知识蒸馏&#xff08;…

SEREN赛恩电源RX01/LX01系列射频手侧

SEREN赛恩电源RX01/LX01系列射频手侧

C:数组传参的本质

1、一维数组传参的本质 数组传参是指在函数调用时将数组作为参数传递给函数。 int main() {int arr[10] { 1,2,3,4,5,6,7,8,9,10 };test(arr);return 0;}数组传参只需要写数组名就可以了。注意&#xff1a;数组名是arr&#xff0c;而不是arr[10] 数组传参形参该怎么写呢&am…

Golang Map 深度剖析:原理、实践与面试要点

嘿,小伙伴们!我是 k 哥。今天,咱们来聊聊 Map 。 在 Go 语言这个神奇的世界里,Map 这个有点神秘的数据结构一直都是开发者们特别关注的。 你是不是在用 Map 的时候,对它里面咋工作的感到好奇?是不是碰到复杂操作的时候,特别想弄明白它背后的原理?别着急,今天这篇文章…

【计算机网络】应用层自定义协议与序列化

记得在上一节我们说过TCP中的读取时需要改进&#xff0c;这节就可以解决读取问题了。 目录 应用层再谈 "协议"网络版计算机方案一方案二 序列化 和 反序列化 重新理解 read、write、recv、send 和 tcp 为什么支持全双工 应用层 再谈 “协议” 我们在UDP与TCP中写的…

支持I2C接口、抗干扰性强、14通道触摸按键的电容式触摸芯片-GTX314L

电容式触摸芯片 - GTX314L是具有多通道触发传感器的14位触摸传感器系列&#xff0c;它是通过持续模式提供中断功能和唤醒功能&#xff0c;广泛适用于各种控制面板应用&#xff0c;可直接兼容原机械式轻触按键的处理信号。 GTX314L芯片内部采用特殊的集成电路&#xff0c;具有高…

基于YOLOv8-pose的手部关键点检测(3)- 实现实时手部关键点检测

目录 前言 1.扩大检测框区域 2.先检测手部&#xff0c;后检测手部关键点 3.正面视角检测 4.侧面视角检测 5.摄像头视角检测 6.遮挡视角检测 7.结论 前言 使用YOLOv8-m对图像进行手部检测&#xff0c;然后扩大检测框区域&#xff0c;并对该区域使用YOLOv8-s-pose使用关键…

大数据技术——实战项目:广告数仓(第六部分)报表数据导出至clickhouse

目录 第11章 报表数据导出 11.1 Clickhouse安装 11.2 Clickhouse建表 11.2.1 创建database 11.2.2 创建table 11.3 Hive数据导出至Clickhouse 第11章 报表数据导出 由于本项目最终要出的报表&#xff0c;要求具备交互功能&#xff0c;以及进行自助分析的能力&#xff0c;…

CSS——字体背景(Font Background)

一、字体族 1、字体的相关样式&#xff1a; ① color 用来设置字体颜色&#xff08;前景颜色&#xff09; ② font-size 字体的大小 和font-size相关的单位&#xff1a; em 相对于当前元素的一个font-size rem 相对于根元素的一个font-size ③ font-family 字体族&#x…