158 lines
4.4 KiB
Go
158 lines
4.4 KiB
Go
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()
|
||
}
|
||
} |