Partially refactor stuff

This commit is contained in:
Kristóf Tóth 2020-04-29 20:48:55 +02:00
parent 86d6751754
commit 853d590d97
6 changed files with 186 additions and 119 deletions

View File

@ -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
View 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
View 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 &microphone{
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
View 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
View File

@ -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()
} }

View File

@ -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()
}