Partially refactor stuff
This commit is contained in:
		@@ -1,53 +0,0 @@
 | 
				
			|||||||
package audio
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"os/exec"
 | 
					 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"regexp"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type MicStreamConfig struct {
 | 
					 | 
				
			||||||
	Format string
 | 
					 | 
				
			||||||
	Device string
 | 
					 | 
				
			||||||
	Encoding string
 | 
					 | 
				
			||||||
	Bitrate int
 | 
					 | 
				
			||||||
	Channels int
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func StreamMic(config MicStreamConfig) *exec.Cmd {
 | 
					 | 
				
			||||||
	cmd := exec.Command("ffmpeg", "-re",
 | 
					 | 
				
			||||||
		"-f", config.Format,
 | 
					 | 
				
			||||||
		"-i", config.Device,
 | 
					 | 
				
			||||||
		"-f", config.Encoding,
 | 
					 | 
				
			||||||
		"-ar", fmt.Sprintf("%d", config.Bitrate),
 | 
					 | 
				
			||||||
		"-ac", fmt.Sprintf("%d", config.Channels),
 | 
					 | 
				
			||||||
		"-",
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
	return cmd
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func GetMicDevice() (string, error) {
 | 
					 | 
				
			||||||
	cmd := exec.Command("ffmpeg",
 | 
					 | 
				
			||||||
		"-f", "avfoundation",
 | 
					 | 
				
			||||||
		"-list_devices", "true",
 | 
					 | 
				
			||||||
		"-i", "\"\"",
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
	// ignore error, this ffmpeg command returns exit status 1
 | 
					 | 
				
			||||||
	outputBytes, _ := cmd.CombinedOutput()
 | 
					 | 
				
			||||||
	output := string(outputBytes)
 | 
					 | 
				
			||||||
	parsedOutput := parseDeviceNumber(output)
 | 
					 | 
				
			||||||
	if parsedOutput == "" {
 | 
					 | 
				
			||||||
		return "", fmt.Errorf("Command: %s\nFailed to parse FFmpeg output:\n%s", cmd, output)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return parsedOutput, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func parseDeviceNumber(ffmpegOutput string) string {
 | 
					 | 
				
			||||||
	re := regexp.MustCompile(`\[(\d)\].*?[Mm]icrophone`)
 | 
					 | 
				
			||||||
	matches := re.FindStringSubmatch(ffmpegOutput)	
 | 
					 | 
				
			||||||
	if (len(matches) == 2) {
 | 
					 | 
				
			||||||
		return matches[1]
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return ""
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										13
									
								
								audio/encoding.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								audio/encoding.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					package audio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Encoding struct {
 | 
				
			||||||
 | 
						Codec string
 | 
				
			||||||
 | 
						Bitrate int
 | 
				
			||||||
 | 
						Channels int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var defaultEncoding = Encoding{
 | 
				
			||||||
 | 
						Codec: "s16le",
 | 
				
			||||||
 | 
						Bitrate: 44100,
 | 
				
			||||||
 | 
						Channels: 2,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										102
									
								
								audio/ffmpeg.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								audio/ffmpeg.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
				
			|||||||
 | 
					package audio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"runtime"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type microphone struct {
 | 
				
			||||||
 | 
						command *exec.Cmd
 | 
				
			||||||
 | 
						audioStream io.Reader
 | 
				
			||||||
 | 
						diagnosticOutput io.Reader
 | 
				
			||||||
 | 
						Format string
 | 
				
			||||||
 | 
						Device string
 | 
				
			||||||
 | 
						Encoding Encoding
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewMicrophone() (*microphone, error) {
 | 
				
			||||||
 | 
						if runtime.GOOS != "darwin" {
 | 
				
			||||||
 | 
							panic(fmt.Errorf("audio.microphone is only supported on macOS"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						format := "avfoundation"
 | 
				
			||||||
 | 
						device, err := getMicDevice(format)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return µphone{
 | 
				
			||||||
 | 
							Format: format,
 | 
				
			||||||
 | 
							Device: device,
 | 
				
			||||||
 | 
							Encoding: defaultEncoding,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *microphone) Start() error {
 | 
				
			||||||
 | 
						m.command = exec.Command("ffmpeg", "-re",
 | 
				
			||||||
 | 
							"-f", m.Format,
 | 
				
			||||||
 | 
							"-i", m.Device,
 | 
				
			||||||
 | 
							"-f", m.Encoding.Codec,
 | 
				
			||||||
 | 
							"-ar", fmt.Sprintf("%d", m.Encoding.Bitrate),
 | 
				
			||||||
 | 
							"-ac", fmt.Sprintf("%d", m.Encoding.Channels),
 | 
				
			||||||
 | 
							"-",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// these should never fail, unless you use the module incorrectly
 | 
				
			||||||
 | 
						audioStream, err := m.command.StdoutPipe()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						diagnosticOutput, err := m.command.StderrPipe()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						m.audioStream, m.diagnosticOutput = audioStream, diagnosticOutput
 | 
				
			||||||
 | 
						return m.command.Start()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *microphone) Stop() error {
 | 
				
			||||||
 | 
						return m.command.Process.Kill()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *microphone) AudioStream() io.Reader {
 | 
				
			||||||
 | 
						if m.audioStream == nil {
 | 
				
			||||||
 | 
							panic(fmt.Errorf("AudioStream() called before Start()"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return m.audioStream
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *microphone) DiagnosticOutput() io.Reader {
 | 
				
			||||||
 | 
						if m.diagnosticOutput == nil {
 | 
				
			||||||
 | 
							panic(fmt.Errorf("DiagnosticOutput() called before Start()"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return m.diagnosticOutput
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getMicDevice(format string) (string, error) {
 | 
				
			||||||
 | 
						cmd := exec.Command("ffmpeg",
 | 
				
			||||||
 | 
							"-f", format,
 | 
				
			||||||
 | 
							"-list_devices", "true",
 | 
				
			||||||
 | 
							"-i", "\"\"",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						// ignore error, this ffmpeg command returns exit status 1
 | 
				
			||||||
 | 
						outputBytes, _ := cmd.CombinedOutput()
 | 
				
			||||||
 | 
						output := string(outputBytes)
 | 
				
			||||||
 | 
						parsedOutput := parseDeviceNumber(output)
 | 
				
			||||||
 | 
						if parsedOutput == "" {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("Command: %s\nFailed to parse FFmpeg output:\n%s", cmd, output)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return parsedOutput, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func parseDeviceNumber(ffmpegOutput string) string {
 | 
				
			||||||
 | 
						re := regexp.MustCompile(`\[(\d)\].*?[Mm]icrophone`)
 | 
				
			||||||
 | 
						matches := re.FindStringSubmatch(ffmpegOutput)	
 | 
				
			||||||
 | 
						if (len(matches) == 2) {
 | 
				
			||||||
 | 
							return matches[1]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										50
									
								
								audio/pulsectl.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								audio/pulsectl.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					package audio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"runtime"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type pulsectl struct {
 | 
				
			||||||
 | 
						PipeName string
 | 
				
			||||||
 | 
						PipeDir string
 | 
				
			||||||
 | 
						Encoding Encoding
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewPulsectl() *pulsectl {
 | 
				
			||||||
 | 
						if runtime.GOOS != "linux" {
 | 
				
			||||||
 | 
							panic(fmt.Errorf("audio.pulsectl is only supported on Linux"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &pulsectl{
 | 
				
			||||||
 | 
							PipeName: "remote-mic",
 | 
				
			||||||
 | 
							PipeDir: "/dev/shm",
 | 
				
			||||||
 | 
							Encoding: defaultEncoding,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *pulsectl) LoadPipeSourceModule() error {
 | 
				
			||||||
 | 
						cmd := exec.Command("pactl", "load-module", "module-pipe-source",
 | 
				
			||||||
 | 
							fmt.Sprintf("source_name=%s", p.PipeName),
 | 
				
			||||||
 | 
							fmt.Sprintf("file=%s", path.Join(p.PipeDir, p.PipeName)),
 | 
				
			||||||
 | 
							fmt.Sprintf("format=%s", p.Encoding.Codec),
 | 
				
			||||||
 | 
							fmt.Sprintf("rate=%d", p.Encoding.Bitrate),
 | 
				
			||||||
 | 
							fmt.Sprintf("channels=%d", p.Encoding.Channels),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						outBytes, err := cmd.CombinedOutput()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("failed to load pipe source module:\n%s", string(outBytes))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *pulsectl) UnloadPipeSourceModule() error {
 | 
				
			||||||
 | 
						cmd := exec.Command("pactl", "unload-module", "module-pipe-source")
 | 
				
			||||||
 | 
						outBytes, err := cmd.CombinedOutput()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("failed to unload pipe source module:\n%s", string(outBytes))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										55
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								main.go
									
									
									
									
									
								
							@@ -6,7 +6,6 @@ import (
 | 
				
			|||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
	"remote-mic/socketops"
 | 
						"remote-mic/socketops"
 | 
				
			||||||
	"remote-mic/pulsectl"
 | 
					 | 
				
			||||||
	"remote-mic/audio"
 | 
						"remote-mic/audio"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -28,43 +27,31 @@ func socketExample() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func pulsectlExample() {
 | 
					func pulsectlExample() {
 | 
				
			||||||
	pulsectl.LoadPipeSource(pulsectl.PipeSourceConfig{
 | 
						pulsectl := audio.NewPulsectl()
 | 
				
			||||||
		"remote-mic",
 | 
						err := pulsectl.LoadPipeSourceModule()
 | 
				
			||||||
		"/dev/shm",
 | 
					 | 
				
			||||||
		"s16le",
 | 
					 | 
				
			||||||
		44100,
 | 
					 | 
				
			||||||
		2,
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	time.Sleep(10 * time.Second)
 | 
					 | 
				
			||||||
	pulsectl.UnloadPipeSource()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func micStreamExample() {
 | 
					 | 
				
			||||||
	cmd := audio.StreamMic(audio.MicStreamConfig{
 | 
					 | 
				
			||||||
		"avfoundation",
 | 
					 | 
				
			||||||
		":0",
 | 
					 | 
				
			||||||
		"s16le",
 | 
					 | 
				
			||||||
		44100,
 | 
					 | 
				
			||||||
		2,
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pipe, _ := cmd.StdoutPipe()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cmd.Start()
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	for {
 | 
					 | 
				
			||||||
		io.Copy(os.Stdout, pipe)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func getDeviceExample() {
 | 
					 | 
				
			||||||
	device, err := audio.GetMicDevice()
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	fmt.Println(device)
 | 
						time.Sleep(10 * time.Second)
 | 
				
			||||||
 | 
						pulsectl.UnloadPipeSourceModule()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func micStreamExample() {
 | 
				
			||||||
 | 
						mic, err := audio.NewMicrophone()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = mic.Start()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer mic.Stop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							io.Copy(os.Stdout, mic.AudioStream())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	getDeviceExample()
 | 
						pulsectlExample()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,32 +0,0 @@
 | 
				
			|||||||
package pulsectl
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"os/exec"
 | 
					 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"path"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type PipeSourceConfig struct {
 | 
					 | 
				
			||||||
	PipeName string
 | 
					 | 
				
			||||||
	PipeDir string
 | 
					 | 
				
			||||||
	Encoding string
 | 
					 | 
				
			||||||
	Bitrate int
 | 
					 | 
				
			||||||
	Channels int
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func LoadPipeSource(config PipeSourceConfig) error {
 | 
					 | 
				
			||||||
	cmd := exec.Command("pactl", "load-module", "module-pipe-source",
 | 
					 | 
				
			||||||
		fmt.Sprintf("source_name=%s", config.PipeName),
 | 
					 | 
				
			||||||
		fmt.Sprintf("file=%s", path.Join(config.PipeDir, config.PipeName)),
 | 
					 | 
				
			||||||
		fmt.Sprintf("format=%s", config.Encoding),
 | 
					 | 
				
			||||||
		fmt.Sprintf("rate=%d", config.Bitrate),
 | 
					 | 
				
			||||||
		fmt.Sprintf("channels=%d", config.Channels),
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
	return cmd.Run()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func UnloadPipeSource() error {
 | 
					 | 
				
			||||||
	cmd := exec.Command("pactl", "unload-module", "module-pipe-source")
 | 
					 | 
				
			||||||
	return cmd.Run()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user