Webassembly + FFmpeg 实现前端抽取首帧

1,841 阅读1分钟

1、准备工作

Emscripten 环境搭建FFmpeg 源码下载FFmpeg developer 库

环境的搭建,下载源码类库之类的笔者就不讲述了,网上的教程很多。

2、整体流程

1、从浏览器端拿到上传的视频文件,用 FileReader 把数据存到 Uint8Array 字节流中。

2、用 emcc 暴露的 _malloc 方法,把字节流存入内存。

3、使用 C/C++ 从内存中读取视频数据,再用 FFmpeg 相关接口把视频流解码,取出默认的 YUV420P 格式的数据, 转成 RGB24类型的数据。

4、把 RGB24 数据以及图片大小回传给浏览器端,使用 canvas 创建 createImageData 容器,把 RGB24 填充到容器内,再用putImageData 把图片渲染到 canvas 上。

5、最后使用 canvastoDataURL 方法导出图片。

3、核心实现

前端部分

  const fileReader = new FileReader();
  fileReader.readAsArrayBuffer(files[0]);
  fileReader.onload = function () {
      const buffer = new Uint8Array(this.result);
      const buffPtr = Module._malloc(buffer.length);

      for(let i=0;i<buffer.length;i++) {
          Module.HEAP8[ptr + i] = buffer[i];
      }

      const rgbPtr = Module._getRgbData(buffPtr, buffer.length);

      ...

      Module._free_buf(buffPtr);
	  Module._free_buf(rgbPtr);
  }

C语言部分

  FILE *fp_open = NULL;
  
  // 回写 buffer 数据的回调
  int read_buffer(void *opaque, uint8_t *buf, int buf_size){
      if(!feof(fp_open)){
          int true_size=fread(buf,1,buf_size,fp_open);
          return true_size;
      }else{
          return -1;
      }
  }

  // 从内存中获取视频数据
  AVFormatContext *avFmtCtx = avformat_alloc_context();
  AVIOContext *avIoCtx = avio_alloc_context(buffer, length, 0, NULL, read_buffer, NULL, NULL);
  avFmtCtx->pb = avIoCtx;
  avformat_open_input(&avFmtCtx, NULL, NULL, NULL);
  	
  ...
  
  // frame -> yuv
  sws_scale(yuvSwsCtx, avFrame->data, avFrame->linesize, 0, avCodecCtx->height, 
      yuvFrame->data, yuvFrame->linesize);
  
  // yuv -> rgb24
  sws_scale(rgbSwsCtx, yuvFrame->data, yuvFrame->linesize, 0, avCodecCtx->height, 
      rgb24Frame->data, rgb24Frame->linesize);

4、最后结语

随着前端技术的不断迭代更新,前端不再止于 "前端",未来还有更大,更多的挑战正等着我们。