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、最后使用 canvas 的 toDataURL 方法导出图片。
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、最后结语
随着前端技术的不断迭代更新,前端不再止于 "前端",未来还有更大,更多的挑战正等着我们。