WebCodec 是一个较新的 Web API,它为网页应用提供了接近硬件级的编解码能力。使用 WebCodec,开发者可以在浏览器中直接对音频和视频数据进行高效编解码,这对于实现复杂的媒体应用(如实时视频通信、视频编辑和游戏流)非常有用。
WebCodec 编解码流程
解码
创建解码器
设置解码器名称
WebCodec设置上参数与其它平台不同的一点,在于其名称字段比较繁琐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| if (VideoType == H264) { ... std::stringstream stream; stream << "avc1."; stream << decimalToHex(profile_idc); stream << decimalToHex(constraint_set_flags); stream << decimalToHex(level_idc); codecName = stream.str(); }else if(VideoType == H265) { std::stringstream stream; stream << "hev1."; switch (general_profile_space) { case 0: break; case 1: stream << 'A'; break; case 2: stream << 'B'; break; case 3: stream << 'C'; break; } stream << general_profile_idc; stream << '.'; MUInt32 val = (MUInt32)general_profile_compatibility_flag; int reversed = 0; for (int i = 0; i < 32; i++) { reversed |= val & 1; if (i == 31) break; reversed <<= 1; val >>= 1; } stream << decimalToHex(reversed, 0); stream << '.'; if (general_tier_flag == 0) { stream << 'L'; } else { stream << 'H'; } stream << general_level_idc; MBool hasByte = false;
std::string constraint_string = ""; for (int i = 5; i >= 0; i--) { int val = 0; if (general_constraint_indicator_flags[i] != '\0') { val = general_constraint_indicator_flags[i]; } if (val != 0 || hasByte) { constraint_string = "." + decimalToHex(val, 0) + constraint_string; hasByte = true; } } stream << constraint_string; codecName = stream.str(); }
|
创建解码器实例
1 2 3 4 5 6 7 8 9 10
| const decoder = new VideoDecoder({ output(frame) { ... }, error(e) { ... } });
|
配置解码器
WebCodec支持Annex-B格式和avc格式的码流,如果设置了description,则表示传入的是avc格式的码流,如果不传,则表示传入的是Annex-B格式的码流
1 2 3 4 5 6 7 8 9
| let config = { codec: codecName, codedHeight: height, codedWidth: width, description: description, hardwareAcceleration: "prefer-hardware" }; decoder.configure(config);
|
处理数据
输入解码数据
使用 decode
方法将压缩的视频数据(通常是 EncodedVideoChunk
)送入解码器。
1 2 3 4 5 6 7 8
| let chunk = new EncodedVideoChunk({ type: is_sync ? "key" : "delta", timestamp: timestamp, duration: duration, data: data }); decoder.decode(chunk);
|
处理输出解码
VideoFrame包含了解码后的一帧数据,可以通过copyTo函数将输入拷贝到cpu中(比较耗时),也可以直接通过texImage2D直接绑定到纹理上。VideoFrame是一个Transferable objects,可以通过postMessage发送到其它Worker中使用。
1 2 3 4 5 6 7
| output(frame) { ... gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame); frame.copyTo(buffer); },
|
释放资源
解码完成后,调用 close
或 flush
释放解码器资源。
1 2
| await decoder.flush(); decoder.close();
|
编码
创建编码器
设置解码器名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| if (VideoType == H264) { stream << "avc1."; switch (Profile) { case H264_PROFILE_BASELINE: { stream << "4200"; } break; case H264_PROFILE_MAIN: { stream << "4d00"; } break; case H264_PROFILE_HIGH: { stream << "6400"; } break; default: { stream << "4200"; } break; }
switch (Level) { case H264_LEVEL_30: { stream << "1e"; } break; case H264_LEVEL_31: { stream << "1f"; } break; case H264_LEVEL_40: { stream << "28"; } break; case H264_LEVEL_41: { stream << "29"; } break; case H264_LEVEL_50: { stream << "32"; } break; case H264_LEVEL_51: { stream << "33"; } break; default: { stream << "1e"; } break; } }
else if (VideoType == H265) { switch (Profile) { case HEVC_PROFILE_MAIN: { stream << "hev1.1.6.L93.B0"; } break; case HEVC_PROFILE_MAIN10: { stream << "hev1.2.4.L120.B0"; } break; default: { stream << "hev1.1.6.L93.B0"; } break; } }
|
创建编码器实例
使用 VideoEncoder
类创建一个视频编码器实例。
1 2 3 4 5 6 7 8 9
| const encoder = new VideoEncoder({ output(chunk, metadata) {
},
error(e) {
} });
|
配置解码器
WebCodec支持Annex-B格式和avc格式的码流,可以在config中指定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const config = { bitrateMode: bitPref, codec: codec, width: width, height: height, hardwareAcceleration: hw, bitrate: bitrate, framerate: frameRate, keyInterval: keyInterval, };
switch(codec_type){ case "H264": config.avc = { format: "annexb" }; break; case "H265": config.hevc = { format: "annexb" }; break; }
encoder.configure(config);
|
处理数据
输入编码数据
通过 encode
方法将 VideoFrame
对象送入编码器进行编码。
1 2 3 4 5 6 7 8 9
| const init = { timestamp: timestamp, duration: duration, codedWidth: frameWidth, codedHeight: frameHeight, format: "I420", }; let videoFrame = new VideoFrame(dst, init); encoder.encode(videoFrame,{keyFrame:key_flag});
|
处理输出解码
通过copyTo可以拷贝编码结果到一个buffer中,调用muxer的write将其写入到文件中
1 2 3 4 5
| output(chunk, metadata) { chunk.copyTo(buffer) },
|
释放资源
编码完成后,调用 close
或 flush
释放编码器资源。
1 2
| await encoder.flush(); encoder.close();
|