Files
nl-im-service/internal/mediaserver/bridge.go
2025-12-15 09:04:14 +08:00

483 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* package mediaserver
*
* WebRTC ↔ RTMP 媒体桥接
* 功能:
* 1. WebRTC 流转 RTMPH5用户 -> 小程序用户)
* 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
}