• wasm 视频解码渲染实现


    实现一个wasm视频解码渲染的小demo,网页端集成emcc编译的ffmpeg库,实现视频解码,使用WebGL实现视频渲染。demo中包含了一个基于mongoose的微型Web服务器,用于网页的Web服务和视频流传输,基本无需额外搭建环境以及编译第三方库,可以简单地移植到嵌入式系统中用于网页视频播放视频。学习过程中主要参考了大神代码和文章

    编译WebAssembly版本的FFmpeg(ffmpeg.wasm):(2)使用Emscripten编译 - 腾讯云开发者社区-腾讯云

    demo地址

    wasm_websocket_player: wasm 解码渲染demo

    1.编译

    1.1 ffmpeg emcc版本编译

    首先需要获取emcc用于编译,Mac下可以直接通过brew install来获取。下一步就是通过emcc,将ffmpeg编译对应的静态库。注意这里需要将ffmpeg中平台相关以及汇编相关的选项禁掉,毕竟这里最终都是在js虚拟机中执行,硬件加速相关的操作都需要去掉。下面是demo中编译ffmpeg使用的命令,源文件在demo的third_party文件下。

    1. mkdir ffmpeg-emcc
    2. cd FFmpeg_new
    3. #make clean
    4. emconfigure ./configure --cc="emcc" --cxx="em++" --ar="emar" \
    5. --ranlib=emranlib --prefix=../ffmpeg-emcc/ \
    6. --enable-cross-compile --target-os=none \
    7. --arch=x86_32 --cpu=generic --enable-gpl \
    8. --disable-avdevice \
    9. --disable-postproc --disable-avfilter \
    10. --disable-programs \
    11. --disable-everything --enable-avformat \
    12. --enable-decoder=hevc --enable-decoder=h264 --enable-decoder=h264_qsv \
    13. --enable-decoder=hevc_qsv \
    14. --enable-decoder=aac \
    15. --disable-ffplay --disable-ffprobe --disable-asm \
    16. --disable-doc --disable-devices --disable-network \
    17. --disable-hwaccels \
    18. --disable-debug \
    19. --enable-protocol=file --disable-indevs --disable-outdevs \
    20. --enable-parser=hevc --enable-parser=h264
    21. emmake make -j4
    22. emmake make install

    1.2 客户端源代码编译

    ffmpeg静态链接库生成后,下一步就可以编译demo中客户端相关的源码,包括我们自己调用ffmpeg库的代码,c层与js层交互的代码,以及ffmpeg静态链接库,最终生成一个js文件和一个.wasm库,在网页中我们通过调用生成的js文件进行解码。下面是编译命令,源文件在demo工程的client文件下的build_with_emcc.sh。

    1. export TOTAL_MEMORY=67108864
    2. CURR_DIR=$(pwd)
    3. export FFMPEG_PATH=$CURR_DIR/../third_party/ffmpeg-emcc
    4. emcc --bind ../common/video_decoder.cc ../common/h264_reader.cc ../common/frame_queue.cc main.cc\
    5. -std=c++11 \
    6. -s USE_PTHREADS=1\
    7. -g \
    8. -I "${FFMPEG_PATH}/include" \
    9. -L ${FFMPEG_PATH}/lib \
    10. -lavutil -lavformat -lavcodec \
    11. -s WASM=1 -Wall \
    12. -s EXPORTED_FUNCTIONS="['_malloc','_free']" \
    13. -s ASSERTIONS=0 \
    14. -s ALLOW_MEMORY_GROWTH=1 \
    15. -s TOTAL_MEMORY=167772160 \
    16. -o ${PWD}/player.js

    最终会生成player.js以及player.wasm文件。

    1.3 demo中server的编译与demo运行

    demo中提供了一个微型Web server,提供http服务以及websocket数据传输。考虑到demo主要用于嵌入式平台,这里选择了mongoose作为Web服务器,只需要在源代码中引入一个.c文件和一个.h文件即可使用,无需复杂的编译和依赖库。demo中使用了一个本地h264文件,server收到客户端请求后会读取这个本地文件,通过avformat读取每帧h264,实际使用中可以将这块的代码更换为当前设备的采集和编码。目前调试是在Mac的arm64版本上编译,直接运行server目录下cmake即可。

    可以直接在server目录下运行run.sh,即可完成客户端编译,服务端编译以及相关文件的拷贝。目前写死使用8000端口。

    2.解码流程实现

    2.1 js传递视频流给wasm解码

    wasm内存分配与释放

    这里首先介绍一下js与底层wasm的交互方式。一般视频流数据数量较小,可以直接为其分配内存空间,这里我们直接通过在js层调用_malloc和_free进行分配和释放内存,这些内存可以被wasm代码所使用。这里首先分配wasm可以使用的内存,下一步就是将js的Uint8Array数据拷贝给这块内存,这样wasm中的代码就可以操作这块内存了。

    js传递数据给wasm

    这里可以在C++层通过EMSCRIPTEN_BINDINGS对C++函数进行封装,基本数据类型可以使用普通的C/C++数据类型,传入js所分配的内存,在C/C++层直接使用uintptr_t类型即可。下面使用我们deocder类来进行说明。
    decoder类的C++类,emscripten::val lambda类型可以将一个js函数传入wasm作为回调函数。

    1. class StreamDecoderWrapper{
    2. public:
    3. StreamDecoderWrapper(){}
    4. ~StreamDecoderWrapper(){}
    5. void OpenAvcDecoder(emscripten::val lambda){
    6. ... ...
    7. decoder.OpenWithCodecID(AV_CODEC_ID_H264);
    8. decoder.RegisterDecodeCallback([lambda, this](AVFrame *frame)->int{
    9. ... ...
    10. auto frame_wrapper = std::make_shared()->Alloc(AVMEDIA_TYPE_AUDIO, out_frame);
    11. ... ...
    12. lambda(frame_wrapper);
    13. return 0;
    14. });
    15. }
    16. void DecodeVideoPacket(uintptr_t buf_p, int size){
    17. uint8_t *data = reinterpret_cast<uint8_t *>(buf_p);
    18. ... ...
    19. }
    20. void CloseDecoder(){
    21. ... ...
    22. }
    23. private:
    24. ... ...
    25. };

    注册StreamDecoderWrapper,让js代码可以识别这个类。这个操作类似jni的动态注册,将字符串与C++类名和方法名对应,这样在js层中可以直接使用这个字符串创建对象并调用方法。

    1. #include
    2. #ifndef NDEBUG
    3. #include
    4. #endif
    5. #include "stream_decoder_wrapper.h"
    6. using namespace emscripten;
    7. EMSCRIPTEN_BINDINGS(module){
    8. ... ...
    9. class_("StreamDecoderWrapper")
    10. .constructor<>()
    11. .function("openAvcDecoder", &StreamDecoderWrapper::OpenAvcDecoder)
    12. .function("decodeVideoPacket", &StreamDecoderWrapper::DecodeVideoPacket)
    13. .function("closeDecoder", &StreamDecoderWrapper::CloseDecoder);
    14. ... ...
    15. }

    js层调用wasm类StreamDecoderWrapper,可以完全当作是一个js类,通过new创建对象并调用方法。

    1. class StreamDecoderWrapperJS{
    2. #stream_decoder_inner = null;
    3. StreamDecoderWrapperJS(){
    4. }
    5. openAvcDecoder(frame_callback){
    6. this.#stream_decoder_inner = new Module.StreamDecoderWrapper()
    7. this.#stream_decoder_inner.openAvcDecoder((videoFrameWrapperJS)=>{
    8. ... ...
    9. frame_callback(videoFrameWrapperJS)
    10. videoFrameWrapperJS.delete();
    11. })
    12. }
    13. decodeVideoPacket(data, size, headsize){
    14. ... ...
    15. let data_array = new Uint8Array(data)
    16. let data_slice = data_array.slice(headsize, headsize+size)
    17. let data_len = size;
    18. let buf = _malloc(data_len);
    19. HEAPU8.set(data_slice, buf);
    20. this.#stream_decoder_inner.decodeVideoPacket(buf, data_len)
    21. _free(buf);
    22. ... ...
    23. }
    24. closeDecoder(){
    25. this.#stream_decoder_inner.closeDecoder();
    26. }
    27. }

    2.2 解码

    在wasm中收到js传来的buffer数据后,就可以进行下一步解码。代码如下,可以看到这里都是普通C/C++的数据类型,js层传来的buf_p在这里直接就是一个uint8_t类型的buffer,拿到正确数据交给ffmpeg进行解码即可。

    1. void DecodeVideoPacket(uintptr_t buf_p, int size){
    2. uint8_t *data = reinterpret_cast<uint8_t *>(buf_p);
    3. if(data && (size != 0)){
    4. ... ...
    5. decoder.Decode(data, size);
    6. ... ...
    7. }
    8. }

    ffmpeg解码代码这里就不再赘述,还不太了解的朋友可以参考ffmpeg中doc下的例子。这里需要明确,AVPacket用于封装视频流buffer,AVFrame用于封装解码后的YUV数据,AVFrame中的数据可以通过 av_frame_move_ref 方法移动其内部存放的buffer,av_frame_unref给buffer减引用,引用为0就销毁buffer。后续在js层使用完毕后释放对象时,我们会使用这些方法,否则会造成浏览器内存泄露。

    2.3 wasm数据传递给js层

    解码完毕后,需要将YUV数据传递回js层,用于渲染。同样,这里也是通过注册C++类,映射一个对应的js类,在js层操作这个类,不同的是上一个我们创建的解码器会存在较长时间,而这里创建的视频帧类在使用完毕后需要立刻释放。

    视频帧frame的C++类。其中wasm中的内存并不需要拷贝,可以直接通过emscripten::typed_memory_view 映射,在js层直接使用映射得到的内存句柄即可。这里把YUV的内存都进行了映射,同时还能返回视频帧的宽高和stride等信息。

    1. #ifndef _VIDEO_FRAME_WRAPPER_H_
    2. #define _VIDEO_FRAME_WRAPPER_H_
    3. #ifdef __cplusplus
    4. extern "C" {
    5. #endif
    6. #include
    7. #include
    8. #ifdef __cplusplus
    9. }
    10. #endif
    11. #include
    12. #include
    13. #include
    14. class VideoFrameWrapper : public std::enable_shared_from_this{
    15. public:
    16. VideoFrameWrapper(){}
    17. ~VideoFrameWrapper(){
    18. Free();
    19. }
    20. int type() const { return type_; }
    21. uint8_t *data() const { return frame_->data[0]; }
    22. int linesizeY() const { return frame_->linesize[0]; }
    23. int linesizeU() const { return frame_->linesize[1]; }
    24. int linesizeV() const { return frame_->linesize[2]; }
    25. int width() const { return frame_->width; }
    26. int height() const { return frame_->height; }
    27. int format() const { return frame_->format; }
    28. double pts() const { return frame_->pts; }
    29. int data_ptr() const { return (int)(frame_->data[0]); } // NOLINT
    30. int size() const {
    31. return av_image_get_buffer_size(
    32. AV_PIX_FMT_YUV420P, frame_->width, frame_->height, 1);
    33. }
    34. emscripten::val GetBytes() {
    35. return emscripten::val(
    36. emscripten::typed_memory_view(size(), frame_->data[0]));
    37. }
    38. emscripten::val GetBytesY() {
    39. return emscripten::val(
    40. emscripten::typed_memory_view(size(), frame_->data[0]));
    41. }
    42. emscripten::val GetBytesU() {
    43. return emscripten::val(
    44. emscripten::typed_memory_view(size(), frame_->data[1]));
    45. }
    46. emscripten::val GetBytesV() {
    47. return emscripten::val(
    48. emscripten::typed_memory_view(size(), frame_->data[2]));
    49. }
    50. std::shared_ptr Alloc(AVMediaType type, AVFrame *frame) {
    51. type_ = type;
    52. frame_ = frame;
    53. return shared_from_this();
    54. }
    55. void Free() {
    56. type_ = AVMEDIA_TYPE_UNKNOWN;
    57. if (frame_ != nullptr) {
    58. av_frame_unref(frame_);
    59. av_frame_free(&frame_);
    60. frame_ = nullptr;
    61. std::cout << "Frame::Free 1 this="<< (std::hex) <<this <
    62. }
    63. }
    64. private:
    65. int type_;
    66. AVFrame *frame_;
    67. };
    68. #endif

    VideoFrameWrapper 传递给js层。这里首先创建一个AVFrame,将解码后的内存转给这个AVFrame,之后创建VideoFrameWrapper,将其作为一个shared_ptr返回给js层。可见wasm可以将shared_ptr传递给js,那么js中也需要对shared_ptr进行管理。

    1. void OpenAvcDecoder(emscripten::val lambda){
    2. std::cout<<"StreamDecoderWrapper::OpenAvcDecoder create"<
    3. decoder.OpenWithCodecID(AV_CODEC_ID_H264);
    4. decoder.RegisterDecodeCallback([lambda, this](AVFrame *frame)->int{
    5. AVFrame *out_frame = av_frame_alloc();
    6. av_frame_move_ref(out_frame, frame);
    7. auto frame_wrapper = std::make_shared()->Alloc(AVMEDIA_TYPE_AUDIO, out_frame);
    8. lambda(frame_wrapper);
    9. return 0;
    10. });
    11. }

    VideoFrameWrapper注册js对象。注意,注册的时候要加一个smart_ptr,这个类在js层也会对对象进行引用操作。同时这里还注册了可以直接访问的属性。

    1. #include
    2. #ifndef NDEBUG
    3. #include
    4. #endif
    5. #include "file_decoder_wrapper.h"
    6. #include "stream_decoder_wrapper.h"
    7. #include "video_frame_wrapper.h"
    8. using namespace emscripten;
    9. EMSCRIPTEN_BINDINGS(module){
    10. ... ...
    11. class_("VideoFrameWrapper")
    12. .smart_ptr>("shared_ptr")
    13. .property("type", &VideoFrameWrapper::type)
    14. .property("data", &VideoFrameWrapper::data_ptr)
    15. .property("linesizeY", &VideoFrameWrapper::linesizeY)
    16. .property("linesizeU", &VideoFrameWrapper::linesizeU)
    17. .property("linesizeV", &VideoFrameWrapper::linesizeV)
    18. .property("width", &VideoFrameWrapper::width)
    19. .property("height", &VideoFrameWrapper::height)
    20. .property("format", &VideoFrameWrapper::format)
    21. .property("pts", &VideoFrameWrapper::pts)
    22. .property("size", &VideoFrameWrapper::size)
    23. .function("getBytes", &VideoFrameWrapper::GetBytes)
    24. .function("getBytesY", &VideoFrameWrapper::GetBytesY)
    25. .function("getBytesU", &VideoFrameWrapper::GetBytesU)
    26. .function("getBytesV", &VideoFrameWrapper::GetBytesV);
    27. }

    js层调用。这里js层可以读取到回调对象的属性,还可以将其作为一个js对象传递,最终这个对象调用delete进行释放。

    1. openAvcDecoder(frame_callback){
    2. this.#stream_decoder_inner = new Module.StreamDecoderWrapper()
    3. this.#stream_decoder_inner.openAvcDecoder((videoFrameWrapperJS)=>{
    4. let w = videoFrameWrapperJS.width;
    5. let h = videoFrameWrapperJS.height;
    6. frame_callback(videoFrameWrapperJS)
    7. videoFrameWrapperJS.delete();
    8. })
    9. }

    3.WebGL渲染

    js层得到YUV的内存句柄就可以使用WebGL进行渲染。浏览器端WebGL可以直接将canvas作为画布,不需要EGL之类的复杂操作。外部获取canvas标签后,直接用其获取context,后续OpenGL操作在这个context上进行即可。

    1. class WebGLPlayer {
    2. constructor(canvas) {
    3. this.canvas = canvas;
    4. this.gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    5. ... ...
    6. }
    7. }

    shader编译,这里和一般OpenGL的shader操作一样,编译顶点和片元shader,获取顶点坐标和纹理坐标索引,获取YUV三个纹理的索引。

    1. #init() {
    2. if (!this.gl) {
    3. console.log("[ERROR] WebGL not supported");
    4. return;
    5. }
    6. const gl = this.gl;
    7. gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
    8. const program = gl.createProgram();
    9. const vertexShaderSource = [
    10. "attribute highp vec3 aPos;",
    11. "attribute vec2 aTexCoord;",
    12. "varying highp vec2 vTexCoord;",
    13. "void main(void) {",
    14. " gl_Position = vec4(aPos, 1.0);",
    15. " vTexCoord = aTexCoord;",
    16. "}",
    17. ].join("\n");
    18. const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    19. gl.shaderSource(vertexShader, vertexShaderSource);
    20. gl.compileShader(vertexShader);
    21. {
    22. const msg = gl.getShaderInfoLog(vertexShader);
    23. if (msg) {
    24. console.log("[ERROR] Vertex shader compile failed");
    25. console.log(msg);
    26. }
    27. }
    28. const fragmentShaderSource = [
    29. "precision highp float;",
    30. "varying lowp vec2 vTexCoord;",
    31. "uniform sampler2D yTex;",
    32. "uniform sampler2D uTex;",
    33. "uniform sampler2D vTex;",
    34. "const mat4 YUV2RGB = mat4(",
    35. " 1.1643828125, 0, 1.59602734375, -.87078515625,",
    36. " 1.1643828125, -.39176171875, -.81296875, .52959375,",
    37. " 1.1643828125, 2.017234375, 0, -1.081390625,",
    38. " 0, 0, 0, 1",
    39. ");",
    40. "void main(void) {",
    41. " // gl_FragColor = vec4(vTexCoord.x, vTexCoord.y, 0., 1.0);",
    42. " gl_FragColor = vec4(",
    43. " texture2D(yTex, vTexCoord).x,",
    44. " texture2D(uTex, vTexCoord).x,",
    45. " texture2D(vTex, vTexCoord).x,",
    46. " 1",
    47. " ) * YUV2RGB;",
    48. "}",
    49. ].join("\n");
    50. const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    51. gl.shaderSource(fragmentShader, fragmentShaderSource);
    52. gl.compileShader(fragmentShader);
    53. {
    54. const msg = gl.getShaderInfoLog(fragmentShader);
    55. if (msg) {
    56. console.log("[ERROR] Fragment shader compile failed");
    57. console.log(msg);
    58. }
    59. }
    60. gl.attachShader(program, vertexShader);
    61. gl.attachShader(program, fragmentShader);
    62. gl.linkProgram(program);
    63. gl.useProgram(program);
    64. if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    65. console.log("[ERROR] Shader link failed");
    66. }
    67. const vertices = new Float32Array([
    68. // positions // texture coords
    69. -1.0, -1.0, 0.0, 0.0, 1.0, // bottom left
    70. 1.0, -1.0, 0.0, 1.0, 1.0, // bottom right
    71. -1.0, 1.0, 0.0, 0.0, 0.0, // top left
    72. 1.0, 1.0, 0.0, 1.0, 0.0, // top right
    73. ])
    74. const verticesBuffer = gl.createBuffer();
    75. gl.bindBuffer(gl.ARRAY_BUFFER, verticesBuffer);
    76. gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
    77. const vertexPositionAttribute = gl.getAttribLocation(program, "aPos");
    78. gl.enableVertexAttribArray(vertexPositionAttribute);
    79. gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 20, 0);
    80. const textureCoordAttribute = gl.getAttribLocation(program, "aTexCoord");
    81. gl.enableVertexAttribArray(textureCoordAttribute);
    82. gl.vertexAttribPointer(textureCoordAttribute, 2, gl.FLOAT, false, 20, 12);
    83. gl.y = new Texture(gl);
    84. gl.u = new Texture(gl);
    85. gl.v = new Texture(gl);
    86. gl.y.bind(0, program, "yTex");
    87. gl.u.bind(1, program, "uTex");
    88. gl.v.bind(2, program, "vTex");
    89. }

    在得到我们上一部抛出的封装了解码数据的VideoFrameWrapper后就可以进行渲染了。这里注意ffmpeg解码后的YUV数据不一定是连续的,一定分别拿出AVFrame的每个分量,分别映射出来,否则可能会导致花屏。最终通过gl.y.fill  gl.u.fill  gl.v.fill 分别给yuv对应纹理上传buffer。这样就完成了渲染操作。

    1. render(frame) {
    2. if (!this.gl) {
    3. console.log("[ERROR] Render failed due to WebGL not supported");
    4. return;
    5. }
    6. const gl = this.gl;
    7. let port_width = gl.canvas.width;
    8. let port_height = gl.canvas.height;
    9. gl.viewport(0, 0, port_width, port_height);
    10. gl.clearColor(0.0, 0.0, 0.0, 0.0);
    11. gl.clear(gl.COLOR_BUFFER_BIT);
    12. const width = frame.width;
    13. const linesize = frame.linesize;
    14. const height = frame.height;
    15. const bytes = frame.bytes;
    16. const byteYLinesize = frame.linesizeY;
    17. const byteULinesize = frame.linesizeU;
    18. const byteVLinesize = frame.linesizeV;
    19. console.log('render width='+width+' linesizeY='+byteYLinesize)
    20. const len_y = byteYLinesize * height;
    21. const len_u = byteULinesize * height >> 1;
    22. const len_v = byteVLinesize * height >> 1;
    23. const byteY = frame.getBytesY()
    24. const byteU = frame.getBytesU()
    25. const byteV = frame.getBytesV()
    26. gl.y.fill(byteYLinesize, height, byteY.subarray(0, len_y));
    27. gl.u.fill(byteULinesize, height >> 1, byteU.subarray(0, len_u));
    28. gl.v.fill(byteVLinesize, height >> 1, byteV.subarray(0, len_v));
    29. gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    30. //gl.finish();
    31. //gl.commit();
    32. }

    渲染播放丢帧问题

    在实际操作中发现播放过程中丢帧严重,经过排查是在解码完成后直接抛出帧,由于解码时间不均匀导致有些帧播放后很快又被新帧覆盖,导致播放卡顿。目前在c层解码完毕后增加了一个delay操作,按照解码时间和帧率进行延迟等待,用于平滑渲染。此处考虑是否可以引入一个线程,或者是否有其比较好的解决方式。目前控制播放代码如下

    1. decoder.RegisterDecodeCallback([lambda, this](AVFrame *frame)->int{
    2. std::cout<<"OpenAvcDecoder debug2"<
    3. AVFrame *out_frame = av_frame_alloc();
    4. av_frame_move_ref(out_frame, frame);
    5. auto frame_wrapper = std::make_shared()->Alloc(AVMEDIA_TYPE_AUDIO, out_frame);
    6. long delay_time = -1;
    7. long curr_ts = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count();
    8. if(last_out_ts != 0){
    9. long curr_gap = curr_ts - last_out_ts;
    10. if(curr_gap > 0 && curr_gap < gap){
    11. delay_time = gap - curr_gap;
    12. }
    13. }
    14. last_out_ts = curr_ts;
    15. if(delay_time > 0){
    16. usleep(delay_time * 1000);
    17. std::cout<<"OpenAvcDecoder delay_time="<
    18. }
    19. lambda(frame_wrapper);
    20. return 0;
    21. });

    4.Server端交互

    server端与js端通过Websocket进行数据交互,目前提供了一个简单的协议头,用于请求视频和停止视频

    1. //json data type == 1
    2. //video data type == 2
    3. // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
    4. //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    5. //| 'A' | 'A' |v=1| type | rec |
    6. //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    7. //| payload length |
    8. //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    server端提供了一个H264FileVideoCapture类用于模拟视频采集和编码,如果需要使用自己的采集编码可以重新实现一个H264FileVideoCapture和MediaStreamer。

    4.1 mongoose支持wasm多线程

    wasm开启多线程,需要浏览器开启Cross-origin保护,否则直接报错。

    这里需要mongoose在收到网页请求的时候,在响应头中增加设置,代码实现如下

    1. struct mg_http_serve_opts opts = {.root_dir = s_web_root};
    2. //wasm 多线程需要增加响应头
    3. opts.extra_headers = "Cross-Origin-Embedder-Policy:require-corp\r\nCross-Origin-Opener-Policy:same-origin\r\n";
    4. mg_http_serve_dir(c, (struct mg_http_message *)ev_data, &opts);

  • 相关阅读:
    Android问题笔记四十二:signal 11 (SIGSEGV), code 1 (SEGV_MAPERR) 的解决方法
    【web开发】spring security配合验证码,java获取地理位置(绘制百度地图)、天气
    Mybatis(动态sql和分页)
    初识 SpringMVC,运行配置第一个Spring MVC 程序
    对象的解构赋值(基本用法1)
    程序猿成长之路之密码学篇-RSA非对称分组加密算法介绍
    327.区间和的个数
    【算法】单调栈
    【文件操作API的使用】
    【java实战项目】90分钟轻松学会java开发飞机大战小游戏
  • 原文地址:https://blog.csdn.net/lidec/article/details/128178176