小言_互联网的博客

wasm 视频解码渲染实现

750人阅读  评论(0)

实现一个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<VideoFrameWrapper>()-> 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 <emscripten/bind.h>
  2. #ifndef NDEBUG
  3. #include <sanitizer/lsan_interface.h>
  4. #endif
  5. #include "stream_decoder_wrapper.h"
  6. using namespace emscripten;
  7. EMSCRIPTEN_BINDINGS( module){
  8. ... ...
  9. class_<StreamDecoderWrapper>( "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 <libavutil/frame.h>
  7. #include <libavutil/imgutils.h>
  8. #ifdef __cplusplus
  9. }
  10. #endif
  11. #include <memory>
  12. #include <iostream>
  13. #include <emscripten/val.h>
  14. class VideoFrameWrapper : public std::enable_shared_from_this<VideoFrameWrapper>{
  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<VideoFrameWrapper> 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 <<std::endl;
  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"<<std::endl;
  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<VideoFrameWrapper>()-> Alloc(AVMEDIA_TYPE_AUDIO, out_frame);
  8. lambda(frame_wrapper);
  9. return 0;
  10. });
  11. }

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


  
  1. #include <emscripten/bind.h>
  2. #ifndef NDEBUG
  3. #include <sanitizer/lsan_interface.h>
  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>( "VideoFrameWrapper")
  12. .smart_ptr<std::shared_ptr<VideoFrameWrapper>>( "shared_ptr<VideoFrameWrapper>")
  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"<<std::endl;
  3. AVFrame *out_frame = av_frame_alloc();
  4. av_frame_move_ref(out_frame, frame);
  5. auto frame_wrapper = std:: make_shared<VideoFrameWrapper>()-> Alloc(AVMEDIA_TYPE_AUDIO, out_frame);
  6. long delay_time = -1;
  7. long curr_ts = std::chrono:: duration_cast<std::chrono::milliseconds>(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="<<delay_time<<std::endl;
  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);


转载:https://blog.csdn.net/lidec/article/details/128178176
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场