/** * package mediaserver * * WebRTC ↔ RTMP 媒体桥接 * 功能: * 1. WebRTC 流转 RTMP(H5用户 -> 小程序用户) * 2. RTMP 流转 WebRTC(小程序用户 -> H5用户) * 3. 音频转码:Opus ↔ AAC * 4. 视频透传:H.264 */ package mediaserver import ( "fmt" "log" "sync" "time" "github.com/pion/rtp" "github.com/pion/webrtc/v3" ) // Bridge WebRTC-RTMP 媒体桥 type Bridge struct { roomID string mu sync.RWMutex running bool stopChan chan struct{} // WebRTC -> RTMP 转换器 webrtcToRTMP map[string]*WebRTCToRTMPConverter // RTMP -> WebRTC 转换器 rtmpToWebRTC map[string]*RTMPToWebRTCConverter } // WebRTCToRTMPConverter WebRTC 转 RTMP 转换器 type WebRTCToRTMPConverter struct { UserID string StreamID string RoomID string Running bool stopChan chan struct{} // 输入: WebRTC Track VideoTrack *webrtc.TrackRemote AudioTrack *webrtc.TrackRemote // 输出: RTMP 流信息 OutputURL string // RTMP 推流地址 // 编解码器 videoCodec string // h264 audioCodec string // opus -> aac // 缓冲区 videoBuffer chan *rtp.Packet audioBuffer chan *rtp.Packet } // RTMPToWebRTCConverter RTMP 转 WebRTC 转换器 type RTMPToWebRTCConverter struct { UserID string StreamID string RoomID string Running bool stopChan chan struct{} // 输入: RTMP 拉流地址 InputURL string // 输出: WebRTC Track VideoTrack *webrtc.TrackLocalStaticRTP AudioTrack *webrtc.TrackLocalStaticRTP // 编解码器 videoCodec string // h264 audioCodec string // aac -> opus } // BridgeManager 桥接管理器 type BridgeManager struct { bridges map[string]*Bridge // roomID -> bridge mu sync.RWMutex } // 全局桥接管理器 var bridgeManager *BridgeManager var bridgeOnce sync.Once // GetBridgeManager 获取桥接管理器单例 func GetBridgeManager() *BridgeManager { bridgeOnce.Do(func() { bridgeManager = &BridgeManager{ bridges: make(map[string]*Bridge), } }) return bridgeManager } // GetOrCreateBridge 获取或创建房间的桥接 func (bm *BridgeManager) GetOrCreateBridge(roomID string) *Bridge { bm.mu.Lock() defer bm.mu.Unlock() if bridge, exists := bm.bridges[roomID]; exists { return bridge } bridge := NewBridge(roomID) bm.bridges[roomID] = bridge return bridge } // RemoveBridge 移除房间的桥接 func (bm *BridgeManager) RemoveBridge(roomID string) { bm.mu.Lock() defer bm.mu.Unlock() if bridge, exists := bm.bridges[roomID]; exists { bridge.Stop() delete(bm.bridges, roomID) } } // GetBridge 获取桥接 func (bm *BridgeManager) GetBridge(roomID string) *Bridge { bm.mu.RLock() defer bm.mu.RUnlock() return bm.bridges[roomID] } // NewBridge 创建新桥接 func NewBridge(roomID string) *Bridge { return &Bridge{ roomID: roomID, stopChan: make(chan struct{}), webrtcToRTMP: make(map[string]*WebRTCToRTMPConverter), rtmpToWebRTC: make(map[string]*RTMPToWebRTCConverter), } } // Start 启动桥接 func (b *Bridge) Start() { b.mu.Lock() defer b.mu.Unlock() if b.running { return } b.running = true log.Printf("🌉 [Bridge] 启动房间桥接 | Room:%s", b.roomID) } // Stop 停止桥接 func (b *Bridge) Stop() { b.mu.Lock() defer b.mu.Unlock() if !b.running { return } // 关闭停止信号 close(b.stopChan) // 停止所有 WebRTC -> RTMP 转换器 for _, converter := range b.webrtcToRTMP { converter.Stop() } b.webrtcToRTMP = make(map[string]*WebRTCToRTMPConverter) // 停止所有 RTMP -> WebRTC 转换器 for _, converter := range b.rtmpToWebRTC { converter.Stop() } b.rtmpToWebRTC = make(map[string]*RTMPToWebRTCConverter) b.running = false log.Printf("🌉 [Bridge] 停止房间桥接 | Room:%s", b.roomID) } // AddWebRTCToRTMP 添加 WebRTC 到 RTMP 的转换 func (b *Bridge) AddWebRTCToRTMP(userID string, videoTrack, audioTrack *webrtc.TrackRemote, outputURL string) (*WebRTCToRTMPConverter, error) { b.mu.Lock() defer b.mu.Unlock() if !b.running { b.running = true } streamID := fmt.Sprintf("w2r_%s_%s_%d", b.roomID, userID, time.Now().UnixNano()) converter := &WebRTCToRTMPConverter{ UserID: userID, StreamID: streamID, RoomID: b.roomID, Running: true, stopChan: make(chan struct{}), VideoTrack: videoTrack, AudioTrack: audioTrack, OutputURL: outputURL, videoCodec: "h264", audioCodec: "opus", videoBuffer: make(chan *rtp.Packet, 100), audioBuffer: make(chan *rtp.Packet, 100), } b.webrtcToRTMP[userID] = converter // 启动转换协程 go converter.Run() log.Printf("🌉 [Bridge] 添加 WebRTC->RTMP | Room:%s User:%s Stream:%s", b.roomID, userID, streamID) return converter, nil } // AddRTMPToWebRTC 添加 RTMP 到 WebRTC 的转换 func (b *Bridge) AddRTMPToWebRTC(userID string, inputURL string) (*RTMPToWebRTCConverter, error) { b.mu.Lock() defer b.mu.Unlock() if !b.running { b.running = true } streamID := fmt.Sprintf("r2w_%s_%s_%d", b.roomID, userID, time.Now().UnixNano()) // 创建本地轨道 videoTrack, err := webrtc.NewTrackLocalStaticRTP( webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264}, fmt.Sprintf("video_%s", streamID), fmt.Sprintf("stream_%s", userID), ) if err != nil { return nil, fmt.Errorf("create video track failed: %w", err) } audioTrack, err := webrtc.NewTrackLocalStaticRTP( webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus}, fmt.Sprintf("audio_%s", streamID), fmt.Sprintf("stream_%s", userID), ) if err != nil { return nil, fmt.Errorf("create audio track failed: %w", err) } converter := &RTMPToWebRTCConverter{ UserID: userID, StreamID: streamID, RoomID: b.roomID, Running: true, stopChan: make(chan struct{}), InputURL: inputURL, VideoTrack: videoTrack, AudioTrack: audioTrack, videoCodec: "h264", audioCodec: "aac", } b.rtmpToWebRTC[userID] = converter // 启动转换协程 go converter.Run() log.Printf("🌉 [Bridge] 添加 RTMP->WebRTC | Room:%s User:%s Stream:%s", b.roomID, userID, streamID) return converter, nil } // RemoveWebRTCToRTMP 移除 WebRTC 到 RTMP 的转换 func (b *Bridge) RemoveWebRTCToRTMP(userID string) { b.mu.Lock() defer b.mu.Unlock() if converter, exists := b.webrtcToRTMP[userID]; exists { converter.Stop() delete(b.webrtcToRTMP, userID) log.Printf("🌉 [Bridge] 移除 WebRTC->RTMP | Room:%s User:%s", b.roomID, userID) } } // RemoveRTMPToWebRTC 移除 RTMP 到 WebRTC 的转换 func (b *Bridge) RemoveRTMPToWebRTC(userID string) { b.mu.Lock() defer b.mu.Unlock() if converter, exists := b.rtmpToWebRTC[userID]; exists { converter.Stop() delete(b.rtmpToWebRTC, userID) log.Printf("🌉 [Bridge] 移除 RTMP->WebRTC | Room:%s User:%s", b.roomID, userID) } } // GetRTMPToWebRTCTracks 获取 RTMP->WebRTC 转换器的输出轨道 func (b *Bridge) GetRTMPToWebRTCTracks(userID string) (*webrtc.TrackLocalStaticRTP, *webrtc.TrackLocalStaticRTP) { b.mu.RLock() defer b.mu.RUnlock() if converter, exists := b.rtmpToWebRTC[userID]; exists { return converter.VideoTrack, converter.AudioTrack } return nil, nil } // --- WebRTCToRTMPConverter 方法 --- // Run 执行 WebRTC 到 RTMP 的转换 func (c *WebRTCToRTMPConverter) Run() { log.Printf("🌉 [W2R] 转换开始 | Stream:%s", c.StreamID) defer log.Printf("🌉 [W2R] 转换结束 | Stream:%s", c.StreamID) // 启动视频读取 if c.VideoTrack != nil { go c.readVideoTrack() } // 启动音频读取 if c.AudioTrack != nil { go c.readAudioTrack() } // 启动 RTMP 推流 go c.pushToRTMP() // 等待停止信号 <-c.stopChan } // readVideoTrack 读取视频轨道 func (c *WebRTCToRTMPConverter) readVideoTrack() { buf := make([]byte, 1500) for c.Running { n, _, err := c.VideoTrack.Read(buf) if err != nil { if c.Running { log.Printf("⚠️ [W2R] 读取视频失败: %v", err) } return } // 解析 RTP 包 packet := &rtp.Packet{} if err := packet.Unmarshal(buf[:n]); err != nil { continue } select { case c.videoBuffer <- packet: default: // 缓冲区满,丢弃 } } } // readAudioTrack 读取音频轨道 func (c *WebRTCToRTMPConverter) readAudioTrack() { buf := make([]byte, 1500) for c.Running { n, _, err := c.AudioTrack.Read(buf) if err != nil { if c.Running { log.Printf("⚠️ [W2R] 读取音频失败: %v", err) } return } // 解析 RTP 包 packet := &rtp.Packet{} if err := packet.Unmarshal(buf[:n]); err != nil { continue } select { case c.audioBuffer <- packet: default: // 缓冲区满,丢弃 } } } // pushToRTMP 推送到 RTMP func (c *WebRTCToRTMPConverter) pushToRTMP() { // TODO: 实际的 RTMP 推流实现 // 1. 连接到 RTMP 服务器 // 2. 从 videoBuffer/audioBuffer 读取 RTP 包 // 3. 解封装 RTP,获取裸数据 // 4. 视频: H.264 NAL 单元 -> FLV Tag // 5. 音频: Opus -> AAC (需要转码) // 6. 发送 FLV Tag 到 RTMP 服务器 for c.Running { select { case <-c.stopChan: return case videoPacket := <-c.videoBuffer: // 处理视频包 _ = videoPacket case audioPacket := <-c.audioBuffer: // 处理音频包 _ = audioPacket } } } // Stop 停止转换 func (c *WebRTCToRTMPConverter) Stop() { if !c.Running { return } c.Running = false close(c.stopChan) } // --- RTMPToWebRTCConverter 方法 --- // Run 执行 RTMP 到 WebRTC 的转换 func (c *RTMPToWebRTCConverter) Run() { log.Printf("🌉 [R2W] 转换开始 | Stream:%s URL:%s", c.StreamID, c.InputURL) defer log.Printf("🌉 [R2W] 转换结束 | Stream:%s", c.StreamID) // TODO: 实际的 RTMP 拉流实现 // 1. 连接到 RTMP 服务器 // 2. 拉取 FLV 流 // 3. 解析 FLV Tag // 4. 视频: H.264 NAL 单元 -> RTP 包 // 5. 音频: AAC -> Opus (需要转码) // 6. 写入 WebRTC Track // 等待停止信号 <-c.stopChan } // Stop 停止转换 func (c *RTMPToWebRTCConverter) Stop() { if !c.Running { return } c.Running = false close(c.stopChan) } // GetConverterCount 获取转换器数量 func (b *Bridge) GetConverterCount() (int, int) { b.mu.RLock() defer b.mu.RUnlock() return len(b.webrtcToRTMP), len(b.rtmpToWebRTC) } // IsRunning 是否运行中 func (b *Bridge) IsRunning() bool { b.mu.RLock() defer b.mu.RUnlock() return b.running } // GetAllConverters 获取所有转换器信息 func (b *Bridge) GetAllConverters() ([]map[string]interface{}, []map[string]interface{}) { b.mu.RLock() defer b.mu.RUnlock() w2r := make([]map[string]interface{}, 0, len(b.webrtcToRTMP)) for _, c := range b.webrtcToRTMP { w2r = append(w2r, map[string]interface{}{ "user_id": c.UserID, "stream_id": c.StreamID, "output_url": c.OutputURL, "running": c.Running, }) } r2w := make([]map[string]interface{}, 0, len(b.rtmpToWebRTC)) for _, c := range b.rtmpToWebRTC { r2w = append(r2w, map[string]interface{}{ "user_id": c.UserID, "stream_id": c.StreamID, "input_url": c.InputURL, "running": c.Running, }) } return w2r, r2w }