Partially refactor stuff
This commit is contained in:
parent
86d6751754
commit
853d590d97
@ -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"
|
||||
"time"
|
||||
"remote-mic/socketops"
|
||||
"remote-mic/pulsectl"
|
||||
"remote-mic/audio"
|
||||
)
|
||||
|
||||
@ -28,43 +27,31 @@ func socketExample() {
|
||||
}
|
||||
|
||||
func pulsectlExample() {
|
||||
pulsectl.LoadPipeSource(pulsectl.PipeSourceConfig{
|
||||
"remote-mic",
|
||||
"/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()
|
||||
pulsectl := audio.NewPulsectl()
|
||||
err := pulsectl.LoadPipeSourceModule()
|
||||
if err != nil {
|
||||
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() {
|
||||
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()
|
||||
}
|
Loading…
Reference in New Issue
Block a user