Files
go-ts/pkg/audio/capture_linux.go

113 lines
1.9 KiB
Go
Raw Permalink Normal View History

//go:build linux
package audio
import (
"fmt"
"sync"
"github.com/gordonklaus/portaudio"
)
// Capturer handles audio capture using PortAudio
type Capturer struct {
stream *portaudio.Stream
running bool
mu sync.Mutex
onAudio func(samples []int16)
currentLevel int
levelMu sync.RWMutex
}
func NewCapturer() (*Capturer, error) {
if err := initPortAudio(); err != nil {
return nil, err
}
return &Capturer{}, nil
}
func (c *Capturer) SetCallback(fn func(samples []int16)) {
c.mu.Lock()
c.onAudio = fn
c.mu.Unlock()
}
func (c *Capturer) Start() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.running {
return nil
}
var err error
c.stream, err = portaudio.OpenDefaultStream(1, 0, 48000, frameSamples, c.processCapture)
if err != nil {
return fmt.Errorf("failed to open portaudio capture stream: %w", err)
}
if err := c.stream.Start(); err != nil {
c.stream.Close()
return fmt.Errorf("failed to start portaudio capture stream: %w", err)
}
c.running = true
return nil
}
func (c *Capturer) Stop() {
c.mu.Lock()
defer c.mu.Unlock()
if !c.running {
return
}
c.running = false
if c.stream != nil {
c.stream.Abort()
}
}
func (c *Capturer) Close() {
c.Stop()
c.mu.Lock()
if c.stream != nil {
c.stream.Close()
}
c.mu.Unlock()
terminatePortAudio()
}
func (c *Capturer) processCapture(in []int16) {
c.mu.Lock()
callback := c.onAudio
running := c.running
c.mu.Unlock()
if !running || callback == nil {
return
}
// Calculate level
level := CalculateRMSLevel(in)
c.levelMu.Lock()
c.currentLevel = level
c.levelMu.Unlock()
// Clone buffer and send to callback
samples := make([]int16, len(in))
copy(samples, in)
callback(samples)
}
func (c *Capturer) GetLevel() int {
c.levelMu.RLock()
defer c.levelMu.RUnlock()
return c.currentLevel
}
func (c *Capturer) IsRunning() bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.running
}