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