483 lines
11 KiB
Go
483 lines
11 KiB
Go
/**
|
||
* 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
|
||
}
|