You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

UDP传输的H.264视频流经WebSocket推送到浏览器失败求助

解决UDP H.264流转前端Video标签无显示的问题

核心问题分析

你的原始UDP流能在QGroundControl正常播放,说明流本身没问题,但前端MSE无法解析显示,大概率是H.264裸流未按MSE要求封装、编码参数不匹配,或是数据包处理逻辑缺失导致的。

针对性解决步骤

1. 修正MSE编码格式配置

你当前使用的video/mp4; codecs="avc1.64001E,mp4a.40.2"包含音频编码,但如果原始流是纯H.264视频(无音频),必须去掉音频部分:

sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.64001E"');

同时要确保avc1.64001E与PX4输出的H.264参数匹配。可以导出一段QGroundControl能播放的流,用FFmpeg验证参数:

ffmpeg -i exported_stream.h264 -vcodec copy -f mp4 temp.mp4
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,profile,level -of default=noprint_wrappers=1:nokey=1 temp.mp4

把得到的参数替换到codecs中,比如Main Profile Level 3.1对应avc1.4D401F。

2. 处理H.264的NALU封装

MSE的MP4容器要求H.264流是带4字节大端长度前缀的NALU单元,但UDP传输的H.264通常是带起始码(0x00000001或0x000001)的裸NALU,直接转发会导致MSE无法识别NALU边界,必须做转换:

前端处理示例(简化版):

function processH264Nalu(rawBuffer) {
    const uint8Array = new Uint8Array(rawBuffer);
    const naluList = [];
    let startIndex = 0;

    // 扫描起始码,切割NALU并添加长度前缀
    for (let i = 3; i < uint8Array.length; i++) {
        if ((uint8Array[i] === 1 && uint8Array[i-1] === 0 && uint8Array[i-2] === 0) || 
            (i > 3 && uint8Array[i] === 1 && uint8Array[i-1] === 0 && uint8Array[i-2] === 0 && uint8Array[i-3] === 0)) {
            const nalu = uint8Array.subarray(startIndex, i);
            const lengthPrefix = new Uint32Array([nalu.length]);
            const processedNalu = new Uint8Array(4 + nalu.length);
            processedNalu.set(new Uint8Array(lengthPrefix.buffer));
            processedNalu.set(nalu, 4);
            naluList.push(processedNalu);
            startIndex = i;
        }
    }
    // 处理最后一个NALU
    if (startIndex < uint8Array.length) {
        const nalu = uint8Array.subarray(startIndex);
        const lengthPrefix = new Uint32Array([nalu.length]);
        const processedNalu = new Uint8Array(4 + nalu.length);
        processedNalu.set(new Uint8Array(lengthPrefix.buffer));
        processedNalu.set(nalu, 4);
        naluList.push(processedNalu);
    }
    // 合并所有处理后的NALU
    const totalLength = naluList.reduce((sum, nalu) => sum + nalu.length, 0);
    const result = new Uint8Array(totalLength);
    let offset = 0;
    naluList.forEach(nalu => {
        result.set(nalu, offset);
        offset += nalu.length;
    });
    return result.buffer;
}

修改接收回调逻辑:

connection.on("Video", function (dataChunk) {
    var byteArray = base64ToArrayBuffer(dataChunk);
    var processedBuffer = processH264Nalu(byteArray);
    
    // 处理队列,避免sourceBuffer忙时丢包
    if (sourceBuffer && !sourceBuffer.updating) {
        sourceBuffer.appendBuffer(processedBuffer);
    } else if (sourceBuffer) {
        bufferQueue.push(processedBuffer);
        sourceBuffer.addEventListener('updateend', function flushQueue() {
            if (bufferQueue.length > 0 && !sourceBuffer.updating) {
                sourceBuffer.appendBuffer(bufferQueue.shift());
            } else if (bufferQueue.length === 0) {
                sourceBuffer.removeEventListener('updateend', flushQueue);
            }
        });
    }
});

3. 确保SPS/PPS优先发送

H.264解码器必须先拿到SPS(序列参数集)和PPS(图像参数集)才能初始化。你需要:

  • 确认UDP流中的SPS/PPS被正确转发(通常每I帧前会周期性发送);
  • 若前端接收顺序混乱,可在C#服务器端缓存SPS/PPS,新连接建立时先推送一次,再发送后续帧。

4. 优化SignalR传输方式

你当前设置了connection.binaryType = 'arraybuffer';,但服务器发送的是Base64字符串,转换冗余且易出错。建议直接传输二进制:

  • C#服务器端将视频数据包以byte[]类型发送;
  • 前端接收时直接使用arraybuffer,去掉base64ToArrayBuffer转换步骤。

5. 添加MSE错误监听排查隐藏问题

在前端补充错误监听,捕获控制台未显示的MSE内部错误:

mediaSource.addEventListener('sourceerror', (e) => {
    console.error('MediaSource错误:', e);
});

sourceBuffer.addEventListener('error', (e) => {
    console.error('SourceBuffer错误:', e);
});

sourceBuffer.addEventListener('updateerror', (e) => {
    console.error('SourceBuffer更新错误:', e);
});

替代方案:使用WebCodecs API

如果MSE始终无法正常工作,可以尝试更底层的WebCodecs API,直接解码H.264裸流并渲染到Canvas:

  • 用VideoDecoder解码NALU单元;
  • 将解码后的帧直接渲染到Canvas元素。

内容的提问来源于stack exchange,提问作者frokwon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 01:57:44