github.com/noriah/catnip@v1.8.5/input/ffmpeg/dshow.go (about) 1 //go:build windows 2 3 package ffmpeg 4 5 import ( 6 "bufio" 7 "bytes" 8 "fmt" 9 "os/exec" 10 "strings" 11 12 "github.com/noriah/catnip/input" 13 "github.com/noriah/catnip/input/common/execread" 14 ) 15 16 func init() { 17 input.RegisterBackend("ffmpeg-dshow", DShow{}) 18 } 19 20 func NewWindowsSession(b FFmpegBackend, cfg input.SessionConfig) (*execread.Session, error) { 21 args := []string{"ffmpeg", "-hide_banner", "-loglevel", "panic"} 22 args = append(args, 23 "-f", "dshow", "-audio_buffer_size", "20", 24 "-sample_rate", fmt.Sprintf("%.0f", cfg.SampleRate), 25 "-channels", fmt.Sprintf("%d", cfg.FrameSize), 26 // "-sample_size", fmt.Sprintf("%d", 16), 27 ) 28 args = append(args, b.InputArgs()...) 29 args = append(args, "-f", "f64le", "-") 30 31 return execread.NewSession(args, false, cfg), nil 32 } 33 34 // DShow is the DirectShow input for FFmpeg on Windows. 35 type DShow struct{} 36 37 func (p DShow) Init() error { 38 return nil 39 } 40 41 func (p DShow) Close() error { 42 return nil 43 } 44 45 // Devices returns a list of dshow devices. 46 func (p DShow) Devices() ([]input.Device, error) { 47 cmd := exec.Command( 48 "ffmpeg", "-hide_banner", "-loglevel", "info", 49 "-f", "dshow", "-list_devices", "true", 50 "-i", "", 51 ) 52 53 o, _ := cmd.CombinedOutput() 54 55 audio := true 56 scanner := bufio.NewScanner(bytes.NewReader(o)) 57 58 var devices []input.Device 59 60 for scanner.Scan() { 61 text := scanner.Text() 62 63 // Trim away the prefix. 64 if strings.HasPrefix(text, "[dshow") { 65 parts := strings.SplitN(text, "] ", 2) 66 if len(parts) == 2 { 67 text = parts[1][1:] 68 } 69 } 70 71 // If we're not scanning a device (which starts with a square bracket) 72 // anymore, then we stop. 73 if strings.HasPrefix(text, ":") { 74 audio = false 75 continue 76 } 77 78 // If we're not under the audio section yet, then skip. 79 if !audio { 80 continue 81 } 82 83 // Parse. 84 parts := strings.SplitN(text, "\" (", 2) 85 if len(parts) != 2 { 86 continue 87 } 88 89 if !strings.HasPrefix(parts[1], "audio") { 90 continue 91 } 92 93 devices = append(devices, DShowDevice{ 94 Name: parts[0], 95 }) 96 } 97 98 if len(devices) == 0 { 99 // This is completely for visual. 100 lines := strings.Split(string(o), "\n") 101 for i, line := range lines { 102 lines[i] = "\t" + line 103 } 104 output := strings.Join(lines, "\n") 105 106 return nil, fmt.Errorf("no devices found; ffmpeg output:\n%s", output) 107 } 108 109 return devices, nil 110 } 111 112 func (p DShow) DefaultDevice() (input.Device, error) { 113 return DShowDevice{"no default device"}, nil 114 } 115 116 func (p DShow) Start(cfg input.SessionConfig) (input.Session, error) { 117 dv, ok := cfg.Device.(DShowDevice) 118 if !ok { 119 return nil, fmt.Errorf("invalid device type %T", cfg.Device) 120 } 121 122 return NewWindowsSession(dv, cfg) 123 } 124 125 type DShowDevice struct { 126 Name string 127 } 128 129 func (d DShowDevice) InputArgs() []string { 130 input := fmt.Sprintf("audio=%s", d.Name) 131 return []string{"-i", input} 132 } 133 134 func (d DShowDevice) String() string { 135 return fmt.Sprintf("%s", d.Name) 136 }