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  }