网站通话使用webrtc,新增ffmpeg

This commit is contained in:
2025-12-16 15:12:03 +08:00
parent 811764fd8a
commit c8ab9b74ab
3 changed files with 103 additions and 30 deletions

View File

@@ -19,6 +19,9 @@ import (
// TranscoderConfig 转码器配置
type TranscoderConfig struct {
// 模式配置
CopyMode bool // 是否使用 copy 模式H.264 输入时使用,只重封装不重新编码)
// 视频配置
VideoCodec string // 输出视频编解码器,默认 libx264
VideoPreset string // x264 预设,默认 ultrafast
@@ -96,20 +99,41 @@ func (t *FFmpegTranscoder) Start() error {
}
// 构建 FFmpeg 命令
// ffmpeg -f webm -i pipe:0 -c:v libx264 -preset ultrafast -tune zerolatency -c:a aac -f flv pipe:1
args := []string{
"-f", "webm", // 输入格式
"-i", "pipe:0", // 从 stdin 读取
"-c:v", t.config.VideoCodec,
"-preset", t.config.VideoPreset,
"-tune", "zerolatency", // 零延迟模
"-b:v", t.config.VideoBitrate,
"-c:a", t.config.AudioCodec,
"-b:a", t.config.AudioBitrate,
"-ar", fmt.Sprintf("%d", t.config.AudioSampleRate),
"-ac", fmt.Sprintf("%d", t.config.AudioChannels),
"-f", "flv", // 输出格式
"pipe:1", // 输出到 stdout
var args []string
if t.config.CopyMode {
// H.264 copy 模式:只重封装,不重新编码(低延迟)
// ffmpeg -f webm -i pipe:0 -c:v copy -c:a aac -f flv pipe:1
args = []string{
"-f", "webm", // 输入格
"-i", "pipe:0", // 从 stdin 读取
"-c:v", "copy", // 视频直接复制,不重新编码
"-c:a", t.config.AudioCodec,
"-b:a", t.config.AudioBitrate,
"-ar", fmt.Sprintf("%d", t.config.AudioSampleRate),
"-ac", fmt.Sprintf("%d", t.config.AudioChannels),
"-f", "flv", // 输出格式
"pipe:1", // 输出到 stdout
}
log.Printf("🎬 [FFmpegTranscoder] 使用 copy 模式H.264 重封装)")
} else {
// VP8/VP9 转码模式:重新编码为 H.264
// ffmpeg -f webm -i pipe:0 -c:v libx264 -preset ultrafast -tune zerolatency -c:a aac -f flv pipe:1
args = []string{
"-f", "webm", // 输入格式
"-i", "pipe:0", // 从 stdin 读取
"-c:v", t.config.VideoCodec,
"-preset", t.config.VideoPreset,
"-tune", "zerolatency", // 零延迟模式
"-b:v", t.config.VideoBitrate,
"-c:a", t.config.AudioCodec,
"-b:a", t.config.AudioBitrate,
"-ar", fmt.Sprintf("%d", t.config.AudioSampleRate),
"-ac", fmt.Sprintf("%d", t.config.AudioChannels),
"-f", "flv", // 输出格式
"pipe:1", // 输出到 stdout
}
log.Printf("🎬 [FFmpegTranscoder] 使用转码模式VP8/VP9 → H.264")
}
t.cmd = exec.Command(t.ffmpegPath, args...)

View File

@@ -181,9 +181,9 @@ type ConnectionHandler struct {
remoteAddr string
// 连接状态
streamID string
isPublish bool
appName string
streamID string
isPublish bool
appName string
msgStreamID uint32
// 统计信息
@@ -773,6 +773,18 @@ func (r *RTMPServer) startHTTPFLVServer() {
// handleFLVRequest 处理 FLV 请求
func (r *RTMPServer) handleFLVRequest(w http.ResponseWriter, req *http.Request) {
// 添加 CORS 头,允许跨域访问
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Range")
w.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Range")
// 处理 OPTIONS 预检请求
if req.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
path := req.URL.Path
if len(path) < 7 {
http.Error(w, "Invalid path", http.StatusBadRequest)
@@ -1007,12 +1019,14 @@ func (r *RTMPServer) GenerateStreamURLs(roomID, userID string) (*RTMPStream, err
token := generateStreamToken(streamID)
stream := &RTMPStream{
ID: streamID,
RoomID: roomID,
UserID: userID,
PushURL: fmt.Sprintf("rtmp://%s:%d/live/%s?token=%s", publicIP, r.config.RTMPPort, streamID, token),
PullURL: fmt.Sprintf("rtmp://%s:%d/live/%s", publicIP, r.config.RTMPPort, streamID),
FLVURL: fmt.Sprintf("http://%s:%d/live/%s.flv", publicIP, r.config.HTTPFLVPort, streamID),
ID: streamID,
RoomID: roomID,
UserID: userID,
PushURL: fmt.Sprintf("rtmp://%s:%d/live/%s?token=%s", publicIP, r.config.RTMPPort, streamID, token),
PullURL: fmt.Sprintf("rtmp://%s:%d/live/%s", publicIP, r.config.RTMPPort, streamID),
// 使用 HTTPS 域名,通过 Nginx 反向代理到 HTTP-FLV 服务
// 避免浏览器混合内容安全策略阻止 HTTP 请求
FLVURL: fmt.Sprintf("https://g-ws.nailaoyun.cn/live/%s.flv", streamID),
CreatedAt: time.Now(),
IsActive: true,
stopChan: make(chan struct{}),

View File

@@ -346,17 +346,23 @@ func (p *WebMToRTMPProxy) handleTextMessage(session *ProxySession, data []byte)
log.Printf("🎬 [WSProxy] 收到编解码器信息 | Stream:%s Video:%s Audio:%s MIME:%s",
session.StreamID, codecInfo.VideoCodec, codecInfo.AudioCodec, codecInfo.MimeType)
// 判断是否需要转码
// H.264 可以直接封装到 FLVVP8/VP9 需要转码
// 判断编解码器类型并初始化对应的转码
// 所有格式都通过 FFmpeg 处理,但 H.264 使用 copy 模式(只重封装)VP8/VP9 需要转码
switch codecInfo.VideoCodec {
case "h264", "avc1":
session.needTranscode = false
log.Printf("✅ [WSProxy] H.264 编码,无需转码,直接重封装为 FLV")
// H.264: 使用 FFmpeg copy 模式,只重封装为 FLV不重新编码
session.needTranscode = true
log.Printf("✅ [WSProxy] H.264 编码,使用 FFmpeg copy 模式重封装为 FLV")
if err := p.initTranscoderForH264(session); err != nil {
log.Printf("⚠️ [WSProxy] 初始化 H.264 转码器失败: %v尝试直接解析", err)
session.needTranscode = false
}
case "vp8", "vp9":
// VP8/VP9: 需要 FFmpeg 转码为 H.264
session.needTranscode = true
log.Printf("🔄 [WSProxy] %s 编码,需要 FFmpeg 转码为 H.264", codecInfo.VideoCodec)
// 初始化转码器
if err := p.initTranscoder(session); err != nil {
log.Printf("⚠️ [WSProxy] 初始化转码器失败: %v将使用非标准 FLV 格式", err)
session.needTranscode = false
@@ -369,7 +375,7 @@ func (p *WebMToRTMPProxy) handleTextMessage(session *ProxySession, data []byte)
return nil
}
// initTranscoder 初始化 FFmpeg 转码器
// initTranscoder 初始化 FFmpeg 转码器VP8/VP9 转码模式)
func (p *WebMToRTMPProxy) initTranscoder(session *ProxySession) error {
transcoder, err := NewFFmpegTranscoder(nil)
if err != nil {
@@ -385,7 +391,36 @@ func (p *WebMToRTMPProxy) initTranscoder(session *ProxySession) error {
// 启动协程读取转码后的 FLV 数据并广播
go p.readTranscodedOutput(session)
log.Printf("✅ [WSProxy] FFmpeg 转码器已初始化 | Stream:%s", session.StreamID)
log.Printf("✅ [WSProxy] FFmpeg 转码器已初始化(转码模式)| Stream:%s", session.StreamID)
return nil
}
// initTranscoderForH264 初始化 FFmpeg 转码器H.264 copy 模式)
// H.264 视频只需要重封装,不需要重新编码,使用 copy 模式可以大大减少延迟
func (p *WebMToRTMPProxy) initTranscoderForH264(session *ProxySession) error {
config := &TranscoderConfig{
CopyMode: true, // 使用 copy 模式
AudioCodec: "aac",
AudioBitrate: "64k",
AudioSampleRate: 44100,
AudioChannels: 2,
}
transcoder, err := NewFFmpegTranscoder(config)
if err != nil {
return fmt.Errorf("创建 H.264 转码器失败: %w", err)
}
if err := transcoder.Start(); err != nil {
return fmt.Errorf("启动 H.264 转码器失败: %w", err)
}
session.transcoder = transcoder
// 启动协程读取转码后的 FLV 数据并广播
go p.readTranscodedOutput(session)
log.Printf("✅ [WSProxy] FFmpeg 转码器已初始化H.264 copy 模式)| Stream:%s", session.StreamID)
return nil
}