Files
go-service/internal/audio/monitor.go
2026-07-03 09:21:02 +03:00

158 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package audio
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"io"
"log"
"math"
"os/exec"
"regexp"
"strings"
"sync"
"time"
)
type Monitor struct {
mu sync.RWMutex
currentLevel int
cancel context.CancelFunc
}
func NewMonitor() *Monitor {
return &Monitor{}
}
func (m *Monitor) GetLevel() int {
m.mu.RLock()
defer m.mu.RUnlock()
return m.currentLevel
}
func (m *Monitor) Start() error {
ctx, cancel := context.WithCancel(context.Background())
m.cancel = cancel
// Используем гибкий поиск USB устройств
device := m.findUSBDevice()
log.Printf("Используем аудиоустройство: %s", device)
cmd := exec.CommandContext(ctx, "arecord",
"-D", device,
"-t", "raw",
"-c", "1",
"-r", "16000",
"-f", "S16_LE")
stdout, err := cmd.StdoutPipe()
if err != nil {
cancel()
return err
}
if err := cmd.Start(); err != nil {
cancel()
return err
}
log.Printf("Аудио-монитор запущен с устройством: %s", device)
go func() {
buffer := make([]byte, 3200)
for {
select {
case <-ctx.Done():
return
default:
_, err := io.ReadFull(stdout, buffer)
if err != nil {
log.Printf("Ошибка чтения аудио: %v", err)
time.Sleep(2 * time.Second)
continue
}
samplesCount := len(buffer) / 2
samples := make([]int16, samplesCount)
reader := bytes.NewReader(buffer)
if err := binary.Read(reader, binary.LittleEndian, &samples); err != nil {
continue
}
var sum float64
for _, sample := range samples {
val := float64(sample)
sum += val * val
}
rms := math.Sqrt(sum / float64(samplesCount))
level := int((rms / 32768.0) * 100 * 4)
if level > 100 {
level = 100
}
m.mu.Lock()
m.currentLevel = level
m.mu.Unlock()
}
}
}()
go func() {
_ = cmd.Wait()
}()
return nil
}
func (m *Monitor) findUSBDevice() string {
cmd := exec.Command("arecord", "-l")
output, err := cmd.Output()
if err != nil {
log.Printf("Не удалось получить список устройств: %v", err)
return "default"
}
lines := strings.Split(string(output), "\n")
for _, line := range lines {
// Ищем любое USB аудиоустройство
lowerLine := strings.ToLower(line)
if strings.Contains(lowerLine, "usb") &&
(strings.Contains(lowerLine, "audio") || strings.Contains(lowerLine, "sound")) {
log.Printf("Найдено USB аудиоустройство: %s", line)
// Извлекаем номера карты и устройства
re := regexp.MustCompile(`card (\d+):.*device (\d+):`)
matches := re.FindStringSubmatch(line)
if len(matches) == 3 {
device := fmt.Sprintf("plughw:%s,%s", matches[1], matches[2])
log.Printf("Используем устройство: %s", device)
return device
}
}
}
// Если не нашли USB, пробуем найти любое устройство захвата
for _, line := range lines {
if strings.Contains(line, "device") && strings.Contains(line, "Capture") {
re := regexp.MustCompile(`card (\d+):.*device (\d+):`)
matches := re.FindStringSubmatch(line)
if len(matches) == 3 {
device := fmt.Sprintf("plughw:%s,%s", matches[1], matches[2])
log.Printf("Используем первое найденное устройство: %s", device)
return device
}
}
}
log.Println("Аудиоустройства не найдены, используем default")
return "default"
}
func (m *Monitor) Stop() {
if m.cancel != nil {
m.cancel()
}
}