网站通话使用webrtc,新增ffmpeg
This commit is contained in:
@@ -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...)
|
||||
|
||||
@@ -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{}),
|
||||
|
||||
@@ -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 可以直接封装到 FLV,VP8/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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user