Javascript和C++数据传递(网页端视频播放器的设计实现总结)
网页端视频播放器的设计实现总结
- 环境配置
1.了解Emscripten和WebAssembly技术,提供Emscripten编译环境(Emscripten安装地址),在cmd中查看emcc是否可用验证环境是否创建成功。
2.搭建本地服务器,首先我们要到Node.js官网下载对应版本的安装包(Node.js下载地址);接着就是安装,和安装普通软件类似,直接下一步下一步就可以了;之后我们来验证node是否安装成功,Win+R输入cmd来调出控制台并输入node -v
和npm -v
来查看node版本和npm(包管理工具)版本;最后执行如下命令便可以完成本地服务器的搭建。
npm install -g serve
serve .
注意:在本地直接点击html文件是以文件方式打开会报错,必须使用http协议的方式打开;同时必须保证浏览器支持webassembly。
二.文件的读取以及主线程与web worker之间数据的传递
1.C/C++代码和web worker都不支持对文件的打开、读写,只能以HTML5的方法对文件进行操作,推荐使用FileReader对象。
2.worker.js必须存放在服务器中才能加载执行。
3.以定时器的方式分段读取文件,代替传统的while循环方式,解决主线程阻塞的问题,使用setInterval方法
4.javaScript中主线程和worker之间的数据传递方式?
主线程中的worker对象通过postMessage向indexedDB数据库发送数据,当indexedDB数据库接收到客户端发送的数据
首先把数据的键值储存并记录到indexedDB数据库表里面,其实相当于把数据保存到一张结构完整的表内。接着,indexedDB
数据库会把接收到的数据值扔给worker的onmessage进行处理。
三.javaScript对内存的操作以及与C/C++接口数据的传递
ArrayBuffer对象、TypedArray视图和DataView视图是 JavaScript 操作二进制数据的接口。该接口设计之初的目的是为了满足 JavaScript
与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。现web worker与底层C/C++接口的二进制数据传递。
二进制数组由三类对象组成。
(1)ArrayBuffer对象:代表内存之中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。
(2)TypedArray视图:共包括 9 种类型的视图,比如Uint8Array(无符号 8 位整数)数组视图, Int16Array(16 位整数)数组视图, Float32Array(32 位浮点数)数组视图等等。
(3)DataView视图:可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号 8 位整数)、第二、三个字节是 Int16(16 位整数)、第四个字节开始是 Float32(32 位浮点数)等等,此外还可以自定义字节序。
基本类型指针传递:
你可以使用getValue(ptr,type)和setValue(ptr,value,type)访问内存。第一个参数ptr是一个指针(代表一个内存地址的数字)。类型type必须为LLVM IR类型,i8、i16、i32、i64、float、double或类似i8 (或只有 )的指针类型。
C/C++中的二进制数据传递到js中:
方式一:(通过返回值获取)
var iyuv_size = iWidth*iHeight*3/2;
var pYUV = Module._GetBuffer();//获取C/C++层二进制数据的地址
var buf = new ArrayBuffer(iyuv_size);//通过二进制对象分配一块连续内存
var ayuv_data = new Uint8Array(buf);//二进制对象绑定到视图,通过视图对内存进行读写操作
ayuv_data.set(Module.HEAPU8.subarray(pYUV,pYUV+iyuv_size));//c/c++中的内存拷贝到刚分配的js内存中
方式二:(通过参数获取)
var iyuv_size = iWidth*iHeight*3/2;
var yuvData = Module._malloc(iyuv_size);//传递地址时才需要malloc,malloc之后获得的是指向地址的指针
var yuvLen = Module._malloc(4);
Module.setValue(yuvLen,iyuv_size,”i32”);//可以设置长度初始值,注意这里的yuvLen是一个地址指针传递,而不是值传递
var res = Module._GetBuffer(yuvData,yuvLen);//第二个参数传递的是指针,目的是从C++层获得yuv的长度,因此要malloc
var nJLen = Module.getValue(yuvLen,”i32”);
var pJData = new Uint8Array(nJLen);
pJData.set(Module.HEAPU8.subarray(yuvData,yuvData+yuvLen));//二进制数据需要绑定视图才可以操作内存
js中的数据传递到C/C++中:
方式一:
var iLen = data.len;
var pBuffer = Module._malloc(iLen)//通过emscripten分配C/C++中的堆内存
var Data = Module.HEAPU8.subarray(pBuffer, pBuffer + iLen);//堆内存绑定到视图对象
Data.set(new Uint8Array(data.buf));//数据写入到emscripten分配的内存中去
InputData(pBuffer,iLen);
方式二:
var iLen = data.len;
var pBuffer = Module._malloc(iLen);
Module.writeArrayToMemory(new Uint8Array(data.buf),pBuffer);//js中的数据拷贝到刚分配的C++缓存中;
InputData(pBuffer,iLen);
Js和C++之间的结构体传递:
https://blog.****.net/pkx1993/article/details/82015315
四.Emscripten编译C/C++到js和wasm文件
1.Emscripten编译优化分为两个步骤:
(1)每个源文件编译成目标文件(.bc文件),通过LLVM优化
(2)目标文件编译成js文件
2.emscripten正确的优化方式:
./emcc -O2 a.cpp -o a.bc//编译成bitcode
./emcc -O2 b.cpp -o b.bc//编译成bitcode
./emcc -O2 a.bc b.bc -o project.js//把bitcode编译成js
注意:各个步骤的优化级别必须设置为一样
3.Js使用库的两种方式:
(1)多个bitcode直接变成js
emcc project.bc libstuff.bc -o final.js
(2)多个bitcode先编译成一个,然后在变成js
emcc project.bc libstuff.bc -o allproject.bc
emcc allproject.bc -o final.js
4.编译过程中遇到的问题及解决方案:
(1)emcc方式编译js文件时,提示C文件调用cpp文件找不到函数时,尝试使用em++方式进行编译
(2) 编译的C/C++文件中的函数不能和平台相关,并且不支持多线程
(3)bind/emscripten.h只对C++文件有效,如果C文件中引入该头文件进行接口的绑定会报错
五.Js文件的导入
1.html中导入js文件
<script src="play.js"></script>
2.web worker中导入js文件
importScripts('decode.js');
Module.postRun.push(function () {
postMessage({'function': "loaded"});
});
//它是以阻塞方法加载js的,只有所有文件加载完成之后,接下来的脚本才能继续执行
3.js包中导入js文件
异步方式:
var _script = document.createElement("script");
_script.type = "text/javascript";
_script.src = "SuperRender.js";
document.getElementsByTagName("head")[0].appendChild(_script);
_script.onload = function(){
console.log("SuperRender.js load sucess");
}
该加载方式是异步加载的,注意要等到js文件加载完,才能调用其中的类或方法,否则会出错。
同步方式:
加载类:import {文件名(不加后缀)} from “类名(加路径)”;
加载非类:import from 文件名;
视频丢帧和花屏问题
1.利用ffmpeg解码得到一帧数据后,需要将数据保存到缓存buffer中,等到送入到解码库中的数据全都处理完,再返回给上层。
2.解码后的YUV数据时由三个分量分开存储的,在送入渲染库webGL之前,需要将yuv三个分量的数据拼接成一块连续的数据再送入到渲染库中进行渲染。