基于pion/webrtc转发H264直播流至浏览器播放异常问题
H264直播流转WebRTC播放异常问题修复方案
基于rtsp-simple-server与pion/webrtc库实现直播流转发至WebRTC时,浏览器播放出现异常。以下是问题代码及针对性修复建议:
原始问题代码
func (c *webrtcSession) runRead(ctx context.Context) error { c.ringBuffer, _ = ringbuffer.New(uint64(c.readBufferCount)) h264FrameDuration := time.Millisecond * 33 for { item, ok := c.ringBuffer.Pull() data := item.(*data) if c.videoTrack != nil && data.trackID == c.videoTrackID { if data.h264NALUs == nil { continue } outBuf := []byte{} for _, nalu := range data.h264NALUs { outBuf = append(outBuf, []byte{0x00, 0x00, 0x00, 0x01}...) outBuf = append(outBuf, nalu...) } c.videoTrack.WriteSample(media.Sample{Data: outBuf, Duration:time.Duration(h264FrameDuration)}) } } }
核心问题点及修复措施
1. 未处理RingBuffer拉取失败与类型断言安全
原始代码未判断ringBuffer.Pull()的ok状态,也未校验item转*data的合法性,容易引发panic或无限循环。
修复:添加状态判断与类型校验:
item, ok := c.ringBuffer.Pull() if !ok { return fmt.Errorf("ringbuffer closed") } data, ok := item.(*data) if !ok { continue }
2. 固定帧时长导致时序混乱
硬编码33ms帧时长,若原始流帧率不是30fps,会导致浏览器解码时序错误,出现卡顿、跳帧。
修复:从原始流获取真实帧率计算时长,或使用RTP时间戳转换:
// 示例:按30fps计算,建议从流元数据获取真实帧率 frameRate := 30 h264FrameDuration := time.Second / time.Duration(frameRate)
3. 缺少解码器初始化所需的SPS/PPS
浏览器WebRTC解码器需要先接收SPS(序列参数集)和PPS(图像参数集)才能正确解码IDR帧,原始代码未确保该逻辑。
修复:缓存并优先发送SPS/PPS,或在IDR帧前补充发送:
// 遍历NALU时标记类型 hasIDR := false hasSPSPPS := false for _, nalu := range data.h264NALUs { if len(nalu) < 1 { continue } naluType := nalu[0] & 0x1F switch naluType { case 7, 8: // SPS/PPS hasSPSPPS = true // 可选:缓存SPS/PPS备用 // cacheSPSPPS(nalu, naluType) case 5: // IDR帧 hasIDR = true } outBuf = append(outBuf, []byte{0x00,0x00,0x00,0x01}...) outBuf = append(outBuf, nalu...) } // 若IDR帧未携带SPS/PPS,补充发送缓存的参数集 if hasIDR && !hasSPSPPS { // sps, pps := getCachedSPSPPS() // if sps != nil && pps != nil { // spsPPSBuf := append([]byte{0x00,0x00,0x00,0x01}, sps...) // spsPPSBuf = append(spsPPSBuf, []byte{0x00,0x00,0x00,0x01}...) // spsPPSBuf = append(spsPPSBuf, pps...) // _ = c.videoTrack.WriteSample(media.Sample{Data: spsPPSBuf, Duration: 0}) // } }
4. 未监听上下文取消
循环未响应ctx.Done(),导致session关闭时无法退出,引发资源泄漏。
修复:在循环内添加上下文监听:
for { select { case <-ctx.Done(): return ctx.Err() default: } // 后续拉取数据逻辑 }
修复后完整代码示例
import "fmt" func (c *webrtcSession) runRead(ctx context.Context) error { ringBuf, err := ringbuffer.New(uint64(c.readBufferCount)) if err != nil { return err } c.ringBuffer = ringBuf // 建议从直播流元数据获取真实帧率,此处为示例值 frameRate := 30 h264FrameDuration := time.Second / time.Duration(frameRate) for { select { case <-ctx.Done(): return ctx.Err() default: } item, ok := c.ringBuffer.Pull() if !ok { return fmt.Errorf("ringbuffer closed unexpectedly") } data, ok := item.(*data) if !ok { continue } if c.videoTrack == nil || data.trackID != c.videoTrackID { continue } if data.h264NALUs == nil || len(data.h264NALUs) == 0 { continue } outBuf := []byte{} hasIDR := false hasSPSPPS := false for _, nalu := range data.h264NALUs { if len(nalu) < 1 { continue } naluType := nalu[0] & 0x1F switch naluType { case 7: // SPS case 8: // PPS hasSPSPPS = true case 5: // IDR hasIDR = true } outBuf = append(outBuf, []byte{0x00, 0x00, 0x00, 0x01}...) outBuf = append(outBuf, nalu...) } // 确保解码器初始化所需的SPS/PPS已发送 if hasIDR && !hasSPSPPS { // 此处需补充缓存的SPS/PPS发送逻辑,示例代码需根据实际场景实现 // sps, pps := getCachedSPSPPS() // if sps != nil && pps != nil { // spsPPSBuf := append([]byte{0x00,0x00,0x00,0x01}, sps...) // spsPPSBuf = append(spsPPSBuf, []byte{0x00,0x00,0x00,0x01}...) // spsPPSBuf = append(spsPPSBuf, pps...) // if err := c.videoTrack.WriteSample(media.Sample{Data: spsPPSBuf, Duration: 0}); err != nil { // return err // } // } } if err := c.videoTrack.WriteSample(media.Sample{Data: outBuf, Duration: h264FrameDuration}); err != nil { return err } } }
内容的提问来源于stack exchange,提问作者sureone
相关产品推荐
相关产品推荐

