原始代码涉及到一些PCD文件头处理等工作,由于这一部份和业务强相关,和本文所聊的压缩关系不大,所以我就把相关代码删了,只留下二进制转码相关的核心代码。
代码很短,应该不难看懂吧?看不懂的评论区举个手,我来给你手摸手教学下。
懒得看或者看不懂的,我作为课代表帮大家把关键点挑出来了,一起看看吧:
1. DataView2. CreateWriteStream
3. Buffer.from(dataview.buffer)
我先帮大家捋一捋整体流程,大致如下:
为什么我们没有用理想操作模型呢?两个文件流pipe一下,中间加一个转换器做一个编解码,搞定!多简单呀。
那是因为,当前这个场景不合适。
ThreeJS天然支持PCD文件的渲染,但前提是,必须有标准头,也就是这个东西。
有了头部元信息之后,剩下的部分就是「点云二进制」数据,ThreeJS天然支持。可以看下对应的源码:(https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/PCDLoader.js)
既然如此,那我们就可以把一个带有标准头的二进制文件直接丢给ThreeJS去渲染即可,ThreeJS会在运行时去解析,我们无需在服务端或者前端做多余的「解码」操作,节约渲染成本。
而如果采用理想模型,这意味着我们在转码Stream的每一个chunk的时候,是直接将chunk转成了二进制,并没有按「点」为单位的去处理,毕竟NodeJS的chunk是按某个固定字节大小来分片的,而不是定制化的按「点」为分片单位。
这样的话,最终转出来的,仅仅是一个二进制文件,而不是一个ThreeJS可以识别的「点云二进制」文件,我们就必须在渲染之前先处理一遍数据,这就不太合适了。
当然了,如果是那么简单就能做的,也就不会有今天这篇文章了,也是因为这个特殊的处理场景,引入了一些比较有意思的知识点,所以才想分享给大家。
首先想想我们为什么要用DataView?DataView又是什么?一起看下MDN的解释:
DataView 视图是一个可以从二进制 ArrayBuffer 对象中读写多种数值类型的底层接口,使用它时,不用考虑不同平台的字节序(endianness)问题。
我们需要以「点」为单位做编码,写入文件,那么也就是说,我们需要操作文件Buffer,而NodeJS为了防止安全和内存泄漏问题,是不允许直接操作Buffer本身的,于是提供了一个DataView接口,非常方便的操作ArrayBuffer。
我们可以将每个「点」的信息逐一写入DataView:
point.forEach((axis, axisIndex) => { dataview.setFloat32(rowIndex * POINT_BYTES_SIZE + axisIndex * PARAMS_LENGTH, Number(axis), true) })
这里我们用了setFloat32 ,因为xyzi四个参数每个都是4字节的浮点数,所以用float32来存,第一个参数是位偏移量,第二个是需要存的值,第三个是字节序(可选)。
dataview.setFloat32(byteOffset, value [, littleEndian])
对所有的点逐一处理之后,我们就拿到了一个存有完整点云信息的DataView,就可以拿去写文件了!
But!这里又有关键点了。你以为直接拿DataView的Buffer写入文件即可,如下:
wstream.write(dataview.buffer)
然后你就会看到报错:
为什么呢?
DataView的Buffer难道不是Buffer么?还真不是!你把鼠标挪到代码上一看:
好家伙,是个ArrayBuffer!没错,官方文档一开始就说了,DataView就是拿来操作ArrayBuffer的,没毛病。
那么怎么处理呢?其实稍微改一下就好啦:
wstream.write(Buffer.from(dataview.buffer))
用 Buffer.from 就可以直接把buffer转出来。
你可能有疑问了,从arrayBuffer转buffer我会了,那反过来呢?哈哈,其实一开始我们读源文件拿来做点云信息解析的时候,就已经这么干了,被我省略了代码,这里补上:
const inputData = fs.readFileSync(input) const ab = inputData.buffer.slice(inputData.byteOffset, inputData.byteOffset + inputData.byt
看到没有,读取文件数据后,我们拿到的是一个buffer,而从buffer转arrayBuffer,只需要一个小小的slice即可,是不是很神奇。
到这里,其实我帮大家挑出来的关键点就都扯清楚了,最后还剩一个疑问,为啥要用createWriteStream 来写文件?有什么特殊考虑吗?
没错,其实一开始是想着,用「流」来写文件,性能更好,不会占用大量内存空间。
但是上面也说了,我们不能简单的对接两头文件流,必须得把「点」数据读出来逐个编码,那么势必就需要产生Buffer占用空间了,这样的话,Stream其实就变鸡肋了,完全可以直接替换成 fs.writeFileSync,而且writeFile还可以直接写DataView,更省事了呢!
fs.writeFileSync(output, dataview)
帮大家总结一下今天的几个小知识点:
1. NodeJS提供了一个DataView,用来操作Buffer,确切的说是 ArrayBuffer。
2. Buffer和ArrayBuffer可以互相转换。
3. Stream可以节约Buffer占用内存空间,但如果不利用好pipe,就等于失去了它的优势,还不如直接file操作。
好啦!今天的分享就到这了,你学废了吗?