audio_device.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. package usbcapture
  2. import (
  3. "bufio"
  4. "fmt"
  5. "log"
  6. "net/http"
  7. "os"
  8. "os/exec"
  9. "regexp"
  10. "strings"
  11. "github.com/gorilla/websocket"
  12. )
  13. // upgrader is used to upgrade HTTP connections to WebSocket connections
  14. var upgrader = websocket.Upgrader{
  15. ReadBufferSize: 1024,
  16. WriteBufferSize: 1024,
  17. CheckOrigin: func(r *http.Request) bool {
  18. return true
  19. },
  20. }
  21. // ListCaptureDevices lists all available audio capture devices in the /dev/snd directory.
  22. func ListCaptureDevices() ([]string, error) {
  23. files, err := os.ReadDir("/dev/snd")
  24. if err != nil {
  25. return nil, fmt.Errorf("failed to read /dev/snd: %w", err)
  26. }
  27. var captureDevs []string
  28. for _, file := range files {
  29. name := file.Name()
  30. if strings.HasPrefix(name, "pcm") && strings.HasSuffix(name, "c") {
  31. fullPath := "/dev/snd/" + name
  32. captureDevs = append(captureDevs, fullPath)
  33. }
  34. }
  35. return captureDevs, nil
  36. }
  37. // FindHDMICaptureCard searches for an HDMI capture card using the `arecord -l` command.
  38. func FindHDMICapturePCMPath() (string, error) {
  39. out, err := exec.Command("arecord", "-l").Output()
  40. if err != nil {
  41. return "", fmt.Errorf("arecord -l failed: %w", err)
  42. }
  43. lines := strings.Split(string(out), "\n")
  44. for _, line := range lines {
  45. lower := strings.ToLower(line)
  46. if strings.Contains(lower, "ms2109") || strings.Contains(lower, "ms2130") {
  47. // Example line:
  48. // card 1: MS2109 [MS2109], device 0: USB Audio [USB Audio]
  49. parts := strings.Fields(line)
  50. var cardNum, devNum string
  51. for i := range parts {
  52. if parts[i] == "card" && i+1 < len(parts) {
  53. cardNum = parts[i+1][:1] // "1"
  54. }
  55. if parts[i] == "device" && i+1 < len(parts) {
  56. devNum = strings.TrimSuffix(parts[i+1], ":") // "0"
  57. }
  58. }
  59. if cardNum != "" && devNum != "" {
  60. return fmt.Sprintf("/dev/snd/pcmC%vD%vc", cardNum, devNum), nil
  61. }
  62. }
  63. }
  64. return "", fmt.Errorf("no HDMI capture card found")
  65. }
  66. // Convert a PCM device name to a hardware device name.
  67. // Example: "pcmC1D0c" -> "hw:1,0"
  68. func pcmDeviceToHW(dev string) (string, error) {
  69. // Regex to extract card and device numbers
  70. re := regexp.MustCompile(`pcmC(\d+)D(\d+)[cp]`)
  71. matches := re.FindStringSubmatch(dev)
  72. if len(matches) < 3 {
  73. return "", fmt.Errorf("invalid device format")
  74. }
  75. card := matches[1]
  76. device := matches[2]
  77. return fmt.Sprintf("hw:%s,%s", card, device), nil
  78. }
  79. func GetDefaultAudioConfig() *AudioConfig {
  80. return &AudioConfig{
  81. SampleRate: 48000,
  82. Channels: 2,
  83. BytesPerSample: 2, // 16-bit
  84. FrameSize: 1920, // 1920 samples per frame = 40ms @ 48kHz
  85. }
  86. }
  87. // Downsample48kTo24kStereo downsamples a 48kHz stereo audio buffer to 24kHz.
  88. // It assumes the input buffer is in 16-bit stereo format (2 bytes per channel).
  89. // The output buffer will also be in 16-bit stereo format.
  90. func downsample48kTo24kStereo(buf []byte) []byte {
  91. const frameSize = 4 // 2 bytes per channel × 2 channels
  92. if len(buf)%frameSize != 0 {
  93. // Trim incomplete frame (rare case)
  94. buf = buf[:len(buf)-len(buf)%frameSize]
  95. }
  96. out := make([]byte, 0, len(buf)/2)
  97. for i := 0; i < len(buf); i += frameSize * 2 {
  98. // Copy every other frame (drop 1 in 2)
  99. if i+frameSize <= len(buf) {
  100. out = append(out, buf[i:i+frameSize]...)
  101. }
  102. }
  103. return out
  104. }
  105. // Downsample48kTo16kStereo downsamples a 48kHz stereo audio buffer to 16kHz.
  106. // It assumes the input buffer is in 16-bit stereo format (2 bytes per channel).
  107. // The output buffer will also be in 16-bit stereo format.
  108. func downsample48kTo16kStereo(buf []byte) []byte {
  109. const frameSize = 4 // 2 bytes per channel × 2 channels
  110. if len(buf)%frameSize != 0 {
  111. // Trim incomplete frame (rare case)
  112. buf = buf[:len(buf)-len(buf)%frameSize]
  113. }
  114. out := make([]byte, 0, len(buf)/3)
  115. for i := 0; i < len(buf); i += frameSize * 3 {
  116. // Copy every third frame (drop 2 in 3)
  117. if i+frameSize <= len(buf) {
  118. out = append(out, buf[i:i+frameSize]...)
  119. }
  120. }
  121. return out
  122. }
  123. // AudioStreamingHandler handles incoming WebSocket connections for audio streaming.
  124. func (i Instance) AudioStreamingHandler(w http.ResponseWriter, r *http.Request) {
  125. // Check if the request contains ?quality=low
  126. quality := r.URL.Query().Get("quality")
  127. qualityKey := []string{"low", "standard", "high"}
  128. selectedQuality := "standard"
  129. for _, q := range qualityKey {
  130. if quality == q {
  131. selectedQuality = q
  132. break
  133. }
  134. }
  135. conn, err := upgrader.Upgrade(w, r, nil)
  136. if err != nil {
  137. log.Println("Failed to upgrade to websocket:", err)
  138. return
  139. }
  140. defer conn.Close()
  141. if i.audiostopchan != nil {
  142. //Another instance already running
  143. log.Println("Audio pipe already running, stopping previous instance")
  144. i.audiostopchan <- true
  145. }
  146. //Get the capture card audio input
  147. pcmdev, err := FindHDMICapturePCMPath()
  148. if err != nil {
  149. log.Println("Failed to find HDMI capture PCM path:", err)
  150. http.Error(w, "Internal Server Error", http.StatusInternalServerError)
  151. return
  152. }
  153. log.Println("Found HDMI capture PCM path:", pcmdev)
  154. // Convert PCM device to hardware device name
  155. hwdev, err := pcmDeviceToHW(pcmdev)
  156. if err != nil {
  157. log.Println("Failed to convert PCM device to hardware device:", err)
  158. http.Error(w, "Internal Server Error", http.StatusInternalServerError)
  159. return
  160. }
  161. log.Println("Using hardware device:", hwdev)
  162. // Create a buffered reader to read audio data
  163. i.audiostopchan = make(chan bool, 1)
  164. log.Println("Starting audio pipe with arecord...")
  165. // Start arecord with 48kHz, 16-bit, stereo
  166. cmd := exec.Command("arecord",
  167. "-f", "S16_LE", // Format: 16-bit little-endian
  168. "-r", fmt.Sprint(i.Config.AudioConfig.SampleRate),
  169. "-c", fmt.Sprint(i.Config.AudioConfig.Channels),
  170. "-D", hwdev, // Use the hardware device
  171. )
  172. stdout, err := cmd.StdoutPipe()
  173. if err != nil {
  174. log.Println("Failed to get arecord stdout pipe:", err)
  175. http.Error(w, "Internal Server Error", http.StatusInternalServerError)
  176. return
  177. }
  178. if err := cmd.Start(); err != nil {
  179. log.Println("Failed to start arecord:", err)
  180. http.Error(w, "Internal Server Error", http.StatusInternalServerError)
  181. return
  182. }
  183. reader := bufio.NewReader(stdout)
  184. bufferSize := i.Config.AudioConfig.FrameSize * i.Config.AudioConfig.Channels * i.Config.AudioConfig.BytesPerSample
  185. log.Printf("Buffer size: %d bytes (FrameSize: %d, Channels: %d, BytesPerSample: %d)",
  186. bufferSize, i.Config.AudioConfig.FrameSize, i.Config.AudioConfig.Channels, i.Config.AudioConfig.BytesPerSample)
  187. buf := make([]byte, bufferSize*2)
  188. // Start a goroutine to handle WebSocket messages
  189. log.Println("Listening for WebSocket messages...")
  190. go func() {
  191. _, msg, err := conn.ReadMessage()
  192. if err == nil {
  193. if string(msg) == "exit" {
  194. log.Println("Received exit command from client")
  195. i.audiostopchan <- true // Signal to stop the audio pipe
  196. return
  197. }
  198. }
  199. }()
  200. log.Println("Starting audio capture loop...")
  201. for {
  202. select {
  203. case <-i.audiostopchan:
  204. log.Println("Audio pipe stopped")
  205. goto DONE
  206. default:
  207. n, err := reader.Read(buf)
  208. if err != nil {
  209. log.Println("Read error:", err)
  210. if i.audiostopchan != nil {
  211. i.audiostopchan <- true // Signal to stop the audio pipe
  212. }
  213. goto DONE
  214. }
  215. if n == 0 {
  216. continue
  217. }
  218. downsampled := buf[:n] // Default to original buffer if no downsampling
  219. switch selectedQuality {
  220. case "high":
  221. // Keep original 48kHz stereo
  222. case "standard":
  223. // Downsample to 24kHz stereo
  224. downsampled = downsample48kTo24kStereo(buf[:n]) // Downsample to 24kHz stereo
  225. copy(buf, downsampled) // Copy downsampled data back into buf
  226. n = len(downsampled) // Update n to the new length
  227. case "low":
  228. downsampled = downsample48kTo16kStereo(buf[:n]) // Downsample to 16kHz stereo
  229. copy(buf, downsampled) // Copy downsampled data back into buf
  230. n = len(downsampled) // Update n to the new length
  231. }
  232. //log.Println("Read bytes:", n, "size of buffer:", len(buf))
  233. //Send only the bytes read to WebSocket
  234. err = conn.WriteMessage(websocket.BinaryMessage, downsampled[:n])
  235. if err != nil {
  236. log.Println("WebSocket send error:", err)
  237. goto DONE
  238. }
  239. }
  240. }
  241. DONE:
  242. i.audiostopchan <- true // Signal to stop the audio pipe
  243. cmd.Process.Kill()
  244. log.Println("Audio pipe finished")
  245. }