github.com/noriah/catnip@v1.8.5/input/stdinput/stdinput.go (about) 1 package stdinput 2 3 import ( 4 "context" 5 "encoding/binary" 6 "io" 7 "math" 8 "os" 9 "sync" 10 "time" 11 _ "unsafe" 12 13 "github.com/noriah/catnip/input" 14 "github.com/pkg/errors" 15 ) 16 17 func init() { 18 input.RegisterBackend("stdin", StdinBackend{}) 19 } 20 21 type StdinBackend struct{} 22 23 func (b StdinBackend) Init() error { 24 return nil 25 } 26 27 func (b StdinBackend) Close() error { 28 return nil 29 } 30 31 func (b StdinBackend) Devices() ([]input.Device, error) { 32 return []input.Device{StdInputDevice{}}, nil 33 } 34 35 func (b StdinBackend) DefaultDevice() (input.Device, error) { 36 return StdInputDevice{}, nil 37 } 38 39 func (b StdinBackend) Start(config input.SessionConfig) (input.Session, error) { 40 return NewStdinSession(config), nil 41 } 42 43 type StdInputDevice struct{} 44 45 func (d StdInputDevice) String() string { 46 return "stdin" 47 } 48 49 type Session struct { 50 cfg input.SessionConfig 51 samples int 52 // maligned. 53 f32mode bool 54 } 55 56 func NewStdinSession(cfg input.SessionConfig) *Session { 57 return &Session{ 58 cfg: cfg, 59 f32mode: true, 60 samples: cfg.SampleSize * cfg.FrameSize, 61 } 62 } 63 64 func (s *Session) Start(ctx context.Context, dst [][]input.Sample, kickChan chan bool, mu *sync.Mutex) error { 65 if !input.EnsureBufferLen(s.cfg, dst) { 66 return errors.New("invalid dst length given") 67 } 68 69 // if initial_flags, err := Fcntl(int(uintptr(syscall.Stdin)), syscall.F_GETFL, 0); err == nil { 70 // initial_flags |= syscall.O_NONBLOCK 71 // if _, err := Fcntl(int(uintptr(syscall.Stdin)), syscall.F_SETFL, initial_flags); err != nil { 72 // return errors.Wrap(err, "failed to set non block flag.") 73 // } 74 // } else { 75 // return errors.Wrap(err, "failed to set non block flag.") 76 // } 77 78 o := os.Stdin 79 defer o.Close() 80 81 framesz := s.cfg.FrameSize 82 reader := floatReader{ 83 order: binary.LittleEndian, 84 f64: !s.f32mode, 85 } 86 87 bufsz := s.samples 88 if !s.f32mode { 89 bufsz *= 2 90 } 91 92 raw := make([]byte, bufsz*4) 93 94 // We double this as a workaround because sampleDuration is less than the 95 // actual time that ReadFull blocks for some reason, probably because the 96 // process decides to discard audio when it overflows. 97 sampleDuration := time.Duration( 98 float64(s.cfg.SampleSize) / s.cfg.SampleRate * float64(time.Second)) 99 // We also keep track of whether the deadline was hit once so we can half 100 // the sample duration. This smooths out the jitter. 101 var readExpired bool 102 103 // timer := time.NewTimer(time.Millisecond * 100) 104 105 for { 106 // Set us a read deadline. If the deadline is reached, we'll write zeros 107 // to the buffer. 108 timeout := sampleDuration 109 if !readExpired { 110 timeout *= 2 111 } 112 // if err := o.SetReadDeadline(time.Now().Add(timeout)); err != nil { 113 // return errors.Wrap(err, "failed to set read deadline") 114 // } 115 116 _, err := io.ReadFull(o, raw) 117 if err != nil { 118 switch { 119 case errors.Is(err, io.EOF): 120 return nil 121 case errors.Is(err, os.ErrDeadlineExceeded): 122 readExpired = true 123 default: 124 return err 125 } 126 } else { 127 readExpired = false 128 } 129 130 if readExpired { 131 mu.Lock() 132 // We can write directly to dst just so we can avoid parsing zero 133 // bytes to floats. 134 for _, buf := range dst { 135 // Go should optimize this to a memclr. 136 for i := range buf { 137 buf[i] = 0 138 } 139 } 140 mu.Unlock() 141 } else { 142 reader.reset(raw) 143 mu.Lock() 144 for n := 0; n < s.samples; n++ { 145 dst[n%framesz][n/framesz] = reader.next() 146 } 147 mu.Unlock() 148 } 149 150 // Signal that we've written to dst. 151 select { 152 case <-ctx.Done(): 153 return ctx.Err() 154 case kickChan <- true: 155 } 156 } 157 } 158 159 type floatReader struct { 160 order binary.ByteOrder 161 buf []byte 162 f64 bool 163 } 164 165 func (f *floatReader) reset(b []byte) { 166 f.buf = b 167 } 168 169 func (f *floatReader) next() float64 { 170 if f.f64 { 171 b := f.buf[:8] 172 f.buf = f.buf[8:] 173 return math.Float64frombits(f.order.Uint64(b)) 174 } 175 176 b := f.buf[:4] 177 f.buf = f.buf[4:] 178 return float64(math.Float32frombits(f.order.Uint32(b))) 179 }