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
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user