使用Go实现webrtc播放音频的流程步骤

作者:

文章目录
  • 怎么通过go语言实现webrtc播放服务器音频,代码没有报错,但是就是运行不了。 package main import ( “bytes” “encoding/json” “fmt” “io” “log” “os” “time” “github.com/gen2brain/malgo” “github.com/gorilla/websocket” “github.com/pion/webrtc/v3” “github.com/zaf/g711” ) type WSMessage struct { Type string `json:”type”` Call string `json:”call,omitempty”` } func mustMarshalJSON(v interface{}) string { data, err := json.Marshal(v) if err != nil { log.Fatalf(“Failed to Marshal JSON: %v”, err) } return string(data) } func connectToWebSocket(url string) (*websocket.Conn, error) { dialer := websocket.DefaultDialer // Attempt to reconnect infinitely for { conn, _, err := dialer.Dial(url, nil) if err != nil { log.Printf(“Failed to connect to WebSocket server: %v. Retrying…”, err) time.Sleep(5 * time.Second) // Wait before retrying continue } return conn, nil } } func main() { wsURL := “wss://chat.ruzhila.cn/rtc/radio” conn, err := connectToWebSocket(wsURL) if err != nil { log.Fatalf(“WebSocket connection failed: %v”, err) } defer conn.Close() peerConnection, err := configurePeerConnection() if err != nil { log.Fatalf(“Peer connection configuration failed: %v”, err) } defer peerConnection.Close() offer, err := peerConnection.CreateOffer(nil) if err != nil { log.Fatalf(“Failed to create offer: %v”, err) } err = peerConnection.SetLocalDescription(offer) if err != nil { log.Fatalf(“Failed to set local description: %v”, err) } <-webrtc.GatheringCompletePromise(peerConnection) callMessage := WSMessage{ Type: “Call”, Call: mustMarshalJSON(*peerConnection.LocalDescription()), } err = conn.WriteJSON(callMessage) if err != nil { log.Fatalf(“Failed to send call message: %v”, err) } go pingServer(conn) handleWebSocketMessages(conn, peerConnection) } func configurePeerConnection() (*webrtc.PeerConnection, error) { config := webrtc.Configuration{} peerConnection, err := webrtc.NewPeerConnection(config) if err != nil { return nil, fmt.Errorf(“Failed to create peer connection: %w”, err) } peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { log.Printf(“Track added: %s”, track.Kind().String()) go handleAudioTrack(track) }) audioTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{ MimeType: webrtc.MimeTypePCMU, ClockRate: 8000, Channels: 1, }, “audio”, “pion”) if err != nil { return nil, fmt.Errorf(“Failed to create audio track: %w”, err) } _, err = peerConnection.AddTrack(audioTrack) if err != nil { return nil, fmt.Errorf(“Failed to add audio track: %w”, err) } peerConnection.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) { log.Printf(“ICE Connection State has changed: %s”, state.String()) }) return peerConnection, nil } func handleWebSocketMessages(conn *websocket.Conn, peerConnection *webrtc.PeerConnection) { for { var message WSMessage err := conn.ReadJSON(&message) if err != nil { log.Printf(“Socket closed or error reading: %v. Attempting to reconnect…”, err) conn, _ = reconnectToWebSocket() continue } if message.Type == “answer” { var answer webrtc.SessionDescription err := json.Unmarshal([]byte(message.Call), &answer) if err != nil { log.Printf(“Failed to unmarshal answer: %v”, err) continue } err = peerConnection.SetRemoteDescription(answer) if err != nil { log.Printf(“Failed to set remote description: %v”, err) continue } log.Printf(“Answer set successfully”) } } } func reconnectToWebSocket() (*websocket.Conn, error) { // You can repeat the connect logic with logging and error handling if required return connectToWebSocket(“wss://chat.ruzhila.cn/rtc/radio”) } func decodePCMU(payload []byte) []byte { return g711.DecodeUlaw(payload) } var audioBuffer bytes.Buffer func handleAudioTrack(track *webrtc.TrackRemote) { log.Println(“Audio track started”) for { rtpPacket, _, err := track.ReadRTP() if err != nil { log.Printf(“Failed to read RTP packet: %v”, err) return } pcmData := decodePCMU(rtpPacket.Payload) if len(pcmData) > 0 { audioBuffer.Write(pcmData) } } } func pingServer(conn *websocket.Conn) { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil { log.Printf(“Failed to send ping message: %v”, err) return } } } } func playWavFile() error { pcmData := audioBuffer.Bytes() if len(pcmData) == 0 { return fmt.Errorf(“No PCM data to write”) } err := os.WriteFile(“output.wav”, pcmData, 0644) if err != nil { return fmt.Errorf(“Failed to write WAV file: %v”, err) } file, err := os.Open(“output.wav”) if err != nil { return fmt.Errorf(“Failed to open WAV file: %v”, err) } defer file.Close() ctx, err := malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) { log.Println(message) }) if err != nil { return fmt.Errorf(“Failed to initialize malgo: %v”, err) } defer ctx.Uninit() defer ctx.Free() deviceConfig := malgo.DefaultDeviceConfig(malgo.Playback) deviceConfig.Playback.Channels = 1 deviceConfig.Playback.Format = malgo.FormatS16 deviceConfig.SampleRate = 8000 deviceConfig.Alsa.NoMMap = 1 onSample:=func(pOutPut,pInPut []byte,frameCount uint32){ io.ReadFull(reader,pOutPut) } deviceCallbacks := malgo.DeviceCallbacks{ Data: onSample, } device, err := malgo.InitDevice(ctx.Context, deviceConfig, deviceCallbacks) if err != nil { fmt.Println(err) os.Exit(1) } defer device.Uninit() err = device.Start() if err != nil { fmt.Println(err) os.Exit(1) } fmt.Println(“Press Enter to quit…”) fmt.Scanln() return nil }
  • 如下是针对上述问题进行专业角度剖析答疑,不喜勿喷,仅供参考:
  • 目录
    • 问题描述
    • 请知悉:如下方案不保证一定适配你的问题!
      • 问题理解
      • 问题分析
      • 改进方案
        • 步骤 1: 配置 WebRTC PeerConnection 和音频流接收
        • 步骤 2: 配置音频播放设备
        • 步骤 3: 音频缓冲管理
        • 步骤 4: 调试与日志输出
      • 小结

      怎么通过go语言实现webrtc播放服务器音频,代码没有报错,但是就是运行不了。

      package main
       
      import (
          "bytes"
          "encoding/json"
          "fmt"
          "io"
          "log"
          "os"
          "time"
       
          "github.com/gen2brain/malgo"
          "github.com/gorilla/websocket"
          "github.com/pion/webrtc/v3"
          "github.com/zaf/g711"
      )
       
      type WSMessage struct {
          Type string `json:"type"`
          Call string `json:"call,omitempty"`
      }
       
      func mustMarshalJSON(v interface{}) string {
          data, err := json.Marshal(v)
          if err != nil {
              log.Fatalf("Failed to Marshal JSON: %v", err)
          }
          return string(data)
      }
       
      func connectToWebSocket(url string) (*websocket.Conn, error) {
          dialer := websocket.DefaultDialer
       
          // Attempt to reconnect infinitely
          for {
              conn, _, err := dialer.Dial(url, nil)
              if err != nil {
                  log.Printf("Failed to connect to WebSocket server: %v. Retrying...", err)
                  time.Sleep(5 * time.Second) // Wait before retrying
                  continue
              }
              return conn, nil
          }
      }
       
      func main() {
          wsURL := "wss://chat.ruzhila.cn/rtc/radio"
          conn, err := connectToWebSocket(wsURL)
          if err != nil {
              log.Fatalf("WebSocket connection failed: %v", err)
          }
          defer conn.Close()
       
          peerConnection, err := configurePeerConnection()
          if err != nil {
              log.Fatalf("Peer connection configuration failed: %v", err)
          }
          defer peerConnection.Close()
       
          offer, err := peerConnection.CreateOffer(nil)
          if err != nil {
              log.Fatalf("Failed to create offer: %v", err)
          }
       
          err = peerConnection.SetLocalDescription(offer)
          if err != nil {
              log.Fatalf("Failed to set local description: %v", err)
          }
       
          <-webrtc.GatheringCompletePromise(peerConnection)
       
          callMessage := WSMessage{
              Type: "Call",
              Call: mustMarshalJSON(*peerConnection.LocalDescription()),
          }
       
          err = conn.WriteJSON(callMessage)
          if err != nil {
              log.Fatalf("Failed to send call message: %v", err)
          }
       
          go pingServer(conn)
          handleWebSocketMessages(conn, peerConnection)
      }
       
      func configurePeerConnection() (*webrtc.PeerConnection, error) {
          config := webrtc.Configuration{}
          peerConnection, err := webrtc.NewPeerConnection(config)
          if err != nil {
              return nil, fmt.Errorf("Failed to create peer connection: %w", err)
          }
       
          peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
              log.Printf("Track added: %s", track.Kind().String())
              go handleAudioTrack(track)
          })
       
          audioTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{
              MimeType:  webrtc.MimeTypePCMU,
              ClockRate: 8000,
              Channels:  1,
          }, "audio", "pion")
       
          if err != nil {
              return nil, fmt.Errorf("Failed to create audio track: %w", err)
          }
       
          _, err = peerConnection.AddTrack(audioTrack)
          if err != nil {
              return nil, fmt.Errorf("Failed to add audio track: %w", err)
          }
       
          peerConnection.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
              log.Printf("ICE Connection State has changed: %s", state.String())
          })
       
          return peerConnection, nil
      }
       
      func handleWebSocketMessages(conn *websocket.Conn, peerConnection *webrtc.PeerConnection) {
          for {
              var message WSMessage
              err := conn.ReadJSON(&message)
              if err != nil {
                  log.Printf("Socket closed or error reading: %v. Attempting to reconnect...", err)
                  conn, _ = reconnectToWebSocket()
                  continue
              }
       
              if message.Type == "answer" {
                  var answer webrtc.SessionDescription
                  err := json.Unmarshal([]byte(message.Call), &answer)
                  if err != nil {
                      log.Printf("Failed to unmarshal answer: %v", err)
                      continue
                  }
       
                  err = peerConnection.SetRemoteDescription(answer)
                  if err != nil {
                      log.Printf("Failed to set remote description: %v", err)
                      continue
                  }
       
                  log.Printf("Answer set successfully")
              }
          }
      }
       
      func reconnectToWebSocket() (*websocket.Conn, error) {
          // You can repeat the connect logic with logging and error handling if required
          return connectToWebSocket("wss://chat.ruzhila.cn/rtc/radio")
      }
       
      func decodePCMU(payload []byte) []byte {
          return g711.DecodeUlaw(payload)
      }
       
      var audioBuffer bytes.Buffer
       
      func handleAudioTrack(track *webrtc.TrackRemote) {
          log.Println("Audio track started")
       
          for {
              rtpPacket, _, err := track.ReadRTP()
              if err != nil {
                  log.Printf("Failed to read RTP packet: %v", err)
                  return
              }
       
              pcmData := decodePCMU(rtpPacket.Payload)
              if len(pcmData) > 0 {
                  audioBuffer.Write(pcmData)
              }
          }
      }
       
      func pingServer(conn *websocket.Conn) {
          ticker := time.NewTicker(30 * time.Second)
          defer ticker.Stop()
          for {
              select {
              case <-ticker.C:
                  if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
                      log.Printf("Failed to send ping message: %v", err)
                      return
                  }
              }
          }
      }
       
      func playWavFile() error {
          pcmData := audioBuffer.Bytes()
          if len(pcmData) == 0 {
              return fmt.Errorf("No PCM data to write")
          }
          err := os.WriteFile("output.wav", pcmData, 0644)
          if err != nil {
              return fmt.Errorf("Failed to write WAV file: %v", err)
          }
       
          file, err := os.Open("output.wav")
          if err != nil {
              return fmt.Errorf("Failed to open WAV file: %v", err)
          }
          defer file.Close()
       
          ctx, err := malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) {
              log.Println(message)
          })
       
          if err != nil {
              return fmt.Errorf("Failed to initialize malgo: %v", err)
          }
          defer ctx.Uninit()
          defer ctx.Free()
       
          deviceConfig := malgo.DefaultDeviceConfig(malgo.Playback)
          deviceConfig.Playback.Channels = 1
          deviceConfig.Playback.Format = malgo.FormatS16
          deviceConfig.SampleRate = 8000
          deviceConfig.Alsa.NoMMap = 1
       
          onSample:=func(pOutPut,pInPut []byte,frameCount uint32){
              io.ReadFull(reader,pOutPut)
          }
       
          deviceCallbacks := malgo.DeviceCallbacks{
              Data: onSample,
          }
          device, err := malgo.InitDevice(ctx.Context, deviceConfig, deviceCallbacks)
          if err != nil {
              fmt.Println(err)
              os.Exit(1)
          }
          defer device.Uninit()
       
          err = device.Start()
          if err != nil {
              fmt.Println(err)
              os.Exit(1)
          }
       
          fmt.Println("Press Enter to quit...")
          fmt.Scanln()
       
          return nil
      }
      

      如下是针对上述问题进行专业角度剖析答疑,不喜勿喷,仅供参考:

      你希望通过 Go 语言 实现 WebRTC 播放音频,已使用 github.com/pion/webrtc 库来配置 WebRTC 连接,并通过 WebSocket 进行信令传递。你已经能够连接 WebRTC 并接收到音频流,但是音频没有正确播放。现阶段的问题是:虽然代码没有报错,但无法播放音频流。

      我们将详细分析你当前的实现,并提供切实可行的解决方案。这个方案包括音频流的接收、解码、缓冲管理和音频播放设备的配置,最终确保音频能通过设备正确播放。

      你提供的代码基本框架是正确的,问题可能出现在以下几个方面:

      音频流的正确接收与解码

      • 在 OnTrack 回调函数中,你已经处理了 WebRTC 音频流,并通过 g711.DecodeUlaw 进行了音频数据的解码,但尚未完全确保这些音频数据能够正确传递到播放设备。

      音频设备配置

      • 你使用了 malgo 库来播放音频,但代码中并没有明确地将解码后的音频数据传递给播放设备,可能导致音频没有播放。

      音频缓冲区的管理

      • 你使用了 audioBuffer 来缓存音频数据,但没有确保解码后的音频数据能够及时传输到设备,导致音频播放过程中出现延迟或中断。

      WebRTC 和信令问题

      • 音频流的接收和解码与信令的正确配置(如 offeranswer)及 ICE 连接的建立密切相关。如果信令过程中的某个环节出错,也可能导致音频无法播放。

      为了确保音频能够正确接收、解码并播放,我们需要对现有代码进行一些改进,具体步骤如下:

      首先,确保 WebRTC 信令的配置和音频流的接收没有问题。在 OnTrack 回调中,确保音频数据可以通过 g711 解码并缓存在 audioBuffer 中。

      1.1 音频流接收与解码

      // 接收音频流并解码
      func handleAudioTrack(track *webrtc.TrackRemote) {
          log.Println("Audio track started")
      
          for {
              rtpPacket, _, err := track.ReadRTP()
              if err != nil {
                  log.Printf("Failed to read RTP packet: %v", err)
                  return
              }
      
              // 解码 PCM 数据
              pcmData := decodePCMU(rtpPacket.Payload)
              if len(pcmData) > 0 {
                  audioBuffer.Write(pcmData) // 将解码后的音频数据写入缓冲区
              }
          }
      }
      

      此部分代码确保了音频数据通过 decodePCMU 解码后被写入缓冲区 audioBuffer

      我们使用 malgo 库来播放解码后的 PCM 数据。关键是正确配置音频设备并将缓冲区中的 PCM 数据传递给设备进行播放。

      2.1 音频设备的初始化与配置

      我们将在 handleAudioTrack 函数中初始化音频设备,并使用 malgo 库播放音频。具体的步骤如下:

      func handleAudioTrack(track *webrtc.TrackRemote) {
          log.Println("Audio track started")
      
          // 初始化 malgo 上下文
          ctx, err := malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) {
              log.Println(message)
          })
          if err != nil {
              log.Printf("Failed to initialize malgo context: %v", err)
              return
          }
          defer ctx.Uninit()
          defer ctx.Free()
      
          // 配置音频播放设备
          deviceConfig := malgo.DefaultDeviceConfig(malgo.Playback)
          deviceConfig.Playback.Channels = 1 // 设置单声道
          deviceConfig.Playback.Format = malgo.FormatS16
          deviceConfig.SampleRate = 8000  // 设置采样率为 8000Hz
          deviceConfig.Alsa.NoMMap = 1    // 配置 ALSA 参数
      
          // 音频播放回调
          onSample := func(pOutPut, pInPut []byte, frameCount uint32) {
              pcmData := audioBuffer.Bytes() // 从缓存中取出解码后的音频数据
              if len(pcmData) > 0 {
                  copy(pOutPut, pcmData) // 将 PCM 数据传递给输出缓冲
                  audioBuffer.Reset()     // 清空缓冲区
              }
          }
      
          // 初始化设备并开始播放
          deviceCallbacks := malgo.DeviceCallbacks{
              Data: onSample,
          }
          device, err := malgo.InitDevice(ctx.Context, deviceConfig, deviceCallbacks)
          if err != nil {
              log.Printf("Failed to initialize audio device: %v", err)
              return
          }
          defer device.Uninit()
      
          err = device.Start()
          if err != nil {
              log.Printf("Failed to start audio device: %v", err)
              return
          }
      
          // 循环读取 RTP 包并解码音频数据
          for {
              rtpPacket, _, err := track.ReadRTP()
              if err != nil {
                  log.Printf("Failed to read RTP packet: %v", err)
                  return
              }
      
              pcmData := decodePCMU(rtpPacket.Payload)
              if len(pcmData) > 0 {
                  audioBuffer.Write(pcmData) // 将解码后的 PCM 数据写入缓冲区
              }
          }
      }
      

      2.2 音频数据的传输与播放

      在 onSample 回调函数中,我们将解码后的 PCM 数据传输给音频设备的输出缓冲区。每次播放时,设备会从缓冲区读取 PCM 数据并进行播放。

      确保 audioBuffer 能够及时地从缓冲区取出数据并传递给设备进行播放。要做到这一点,audioBuffer 必须确保缓存中有足够的 PCM 数据进行播放,否则可能会出现无音频输出的情况。

      • 音频缓冲区的大小和数据处理:你可以设置一个较大的缓冲区,并定期将数据写入音频设备的播放缓冲区。确保缓冲区不会过早被清空,避免音频播放中断。
      • 数据流的连续性:确保解码后的音频数据连续地传输到设备,避免因数据不足导致播放中断或卡顿。

      为了调试音频播放的过程,可以在各个步骤中加入详细的日志输出,以确保数据流的每一部分都能正常工作:

      log.Printf("Decoded %d bytes of PCM data", len(pcmData))
      log.Printf("Writing %d bytes to playback buffer", len(pOutPut))
      

      通过这些日志,你可以更清晰地看到每次音频数据的解码、缓存和传输过程。

      通过以下几个步骤,我们可以确保 WebRTC 音频流的正确接收、解码和播放

      音频流接收与解码

      • 使用 OnTrack 回调接收音频流,并通过 g711.DecodeUlaw 解码 PCM 数据。

      音频设备的配置与播放

      • 使用 malgo 库初始化音频设备,配置播放参数(如通道数、采样率等),并通过回调函数将解码后的音频数据传递给播放设备。

      音频缓冲管理

      • 使用 audioBuffer 缓存解码后的音频数据,并确保数据能够及时传输给设备进行播放。

      调试与日志

      • 加入详细的日志输出,帮助你调试音频数据的接收、解码和播放过程。

      这样,你的 Go WebRTC 音频播放 方案应该能够成功接收、解码并播放音频流,解决当前运行时无法播放音频的问题。

      希望如上措施及解决方案能够帮到有需要的你。

      以上就是使用Go实现webrtc播放音频的流程步骤的详细内容,更多关于Go webrtc播放音频的资料请关注风君子博客其它相关文章!

      您可能感兴趣的文章:

      • Go语言开发浏览器视频流rtsp转webrtc播放
      • Go利用ffmpeg进行视频和音频处理
      • Go语言调用ffmpeg-api实现音频重采样

      站内搜索