在前面的章节中,我们解决emcc环境以及使用emcc来编译ffmpeg得到网页开发中可以使用的js库,本章节,我们就来实现一个简单的播放器.

视频课程以及源码下载:
https://edu.csdn.net/course/detail/35615

章节列表:

  • 搭建webassembly网页播放器(一)—centeros 虚拟环境搭建
  • 搭建webassembly网页播放器(二)—emcc环境搭建
  • 搭建webassembly网页播放器(三)—emcc编译ffmpeg编译库
  • 搭建webassembly网页播放器(四)—网页调用ffmpeg.js单元测试用例
  • 搭建webassembly网页播放器(五)—网页播放器开发
  • 搭建webassembly网页播放器(六)—websocket后台服务程序

播放效果如下:

网页播放器开发

  • c程序定义的接口如何在JS层调用
  • JS向C/C++传递数据
  • C/C++向JS传递数据
  • webGL 渲染

c程序定义的接口如何在JS层调用

前端我们使用websocket从server端来不断的拉取h264数据流(Server端功能在第六章节进行介绍!)

     var ws_url = "ws://192.168.5.6:6789"let ptr; // 解码器句柄,对应c++层的HANDLEvar websock  = new WebSocket(ws_url);websock.binaryType = "arraybuffer";websock.onopen = function(event){console.log("websocket 连接成功.")ptr = Module._initDecoder();var canvas_width = 640;var canvas_height = 480;renderer = initialCanvas(video,canvas_width,canvas_height);}

在我们连接上websocket server的时候,我们调用了初始化ffmpeg的函数:

Module._initDecoder();

在C/C++层这个定义如下:

/*** 初始化H264解码器* @return 成功返回句柄,失败返回NULL*/
LPJSDecodeHandle EMSCRIPTEN_KEEPALIVE initDecoder();

JS层调用C/C++接口的方式方法极为简单,Moudle._+函数名即可.

JS向C/C++传递数据

到了这一步,我们在JS层通过websocket协议获取到了H264数据,但是我们的解码是在C/C++ 层,因此我们需要把数据从JS层传递到C/C++ 层.

var js_h264_buf =  new Uint8Array(event.data);
var js_h264_len = js_h264_buf.length;
console.log("收到一帧数据,长度为:" ,js_h264_len);var dst = Module._GetBuffer(ptr,js_h264_len);    // 通知C/C++分配好一块内存用来接收JS收到的H264流.
HEAPU8.set(js_h264_buf,dst);    // 将JS层的数据传递给C/C++层.

这里大家要理解句柄的概念,本质上在C/C++层,指针就是变量在内存中的地址. 通过指针我们就可以找到变量。

上述的流程是 :

  1. 调用接口函数 GetBuffer,告诉C/C++层分配好一块内存,用来接收JS传递过来的数据.
  2. 通过HEAPU8.set() 把js层的二进制数据传递到C/C++

HEAPU8.set()是webassembly的内置函数,存储在于ffmpeg.js中.

C/C++向JS传递数据

C/C++在解码成功后,得到了YUV数据,这个时候需要反向把数据从C/C++层传递给JS层.

if( Module._Decode(ptr,js_h264_len) >= 0 ){var width  = Module._GetWidth(ptr);var height = Module._GetHeight(ptr);var renderlength = width * height * 3 / 2;console.log("解码成功 , width:%d height:%d" ,width,height);var yuv_wasm_data = Module._GetRenderData(ptr); // 得到C/C++生成的YUV数据.// 将数据从C/C++层拷贝到JS层var RenderBuffer = new Uint8Array ( Module.HEAPU8.subarray(yuv_wasm_data,yuv_wasm_data + renderlength) );render(RenderBuffer,width,height);}else{console.log("解码失败.");}
  1. Module._GetRenderData(ptr) 通过接口函数,我们提取C/C++ YUV数据的句柄.
  2. Module.HEAPU8.subarray 实现数据从C/C++到JS层的传递.
  3. JS层收到数据后,直接调用webGL进行渲染,这种方式比获取RGB贴图快N倍。

webGL 渲染

webGL模块直接封装为了控件

//代码摘自:https://github.com/ivan-94/video-push/blob/master/yuv/index.html#L312

 <script>const video = document.getElementById('glcanvas');let renderer;class WebglScreen {constructor(canvas) {this.canvas = canvas;this.gl =canvas.getContext('webgl') ||canvas.getContext('experimental-webgl');this._init();}_init() {let gl = this.gl;if (!gl) {console.log('gl not support!');return;}// 图像预处理gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);// GLSL 格式的顶点着色器代码let vertexShaderSource = `attribute lowp vec4 a_vertexPosition;attribute vec2 a_texturePosition;varying vec2 v_texCoord;void main() {gl_Position = a_vertexPosition;v_texCoord = a_texturePosition;}`;let fragmentShaderSource = `precision lowp float;uniform sampler2D samplerY;uniform sampler2D samplerU;uniform sampler2D samplerV;varying vec2 v_texCoord;void main() {float r,g,b,y,u,v,fYmul;y = texture2D(samplerY, v_texCoord).r;u = texture2D(samplerU, v_texCoord).r;v = texture2D(samplerV, v_texCoord).r;fYmul = y * 1.1643828125;r = fYmul + 1.59602734375 * v - 0.870787598;g = fYmul - 0.39176171875 * u - 0.81296875 * v + 0.52959375;b = fYmul + 2.01723046875 * u - 1.081389160375;gl_FragColor = vec4(r, g, b, 1.0);}`;let vertexShader = this._compileShader(vertexShaderSource,gl.VERTEX_SHADER,);let fragmentShader = this._compileShader(fragmentShaderSource,gl.FRAGMENT_SHADER,);let program = this._createProgram(vertexShader, fragmentShader);this._initVertexBuffers(program);// 激活指定的纹理单元gl.activeTexture(gl.TEXTURE0);gl.y = this._createTexture();gl.uniform1i(gl.getUniformLocation(program, 'samplerY'), 0);gl.activeTexture(gl.TEXTURE1);gl.u = this._createTexture();gl.uniform1i(gl.getUniformLocation(program, 'samplerU'), 1);gl.activeTexture(gl.TEXTURE2);gl.v = this._createTexture();gl.uniform1i(gl.getUniformLocation(program, 'samplerV'), 2);}/*** 初始化顶点 buffer* @param {glProgram} program 程序*/_initVertexBuffers(program) {let gl = this.gl;let vertexBuffer = gl.createBuffer();let vertexRectangle = new Float32Array([1.0,1.0,0.0,-1.0,1.0,0.0,1.0,-1.0,0.0,-1.0,-1.0,0.0,]);gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);// 向缓冲区写入数据gl.bufferData(gl.ARRAY_BUFFER, vertexRectangle, gl.STATIC_DRAW);// 找到顶点的位置let vertexPositionAttribute = gl.getAttribLocation(program,'a_vertexPosition',);// 告诉显卡从当前绑定的缓冲区中读取顶点数据gl.vertexAttribPointer(vertexPositionAttribute,3,gl.FLOAT,false,0,0,);// 连接vertexPosition 变量与分配给它的缓冲区对象gl.enableVertexAttribArray(vertexPositionAttribute);let textureRectangle = new Float32Array([1.0,0.0,0.0,0.0,1.0,1.0,0.0,1.0,]);let textureBuffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer);gl.bufferData(gl.ARRAY_BUFFER, textureRectangle, gl.STATIC_DRAW);let textureCoord = gl.getAttribLocation(program, 'a_texturePosition');gl.vertexAttribPointer(textureCoord, 2, gl.FLOAT, false, 0, 0);gl.enableVertexAttribArray(textureCoord);}/*** 创建并编译一个着色器* @param {string} shaderSource GLSL 格式的着色器代码* @param {number} shaderType 着色器类型, VERTEX_SHADER 或 FRAGMENT_SHADER。* @return {glShader} 着色器。*/_compileShader(shaderSource, shaderType) {// 创建着色器程序let shader = this.gl.createShader(shaderType);// 设置着色器的源码this.gl.shaderSource(shader, shaderSource);// 编译着色器this.gl.compileShader(shader);const success = this.gl.getShaderParameter(shader,this.gl.COMPILE_STATUS,);if (!success) {let err = this.gl.getShaderInfoLog(shader);this.gl.deleteShader(shader);console.error('could not compile shader', err);return;}return shader;}/*** 从 2 个着色器中创建一个程序* @param {glShader} vertexShader 顶点着色器。* @param {glShader} fragmentShader 片断着色器。* @return {glProgram} 程序*/_createProgram(vertexShader, fragmentShader) {const gl = this.gl;let program = gl.createProgram();// 附上着色器gl.attachShader(program, vertexShader);gl.attachShader(program, fragmentShader);gl.linkProgram(program);// 将 WebGLProgram 对象添加到当前的渲染状态中gl.useProgram(program);const success = this.gl.getProgramParameter(program,this.gl.LINK_STATUS,);if (!success) {console.err('program fail to link' + this.gl.getShaderInfoLog(program),);return;}return program;}/*** 设置纹理*/_createTexture(filter = this.gl.LINEAR) {let gl = this.gl;let t = gl.createTexture();// 将给定的 glTexture 绑定到目标(绑定点gl.bindTexture(gl.TEXTURE_2D, t);// 纹理包装 参考https://github.com/fem-d/webGL/blob/master/blog/WebGL基础学习篇(Lesson%207).md -> Texture wrappinggl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);// 设置纹理过滤方式gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);return t;}/*** 渲染图片出来* @param {number} width 宽度* @param {number} height 高度*/renderImg(width, height, data) {let gl = this.gl;// 设置视口,即指定从标准设备到窗口坐标的x、y仿射变换gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);// 设置清空颜色缓冲时的颜色值gl.clearColor(0, 0, 0, 0);// 清空缓冲gl.clear(gl.COLOR_BUFFER_BIT);let uOffset = width * height;let vOffset = (width >> 1) * (height >> 1);gl.bindTexture(gl.TEXTURE_2D, gl.y);// 填充纹理gl.texImage2D(gl.TEXTURE_2D,0,gl.LUMINANCE,width,height,0,gl.LUMINANCE,gl.UNSIGNED_BYTE,data.subarray(0, uOffset),);gl.bindTexture(gl.TEXTURE_2D, gl.u);gl.texImage2D(gl.TEXTURE_2D,0,gl.LUMINANCE,width >> 1,height >> 1,0,gl.LUMINANCE,gl.UNSIGNED_BYTE,data.subarray(uOffset, uOffset + vOffset),);gl.bindTexture(gl.TEXTURE_2D, gl.v);gl.texImage2D(gl.TEXTURE_2D,0,gl.LUMINANCE,width >> 1,height >> 1,0,gl.LUMINANCE,gl.UNSIGNED_BYTE,data.subarray(uOffset + vOffset, data.length),);gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);}/*** 根据重新设置 canvas 大小* @param {number} width 宽度* @param {number} height 高度* @param {number} maxWidth 最大宽度*/setSize(width, height, maxWidth) {let canvasWidth = Math.min(maxWidth, width);this.canvas.width = canvasWidth;this.canvas.height = (canvasWidth * height) / width;}destroy() {const { gl } = this;gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT,);}} // end of webglconst initialCanvas = (canvas, width, height) => {canvas.width  = width;canvas.height = height;return new WebglScreen(canvas);};const render = (buff,width,height) =>{if (renderer == null) {return;}renderer.renderImg(width, height, buff);};</script>

搭建webassembly网页播放器(五)---网页播放器开发相关推荐

  1. 文件管理器android开源,寻找优秀的安卓文件管理器 五款文件管理器大评比

    文件管理器是手机的必备软件,无论在以前的塞班还是如今的Android.没有文件管理器,即使手机里的各种文件和文件夹都乱了套不说,从电脑传输到手机里的精彩应用也没法打开.Android作为目前普及率最高 ...

  2. 搭建webassembly网页播放器(六)---websocket后台服务程序

    这里我们主要介绍后台搭建技术,前端搭建好后,需要后端配置展现,实现的效果就是 :网页连接上我们的 websocket服务后,我们就从H264文件中不断的提取出H264帧,然后传递给前端,前端调用web ...

  3. 搭建webassembly网页播放器(三)---emcc编译ffmpeg编译库

    经过前面的2个部分,我们完成了环境的搭建,接下来我们使用emcc编译ffmpeg库,注意同样由于软链接的缘故,我们不能在windows和linux的共享文件夹里使用emcc编译ffmpeg, 这里我们 ...

  4. 搭建webassembly网页播放器(二)---emcc环境搭建

    emcc全称 emscripten,最重要的功能就是让网页js调用c/c++ 成为可能,是我们基于webassembly搭建网页播放器必须依赖的编译工具. emcc官网的搭建教程较为简单,安装过程中最 ...

  5. 在线播放器 在网页中插入MediaPlayer 兼容IE和FF的代码调试

    <!– 在线播放器 在网页中插入MediaPlayer 兼容IE和FF的代码调试 Internet Explorer 和 Netscape 都支持 <embed> 元素,但它不是标准 ...

  6. HTML+CSS+JAVASCRIPT 高仿低配网页版网易云音乐播放器 1

    HTML+CSS+JAVASCRIPT 高仿低配网页版网易云音乐播放器 前言 没有使用任何框架,只是想用最简单纯js的代码实现下 前台: Javascript+jQuery 后台: php/nodej ...

  7. 多功能流媒体播放器实现网页无插件直播之EasyPlayer.js如何实现播放完自动循环播放

    EasyPlayer-Android播放器是一款可针对RTSP.RTMP.RTSP&RTMP协议进行过优化的流媒体播放器,其中我们引以为傲的两个技术优势就是起播速度快和播放延迟低.最近我们遇到 ...

  8. jsp网页嵌入PHP网页,JSP_(jsp/html)网页上嵌入播放器(常用播放器代码整理),这个其实很简单,只要在HTML上 - phpStudy...

    (jsp/html)网页上嵌入播放器(常用播放器代码整理) 这个其实很简单,只要在HTML上添加以上代码就OK了,前提是你的电脑上已经安装了播放器,如RealPlay. 还有更多的的播放器和设置可供选 ...

  9. html5 无插件视频播放器,多功能流媒体播放器网页无插件直播EasyPlayer.js如何实现播放完自动循环播放...

    原标题:多功能流媒体播放器网页无插件直播EasyPlayer.js如何实现播放完自动循环播放 EasyPlayer-Android播放器是一款可针对RTSP.RTMP.RTSP&RTMP协议进 ...

最新文章

  1. C++中的explicit关键字介绍
  2. bs和cs架构的区别和优缺点_C/S和B/S两种架构区别与优缺点分析
  3. python2中的print语句可以不用小括号。_Python 2与Python 3的区别
  4. jquery的$.extend和$.fn.extend作用及区别(—)
  5. [NOTE] sqli-labs Basic Challenges
  6. Angular2组件与指令的小实践——实现一个图片轮播组件
  7. 观察者模式的应用场景
  8. 012的悲剧终于预言了
  9. Java springcloud B2B2C o2o多用户商城 springcloud架构- ribbon
  10. ruijie交换机lacp动态_vmware esxi 做链路聚合LACP踩坑
  11. hhkb适合写java吗_起底这届HHKB最强新品键盘,究竟好在哪儿?
  12. 【LBS】移动互联网基于LBS地理位置应用开发必备
  13. Gensim官方教程翻译(二)——语料库与向量空间(Corpora and Vector Spaces)
  14. Dubbo实战(一)快速入门
  15. Ultimate Email Toolkit:16种出色的电子邮件工具
  16. Oracle入门--水表项目(单表查询,链接查询,左右外连接,子查询,分页查询)(3)
  17. Java去掉文件中的逗号
  18. NOD32 无限升级补丁
  19. 十.四轮车驱动开发之三: 巧用编码器获取电机转速信息
  20. finalshell强劲功能介绍

热门文章

  1. 基于RFID的药品防伪溯源
  2. 【5G】有史以来最强的5G入门科普
  3. java读取文本文件从第二行_Java:我不明白为什么文本阅读器开始从第二行读取文本...
  4. TXT 文本阅读器源码
  5. sensor_msgs/Image消息格式
  6. 萝卜和青菜--Android camera框架与使用
  7. 误入歧途的蓝屏代码——STOP:c000021a Unknown Hard Error
  8. 全球名校AI课程库(39)| 马里兰大学 · 数据结构课程『Data Structures』
  9. iOS 调出细细的字体
  10. Riscv五级流水线64位cpu,systemverilog编写,指令集rv64i,支持csr寄存器,可跑通dhrystone测试