Voice.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. package VoiceCall
  2. import (
  3. "fmt"
  4. "github.com/go-resty/resty/v2"
  5. lksdk "github.com/livekit/server-sdk-go/v2"
  6. "github.com/pion/webrtc/v4"
  7. "github.com/pion/webrtc/v4/pkg/media/oggwriter"
  8. "io"
  9. "os/exec"
  10. "runtime"
  11. "strings"
  12. "time"
  13. )
  14. type Voice struct {
  15. VoiceServiceAddress string `toml:"VoiceServiceAddress"`
  16. RoomID string `toml:"RoomID"`
  17. ParticipantName string `toml:"ParticipantName"`
  18. MakeTokenAddress string `toml:"MakeTokenAddress"`
  19. Volume int `toml:"Volume"`
  20. RecordingEquipment string `toml:"RecordingEquipment"`
  21. player map[string]*TrackWriter
  22. roomObj *lksdk.Room
  23. refresh func()
  24. }
  25. var HTTPClient3s = resty.New()
  26. func (receiver *Voice) getToken() (string, error) {
  27. get, err := HTTPClient3s.R().Get(fmt.Sprintf(receiver.MakeTokenAddress, receiver.RoomID, receiver.ParticipantName))
  28. if err != nil || get.StatusCode() != 200 {
  29. return "", err
  30. }
  31. return string(get.Body()), nil
  32. }
  33. func (receiver *Voice) SetOnRefresh(refresh func()) {
  34. receiver.refresh = refresh
  35. }
  36. func (receiver *Voice) ConnectRoom() error {
  37. var err error
  38. var token string
  39. token, err = receiver.getToken()
  40. if err != nil {
  41. return err
  42. }
  43. receiver.player = make(map[string]*TrackWriter)
  44. receiver.roomObj, err = lksdk.ConnectToRoomWithToken(receiver.VoiceServiceAddress, token, &lksdk.RoomCallback{
  45. ParticipantCallback: lksdk.ParticipantCallback{
  46. OnTrackSubscribed: receiver.onTrackSubscribed,
  47. OnTrackUnsubscribed: receiver.onTrackUnsubscribed,
  48. },
  49. })
  50. if err != nil {
  51. return err
  52. }
  53. return nil
  54. }
  55. func (receiver *Voice) PushMicrophone() error {
  56. // 判断当前是windows还是linux
  57. var cmd *exec.Cmd
  58. if strings.Contains(runtime.GOOS, "windows") {
  59. cmd = exec.Command(
  60. "ffmpeg",
  61. "-f", "dshow",
  62. "-i", receiver.RecordingEquipment,
  63. "-ac", "2", // 强制为单声道
  64. "-ar", "48000", // 设置采样率为 48kHz
  65. "-c:a", "libopus",
  66. "-b:a", "32k", // 设置比特率为 32 kbps
  67. "-frame_duration", "10", // 每帧持续 20ms
  68. "-application", "lowdelay", // 设置低延迟编码
  69. "-f", "ogg",
  70. "-page_duration", "10000",
  71. "pipe:1",
  72. )
  73. } else {
  74. cmd = exec.Command(
  75. "ffmpeg",
  76. "-f", "alsa", // 使用 ALSA 捕获音频
  77. "-i", receiver.RecordingEquipment, // 音频设备,例如 "hw:0,0"
  78. "-ac", "2", // 强制为单声道
  79. "-ar", "48000", // 设置采样率为 48kHz
  80. "-c:a", "libopus", // 使用 Opus 编解码器
  81. "-b:a", "32k", // 设置比特率为 32 kbps
  82. "-frame_duration", "10", // 每帧持续 20ms
  83. "-application", "lowdelay", // 设置低延迟编码
  84. "-f", "ogg", // 输出格式为 OGG
  85. "-page_duration", "10000", // 设置页持续时间
  86. "pipe:1", // 输出到管道
  87. )
  88. }
  89. // Get the stdout pipe
  90. stdout, err := cmd.StdoutPipe()
  91. if err != nil {
  92. fmt.Println("Error creating StdoutPipe:", err)
  93. return err
  94. }
  95. // Start the ffmpeg command
  96. if err = cmd.Start(); err != nil {
  97. fmt.Println("Error starting ffmpeg command:", err)
  98. return err
  99. }
  100. // Create a new local reader track
  101. track, err := lksdk.NewLocalReaderTrack(stdout, webrtc.MimeTypeOpus,
  102. lksdk.ReaderTrackWithFrameDuration(10*time.Millisecond),
  103. lksdk.ReaderTrackWithOnWriteComplete(func() { fmt.Println("track finished") }),
  104. )
  105. if err != nil {
  106. fmt.Println("Error creating local reader track:", err)
  107. return err
  108. }
  109. // Publish the track to the room
  110. if _, err = receiver.roomObj.LocalParticipant.PublishTrack(track, &lksdk.TrackPublicationOptions{Name: "live_audio"}); err != nil {
  111. fmt.Println("Error publishing track:", err)
  112. return err
  113. }
  114. err = cmd.Wait()
  115. if err != nil {
  116. if err == io.EOF {
  117. fmt.Println("ffmpeg process exited successfully")
  118. } else {
  119. fmt.Println("Error waiting for ffmpeg process:", err)
  120. }
  121. return err
  122. }
  123. // 与房间断开连接
  124. receiver.roomObj.Disconnect()
  125. return nil
  126. }
  127. func (receiver *Voice) onTrackUnsubscribed(track *webrtc.TrackRemote, publication *lksdk.RemoteTrackPublication, rp *lksdk.RemoteParticipant) {
  128. fileName := fmt.Sprintf("%s-%s", rp.Identity(), track.ID())
  129. v, ok := receiver.player[fileName]
  130. if ok {
  131. v.stop()
  132. delete(receiver.player, fileName)
  133. println(receiver.player)
  134. }
  135. }
  136. func (receiver *Voice) onTrackSubscribed(track *webrtc.TrackRemote, publication *lksdk.RemoteTrackPublication, rp *lksdk.RemoteParticipant) {
  137. switch {
  138. case strings.EqualFold(track.Codec().MimeType, "audio/opus"):
  139. default:
  140. return
  141. }
  142. fileName := fmt.Sprintf("%s-%s", rp.Identity(), track.ID())
  143. receiver.player[fileName] = NewTrackWriter(track)
  144. println(receiver.player)
  145. }
  146. type TrackWriter struct {
  147. track *webrtc.TrackRemote
  148. pr *io.PipeReader
  149. pw *io.PipeWriter
  150. cmd *exec.Cmd
  151. }
  152. func NewTrackWriter(track *webrtc.TrackRemote) *TrackWriter {
  153. pr, pw := io.Pipe()
  154. t := &TrackWriter{
  155. pr: pr,
  156. pw: pw,
  157. track: track,
  158. }
  159. go t.start()
  160. return t
  161. }
  162. func (t *TrackWriter) start() {
  163. t.cmd = exec.Command("ffplay", "-nodisp", "-f", "ogg", "-")
  164. //t.cmd = exec.Command("ffplay", "-f", "ogg", "-")
  165. t.cmd.Stdin = t.pr
  166. err := t.cmd.Start()
  167. if err != nil {
  168. fmt.Printf("Error starting ffplay: %v\n", err)
  169. return
  170. }
  171. writer, err := oggwriter.NewWith(t.pw, 48000, 2)
  172. if err != nil {
  173. println(err)
  174. return
  175. }
  176. defer writer.Close()
  177. for {
  178. rtpPacket, _, err := t.track.ReadRTP()
  179. if err != nil {
  180. break
  181. }
  182. err = writer.WriteRTP(rtpPacket)
  183. if err != nil {
  184. return
  185. }
  186. }
  187. }
  188. func (t *TrackWriter) stop() {
  189. t.pr.Close()
  190. t.pw.Close()
  191. if t.cmd.Process != nil {
  192. err := t.cmd.Process.Kill()
  193. if err != nil {
  194. fmt.Printf("Error sending SIGTERM to ffplay process: %v\n", err)
  195. return
  196. }
  197. }
  198. t.cmd.Wait()
  199. }