113 lines
1.9 KiB
Go
113 lines
1.9 KiB
Go
//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
|
|
}
|